diff --git a/AppKeychain.h b/AppKeychain.h deleted file mode 100644 index 9e323ac..0000000 --- a/AppKeychain.h +++ /dev/null @@ -1,26 +0,0 @@ -// -// AppKeychain.h -// Backup -// -// Created by Stefan Reitshamer on 8/26/09. -// Copyright 2009 PhotoMinds LLC. All rights reserved. -// - - - -@interface AppKeychain : NSObject { - NSString *backupAppPath; - NSString *agentAppPath; - SecAccessRef access; -} -+ (BOOL)accessKeyID:(NSString **)accessKeyID secretAccessKey:(NSString **)secret error:(NSError **)error; -+ (BOOL)containsEncryptionPasswordForS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID; -+ (BOOL)encryptionPassword:(NSString **)encryptionPassword forS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID error:(NSError **)error; -+ (BOOL)twitterAccessKey:(NSString **)theAccessKey secret:(NSString **)secret error:(NSError **)error; - -- (id)initWithBackupAppPath:(NSString *)backupAppPath agentAppPath:(NSString *)agentAppPath; -- (BOOL)setAccessKeyID:(NSString *)theAccessKeyID secretAccessKey:(NSString *)theSecretAccessKey error:(NSError **)error; -- (BOOL)setEncryptionKey:(NSString *)theEncryptionPassword forS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID error:(NSError **)error; -- (BOOL)setTwitterAccessKey:(NSString *)theKey secret:(NSString *)theSecret error:(NSError **)error; -- (BOOL)deleteTwitterAccessKey:(NSError **)error; -@end diff --git a/AppKeychain.m b/AppKeychain.m deleted file mode 100644 index 9212273..0000000 --- a/AppKeychain.m +++ /dev/null @@ -1,271 +0,0 @@ -// -// AppKeychain.m -// Backup -// -// Created by Stefan Reitshamer on 8/26/09. -// Copyright 2009 PhotoMinds LLC. All rights reserved. -// - -#import "AppKeychain.h" -#import "SetNSError.h" -#import "UserLibrary.h" - -#define ARQ_S3_LABEL @"Arq S3" -#define ARQ_TWITTER_LABEL @"Arq Twitter Access Token" -#define ARQ_ENCRYPTION_ACCOUNT_LABEL @"EncryptionKey" - -@interface AppKeychain (internal) -+ (NSString *)encryptionPasswordLabelForS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID; -+ (BOOL)account:(NSString **)account password:(NSString **)password forLabel:(NSString *)label error:(NSError **)error; -+ (BOOL)findItem:(NSString *)theLabel item:(SecKeychainItemRef *)item error:(NSError **)error; -- (BOOL)loadSecAccess:(NSError **)error; -- (BOOL)deleteLabel:(NSString *)theLabel error:(NSError **)error; -- (BOOL)addLabel:(NSString *)theLabel account:(NSString *)theAccount password:(NSString *)thePassword error:(NSError **)error; -@end - -@implementation AppKeychain -+ (BOOL)accessKeyID:(NSString **)accessKey secretAccessKey:(NSString **)secret error:(NSError **)error { - NSString *account = nil; - NSString *password = nil; - if (![AppKeychain account:&account password:&password forLabel:ARQ_S3_LABEL error:error]) { - return NO; - } - if (accessKey != nil) { - *accessKey = account; - } - if (secret != nil) { - *secret = password; - } - return YES; -} -+ (BOOL)containsEncryptionPasswordForS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID { - NSString *encryptionPassword = nil; - NSError *myError = nil; - return [AppKeychain encryptionPassword:&encryptionPassword forS3BucketName:theS3BucketName computerUUID:theComputerUUID error:&myError]; -} -+ (BOOL)encryptionPassword:(NSString **)encryptionPassword forS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID error:(NSError **)error { - NSString *label = [AppKeychain encryptionPasswordLabelForS3BucketName:theS3BucketName computerUUID:theComputerUUID]; - NSString *account = nil; - NSString *password = nil; - if (![AppKeychain account:&account password:&password forLabel:label error:error]) { - return NO; - } - if (encryptionPassword != nil) { - *encryptionPassword = password; - } - return YES; -} -+ (BOOL)twitterAccessKey:(NSString **)theAccessKey secret:(NSString **)secret error:(NSError **)error { - return [AppKeychain account:theAccessKey password:secret forLabel:ARQ_TWITTER_LABEL error:error]; -} - - -- (id)initWithBackupAppPath:(NSString *)theBackupAppPath - agentAppPath:(NSString *)theAgentAppPath { - if (self = [super init]) { - backupAppPath = [theBackupAppPath copy]; - agentAppPath = [theAgentAppPath copy]; - } - return self; -} -- (void)dealloc { - if (access != NULL) { - CFRelease(access); - } - [backupAppPath release]; - [agentAppPath release]; - [super dealloc]; -} -- (BOOL)setAccessKeyID:(NSString *)theAccessKeyID - secretAccessKey:(NSString *)theSecretAccessKey - error:(NSError **)error { - return [self addLabel:ARQ_S3_LABEL account:theAccessKeyID password:theSecretAccessKey error:error]; -} -- (BOOL)setEncryptionKey:(NSString *)theEncryptionPassword - forS3BucketName:(NSString *)theS3BucketName - computerUUID:(NSString *)theComputerUUID - error:(NSError **)error { - NSString *label = [AppKeychain encryptionPasswordLabelForS3BucketName:theS3BucketName computerUUID:theComputerUUID]; - return [self addLabel:label account:ARQ_ENCRYPTION_ACCOUNT_LABEL password:theEncryptionPassword error:error]; -} -- (BOOL)setTwitterAccessKey:(NSString *)theKey secret:(NSString *)theSecret error:(NSError **)error { - return [self addLabel:ARQ_TWITTER_LABEL account:theKey password:theSecret error:error]; -} -- (BOOL)deleteTwitterAccessKey:(NSError **)error { - return [self deleteLabel:ARQ_TWITTER_LABEL error:error]; -} -@end - -@implementation AppKeychain (internal) -+ (NSString *)encryptionPasswordLabelForS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID { - return [NSString stringWithFormat:@"Arq Encryption:%@:%@", theS3BucketName, theComputerUUID]; -} -+ (BOOL)account:(NSString **)account password:(NSString **)password forLabel:(NSString *)theLabel error:(NSError **)error { - SecKeychainItemRef item = NULL; - if (![AppKeychain findItem:theLabel item:&item error:error]) { - return NO; - } - if (item == NULL) { - SETNSERROR(@"AppKeychainErrorDomain", -1, @"Keychain item %@ not found", theLabel); - return NO; - } - SecKeychainAttributeList *outAttrList = NULL; - UInt32 length; - void *data; - UInt32 tags[] = { - kSecAccountItemAttr - }; - UInt32 formats[] = { - CSSM_DB_ATTRIBUTE_FORMAT_STRING - }; - SecKeychainAttributeInfo info = { - 1, - tags, - formats - }; - OSStatus oss = SecKeychainItemCopyAttributesAndData(item, &info, NULL, &outAttrList, &length, &data); - if (oss != noErr) { - NSString *errorMessage = (NSString *)SecCopyErrorMessageString(oss, NULL); - SETNSERROR(@"AppKeychainErrorDomain", -1, @"Error reading data from Keychain item %@: %@ (code %d)", theLabel, errorMessage, oss); - [errorMessage release]; - return NO; - } - if (account != nil) { - *account = nil; - for (UInt32 index = 0; index < outAttrList->count; index++) { - SecKeychainAttribute *attr = outAttrList->attr + index; - if (attr->tag == kSecAccountItemAttr) { - *account = [[[NSString alloc] initWithBytes:attr->data length:attr->length encoding:NSUTF8StringEncoding] autorelease]; - break; - } - } - } - if (password != nil) { - *password = [[[NSString alloc] initWithBytes:data length:length encoding:NSUTF8StringEncoding] autorelease]; - } - SecKeychainItemFreeAttributesAndData(outAttrList, data); - CFRelease(item); - return YES; -} -+ (BOOL)findItem:(NSString *)theLabel item:(SecKeychainItemRef *)item error:(NSError **)error { - *item = NULL; - const char *label = [theLabel UTF8String]; - SecKeychainAttribute attrs[] = { - { - kSecLabelItemAttr, - strlen(label), - (char *)label - } - }; - SecKeychainAttributeList attributes = { - sizeof(attrs) / sizeof(attrs[0]), - attrs - }; - SecKeychainSearchRef searchRef; - OSStatus oss; - oss = SecKeychainSearchCreateFromAttributes(NULL, kSecGenericPasswordItemClass, &attributes, &searchRef); - if (oss != noErr) { - SETNSERROR(@"AppKeychainErrorDomain", -1, @"error creating keychain search"); - return NO; - } - SecKeychainSearchCopyNext(searchRef, item); - CFRelease(searchRef); - return YES; -} - -- (BOOL)loadSecAccess:(NSError **)error { - if (access == NULL) { - NSMutableArray *trustedApplications = [NSMutableArray array]; - OSStatus oss; - SecTrustedApplicationRef agentApp; - oss = SecTrustedApplicationCreateFromPath([agentAppPath fileSystemRepresentation], &agentApp); - if (oss != noErr) { - CFStringRef msg = SecCopyErrorMessageString(oss, NULL); - SETNSERROR(@"AppKeychainErrorDomain", -1, @"Error creating Agent trusted application %@: %@ (code %d)", agentAppPath, (NSString *)msg, oss); - CFRelease(msg); - return NO; - } - [trustedApplications addObject:(id)agentApp]; - CFRelease(agentApp); - - SecTrustedApplicationRef backupApp; - oss = SecTrustedApplicationCreateFromPath([backupAppPath fileSystemRepresentation], &backupApp); - if (oss != noErr) { - CFStringRef msg = SecCopyErrorMessageString(oss, NULL); - SETNSERROR(@"AppKeychainErrorDomain", -1, @"Error creating trusted application: %@ (code %d)", (NSString *)msg, oss); - CFRelease(agentApp); - CFRelease(msg); - return NO; - } - [trustedApplications addObject:(id)backupApp]; - CFRelease(backupApp); - - - oss = SecAccessCreate((CFStringRef)@"Arq", (CFArrayRef)trustedApplications, &access); - if (oss != noErr) { - CFStringRef msg = SecCopyErrorMessageString(oss, NULL); - SETNSERROR(@"AppKeychainErrorDomain", -1, @"Error creating SecAccessRef: %@ (code %d)", (NSString *)msg, oss); - CFRelease(msg); - return NO; - } - } - return YES; -} -- (BOOL)deleteLabel:(NSString *)theLabel error:(NSError **)error { - SecKeychainItemRef item = NULL; - if (![AppKeychain findItem:theLabel item:&item error:error]) { - return NO; - } - if (item != NULL) { - OSStatus oss = SecKeychainItemDelete(item); - CFRelease(item); - if (oss != noErr) { - NSString *errorMessage = (NSString *)SecCopyErrorMessageString(oss, NULL); - SETNSERROR(@"AppKeychainErrorDomain", -1, @"error deleting item for label %@: %@ (code %d)", theLabel, errorMessage, oss); - [errorMessage release]; - return NO; - } - } - return YES; -} -- (BOOL)addLabel:(NSString *)theLabel account:(NSString *)theAccount password:(NSString *)thePassword error:(NSError **)error { - if (![self loadSecAccess:error]) { - return NO; - } - if (![self deleteLabel:theLabel error:error]) { - return NO; - } - const char *label = [theLabel UTF8String]; - const char *account = [theAccount UTF8String]; - const char *password = [thePassword UTF8String]; - SecKeychainAttribute attrs[] = { - { - kSecLabelItemAttr, - strlen(label), - (char *)label - }, - { - kSecAccountItemAttr, - strlen(account), - (char *)account - }, - { - kSecServiceItemAttr, - strlen(label), - (char *)label - } - }; - SecKeychainAttributeList attributes = { - sizeof(attrs) / sizeof(attrs[0]), - attrs - }; - OSStatus oss = SecKeychainItemCreateFromContent(kSecGenericPasswordItemClass, &attributes, strlen(password), password, NULL, access, NULL); - if (oss != noErr) { - NSString *errorMessage = (NSString *)SecCopyErrorMessageString(oss, NULL); - SETNSERROR(@"AppKeychainErrorDomain", -1, @"Error creating keychain item: %@ (code %d)", errorMessage, oss); - [errorMessage release]; - return NO; - } - return YES; -} -@end diff --git a/ArqFark.h b/ArqFark.h deleted file mode 100644 index c44ee3c..0000000 --- a/ArqFark.h +++ /dev/null @@ -1,26 +0,0 @@ -// -// ArqFark.h -// Arq -// -// Created by Stefan Reitshamer on 6/22/10. -// Copyright 2010 __MyCompanyName__. All rights reserved. -// - - -@class S3Service; -@class ServerBlob; - -@interface ArqFark : NSObject { - S3Service *s3; - NSString *s3BucketName; - NSString *computerUUID; - NSThread *creatorThread; -} -+ (NSString *)errorDomain; -- (id)initWithS3Service:(S3Service *)theS3 - s3BucketName:(NSString *)theS3BucketName - computerUUID:(NSString *)theComputerUUID; -- (NSData *)bucketDataForRelativePath:(NSString *)bucketDataRelativePath error:(NSError **)error; -- (NSData *)dataForSHA1:(NSString *)sha1 error:(NSError **)error; -- (ServerBlob *)newServerBlobForSHA1:(NSString *)sha1 error:(NSError **)error; -@end diff --git a/ArqFark.m b/ArqFark.m deleted file mode 100644 index b3a3ba1..0000000 --- a/ArqFark.m +++ /dev/null @@ -1,74 +0,0 @@ -// -// ArqFark.m -// Arq -// -// Created by Stefan Reitshamer on 6/22/10. -// Copyright 2010 __MyCompanyName__. All rights reserved. -// - -#import "ArqFark.h" -#import "ArqPackSet.h" -#import "ServerBlob.h" -#import "NSErrorCodes.h" -#import "S3Service.h" -#import "RegexKitLite.h" -#import "DiskPackIndex.h" -#import "FarkPath.h" -#import "SetNSError.h" -#import "NSError_extra.h" - -#define MAX_RETRIES 10 - -@implementation ArqFark -+ (NSString *)errorDomain { - return @"ArqFarkErrorDomain"; -} -- (id)initWithS3Service:(S3Service *)theS3 - s3BucketName:(NSString *)theS3BucketName - computerUUID:(NSString *)theComputerUUID { - if (self = [super init]) { - s3 = [theS3 retain]; - s3BucketName = [theS3BucketName retain]; - computerUUID = [theComputerUUID retain]; - creatorThread = [[NSThread currentThread] retain]; - } - return self; -} -- (void)dealloc { - [s3 release]; - [s3BucketName release]; - [computerUUID release]; - [creatorThread release]; - [super dealloc]; -} -- (NSData *)bucketDataForRelativePath:(NSString *)bucketDataRelativePath error:(NSError **)error { - NSAssert([NSThread currentThread] == creatorThread, @"must be on same thread!"); - - NSError *myError = nil; - NSData *data = [s3 dataAtPath:[FarkPath s3PathForBucketDataRelativePath:bucketDataRelativePath s3BucketName:s3BucketName computerUUID:computerUUID] error:&myError]; - if (data == nil) { - if ([myError isErrorWithDomain:[S3Service errorDomain] code:ERROR_NOT_FOUND]) { - SETNSERROR([ArqFark errorDomain], ERROR_NOT_FOUND, @"bucket data not found for path %@", bucketDataRelativePath); - } else { - if (error != NULL) { - *error = myError; - } - } - } - return data; -} -- (NSData *)dataForSHA1:(NSString *)sha1 error:(NSError **)error { - ServerBlob *sb = [self newServerBlobForSHA1:sha1 error:error]; - if (sb == nil) { - return nil; - } - NSData *data = [sb slurp:error]; - [sb release]; - return data; -} -- (ServerBlob *)newServerBlobForSHA1:(NSString *)sha1 error:(NSError **)error { -// NSAssert([NSThread currentThread] == creatorThread, @"must be on same thread!"); - NSString *s3Path = [NSString stringWithFormat:@"/%@/%@/objects/%@", s3BucketName, computerUUID, sha1]; - return [s3 newServerBlobAtPath:s3Path error:error]; -} -@end diff --git a/ArqPackSet.h b/ArqPackSet.h deleted file mode 100644 index 7238154..0000000 --- a/ArqPackSet.h +++ /dev/null @@ -1,28 +0,0 @@ -// -// ArqPackSet.h -// Arq -// -// Created by Stefan Reitshamer on 6/22/10. -// Copyright 2010 __MyCompanyName__. All rights reserved. -// - - -@class S3Service; -@class ServerBlob; - -@interface ArqPackSet : NSObject { - S3Service *s3; - NSString *s3BucketName; - NSString *computerUUID; - NSString *packSetName; - NSDictionary *packIndexEntries; -} -+ (NSString *)errorDomain; -- (id)initWithS3Service:(S3Service *)theS3 - s3BucketName:(NSString *)theS3BucketName - computerUUID:(NSString *)theComputerUUID - packSetName:(NSString *)thePackSetName; -- (NSString *)packSetName; -- (ServerBlob *)newServerBlobForSHA1:(NSString *)sha1 error:(NSError **)error; -- (BOOL)containsBlob:(BOOL *)contains forSHA1:(NSString *)sha1 packSHA1:(NSString **)packSHA1 error:(NSError **)error; -@end diff --git a/ArqPackSet.m b/ArqPackSet.m deleted file mode 100644 index 7458200..0000000 --- a/ArqPackSet.m +++ /dev/null @@ -1,212 +0,0 @@ -// -// ArqPackSet.m -// Arq -// -// Created by Stefan Reitshamer on 6/22/10. -// Copyright 2010 __MyCompanyName__. All rights reserved. -// - -#import "ArqPackSet.h" -#import "S3Service.h" -#import "RegexKitLite.h" -#import "DiskPackIndex.h" -#import "PackIndexEntry.h" -#import "SetNSError.h" -#import "NSErrorCodes.h" -#import "DiskPack.h" -#import "AppKeychain.h" -#import "S3AuthorizationProvider.h" -#import "NSError_extra.h" - -#define MAX_RETRIES (10) - -@interface ArqPackSet (internal) -- (ServerBlob *)newInternalServerBlobForSHA1:(NSString *)sha1 error:(NSError **)error; -- (BOOL)loadPackIndexEntries:(NSError **)error; -- (NSDictionary *)doLoadPackIndexEntries:(NSError **)error; -@end - -@implementation ArqPackSet -+ (NSString *)errorDomain { - return @"ArqPackSetErrorDomain"; -} - -- (id)initWithS3Service:(S3Service *)theS3 - s3BucketName:(NSString *)theS3BucketName - computerUUID:(NSString *)theComputerUUID - packSetName:(NSString *)thePackSetName { - if (self = [super init]) { - s3 = [theS3 retain]; - s3BucketName = [theS3BucketName retain]; - computerUUID = [theComputerUUID retain]; - packSetName = [thePackSetName retain]; - } - return self; -} -- (void)dealloc { - [s3 release]; - [s3BucketName release]; - [computerUUID release]; - [packSetName release]; - [packIndexEntries release]; - [super dealloc]; -} -- (NSString *)packSetName { - return packSetName; -} -- (ServerBlob *)newServerBlobForSHA1:(NSString *)sha1 error:(NSError **)error { - ServerBlob *sb = nil; - NSError *myError = nil; - NSUInteger i = 0; - NSAutoreleasePool *pool = nil; - for (i = 0; i < MAX_RETRIES; i++) { - [pool drain]; - pool = [[NSAutoreleasePool alloc] init]; - myError = nil; - sb = [self newInternalServerBlobForSHA1:sha1 error:&myError]; - if (sb == nil) { - if ([myError isErrorWithDomain:[ArqPackSet errorDomain] code:ERROR_PACK_INDEX_ENTRY_NOT_RESOLVABLE]) { - HSLogInfo(@"pack index entry not resolvable; reloading pack index entries from disk cache"); - [packIndexEntries release]; - packIndexEntries = nil; - } else { - break; - } - } else { - break; - } - } - [myError retain]; - [pool drain]; - [myError autorelease]; - if (sb == nil) { - if ([myError isErrorWithDomain:[ArqPackSet errorDomain] code:ERROR_PACK_INDEX_ENTRY_NOT_RESOLVABLE]) { - SETNSERROR([ArqPackSet errorDomain], ERROR_NOT_FOUND, @"failed %lu times to load blob for sha1 %@ from pack set %@", i, sha1, packSetName); - } else if (error != NULL) { - *error = myError; - } - } - return sb; -} -- (BOOL)containsBlob:(BOOL *)contains forSHA1:(NSString *)sha1 packSHA1:(NSString **)packSHA1 error:(NSError **)error { - if (packIndexEntries == nil && ![self loadPackIndexEntries:error]) { - return NO; - } - PackIndexEntry *pie = [packIndexEntries objectForKey:sha1]; - *contains = (pie != nil); - if (pie != nil) { - *packSHA1 = [pie packSHA1]; - } - return YES; -} -@end - -@implementation ArqPackSet (internal) -- (ServerBlob *)newInternalServerBlobForSHA1:(NSString *)sha1 error:(NSError **)error { - if (packIndexEntries == nil && ![self loadPackIndexEntries:error]) { - return nil; - } - PackIndexEntry *pie = [packIndexEntries objectForKey:sha1]; - if (pie == nil) { - SETNSERROR([ArqPackSet errorDomain], ERROR_NOT_FOUND, @"sha1 %@ not found in pack set %@", sha1, packSetName); - return NO; - } - DiskPack *diskPack = [[DiskPack alloc] initWithS3Service:s3 - s3BucketName:s3BucketName - computerUUID:computerUUID - packSetName:packSetName - packSHA1:[pie packSHA1] - targetUID:getuid() - targetGID:getgid()]; - ServerBlob *sb = nil; - do { - NSError *myError = nil; - if (![diskPack makeLocal:&myError]) { - NSString *msg = [NSString stringWithFormat:@"error making disk pack %@ (pack set %@, computerUUID %@, s3BucketName %@) containing sha1 %@ local: %@", [pie packSHA1], packSetName, computerUUID, s3BucketName, sha1, [myError localizedDescription]]; - HSLogError(@"%@", msg); - SETNSERROR([ArqPackSet errorDomain], ERROR_PACK_INDEX_ENTRY_NOT_RESOLVABLE, @"%@", msg); - break; - } - sb = [diskPack newServerBlobForObjectAtOffset:[pie offset] error:&myError]; - if (sb == nil) { - SETNSERROR([ArqPackSet errorDomain], ERROR_PACK_INDEX_ENTRY_NOT_RESOLVABLE, @"error reading sha1 %@ from disk pack %@ (pack set %@, computerUUID %@, s3BucketName %@): %@", sha1, [pie packSHA1], packSetName, computerUUID, s3BucketName, [myError localizedDescription]); - break; - } - } while(0); - [diskPack release]; - return sb; -} -- (BOOL)loadPackIndexEntries:(NSError **)error { - BOOL ret = YES; - NSAutoreleasePool *pool = nil; - for (;;) { - [pool drain]; - pool = [[NSAutoreleasePool alloc] init]; - NSError *myError = nil; - NSDictionary *entries = [self doLoadPackIndexEntries:&myError]; - if (entries != nil) { - packIndexEntries = [entries retain]; - break; - } - if ([myError code] != ERROR_NOT_FOUND) { - if (error != NULL) { - *error = myError; - } - ret = NO; - break; - } - // If it's a not-found error, it can be because Arq Agent replaced a pack with another one between when we got - // the S3 list and when we tried to make them local. - HSLogDebug(@"error loading pack index entries (retrying): %@", myError); - } - if (!ret && error != NULL) { - [*error retain]; - } - [pool drain]; - if (!ret && error != NULL) { - [*error autorelease]; - } - return ret; -} -- (NSDictionary *)doLoadPackIndexEntries:(NSError **)error { - NSMutableDictionary *entries = [NSMutableDictionary dictionary]; - NSString *packSHA1Prefix = [NSString stringWithFormat:@"/%@/%@/packsets/%@/", s3BucketName, computerUUID, packSetName]; - NSArray *packSHA1Paths = [s3 pathsWithPrefix:packSHA1Prefix error:error]; - if (packSHA1Paths == nil) { - return nil; - } - for (NSString *packSHA1Path in packSHA1Paths) { - NSRange sha1Range = [packSHA1Path rangeOfRegex:@"/(\\w+)\\.pack$" capture:1]; - if (sha1Range.location != NSNotFound) { - NSString *packSHA1 = [packSHA1Path substringWithRange:sha1Range]; - BOOL ret = NO; - DiskPackIndex *index = [[DiskPackIndex alloc] initWithS3Service:s3 - s3BucketName:s3BucketName - computerUUID:computerUUID - packSetName:packSetName - packSHA1:packSHA1 - targetUID:getuid() - targetGID:getgid()]; - do { - if (![index makeLocal:error]) { - break; - } - NSArray *pies = [index allPackIndexEntries:error]; - if (pies == nil) { - break; - } - HSLogTrace(@"found %lu entries in s3 pack sha1 %@ packset %@ computer %@ s3bucket %@", [pies count], packSHA1, packSetName, computerUUID, s3BucketName); - for (PackIndexEntry *pie in pies) { - [entries setObject:pie forKey:[pie objectSHA1]]; - } - ret = YES; - } while (0); - [index release]; - if (!ret) { - return nil; - } - } - } - return entries; -} -@end diff --git a/ArqRepo.h b/ArqRepo.h deleted file mode 100644 index 9ef0ace..0000000 --- a/ArqRepo.h +++ /dev/null @@ -1,43 +0,0 @@ -// -// ArqRepo.h -// Arq -// -// Created by Stefan Reitshamer on 6/23/10. -// Copyright 2010 __MyCompanyName__. All rights reserved. -// - - -@class S3Service; -@class ArqFark; -@class ArqPackSet; -@class Commit; -@class Tree; -@class ServerBlob; -@class CryptoKey; -@class BlobKey; - -@interface ArqRepo : NSObject { - NSString *bucketUUID; - ArqFark *arqFark; - CryptoKey *cryptoKey; - CryptoKey *stretchedCryptoKey; - ArqPackSet *treesPackSet; - ArqPackSet *blobsPackSet; -} -+ (NSString *)errorDomain; -- (id)initWithS3Service:(S3Service *)theS3 - s3BucketName:(NSString *)theS3BucketName - computerUUID:(NSString *)theComputerUUID - bucketUUID:(NSString *)theBucketUUID - encryptionPassword:(NSString *)theEncryptionPassword - salt:(NSData *)theEncryptionSalt - error:(NSError **)error; - -- (NSString *)bucketUUID; -- (BlobKey *)headBlobKey:(NSError **)error; -- (Commit *)commitForBlobKey:(BlobKey *)treeBlobKey error:(NSError **)error; -- (Tree *)treeForBlobKey:(BlobKey *)treeBlobKey error:(NSError **)error; -- (NSData *)blobDataForBlobKey:(BlobKey *)treeBlobKey error:(NSError **)error; -- (ServerBlob *)newServerBlobForBlobKey:(BlobKey *)treeBlobKey error:(NSError **)error; -- (BOOL)containsPackedBlob:(BOOL *)contains forBlobKey:(BlobKey *)theBlobKey packSetName:(NSString **)packSetName packSHA1:(NSString **)packSHA1 error:(NSError **)error; -@end diff --git a/ArqRepo.m b/ArqRepo.m deleted file mode 100644 index 7a38968..0000000 --- a/ArqRepo.m +++ /dev/null @@ -1,243 +0,0 @@ -// -// ArqRepo.m -// Arq -// -// Created by Stefan Reitshamer on 6/23/10. -// Copyright 2010 __MyCompanyName__. All rights reserved. -// - -#import "ArqRepo.h" -#import "ArqFark.h" -#import "ArqPackSet.h" -#import "Commit.h" -#import "Tree.h" -#import "NSErrorCodes.h" -#import "ServerBlob.h" -#import "DataInputStream.h" -#import "DecryptedInputStream.h" -#import "NSData-Encrypt.h" -#import "SetNSError.h" -#import "NSError_extra.h" -#import "GunzipInputStream.h" -#import "CryptoKey.h" -#import "BlobKey.h" -#import "Encryption.h" -#import "NSData-GZip.h" - - -@implementation ArqRepo -+ (NSString *)errorDomain { - return @"ArqRepoErrorDomain"; -} -- (id)initWithS3Service:(S3Service *)theS3 - s3BucketName:(NSString *)theS3BucketName - computerUUID:(NSString *)theComputerUUID - bucketUUID:(NSString *)theBucketUUID - encryptionPassword:(NSString *)theEncryptionPassword - salt:(NSData *)theEncryptionSalt - error:(NSError **)error { - if (self = [super init]) { - bucketUUID = [theBucketUUID retain]; - - if (theEncryptionPassword == nil) { - SETNSERROR([Encryption errorDomain], -1, @"missing encryption password"); - [self release]; - return nil; - } - - cryptoKey = [[CryptoKey alloc] initLegacyWithPassword:theEncryptionPassword error:error]; - if (cryptoKey == nil) { - [self release]; - return nil; - } - stretchedCryptoKey = [[CryptoKey alloc] initWithPassword:theEncryptionPassword salt:theEncryptionSalt error:error]; - if (stretchedCryptoKey == nil) { - [self release]; - return nil; - } - - arqFark = [[ArqFark alloc] initWithS3Service:theS3 s3BucketName:theS3BucketName computerUUID:theComputerUUID]; - treesPackSet = [[ArqPackSet alloc] initWithS3Service:theS3 s3BucketName:theS3BucketName computerUUID:theComputerUUID packSetName:[theBucketUUID stringByAppendingString:@"-trees"]]; - blobsPackSet = [[ArqPackSet alloc] initWithS3Service:theS3 s3BucketName:theS3BucketName computerUUID:theComputerUUID packSetName:[theBucketUUID stringByAppendingString:@"-blobs"]]; - } - return self; -} -- (void)dealloc { - [bucketUUID release]; - [cryptoKey release]; - [stretchedCryptoKey release]; - [arqFark release]; - [treesPackSet release]; - [blobsPackSet release]; - [super dealloc]; -} -- (NSString *)bucketUUID { - return bucketUUID; -} -- (BlobKey *)headBlobKey:(NSError **)error { - NSString *bucketDataRelativePath = [NSString stringWithFormat:@"/%@/refs/heads/master", bucketUUID]; - NSError *myError = nil; - NSData *data = [arqFark bucketDataForRelativePath:bucketDataRelativePath error:&myError]; - if (data == nil) { - if ([myError isErrorWithDomain:[ArqFark errorDomain] code:ERROR_NOT_FOUND]) { - SETNSERROR([ArqRepo errorDomain], ERROR_NOT_FOUND, @"no head for bucketUUID %@", bucketUUID); - } else { - if (error != NULL) { - *error = myError; - } - } - return nil; - } - NSString *sha1 = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]; - BOOL stretch = NO; - if ([sha1 length] > 40) { - stretch = [sha1 characterAtIndex:40] == 'Y'; - sha1 = [sha1 substringToIndex:40]; - } - return [[[BlobKey alloc] initWithSHA1:sha1 storageType:StorageTypeS3 stretchEncryptionKey:stretch compressed:NO] autorelease]; -} -- (Commit *)commitForBlobKey:(BlobKey *)commitBlobKey error:(NSError **)error { - NSError *myError = nil; - ServerBlob *sb = [treesPackSet newServerBlobForSHA1:[commitBlobKey sha1] error:&myError]; - if (sb == nil) { - if ([myError isErrorWithDomain:[ArqPackSet errorDomain] code:ERROR_NOT_FOUND]) { - HSLogDebug(@"commit %@ not found in pack set %@", commitBlobKey, [treesPackSet packSetName]); - SETNSERROR([ArqRepo errorDomain], ERROR_NOT_FOUND, @"commit %@ not found", commitBlobKey); - } else { - HSLogError(@"commit %@ not found for: %@", commitBlobKey, [myError localizedDescription]); - if (error != NULL) { - *error = myError; - } - } - return nil; - } - NSData *encrypted = [sb slurp:error]; - [sb release]; - if (encrypted == nil) { - return nil; - } - NSData *data = [encrypted decryptWithCryptoKey:([commitBlobKey stretchEncryptionKey] ? stretchedCryptoKey : cryptoKey) error:error]; - if (data == nil) { - return nil; - } - - DataInputStream *dis = [[DataInputStream alloc] initWithData:data]; - BufferedInputStream *bis = [[BufferedInputStream alloc] initWithUnderlyingStream:dis]; - Commit *commit = [[[Commit alloc] initWithBufferedInputStream:bis error:error] autorelease]; - [bis release]; - [dis release]; - return commit; -} - -// Returns NO if commit not found: -- (Tree *)treeForBlobKey:(BlobKey *)blobKey error:(NSError **)error { - NSError *myError = nil; - ServerBlob *sb = [treesPackSet newServerBlobForSHA1:[blobKey sha1] error:&myError]; - if (sb == nil) { - if ([myError isErrorWithDomain:[ArqPackSet errorDomain] code:ERROR_NOT_FOUND]) { - HSLogDebug(@"tree %@ not found in pack set %@", blobKey, [treesPackSet packSetName]); - SETNSERROR([ArqRepo errorDomain], ERROR_NOT_FOUND, @"tree %@ not found", blobKey); - } else { - HSLogError(@"error reading tree %@: %@", blobKey, [myError localizedDescription]); - if (error != NULL) { - *error = myError; - } - } - return nil; - } - NSData *encrypted = [sb slurp:error]; - [sb release]; - if (encrypted == nil) { - return nil; - } - NSData *data = [encrypted decryptWithCryptoKey:([blobKey stretchEncryptionKey] ? stretchedCryptoKey : cryptoKey) error:error]; - if (data == nil) { - return nil; - } - if ([blobKey compressed]) { - data = [data gzipInflate]; - } - DataInputStream *dis = [[DataInputStream alloc] initWithData:data]; - BufferedInputStream *bis = [[BufferedInputStream alloc] initWithUnderlyingStream:dis]; - Tree *tree = [[[Tree alloc] initWithBufferedInputStream:bis error:error] autorelease]; - [bis release]; - [dis release]; - return tree; -} -- (NSData *)blobDataForBlobKey:(BlobKey *)treeBlobKey error:(NSError **)error { - ServerBlob *sb = [self newServerBlobForBlobKey:treeBlobKey error:error]; - if (sb == nil) { - return nil; - } - NSData *data = [sb slurp:error]; - [sb release]; - return data; -} -- (ServerBlob *)newServerBlobForBlobKey:(BlobKey *)theBlobKey error:(NSError **)error { - NSError *myError = nil; - ServerBlob *sb = [blobsPackSet newServerBlobForSHA1:[theBlobKey sha1] error:&myError]; - if (sb == nil) { - if ([myError isErrorWithDomain:[ArqPackSet errorDomain] code:ERROR_NOT_FOUND]) { - HSLogTrace(@"%@ not found in pack set %@; looking in S3", theBlobKey, [blobsPackSet packSetName]); - sb = [arqFark newServerBlobForSHA1:[theBlobKey sha1] error:&myError]; - if (sb == nil) { - if ([myError isErrorWithDomain:[ArqFark errorDomain] code:ERROR_NOT_FOUND]) { - SETNSERROR([ArqRepo errorDomain], ERROR_NOT_FOUND, @"object %@ not found", theBlobKey); - } else { - if (error != NULL) { - *error = myError; - } - } - } - } else { - HSLogError(@"error trying to read from pack set: %@", [myError localizedDescription]); - if (error != NULL) { - *error = myError; - } - } - } - if (sb == nil) { - return nil; - } - - NSString *mimeType = [sb mimeType]; - NSString *downloadName = [sb downloadName]; - NSData *encrypted = [sb slurp:error]; - [sb release]; - sb = nil; - if (encrypted == nil) { - return nil; - } - NSData *data = [encrypted decryptWithCryptoKey:([theBlobKey stretchEncryptionKey] ? stretchedCryptoKey : cryptoKey) error:error]; - if (data == nil) { - return nil; - } - id blobIS = [[DataInputStream alloc] initWithData:data]; - sb = [[ServerBlob alloc] initWithInputStream:blobIS mimeType:mimeType downloadName:downloadName]; - [blobIS release]; - return sb; -} -- (BOOL)containsPackedBlob:(BOOL *)contains forBlobKey:(BlobKey *)theBlobKey packSetName:(NSString **)packSetName packSHA1:(NSString **)packSHA1 error:(NSError **)error { - if (![blobsPackSet containsBlob:contains forSHA1:[theBlobKey sha1] packSHA1:packSHA1 error:error]) { - return NO; - } - if (*contains) { - *packSetName = [blobsPackSet packSetName]; - return YES; - } - - if (![treesPackSet containsBlob:contains forSHA1:[theBlobKey sha1] packSHA1:packSHA1 error:error]) { - return NO; - } - if (*contains) { - *packSetName = [treesPackSet packSetName]; - } - return YES; -} - - -#pragma mark NSObject -- (NSString *)description { - return [NSString stringWithFormat:@"", bucketUUID]; -} -@end diff --git a/ArqRestoreCommand.h b/ArqRestoreCommand.h index 1b89ea8..aee6859 100644 --- a/ArqRestoreCommand.h +++ b/ArqRestoreCommand.h @@ -1,47 +1,18 @@ -/* - Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ +// +// ArqRestoreCommand.h +// arq_restore +// +// Created by Stefan Reitshamer on 7/25/14. +// +// +@class Target; -@class S3Service; @interface ArqRestoreCommand : NSObject { - NSString *accessKey; - NSString *secretKey; - NSString *encryptionPassword; - S3Service *s3; - NSString *path; - NSString *commitSHA1; - BOOL vFlag; + Target *target; } -- (BOOL)readArgc:(int)argc argv:(const char **)argv; -- (BOOL)execute:(NSError **)error; + +- (NSString *)errorDomain; +- (BOOL)executeWithArgc:(int)argc argv:(const char **)argv error:(NSError **)error; @end diff --git a/ArqRestoreCommand.m b/ArqRestoreCommand.m index c81498a..9a3eb76 100644 --- a/ArqRestoreCommand.m +++ b/ArqRestoreCommand.m @@ -1,305 +1,350 @@ -/* - Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ +// +// ArqRestoreCommand.m +// arq_restore +// +// Created by Stefan Reitshamer on 7/25/14. +// +// #import "ArqRestoreCommand.h" -#import "SetNSError.h" -#import "S3AuthorizationProvider.h" -#import "S3Service.h" -#import "RegexKitLite.h" -#import "DictNode.h" -#import "HTTP.h" -#import "Restorer.h" -#import "NSErrorCodes.h" -#import "NSError_extra.h" -#import "UserAndComputer.h" -#import "ArqSalt.h" -#import "ArqRepo.h" +#import "Target.h" +#import "AWSRegion.h" #import "BackupSet.h" -#import "ReflogPrinter.h" -#import "NSData-Encrypt.h" -#import "CryptoKey.h" +#import "S3Service.h" +#import "UserAndComputer.h" +#import "Bucket.h" +#import "Repo.h" -#define BUCKET_PLIST_SALT "BucketPL" - - -@interface ArqRestoreCommand (internal) -- (BOOL)printArqFolders:(NSError **)error; -- (BOOL)processPath:(NSError **)error; -- (BOOL)validateS3Keys:(NSError **)error; -@end - @implementation ArqRestoreCommand -- (id)init { - if (self = [super init]) { - char *theAccessKey = getenv("ARQ_ACCESS_KEY"); - if (theAccessKey != NULL) { - accessKey = [[NSString alloc] initWithUTF8String:theAccessKey]; - } - char *theSecretKey = getenv("ARQ_SECRET_KEY"); - if (theSecretKey != NULL) { - secretKey = [[NSString alloc] initWithUTF8String:theSecretKey]; - } - char *theEncryptionPassword = getenv("ARQ_ENCRYPTION_PASSWORD"); - if (theEncryptionPassword != NULL) { - encryptionPassword = [[NSString alloc] initWithUTF8String:theEncryptionPassword]; - } - if (accessKey != nil && secretKey != nil) { - S3AuthorizationProvider *sap = [[S3AuthorizationProvider alloc] initWithAccessKey:accessKey secretKey:secretKey]; - s3 = [[S3Service alloc] initWithS3AuthorizationProvider:sap useSSL:NO retryOnTransientError:YES]; - [sap release]; - } - } - return self; -} - (void)dealloc { - [accessKey release]; - [secretKey release]; - [encryptionPassword release]; - [s3 release]; - [path release]; - [commitSHA1 release]; + [target release]; [super dealloc]; } -- (BOOL)readArgc:(int)argc argv:(const char **)argv { - for (int i = 1; i < argc; i++) { - if (*argv[i] == '-') { - if (!strcmp(argv[i], "-l")) { - if (argc <= i+1) { - fprintf(stderr, "missing log_level argument (error,warn,info,debug or trace)\n"); - return NO; - } - i++; - NSString *level = [NSString stringWithUTF8String:argv[i]]; - setHSLogLevel(hsLogLevelForName(level)); - } else if (!strcmp(argv[i], "-v")) { - printf("%s version 20-Aug-2012\n", argv[0]); - exit(0); - } else { - fprintf(stderr, "unknown option %s\n", argv[i]); - return NO; - } - } else if (path == nil) { - path = [[NSString alloc] initWithUTF8String:argv[i]]; - } else if (commitSHA1 == nil) { - commitSHA1 = [[NSString alloc] initWithUTF8String:argv[i]]; - } else { - fprintf(stderr, "warning: ignoring argument '%s'\n", argv[i]); - } + +- (NSString *)errorDomain { + return @"ArqRestoreCommandErrorDomain"; +} + +- (BOOL)executeWithArgc:(int)argc argv:(const char **)argv error:(NSError **)error { + NSMutableArray *args = [NSMutableArray array]; + for (int i = 0; i < argc; i++) { + [args addObject:[[[NSString alloc] initWithBytes:argv[i] length:strlen(argv[i]) encoding:NSUTF8StringEncoding] autorelease]]; } + + if ([args count] < 2) { + SETNSERROR([self errorDomain], ERROR_USAGE, @"missing arguments"); + return NO; + } + + int index = 1; + if ([[args objectAtIndex:1] isEqualToString:@"-l"]) { + if ([args count] < 4) { + SETNSERROR([self errorDomain], ERROR_USAGE, @"missing arguments"); + return NO; + } + setHSLogLevel(hsLogLevelForName([args objectAtIndex:2])); + index += 2; + } + + NSString *cmd = [args objectAtIndex:index]; + + int targetParamsIndex = index + 1; + if ([cmd isEqualToString:@"listcomputers"]) { + // Valid command, but no additional args. + + } else if ([cmd isEqualToString:@"listfolders"]) { + if ([args count] < 4) { + SETNSERROR([self errorDomain], ERROR_USAGE, @"missing arguments for listfolders command"); + return NO; + } + targetParamsIndex = 4; + } else if ([cmd isEqualToString:@"restore"]) { + if ([args count] < 5) { + SETNSERROR([self errorDomain], ERROR_USAGE, @"missing arguments"); + return NO; + } + targetParamsIndex = 5; + } else { + SETNSERROR([self errorDomain], ERROR_USAGE, @"unknown command: %@", cmd); + return NO; + } + + if (targetParamsIndex >= argc) { + SETNSERROR([self errorDomain], ERROR_USAGE, @"missing target type params"); + return NO; + } + target = [[self targetForParams:[args subarrayWithRange:NSMakeRange(targetParamsIndex, argc - targetParamsIndex)] error:error] retain]; + if (target == nil) { + return NO; + } + + if ([cmd isEqualToString:@"listcomputers"]) { + if (![self listComputers:error]) { + return NO; + } + } else if ([cmd isEqualToString:@"listfolders"]) { + if (![self listBucketsForComputerUUID:[args objectAtIndex:2] encryptionPassword:[args objectAtIndex:3] error:error]) { + return NO; + } + } else if ([cmd isEqualToString:@"restore"]) { + if (![self restoreComputerUUID:[args objectAtIndex:2] bucketUUID:[args objectAtIndex:4] encryptionPassword:[args objectAtIndex:3] error:error]) { + return NO; + } + } else { + SETNSERROR([self errorDomain], ERROR_USAGE, @"unknown command: %@", cmd); + return NO; + } + return YES; } -- (BOOL)execute:(NSError **)error { - BOOL ret = YES; - if (path == nil) { - ret = [self printArqFolders:error]; + + +#pragma mark internal +- (Target *)targetForParams:(NSArray *)theParams error:(NSError **)error { + NSString *theTargetType = [theParams objectAtIndex:0]; + + Target *ret = nil; + if ([theTargetType isEqualToString:@"aws"]) { + if ([theParams count] != 4) { + SETNSERROR([self errorDomain], ERROR_USAGE, @"invalid aws parameters"); + return nil; + } + + NSString *theAccessKey = [theParams objectAtIndex:1]; + NSString *theSecretKey = [theParams objectAtIndex:2]; + NSString *theBucketName = [theParams objectAtIndex:3]; + AWSRegion *awsRegion = [self awsRegionForAccessKey:theAccessKey secretKey:theSecretKey bucketName:theBucketName error:error]; + if (awsRegion == nil) { + return nil; + } + NSURL *s3Endpoint = [awsRegion s3EndpointWithSSL:YES]; + int port = [[s3Endpoint port] intValue]; + NSString *portString = @""; + if (port != 0) { + portString = [NSString stringWithFormat:@":%d", port]; + } + NSURL *targetEndpoint = [NSURL URLWithString:[NSString stringWithFormat:@"%@://%@@%@%@/%@", [s3Endpoint scheme], theAccessKey, [s3Endpoint host], portString, theBucketName]]; + ret = [[[Target alloc] initWithEndpoint:targetEndpoint secret:theSecretKey passphrase:nil] autorelease]; + } else if ([theTargetType isEqualToString:@"sftp"]) { + if ([theParams count] != 6 && [theParams count] != 7) { + SETNSERROR([self errorDomain], ERROR_USAGE, @"invalid sftp parameters"); + return nil; + } + + NSString *hostname = [theParams objectAtIndex:1]; + int port = [[theParams objectAtIndex:2] intValue]; + NSString *path = [theParams objectAtIndex:3]; + NSString *username = [theParams objectAtIndex:4]; + NSString *secret = [theParams objectAtIndex:5]; + NSString *keyfilePassphrase = [theParams count] > 6 ? [theParams objectAtIndex:6] : nil; + + if (![path hasPrefix:@"/"]) { + path = [@"/~/" stringByAppendingString:path]; + } + NSString *escapedPath = (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)path, NULL, (CFStringRef)@"!*'();:@&=+$,?%#[]", kCFStringEncodingUTF8); + NSString *escapedUsername = (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)username, NULL, (CFStringRef)@"!*'();:@&=+$,?%#[]", kCFStringEncodingUTF8); + NSURL *endpoint = [NSURL URLWithString:[NSString stringWithFormat:@"sftp://%@@%@:%d%@", escapedUsername, hostname, port, escapedPath]]; + + ret = [[[Target alloc] initWithEndpoint:endpoint secret:secret passphrase:keyfilePassphrase] autorelease]; + } else if ([theTargetType isEqualToString:@"greenqloud"] + || [theTargetType isEqualToString:@"dreamobjects"] + || [theTargetType isEqualToString:@"googlecloudstorage"] + + || [theTargetType isEqualToString:@"s3compatible"]) { + if ([theParams count] != 4) { + SETNSERROR([self errorDomain], ERROR_USAGE, @"invalid %@ parameters", theTargetType); + return nil; + } + + NSString *theAccessKey = [theParams objectAtIndex:1]; + NSString *theSecretKey = [theParams objectAtIndex:2]; + NSString *theBucketName = [theParams objectAtIndex:3]; + NSString *theHostname = nil; + if ([theTargetType isEqualToString:@"greenqloud"]) { + theHostname = @"s.greenqloud.com"; + } else if ([theTargetType isEqualToString:@"dreamobjects"]) { + theHostname = @"objects.dreamhost.com"; + } else if ([theTargetType isEqualToString:@"googlecloudstorage"]) { + theHostname = @"storage.googleapis.com"; + } else { + SETNSERROR([self errorDomain], ERROR_USAGE, @"no hostname for target type: %@", theTargetType); + return nil; + } + + NSURL *endpoint = [NSURL URLWithString:[NSString stringWithFormat:@"https://%@@%@/%@", theAccessKey, theHostname, theBucketName]]; + ret = [[[Target alloc] initWithEndpoint:endpoint secret:theSecretKey passphrase:nil] autorelease]; + } else if ([theTargetType isEqualToString:@"googledrive"]) { + if ([theParams count] != 3) { + SETNSERROR([self errorDomain], ERROR_USAGE, @"invalid googledrive parameters"); + return nil; + } + + NSString *theRefreshToken = [theParams objectAtIndex:1]; + NSString *thePath = [theParams objectAtIndex:2]; + + NSString *escapedPath = (NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)thePath, CFSTR("/"), CFSTR("@?=&+"), kCFStringEncodingUTF8); + [escapedPath autorelease]; + + NSURL *endpoint = [NSURL URLWithString:[NSString stringWithFormat:@"googledrive://unknown_email_address@www.googleapis.com%@", escapedPath]]; + ret = [[[Target alloc] initWithEndpoint:endpoint secret:theRefreshToken passphrase:nil] autorelease]; } else { - ret = [self processPath:error]; - } + SETNSERROR([self errorDomain], ERROR_USAGE, @"unknown target type: %@", theTargetType); + return nil; + } return ret; } -@end -@implementation ArqRestoreCommand (internal) -- (BOOL)printArqFolders:(NSError **)error { - if (![self validateS3Keys:error]) { +- (AWSRegion *)awsRegionForAccessKey:(NSString *)theAccessKey secretKey:(NSString *)theSecretKey bucketName:(NSString *)theBucketName error:(NSError **)error { + return nil; +} + +- (BOOL)listComputers:(NSError **)error { + NSArray *expandedTargetList = [self expandedTargetList:error]; + if (expandedTargetList == nil) { return NO; } - NSArray *backupSets = [BackupSet allBackupSetsForAccessKeyID:accessKey secretAccessKey:secretKey error:error]; - if (backupSets == nil) { + NSMutableArray *ret = [NSMutableArray array]; + for (Target *theTarget in expandedTargetList) { + NSError *myError = nil; + HSLogDebug(@"getting backup sets for %@", theTarget); + + NSArray *backupSets = [BackupSet allBackupSetsForTarget:theTarget targetConnectionDelegate:nil error:&myError]; + if (backupSets == nil) { + if ([myError isErrorWithDomain:[S3Service errorDomain] code:S3SERVICE_ERROR_AMAZON_ERROR] && [[[myError userInfo] objectForKey:@"HTTPStatusCode"] intValue] == 403) { + HSLogError(@"access denied getting backup sets for %@", theTarget); + } else { + HSLogError(@"error getting backup sets for %@: %@", theTarget, myError); + SETERRORFROMMYERROR; + return nil; + } + } else { + printf("target: %s\n", [[theTarget endpointDisplayName] UTF8String]); + for (BackupSet *backupSet in backupSets) { + printf("\tcomputer %s\n", [[backupSet computerUUID] UTF8String]); + printf("\t\t%s (%s)\n", [[[backupSet userAndComputer] computerName] UTF8String], [[[backupSet userAndComputer] userName] UTF8String]); + } + } + } + return ret; +} +- (NSArray *)expandedTargetList:(NSError **)error { + NSMutableArray *expandedTargetList = [NSMutableArray arrayWithObject:target]; + if ([target targetType] == kTargetAWS + || [target targetType] == kTargetDreamObjects + || [target targetType] == kTargetGoogleCloudStorage + || [target targetType] == kTargetGreenQloud + || [target targetType] == kTargetS3Compatible) { + NSError *myError = nil; + NSArray *targets = [self expandedTargetsForS3Target:target error:&myError]; + if (targets == nil) { + HSLogError(@"failed to expand target list for %@: %@", target, myError); + } else { + [expandedTargetList setArray:targets]; + HSLogDebug(@"expandedTargetList is now: %@", expandedTargetList); + } + } + return expandedTargetList; +} +- (NSArray *)expandedTargetsForS3Target:(Target *)theTarget error:(NSError **)error { + S3Service *s3 = [theTarget s3:error]; + if (s3 == nil) { + return nil; + } + NSArray *s3BucketNames = [s3 s3BucketNamesWithTargetConnectionDelegate:nil error:error]; + if (s3BucketNames == nil) { + return nil; + } + HSLogDebug(@"s3BucketNames for %@: %@", theTarget, s3BucketNames); + + NSURL *originalEndpoint = [theTarget endpoint]; + NSMutableArray *ret = [NSMutableArray array]; + + for (NSString *s3BucketName in s3BucketNames) { + NSURL *endpoint = nil; + if ([theTarget targetType] == kTargetAWS) { + NSString *location = [s3 locationOfS3Bucket:s3BucketName targetConnectionDelegate:nil error:error]; + if (location == nil) { + return nil; + } + AWSRegion *awsRegion = [AWSRegion regionWithLocation:location]; + HSLogDebug(@"awsRegion for s3BucketName %@: %@", s3BucketName, location); + + NSURL *s3Endpoint = [awsRegion s3EndpointWithSSL:YES]; + HSLogDebug(@"s3Endpoint: %@", s3Endpoint); + endpoint = [NSURL URLWithString:[NSString stringWithFormat:@"https://%@@%@/%@", [originalEndpoint user], [s3Endpoint host], s3BucketName]]; + } else { + endpoint = [NSURL URLWithString:[NSString stringWithFormat:@"%@://%@@%@/%@", [originalEndpoint scheme], [originalEndpoint user], [originalEndpoint host], s3BucketName]]; + } + HSLogDebug(@"endpoint: %@", endpoint); + + Target *theTarget = [[[Target alloc] initWithEndpoint:endpoint secret:[theTarget secret:NULL] passphrase:[theTarget passphrase:NULL]] autorelease]; + [ret addObject:theTarget]; + } + return ret; +} + +- (BOOL)listBucketsForComputerUUID:(NSString *)theComputerUUID encryptionPassword:(NSString *)theEncryptionPassword error:(NSError **)error { + NSArray *buckets = [Bucket bucketsWithTarget:target computerUUID:theComputerUUID encryptionPassword:theEncryptionPassword targetConnectionDelegate:nil error:error]; + if (buckets == nil) { return NO; } - for (BackupSet *backupSet in backupSets) { - NSString *s3BucketName = [backupSet s3BucketName]; - NSString *computerUUID = [backupSet computerUUID]; - UserAndComputer *uac = [backupSet userAndComputer]; - printf("S3 bucket: %s", [s3BucketName UTF8String]); - if (uac != nil) { - printf(" %s (%s)", [[uac computerName] UTF8String], [[uac userName] UTF8String]); - } else { - printf(" (unknown computer)"); - } - printf(" UUID %s", [computerUUID UTF8String]); - NSString *computerBucketsPrefix = [NSString stringWithFormat:@"/%@/%@/buckets", s3BucketName, computerUUID]; - NSArray *s3BucketUUIDPaths = [s3 pathsWithPrefix:computerBucketsPrefix error:error]; - if (s3BucketUUIDPaths == nil) { - return NO; - } - if ([s3BucketUUIDPaths count] == 0) { - printf(" (no folders found)"); - } - printf("\n"); - for (NSString *uuidPath in s3BucketUUIDPaths) { - NSData *data = [s3 dataAtPath:uuidPath error:error]; - if (data == nil) { - return NO; - } - - // Decrypt the plist if necessary: - unsigned long length = 9; - if ([data length] < length) { - length = [data length]; - } - if (length >= 9 && !strncmp([data bytes], "encrypted", length)) { - NSData *encryptedData = [data subdataWithRange:NSMakeRange(9, [data length] - 9)]; - CryptoKey *cryptoKey = [[[CryptoKey alloc] initWithPassword:encryptionPassword salt:[NSData dataWithBytes:BUCKET_PLIST_SALT length:8] error:error] autorelease]; - if (cryptoKey == nil) { - return NO; - } - data = [encryptedData decryptWithCryptoKey:cryptoKey error:error]; - if (data == nil) { - return NO; - } - } - - DictNode *plist = [DictNode dictNodeWithXMLData:data error:error]; - if (plist == nil) { - return NO; - } - printf(" %s\n", [[[plist stringNodeForKey:@"LocalPath"] stringValue] UTF8String]); - printf(" UUID: %s\n", [[uuidPath lastPathComponent] UTF8String]); - printf(" reflog command: arq_restore %s reflog\n", [uuidPath UTF8String]); - printf(" restore command: arq_restore %s\n", [uuidPath UTF8String]); - printf("\n"); - } + + printf("target %s\n", [[target endpointDisplayName] UTF8String]); + printf("computer %s\n", [theComputerUUID UTF8String]); + + for (Bucket *bucket in buckets) { + printf("\tfolder %s\n", [[bucket localPath] UTF8String]); + printf("\t\tuuid %s\n", [[bucket bucketUUID] UTF8String]); + } + return YES; } -- (BOOL)processPath:(NSError **)error { - if (![self validateS3Keys:error]) { +- (BOOL)restoreComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID encryptionPassword:(NSString *)theEncryptionPassword error:(NSError **)error { + Bucket *myBucket = nil; + NSArray *expandedTargetList = [self expandedTargetList:error]; + if (expandedTargetList == nil) { return NO; } - if (encryptionPassword == nil) { - SETNSERROR(@"ArqErrorDomain", -1, @"missing ARQ_ENCRYPTION_PASSWORD environment variable"); - return NO; - } - NSString *pattern = @"^/([^/]+)/([^/]+)/buckets/([^/]+)"; - NSRange s3BucketNameRange = [path rangeOfRegex:pattern capture:1]; - NSRange computerUUIDRange = [path rangeOfRegex:pattern capture:2]; - NSRange bucketUUIDRange = [path rangeOfRegex:pattern capture:3]; - if (s3BucketNameRange.location == NSNotFound || computerUUIDRange.location == NSNotFound || bucketUUIDRange.location == NSNotFound) { - SETNSERROR(@"ArqErrorDomain", -1, @"invalid S3 path"); - return NO; - } - NSString *s3BucketName = [path substringWithRange:s3BucketNameRange]; - NSString *computerUUID = [path substringWithRange:computerUUIDRange]; - NSString *bucketUUID = [path substringWithRange:bucketUUIDRange]; - - NSString *bucketName = @"(unknown)"; - NSData *data = [s3 dataAtPath:path error:NULL]; - if (data != nil) { - if (!strncmp([data bytes], "encrypted", 9)) { - data = [data subdataWithRange:NSMakeRange(9, [data length] - 9)]; - CryptoKey *cryptoKey = [[[CryptoKey alloc] initWithPassword:encryptionPassword salt:[NSData dataWithBytes:BUCKET_PLIST_SALT length:8] error:error] autorelease]; - if (cryptoKey == nil) { - return NO; - } - data = [data decryptWithCryptoKey:cryptoKey error:error]; - if (data == nil) { - HSLogError(@"failed to decrypt %@", path); - return NO; - } - } - DictNode *plist = [DictNode dictNodeWithXMLData:data error:NULL]; - if (plist != nil) { - bucketName = [[plist stringNodeForKey:@"BucketName"] stringValue]; - } - } - - NSError *uacError = nil; - NSData *uacData = [s3 dataAtPath:[NSString stringWithFormat:@"/%@/%@/computerinfo", s3BucketName, computerUUID] error:&uacError]; - UserAndComputer *uac = nil; - if (uacData != nil) { - uac = [[[UserAndComputer alloc] initWithXMLData:uacData error:&uacError] autorelease]; - } - - NSError *saltError = nil; - ArqSalt *arqSalt = [[[ArqSalt alloc] initWithAccessKeyID:accessKey secretAccessKey:secretKey s3BucketName:s3BucketName computerUUID:computerUUID] autorelease]; - NSData *salt = [arqSalt salt:&saltError]; - if (salt == nil) { - if ([saltError code] != ERROR_NOT_FOUND) { - if (error != NULL) { - *error = saltError; - } + for (Target *theTarget in expandedTargetList) { + NSArray *buckets = [Bucket bucketsWithTarget:theTarget computerUUID:theComputerUUID encryptionPassword:theEncryptionPassword targetConnectionDelegate:nil error:error]; + if (buckets == nil) { return NO; } + for (Bucket *bucket in buckets) { + if ([[bucket bucketUUID] isEqualToString:theBucketUUID]) { + myBucket = bucket; + break; + } + } + + if (myBucket != nil) { + break; + } + } + if (myBucket == nil) { + SETNSERROR([self errorDomain], ERROR_NOT_FOUND, @"folder %@ not found", theBucketUUID); + return NO; } - ArqRepo *repo = [[[ArqRepo alloc] initWithS3Service:s3 s3BucketName:s3BucketName computerUUID:computerUUID bucketUUID:bucketUUID encryptionPassword:encryptionPassword salt:salt error:error] autorelease]; + Repo *repo = [[[Repo alloc] initWithBucket:myBucket encryptionPassword:theEncryptionPassword targetUID:getuid() targetGID:getgid() loadExistingMutablePackFiles:NO targetConnectionDelegate:nil repoDelegate:nil error:error] autorelease]; if (repo == nil) { return NO; } - if ([commitSHA1 isEqualToString:@"reflog"]) { - printf("printing reflog for %s\n", [bucketName UTF8String]); - ReflogPrinter *printer = [[[ReflogPrinter alloc] initWithS3BucketName:s3BucketName computerUUID:computerUUID bucketUUID:bucketUUID s3:s3 repo:repo] autorelease]; - if (![printer printReflog:error]) { - return NO; - } - } else { - printf("restoring %s from ", [bucketName UTF8String]); - if (uac != nil) { - printf("%s (%s)", [[uac computerName] UTF8String], [[uac userName] UTF8String]); - } else { - printf("(unknown computer)"); - } - printf(" to %s/%s\n", [[[NSFileManager defaultManager] currentDirectoryPath] UTF8String], [bucketName UTF8String]); - - Restorer *restorer = [[[Restorer alloc] initWithRepo:repo bucketName:bucketName commitSHA1:commitSHA1] autorelease]; - if (![restorer restore:error]) { - return NO; - } - printf("restored files are in %s\n", [bucketName fileSystemRepresentation]); - NSDictionary *errorsByPath = [restorer errorsByPath]; - if ([errorsByPath count] > 0) { - printf("Errors occurred:\n"); - NSArray *sortedKeys = [[errorsByPath allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]; - for (NSString *key in sortedKeys) { - printf("%s\t\t%s\n", [key UTF8String], [[[errorsByPath objectForKey:key] localizedDescription] UTF8String]); - } - } - } - return YES; -} -- (BOOL)validateS3Keys:(NSError **)error { - if (accessKey == nil) { - SETNSERROR(@"ArqErrorDomain", -1, @"missing ARQ_ACCESS_KEY environment variable"); - return NO; - } - if (secretKey == nil) { - SETNSERROR(@"ArqErrorDomain", -1, @"missing ARQ_SECRET_KEY environment variable"); + + BlobKey *commitBlobKey = [repo headBlobKey:error]; + if (commitBlobKey == nil) { return NO; } + + printf("target %s\n", [[[myBucket target] endpointDisplayName] UTF8String]); + printf("computer %s\n", [[myBucket computerUUID] UTF8String]); + printf("\nrestoring folder %s\n\n", [[myBucket localPath] UTF8String]); + return YES; } + @end diff --git a/ArqSalt.h b/ArqSalt.h index ae8d9e9..5868cfd 100644 --- a/ArqSalt.h +++ b/ArqSalt.h @@ -1,25 +1,24 @@ // -// ArqSalt.h -// Arq -// // Created by Stefan Reitshamer on 7/16/11. -// Copyright 2011 __MyCompanyName__. All rights reserved. +// Copyright 2011 Haystack Software. All rights reserved. // - +#import "TargetConnection.h" +@class AWSRegion; +@class Target; @interface ArqSalt : NSObject { - NSString *accessKeyID; - NSString *secretAccessKey; - NSString *s3BucketName; + Target *target; + uid_t uid; + gid_t gid; NSString *computerUUID; - NSString *localPath; - NSString *s3Path; } -- (id)initWithAccessKeyID:(NSString *)theAccessKeyID - secretAccessKey:(NSString *)theSecretAccessKey - s3BucketName:(NSString *)theS3BucketName - computerUUID:(NSString *)theComputerUUID; -- (NSData *)salt:(NSError **)error; +- (id)initWithTarget:(Target *)theTarget + targetUID:(uid_t)theTargetUID + targetGID:(gid_t)theTargetGID + computerUUID:(NSString *)theComputerUUID; +- (NSData *)saltWithTargetConnectionDelegate:(id )theDelegate error:(NSError **)error; +- (BOOL)saveSalt:(NSData *)theSalt targetConnectionDelegate:(id )theDelegate error:(NSError **)error; +- (NSData *)createSaltWithTargetConnectionDelegate:(id )theDelegate error:(NSError **)error; @end diff --git a/ArqSalt.m b/ArqSalt.m index d10fb8b..54ff82b 100644 --- a/ArqSalt.m +++ b/ArqSalt.m @@ -1,70 +1,77 @@ // -// ArqSalt.m -// Arq -// // Created by Stefan Reitshamer on 7/16/11. -// Copyright 2011 __MyCompanyName__. All rights reserved. +// Copyright 2011 Haystack Software. All rights reserved. // #import "ArqSalt.h" -#import "S3AuthorizationProvider.h" -#import "S3Service.h" -#import "Blob.h" -#import "BlobACL.h" #import "NSFileManager_extra.h" #import "UserLibrary_Arq.h" +#import "Target.h" +#import "TargetConnection.h" +#import "Streams.h" + #define SALT_LENGTH (8) -@interface ArqSalt (internal) -- (NSData *)createRandomSalt; -@end @implementation ArqSalt -- (id)initWithAccessKeyID:(NSString *)theAccessKeyID - secretAccessKey:(NSString *)theSecretAccessKey - s3BucketName:(NSString *)theS3BucketName - computerUUID:(NSString *)theComputerUUID { +- (id)initWithTarget:(Target *)theTarget + targetUID:(uid_t)theTargetUID + targetGID:(gid_t)theTargetGID + computerUUID:(NSString *)theComputerUUID { if (self = [super init]) { - accessKeyID = [theAccessKeyID retain]; - secretAccessKey = [theSecretAccessKey retain]; - s3BucketName = [theS3BucketName retain]; + target = [theTarget retain]; + uid = theTargetUID; + gid = theTargetGID; computerUUID = [theComputerUUID retain]; - localPath = [[NSString alloc] initWithFormat:@"%@/Cache.noindex/%@/%@/salt.dat", [UserLibrary arqUserLibraryPath], s3BucketName, computerUUID]; - s3Path = [[NSString alloc] initWithFormat:@"/%@/%@/salt", s3BucketName, computerUUID]; } return self; } - (void)dealloc { - [accessKeyID release]; - [secretAccessKey release]; - [s3BucketName release]; + [target release]; [computerUUID release]; - [localPath release]; - [s3Path release]; [super dealloc]; } -- (NSData *)salt:(NSError **)error { - NSData *ret = [NSData dataWithContentsOfFile:localPath options:NSUncachedRead error:error]; +- (NSData *)saltWithTargetConnectionDelegate:(id)theDelegate error:(NSError **)error { + NSData *ret = [NSData dataWithContentsOfFile:[self localPath] options:NSUncachedRead error:error]; if (ret == nil) { - S3AuthorizationProvider *sap = [[[S3AuthorizationProvider alloc] initWithAccessKey:accessKeyID secretKey:secretAccessKey] autorelease]; - S3Service *s3 = [[[S3Service alloc] initWithS3AuthorizationProvider:sap useSSL:YES retryOnTransientError:NO] autorelease]; - ret = [s3 dataAtPath:s3Path error:error]; - if (ret == nil) { - return nil; - } - NSError *myError = nil; - if (![[NSFileManager defaultManager] ensureParentPathExistsForPath:localPath error:&myError] - || ![ret writeToFile:localPath options:NSAtomicWrite error:&myError]) { - HSLogError(@"error caching salt data to %@: %@", localPath, myError); - } + id targetConnection = [target newConnection]; + do { + ret = [targetConnection saltDataForComputerUUID:computerUUID delegate:theDelegate error:error]; + if (ret != nil) { + NSError *myError = nil; + if (![[NSFileManager defaultManager] ensureParentPathExistsForPath:[self localPath] targetUID:uid targetGID:gid error:&myError] + || ![Streams writeData:ret atomicallyToFile:[self localPath] targetUID:uid targetGID:gid bytesWritten:NULL error:&myError]) { + HSLogError(@"error caching salt data to %@: %@", [self localPath], myError); + } + } + } while(0); + [targetConnection release]; } return ret; } -@end +- (BOOL)saveSalt:(NSData *)theSalt targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + id targetConnection = [target newConnection]; + BOOL ret = YES; + do { + ret = [targetConnection setSaltData:theSalt forComputerUUID:computerUUID delegate:theDelegate error:error] + && [[NSFileManager defaultManager] ensureParentPathExistsForPath:[self localPath] targetUID:uid targetGID:gid error:error] + && [Streams writeData:theSalt atomicallyToFile:[self localPath] targetUID:uid targetGID:gid bytesWritten:NULL error:error]; + } while (0); + [targetConnection release]; + return ret; +} +- (NSData *)createSaltWithTargetConnectionDelegate:(id)theDelegate error:(NSError **)error { + NSData *theSalt = [self createRandomSalt]; + if (![self saveSalt:theSalt targetConnectionDelegate:theDelegate error:error]) { + return nil; + } + return theSalt; +} + -@implementation ArqSalt (internal) +#pragma mark internal - (NSData *)createRandomSalt { unsigned char buf[SALT_LENGTH]; for (NSUInteger i = 0; i < SALT_LENGTH; i++) { @@ -72,4 +79,7 @@ } return [[[NSData alloc] initWithBytes:buf length:SALT_LENGTH] autorelease]; } +- (NSString *)localPath { + return [NSString stringWithFormat:@"%@/Cache.noindex/%@/%@/salt.dat", [UserLibrary arqUserLibraryPath], [target targetUUID], computerUUID]; +} @end diff --git a/ArqVerifyCommand.h b/ArqVerifyCommand.h deleted file mode 100644 index a0af253..0000000 --- a/ArqVerifyCommand.h +++ /dev/null @@ -1,26 +0,0 @@ -// -// ArqVerifyCommand.h -// arq_restore -// -// Created by Stefan Reitshamer on 6/17/10. -// Copyright 2010 __MyCompanyName__. All rights reserved. -// - - -@class S3Service; - -@interface ArqVerifyCommand : NSObject { - NSString *accessKey; - NSString *secretKey; - NSString *encryptionPassword; - S3Service *s3; - NSSet *objectSHA1s; - BOOL verbose; -} -- (id)initWithAccessKey:(NSString *)theAccessKey secretKey:(NSString *)theSecretKey encryptionPassword:(NSString *)theEncryptionPassword; -- (void)setVerbose:(BOOL)isVerbose; -- (BOOL)verifyAll:(NSError **)error; -- (BOOL)verifyS3BucketName:(NSString *)s3BucketName error:(NSError **)error; -- (BOOL)verifyS3BucketName:(NSString *)s3BucketName computerUUID:(NSString *)computerUUID error:(NSError **)error; -- (BOOL)verifyS3BucketName:(NSString *)s3BucketName computerUUID:(NSString *)computerUUID bucketUUID:(NSString *)bucketUUID error:(NSError **)error; -@end diff --git a/ArqVerifyCommand.m b/ArqVerifyCommand.m deleted file mode 100644 index 36c2878..0000000 --- a/ArqVerifyCommand.m +++ /dev/null @@ -1,194 +0,0 @@ -// -// ArqVerifyCommand.m -// arq_restore -// -// Created by Stefan Reitshamer on 6/17/10. -// Copyright 2010 __MyCompanyName__. All rights reserved. -// - -#import "ArqVerifyCommand.h" -#import "S3AuthorizationProvider.h" -#import "S3Service.h" -#import "HTTP.h" -#import "RegexKitLite.h" -#import "BucketVerifier.h" -#import "NSError_extra.h" -#import "NSErrorCodes.h" -#import "ArqSalt.h" -#import "ArqRepo.h" - -@interface ArqVerifyCommand (internal) -- (BOOL)loadObjectSHA1sForS3BucketName:(NSString *)s3BucketName computerUUID:(NSString *)computerUUID error:(NSError **)error; -@end - -@implementation ArqVerifyCommand -- (id)initWithAccessKey:(NSString *)theAccessKey secretKey:(NSString *)theSecretKey encryptionPassword:(NSString *)theEncryptionPassword { - if (self = [super init]) { - accessKey = [theAccessKey retain]; - secretKey = [theSecretKey retain]; - encryptionPassword = [theEncryptionPassword retain]; - S3AuthorizationProvider *sap = [[S3AuthorizationProvider alloc] initWithAccessKey:accessKey secretKey:secretKey]; - s3 = [[S3Service alloc] initWithS3AuthorizationProvider:sap useSSL:NO retryOnTransientError:YES]; - [sap release]; - } - return self; -} -- (void)dealloc { - [accessKey release]; - [secretKey release]; - [encryptionPassword release]; - [s3 release]; - [super dealloc]; -} -- (void)setVerbose:(BOOL)isVerbose { - verbose = isVerbose; -} -- (BOOL)verifyAll:(NSError **)error { - NSArray *s3BucketNames = [S3Service s3BucketNamesForAccessKeyID:accessKey]; - for (NSString *s3BucketName in s3BucketNames) { - printf("s3bucket name: %s\n", [s3BucketName UTF8String]); - } - for (NSString *s3BucketName in s3BucketNames) { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - BOOL ret = [self verifyS3BucketName:s3BucketName error:error]; - if (error != NULL) { - [*error retain]; - } - [pool drain]; - if (error != NULL) { - [*error autorelease]; - } - if (!ret) { - return NO; - } - } - return YES; -} -- (BOOL)verifyS3BucketName:(NSString *)s3BucketName error:(NSError **)error { - printf("verifying s3Bucket %s\n", [s3BucketName UTF8String]); - NSString *computerUUIDPrefix = [NSString stringWithFormat:@"/%@/", s3BucketName]; - NSError *myError = nil; - NSArray *computerUUIDs = [s3 commonPrefixesForPathPrefix:computerUUIDPrefix delimiter:@"/" error:&myError]; - if (computerUUIDs == nil) { - if ([myError isErrorWithDomain:[S3Service errorDomain] code:ERROR_NOT_FOUND]) { - // Skip. - printf("no computer UUIDs found in bucket %s\n", [s3BucketName UTF8String]); - return YES; - } else { - if (error != NULL) { - *error = myError; - } - return NO; - } - } - for (NSString *computerUUID in computerUUIDs) { - printf("found computer UUID %s\n", [computerUUID UTF8String]); - } - for (NSString *computerUUID in computerUUIDs) { - if (![self verifyS3BucketName:s3BucketName computerUUID:computerUUID error:error]) { - return NO; - } - } - return YES; -} -- (BOOL)verifyS3BucketName:(NSString *)s3BucketName computerUUID:(NSString *)computerUUID error:(NSError **)error { - printf("\nverifying computerUUID %s s3Bucket %s\n", [computerUUID UTF8String], [s3BucketName UTF8String]); - NSString *computerBucketsPrefix = [NSString stringWithFormat:@"/%@/%@/buckets", s3BucketName, computerUUID]; - NSArray *s3BucketUUIDPaths = [s3 pathsWithPrefix:computerBucketsPrefix error:error]; - if (s3BucketUUIDPaths == nil) { - return NO; - } - - NSMutableArray *bucketUUIDs = [NSMutableArray array]; - for (NSString *s3BucketUUIDPath in s3BucketUUIDPaths) { - NSString *bucketUUID = [s3BucketUUIDPath lastPathComponent]; - printf("found bucket UUID %s\n", [bucketUUID UTF8String]); - [bucketUUIDs addObject:bucketUUID]; - } - - [objectSHA1s release]; - objectSHA1s = nil; - if (![self loadObjectSHA1sForS3BucketName:s3BucketName computerUUID:computerUUID error:error]) { - return NO; - } - - BOOL ret = YES; - NSAutoreleasePool *pool = nil; - for (NSString *bucketUUID in bucketUUIDs) { - [pool release]; - pool = [[NSAutoreleasePool alloc] init]; - if (![self verifyS3BucketName:s3BucketName computerUUID:computerUUID bucketUUID:bucketUUID error:error]) { - ret = NO; - break; - } - } - if (!ret && error != NULL) { - [*error retain]; - } - [pool drain]; - if (!ret && error != NULL) { - [*error autorelease]; - } - return ret; -} -- (BOOL)verifyS3BucketName:(NSString *)s3BucketName computerUUID:(NSString *)computerUUID bucketUUID:(NSString *)bucketUUID error:(NSError **)error { - if (objectSHA1s == nil) { - if (![self loadObjectSHA1sForS3BucketName:s3BucketName computerUUID:computerUUID error:error]) { - return NO; - } - } - - NSError *saltError = nil; - ArqSalt *arqSalt = [[[ArqSalt alloc] initWithAccessKeyID:accessKey secretAccessKey:secretKey s3BucketName:s3BucketName computerUUID:computerUUID] autorelease]; - NSData *salt = [arqSalt salt:&saltError]; - if (salt == nil) { - if ([saltError code] != ERROR_NOT_FOUND) { - if (error != NULL) { - *error = saltError; - } - return NO; - } - } - ArqRepo *repo = [[[ArqRepo alloc] initWithS3Service:s3 - s3BucketName:s3BucketName - computerUUID:computerUUID - bucketUUID:bucketUUID - encryptionPassword:encryptionPassword - salt:salt - error:error] autorelease]; - if (repo == nil) { - return NO; - } - printf("\nverifying bucketUUID %s computerUUID %s s3Bucket %s\n", [bucketUUID UTF8String], [computerUUID UTF8String], [s3BucketName UTF8String]); - - BucketVerifier *bucketVerifier = [[[BucketVerifier alloc] initWithS3Service:s3 - s3BucketName:s3BucketName - computerUUID:computerUUID - bucketUUID:bucketUUID - s3ObjectSHA1s:objectSHA1s - verbose:verbose - repo:repo] autorelease]; - if (![bucketVerifier verify:error]) { - return NO; - } - return YES; -} -@end - -@implementation ArqVerifyCommand (internal) -- (BOOL)loadObjectSHA1sForS3BucketName:(NSString *)s3BucketName computerUUID:(NSString *)computerUUID error:(NSError **)error { - NSMutableSet *theObjectSHA1s = [NSMutableSet set]; - NSString *objectsPrefix = [NSString stringWithFormat:@"/%@/%@/objects", s3BucketName, computerUUID]; - printf("loading S3 object SHA1s with prefix %s\n", [objectsPrefix UTF8String]); - NSArray *objectPaths = [s3 pathsWithPrefix:objectsPrefix error:error]; - if (objectPaths == nil) { - return NO; - } - for (NSString *objectPath in objectPaths) { - [theObjectSHA1s addObject:[objectPath lastPathComponent]]; - } - objectSHA1s = [theObjectSHA1s retain]; - printf("loaded %u object SHA1s with prefix %s\n", [objectSHA1s count], [objectsPrefix UTF8String]); - return YES; -} -@end diff --git a/BackupSet.h b/BackupSet.h index 7dd566e..a44d0ec 100644 --- a/BackupSet.h +++ b/BackupSet.h @@ -1,53 +1,30 @@ -/* - Copyright (c) 2010, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ +// +// BackupSet.h +// Arq +// +// Created by Stefan Reitshamer on 4/11/11. +// Copyright 2011 Haystack Software. All rights reserved. +// @class UserAndComputer; +@class AppConfig; +@class Target; +@protocol TargetConnectionDelegate; + @interface BackupSet : NSObject { - NSString *accessKeyID; - NSString *secretAccessKey; - NSString *s3BucketName; + Target *target; NSString *computerUUID; UserAndComputer *uac; } -+ (NSArray *)allBackupSetsForAccessKeyID:(NSString *)theAccessKeyID secretAccessKey:(NSString *)theSecretAccessKey error:(NSError **)error; ++ (NSArray *)allBackupSetsForTarget:(Target *)theTarget targetConnectionDelegate:(id )theDelegate error:(NSError **)error; -- (id)initWithAccessKeyID:(NSString *)theAccessKeyID - secretAccessKey:(NSString *)theSecretAccessKey - s3BucketName:(NSString *)theS3BucketName - computerUUID:(NSString *)theComputerUUID - userAndComputer:(UserAndComputer *)theUAC; -- (NSString *)s3BucketName; +- (id)initWithTarget:(Target *)theTarget + computerUUID:(NSString *)theComputerUUID + userAndComputer:(UserAndComputer *)theUAC; +- (NSString *)errorDomain; +- (Target *)target; - (NSString *)computerUUID; - (UserAndComputer *)userAndComputer; @end diff --git a/BackupSet.m b/BackupSet.m index 6f0e810..57e9678 100644 --- a/BackupSet.m +++ b/BackupSet.m @@ -1,87 +1,53 @@ -/* - Copyright (c) 2010, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ +// +// BackupSet.m +// Arq +// +// Created by Stefan Reitshamer on 4/11/11. +// Copyright 2011 Haystack Software. All rights reserved. +// #import "BackupSet.h" #import "S3AuthorizationProvider.h" #import "S3Service.h" +#import "GlacierAuthorizationProvider.h" +#import "GlacierService.h" #import "UserAndComputer.h" -#import "NSErrorCodes.h" -#import "NSData-Encrypt.h" +#import "S3DeleteReceiver.h" #import "CryptoKey.h" #import "RegexKitLite.h" -#import "ArqRepo.h" #import "BlobKey.h" -#import "SetNSError.h" #import "Commit.h" #import "S3ObjectMetadata.h" #import "ArqSalt.h" -#import "S3Region.h" +#import "AWSRegion.h" +#import "Bucket.h" +#import "Target.h" +#import "TargetConnection.h" +#import "Repo.h" + @implementation BackupSet -+ (NSArray *)allBackupSetsForAccessKeyID:(NSString *)theAccessKeyID secretAccessKey:(NSString *)theSecretAccessKey error:(NSError **)error { - S3AuthorizationProvider *sap = [[[S3AuthorizationProvider alloc] initWithAccessKey:theAccessKeyID secretKey:theSecretAccessKey] autorelease]; - S3Service *s3 = [[[S3Service alloc] initWithS3AuthorizationProvider:sap useSSL:YES retryOnTransientError:NO] autorelease]; - NSArray *s3BucketNames = [s3 s3BucketNames:error]; - if (s3BucketNames == nil) { ++ (NSArray *)allBackupSetsForTarget:(Target *)theTarget targetConnectionDelegate:(id )theDelegate error:(NSError **)error { + id targetConnection = [[theTarget newConnection] autorelease]; + NSArray *theComputerUUIDs = [targetConnection computerUUIDsWithDelegate:theDelegate error:error]; + if (theComputerUUIDs == nil) { return nil; } + NSMutableArray *ret = [NSMutableArray array]; - for (NSString *theS3BucketName in s3BucketNames) { - if ([theS3BucketName rangeOfString:@"-com-haystacksoftware-arq"].location == NSNotFound - && [theS3BucketName rangeOfString:@".com.haystacksoftware.arq"].location == NSNotFound - && [theS3BucketName rangeOfString:@"comhaystacksoftwarearq"].location == NSNotFound) { - HSLogDebug(@"skipping bucket %@", theS3BucketName); + for (NSString *theComputerUUID in theComputerUUIDs) { + NSError *uacError = nil; + NSData *uacData = [targetConnection computerInfoForComputerUUID:theComputerUUID delegate:theDelegate error:&uacError]; + if (uacData == nil) { + HSLogWarn(@"unable to read %@ (skipping): %@", theComputerUUID, [uacError localizedDescription]); } else { - NSString *queryPrefix = [NSString stringWithFormat:@"/%@/", [theS3BucketName lowercaseString]]; - NSArray *theS3ComputerUUIDs = [s3 commonPrefixesForPathPrefix:queryPrefix delimiter:@"/" error:error]; - if (theS3ComputerUUIDs == nil) { - return nil; - } - for (NSString *s3ComputerUUID in theS3ComputerUUIDs) { - NSString *computerInfoPath = [NSString stringWithFormat:@"/%@/%@/computerinfo", theS3BucketName, s3ComputerUUID]; - NSError *uacError = nil; - NSData *uacData = [s3 dataAtPath:computerInfoPath error:&uacError]; - UserAndComputer *uac = nil; - if (uacData != nil) { - uac = [[[UserAndComputer alloc] initWithXMLData:uacData error:&uacError] autorelease]; - if (uac == nil) { - HSLogError(@"error parsing UserAndComputer data: %@", uacError); - } - } - BackupSet *backupSet = [[[BackupSet alloc] initWithAccessKeyID:theAccessKeyID - secretAccessKey:theSecretAccessKey - s3BucketName:theS3BucketName - computerUUID:s3ComputerUUID - userAndComputer:uac] autorelease]; + UserAndComputer *uac = [[[UserAndComputer alloc] initWithXMLData:uacData error:&uacError] autorelease]; + if (uac == nil) { + HSLogError(@"error parsing UserAndComputer data %@: %@", theComputerUUID, uacError); + } else { + BackupSet *backupSet = [[[BackupSet alloc] initWithTarget:theTarget + computerUUID:theComputerUUID + userAndComputer:uac] autorelease]; [ret addObject:backupSet]; } } @@ -91,30 +57,27 @@ return ret; } -- (id)initWithAccessKeyID:(NSString *)theAccessKeyID - secretAccessKey:(NSString *)theSecretAccessKey - s3BucketName:(NSString *)theS3BucketName - computerUUID:(NSString *)theComputerUUID - userAndComputer:(UserAndComputer *)theUAC { +- (id)initWithTarget:(Target *)theTarget + computerUUID:(NSString *)theComputerUUID + userAndComputer:(UserAndComputer *)theUAC { if (self = [super init]) { - accessKeyID = [theAccessKeyID retain]; - secretAccessKey = [theSecretAccessKey retain]; - s3BucketName = [theS3BucketName retain]; + target = [theTarget retain]; computerUUID = [theComputerUUID retain]; uac = [theUAC retain]; } return self; } - (void)dealloc { - [accessKeyID release]; - [secretAccessKey release]; - [s3BucketName release]; + [target release]; [computerUUID release]; [uac release]; [super dealloc]; } -- (NSString *)s3BucketName { - return s3BucketName; +- (NSString *)errorDomain { + return @"BackupSetErrorDomain"; +} +- (Target *)target { + return target; } - (NSString *)computerUUID { return computerUUID; @@ -126,11 +89,10 @@ #pragma mark NSObject - (NSString *)description { - NSString *bucketRegion = [[S3Region s3RegionForBucketName:s3BucketName] displayName]; if (uac != nil) { - return [NSString stringWithFormat:@"%@ (%@) : %@ (%@)", [uac computerName], [uac userName], bucketRegion, computerUUID]; + return [NSString stringWithFormat:@"%@ : %@ (%@)", [uac computerName], [uac userName], computerUUID]; } - return [NSString stringWithFormat:@"unknown computer : %@ (%@)", bucketRegion, computerUUID]; + return [NSString stringWithFormat:@"unknown computer : %@", computerUUID]; } @end diff --git a/BaseTargetConnection.h b/BaseTargetConnection.h new file mode 100644 index 0000000..e8f7f87 --- /dev/null +++ b/BaseTargetConnection.h @@ -0,0 +1,48 @@ +// +// BaseTargetConnection.h +// Arq +// +// Created by Stefan Reitshamer on 4/12/14. +// Copyright (c) 2014 Stefan Reitshamer. All rights reserved. +// + +@class Target; +@protocol RemoteFS; +@protocol TargetConnectionDelegate; + + +@interface BaseTargetConnection : NSObject { + Target *target; + id remoteFS; + NSString *pathPrefix; +} + +- (id)initWithTarget:(Target *)theTarget remoteFS:(id )theRemoteFS; + +- (NSArray *)computerUUIDsWithDelegate:(id )theDelegate error:(NSError **)error; +- (NSArray *)bucketUUIDsForComputerUUID:(NSString *)theComputerUUID deleted:(BOOL)deleted delegate:(id )theDelegate error:(NSError **)error; + +- (NSData *)bucketPlistDataForComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID deleted:(BOOL)deleted delegate:(id )theDelegate error:(NSError **)error; +- (BOOL)saveBucketPlistData:(NSData *)theData forComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID deleted:(BOOL)deleted delegate:(id )theDelegate error:(NSError **)error; +- (BOOL)deleteBucketPlistDataForComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID deleted:(BOOL)deleted delegate:(id )theDelegate error:(NSError **)error; + +- (NSData *)computerInfoForComputerUUID:(NSString *)theComputerUUID delegate:(id )theDelegate error:(NSError **)error; +- (BOOL)saveComputerInfo:(NSData *)theData forComputerUUID:(NSString *)theComputerUUID delegate:(id )theDelegate error:(NSError **)error; + +- (NSArray *)objectsWithPrefix:(NSString *)thePrefix delegate:(id )theDelegate error:(NSError **)error; +- (NSArray *)pathsWithPrefix:(NSString *)thePrefix delegate:(id )theDelegate error:(NSError **)error; +- (BOOL)deleteObjectsForComputerUUID:(NSString *)theComputerUUID delegate:(id )theDelegate error:(NSError **)error; +- (BOOL)deletePaths:(NSArray *)thePaths delegate:(id )theDelegate error:(NSError **)error; + +- (NSNumber *)fileExistsAtPath:(NSString *)thePath dataSize:(unsigned long long *)theDataSize delegate:(id )theDelegate error:(NSError **)error; +- (NSData *)contentsOfFileAtPath:(NSString *)thePath delegate:(id )theDelegate error:(NSError **)error; +- (BOOL)writeData:(NSData *)theData toFileAtPath:(NSString *)thePath dataTransferDelegate:(id )theDataTransferDelegate targetConnectionDelegate:(id )theTargetConnectionDelegate error:(NSError **)error; +- (BOOL)removeItemAtPath:(NSString *)thePath delegate:(id )theDelegate error:(NSError **)error; +- (NSNumber *)sizeOfItemAtPath:(NSString *)thePath delegate:(id )theDelegate error:(NSError **)error; +- (NSNumber *)isObjectRestoredAtPath:(NSString *)thePath delegate:(id )theDelegate error:(NSError **)error; +- (BOOL)restoreObjectAtPath:(NSString *)thePath forDays:(NSUInteger)theDays alreadyRestoredOrRestoring:(BOOL *)alreadyRestoredOrRestoring delegate:(id )theDelegate error:(NSError **)error; + +- (NSData *)saltDataForComputerUUID:(NSString *)theComputerUUID delegate:(id )theDelegate error:(NSError **)error; +- (BOOL)setSaltData:(NSData *)theData forComputerUUID:(NSString *)theComputerUUID delegate:(id )theDelegate error:(NSError **)error; + +@end diff --git a/BaseTargetConnection.m b/BaseTargetConnection.m new file mode 100644 index 0000000..36567a6 --- /dev/null +++ b/BaseTargetConnection.m @@ -0,0 +1,142 @@ +// +// TargetConnection.m +// Arq +// +// Created by Stefan Reitshamer on 4/12/14. +// Copyright (c) 2014 Stefan Reitshamer. All rights reserved. +// + +#import "Target.h" +#import "RemoteFS.h" +#import "BaseTargetConnection.h" +#import "RegexKitLite.h" +#import "TargetConnection.h" + + +@implementation BaseTargetConnection +- (id)initWithTarget:(Target *)theTarget remoteFS:(id)theRemoteFS { + if (self = [super init]) { + target = [theTarget retain]; + remoteFS = [theRemoteFS retain]; + + if ([[[theTarget endpoint] path] isEqualToString:@"/"]) { + pathPrefix = [@"" retain]; + } else { + pathPrefix = [[[theTarget endpoint] path] retain]; + } + } + return self; +} +- (void)dealloc { + [target release]; + [remoteFS release]; + [pathPrefix release]; + [super dealloc]; +} + +- (NSArray *)computerUUIDsWithDelegate:(id )theDelegate error:(NSError **)error { + NSArray *computerUUIDs = [remoteFS contentsOfDirectoryAtPath:[[target endpoint] path] targetConnectionDelegate:theDelegate error:error]; + if (computerUUIDs == nil) { + return nil; + } + + NSMutableArray *ret = [NSMutableArray array]; + for (NSString *computerUUID in computerUUIDs) { + if ([computerUUID rangeOfRegex:@"^(\\S{8}-\\S{4}-\\S{4}-\\S{4}-\\S{12})$"].location != NSNotFound) { + [ret addObject:computerUUID]; + } + } + return ret; +} + +- (NSArray *)bucketUUIDsForComputerUUID:(NSString *)theComputerUUID deleted:(BOOL)deleted delegate:(id )theDelegate error:(NSError **)error { + NSString *subdir = deleted ? @"deletedbuckets" : @"buckets"; + NSString *bucketsPrefix = [NSString stringWithFormat:@"%@/%@/%@/", pathPrefix, theComputerUUID, subdir]; + NSArray *bucketUUIDs = [remoteFS contentsOfDirectoryAtPath:bucketsPrefix targetConnectionDelegate:theDelegate error:error]; + if (bucketUUIDs == nil) { + return nil; + } + + NSMutableArray *ret = [NSMutableArray array]; + for (NSString *bucketUUID in bucketUUIDs) { + if ([bucketUUID rangeOfRegex:@"^(\\S{8}-\\S{4}-\\S{4}-\\S{4}-\\S{12})$"].location != NSNotFound) { + [ret addObject:bucketUUID]; + } + } + return ret; +} + +- (NSData *)bucketPlistDataForComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID deleted:(BOOL)deleted delegate:(id )theDelegate error:(NSError **)error { + NSString *subdir = deleted ? @"deletedbuckets" : @"buckets"; + NSString *path = [NSString stringWithFormat:@"%@/%@/%@/%@", pathPrefix, theComputerUUID, subdir, theBucketUUID]; + return [remoteFS contentsOfFileAtPath:path dataTransferDelegate:nil targetConnectionDelegate:theDelegate error:error]; +} +- (BOOL)saveBucketPlistData:(NSData *)theData forComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID deleted:(BOOL)deleted delegate:(id )theDelegate error:(NSError **)error { + NSString *subdir = deleted ? @"deletedbuckets" : @"buckets"; + NSString *path = [NSString stringWithFormat:@"%@/%@/%@/%@", pathPrefix, theComputerUUID, subdir, theBucketUUID]; + return [remoteFS writeData:theData atomicallyToFileAtPath:path dataTransferDelegate:nil targetConnectionDelegate:theDelegate error:error]; +} +- (BOOL)deleteBucketPlistDataForComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID deleted:(BOOL)deleted delegate:(id )theDelegate error:(NSError **)error { + NSString *subdir = deleted ? @"deletedbuckets" : @"buckets"; + NSString *path = [NSString stringWithFormat:@"%@/%@/%@/%@", pathPrefix, theComputerUUID, subdir, theBucketUUID]; + return [remoteFS removeItemAtPath:path targetConnectionDelegate:theDelegate error:error]; +} + +- (NSData *)computerInfoForComputerUUID:(NSString *)theComputerUUID delegate:(id )theDelegate error:(NSError **)error { + NSString *path = [NSString stringWithFormat:@"%@/%@/computerinfo", pathPrefix, theComputerUUID]; + return [remoteFS contentsOfFileAtPath:path dataTransferDelegate:nil targetConnectionDelegate:theDelegate error:error]; +} +- (BOOL)saveComputerInfo:(NSData *)theData forComputerUUID:(NSString *)theComputerUUID delegate:(id )theDelegate error:(NSError **)error { + NSString *path = [NSString stringWithFormat:@"%@/%@/computerinfo", pathPrefix, theComputerUUID]; + return [remoteFS writeData:theData atomicallyToFileAtPath:path dataTransferDelegate:nil targetConnectionDelegate:theDelegate error:error]; +} + +- (NSArray *)objectsWithPrefix:(NSString *)thePrefix delegate:(id )theDelegate error:(NSError **)error { + return [remoteFS objectsAtPath:thePrefix targetConnectionDelegate:theDelegate error:error]; +} +- (NSArray *)pathsWithPrefix:(NSString *)thePrefix delegate:(id )theDelegate error:(NSError **)error { + return [remoteFS pathsOfObjectsAtPath:thePrefix targetConnectionDelegate:theDelegate error:error]; +} +- (BOOL)deleteObjectsForComputerUUID:(NSString *)theComputerUUID delegate:(id )theDelegate error:(NSError **)error { + return [remoteFS removeItemAtPath:[NSString stringWithFormat:@"%@/%@", pathPrefix, theComputerUUID] targetConnectionDelegate:theDelegate error:error]; +} +- (BOOL)deletePaths:(NSArray *)thePaths delegate:(id )theDelegate error:(NSError **)error { + for (NSString *path in thePaths) { + if (![remoteFS removeItemAtPath:path targetConnectionDelegate:theDelegate error:error]) { + return NO; + } + } + return YES; +} + +- (NSNumber *)fileExistsAtPath:(NSString *)thePath dataSize:(unsigned long long *)theDataSize delegate:(id )theDelegate error:(NSError **)error { + return [remoteFS fileExistsAtPath:thePath dataSize:theDataSize targetConnectionDelegate:theDelegate error:error]; +} +- (NSData *)contentsOfFileAtPath:(NSString *)thePath delegate:(id )theDelegate error:(NSError **)error { + return [remoteFS contentsOfFileAtPath:thePath dataTransferDelegate:nil targetConnectionDelegate:theDelegate error:error]; +} +- (BOOL)writeData:(NSData *)theData toFileAtPath:(NSString *)thePath dataTransferDelegate:(id )theDataTransferDelegate targetConnectionDelegate:(id )theTargetConnectionDelegate error:(NSError **)error { + return [remoteFS writeData:theData atomicallyToFileAtPath:thePath dataTransferDelegate:theDataTransferDelegate targetConnectionDelegate:theTargetConnectionDelegate error:error]; +} +- (BOOL)removeItemAtPath:(NSString *)thePath delegate:(id )theDelegate error:(NSError **)error { + return [remoteFS removeItemAtPath:thePath targetConnectionDelegate:theDelegate error:error]; +} +- (NSNumber *)sizeOfItemAtPath:(NSString *)thePath delegate:(id )theDelegate error:(NSError **)error { + return [remoteFS sizeOfItemAtPath:thePath targetConnectionDelegate:theDelegate error:error]; +} +- (NSNumber *)isObjectRestoredAtPath:(NSString *)thePath delegate:(id )theDelegate error:(NSError **)error { + return [remoteFS isObjectRestoredAtPath:thePath targetConnectionDelegate:theDelegate error:error]; +} +- (BOOL)restoreObjectAtPath:(NSString *)thePath forDays:(NSUInteger)theDays alreadyRestoredOrRestoring:(BOOL *)alreadyRestoredOrRestoring delegate:(id )theDelegate error:(NSError **)error { + return [remoteFS restoreObjectAtPath:thePath forDays:theDays alreadyRestoredOrRestoring:alreadyRestoredOrRestoring targetConnectionDelegate:theDelegate error:error]; +} + +- (NSData *)saltDataForComputerUUID:(NSString *)theComputerUUID delegate:(id )theDelegate error:(NSError **)error { + NSString *s3Path = [NSString stringWithFormat:@"%@/%@/salt", pathPrefix, theComputerUUID]; + return [remoteFS contentsOfFileAtPath:s3Path dataTransferDelegate:nil targetConnectionDelegate:theDelegate error:error]; +} +- (BOOL)setSaltData:(NSData *)theData forComputerUUID:(NSString *)theComputerUUID delegate:(id )theDelegate error:(NSError **)error { + NSString *s3Path = [NSString stringWithFormat:@"%@/%@/salt", pathPrefix, theComputerUUID]; + return [remoteFS writeData:theData atomicallyToFileAtPath:s3Path dataTransferDelegate:nil targetConnectionDelegate:theDelegate error:error]; +} +@end diff --git a/BlobKey.h b/BlobKey.h index dcaea16..050c8c6 100644 --- a/BlobKey.h +++ b/BlobKey.h @@ -14,19 +14,21 @@ NSString *archiveId; uint64_t archiveSize; NSDate *archiveUploadedDate; - NSString *sha1; + unsigned char *sha1Bytes; BOOL stretchEncryptionKey; BOOL compressed; } -- (id)initWithSHA1:(NSString *)theSHA1 archiveId:(NSString *)theArchiveId archiveSize:(uint64_t)theArchiveSize archiveUploadedDate:(NSDate *)theArchiveUploadedDate compressed:(BOOL)isCompressed; -- (id)initWithSHA1:(NSString *)theSHA1 storageType:(StorageType)theStorageType stretchEncryptionKey:(BOOL)isStretchedKey compressed:(BOOL)isCompressed; -- (id)initWithStorageType:(StorageType)theStorageType archiveId:(NSString *)theArchiveId archiveSize:(uint64_t)theArchiveSize archiveUploadedDate:(NSDate *)theArchiveUploadedDate sha1:(NSString *)theSHA1 stretchEncryptionKey:(BOOL)isStretchedKey compressed:(BOOL)isCompressed; +- (id)initWithSHA1:(NSString *)theSHA1 archiveId:(NSString *)theArchiveId archiveSize:(uint64_t)theArchiveSize archiveUploadedDate:(NSDate *)theArchiveUploadedDate compressed:(BOOL)isCompressed error:(NSError **)error; +- (id)initWithSHA1:(NSString *)theSHA1 storageType:(StorageType)theStorageType stretchEncryptionKey:(BOOL)isStretchedKey compressed:(BOOL)isCompressed error:(NSError **)error; +- (id)initWithStorageType:(StorageType)theStorageType archiveId:(NSString *)theArchiveId archiveSize:(uint64_t)theArchiveSize archiveUploadedDate:(NSDate *)theArchiveUploadedDate sha1:(NSString *)theSHA1 stretchEncryptionKey:(BOOL)isStretchedKey compressed:(BOOL)isCompressed error:(NSError **)error; +- (id)initCopyOfBlobKey:(BlobKey *)theBlobKey withStorageType:(StorageType)theStorageType; - (StorageType)storageType; - (NSString *)archiveId; - (uint64_t)archiveSize; - (NSDate *)archiveUploadedDate; - (NSString *)sha1; +- (unsigned char *)sha1Bytes; - (BOOL)stretchEncryptionKey; - (BOOL)compressed; - (BOOL)isEqualToBlobKey:(BlobKey *)other; diff --git a/BlobKey.m b/BlobKey.m index b9ec643..faa93e7 100644 --- a/BlobKey.m +++ b/BlobKey.m @@ -11,13 +11,28 @@ #import "BooleanIO.h" #import "IntegerIO.h" #import "NSObject_extra.h" +#import "NSString_extra.h" +#import "SHA1Hash.h" @implementation BlobKey -- (id)initWithSHA1:(NSString *)theSHA1 archiveId:(NSString *)theArchiveId archiveSize:(uint64_t)theArchiveSize archiveUploadedDate:(NSDate *)theArchiveUploadedDate compressed:(BOOL)isCompressed { +- (id)initWithSHA1:(NSString *)theSHA1 archiveId:(NSString *)theArchiveId archiveSize:(uint64_t)theArchiveSize archiveUploadedDate:(NSDate *)theArchiveUploadedDate compressed:(BOOL)isCompressed error:(NSError **)error { if (self = [super init]) { storageType = StorageTypeGlacier; - sha1 = [theSHA1 retain]; + + NSData *sha1Data = [theSHA1 hexStringToData:error]; + if (sha1Data == nil) { + [self release]; + return nil; + } + if ([sha1Data length] != 20) { + SETNSERROR(@"BlobKeyErrorDomain", -1, @"invalid sha1 %@ for BlobKey (must be 20 bytes)", theSHA1); + [self release]; + return nil; + } + sha1Bytes = (unsigned char *)malloc(20); + memcpy(sha1Bytes, [sha1Data bytes], 20); + archiveId = [theArchiveId retain]; archiveSize = theArchiveSize; archiveUploadedDate = [theArchiveUploadedDate retain]; @@ -25,31 +40,66 @@ } return self; } -- (id)initWithSHA1:(NSString *)theSHA1 storageType:(StorageType)theStorageType stretchEncryptionKey:(BOOL)isStretchedKey compressed:(BOOL)isCompressed { +- (id)initWithSHA1:(NSString *)theSHA1 storageType:(StorageType)theStorageType stretchEncryptionKey:(BOOL)isStretchedKey compressed:(BOOL)isCompressed error:(NSError **)error { if (self = [super init]) { storageType = theStorageType; - sha1 = [theSHA1 retain]; + + NSData *sha1Data = [theSHA1 hexStringToData:error]; + if (sha1Data == nil) { + [self release]; + return nil; + } + if ([sha1Data length] != 20) { + SETNSERROR(@"BlobKeyErrorDomain", -1, @"invalid sha1 %@ for BlobKey (must be 20 bytes)", theSHA1); + [self release]; + return nil; + } + sha1Bytes = (unsigned char *)malloc(20); + memcpy(sha1Bytes, [sha1Data bytes], 20); + stretchEncryptionKey = isStretchedKey; compressed = isCompressed; } return self; } -- (id)initWithStorageType:(StorageType)theStorageType archiveId:(NSString *)theArchiveId archiveSize:(uint64_t)theArchiveSize archiveUploadedDate:(NSDate *)theArchiveUploadedDate sha1:(NSString *)theSHA1 stretchEncryptionKey:(BOOL)isStretchedKey compressed:(BOOL)isCompressed { +- (id)initWithStorageType:(StorageType)theStorageType archiveId:(NSString *)theArchiveId archiveSize:(uint64_t)theArchiveSize archiveUploadedDate:(NSDate *)theArchiveUploadedDate sha1:(NSString *)theSHA1 stretchEncryptionKey:(BOOL)isStretchedKey compressed:(BOOL)isCompressed error:(NSError **)error { if (self = [super init]) { storageType = theStorageType; archiveId = [theArchiveId retain]; archiveSize = theArchiveSize; archiveUploadedDate = [theArchiveUploadedDate retain]; - sha1 = [theSHA1 retain]; + + NSData *sha1Data = [theSHA1 hexStringToData:error]; + if (sha1Data == nil) { + [self release]; + return nil; + } + if ([sha1Data length] != 20) { + SETNSERROR(@"BlobKeyErrorDomain", -1, @"invalid sha1 %@ for BlobKey (must be 20 bytes)", theSHA1); + [self release]; + return nil; + } + sha1Bytes = (unsigned char *)malloc(20); + memcpy(sha1Bytes, [sha1Data bytes], 20); + stretchEncryptionKey = isStretchedKey; compressed = isCompressed; } return self; } +- (id)initCopyOfBlobKey:(BlobKey *)theBlobKey withStorageType:(StorageType)theStorageType { + return [[BlobKey alloc] initWithStorageType:theStorageType + archiveId:[theBlobKey archiveId] + archiveSize:[theBlobKey archiveSize] + archiveUploadedDate:[theBlobKey archiveUploadedDate] + sha1Bytes:[theBlobKey sha1Bytes] + stretchEncryptionKey:[theBlobKey stretchEncryptionKey] + compressed:[theBlobKey compressed]]; +} - (void)dealloc { [archiveId release]; [archiveUploadedDate release]; - [sha1 release]; + free(sha1Bytes); [super dealloc]; } @@ -66,7 +116,10 @@ return archiveUploadedDate; } - (NSString *)sha1 { - return sha1; + return [NSString hexStringWithBytes:sha1Bytes length:20]; +} +- (unsigned char *)sha1Bytes { + return sha1Bytes; } - (BOOL)stretchEncryptionKey { return stretchEncryptionKey; @@ -75,7 +128,7 @@ return compressed; } - (BOOL)isEqualToBlobKey:(BlobKey *)other { - if (![[other sha1] isEqualToString:sha1]) { + if (memcmp(sha1Bytes, [other sha1Bytes], 20) != 0) { return NO; } if (stretchEncryptionKey != [other stretchEncryptionKey]) { @@ -87,16 +140,17 @@ #pragma mark NSCopying - (id)copyWithZone:(NSZone *)zone { - return [[BlobKey alloc] initWithStorageType:storageType archiveId:archiveId archiveSize:archiveSize archiveUploadedDate:archiveUploadedDate sha1:sha1 stretchEncryptionKey:stretchEncryptionKey compressed:compressed]; + return [[BlobKey alloc] initWithStorageType:storageType archiveId:archiveId archiveSize:archiveSize archiveUploadedDate:archiveUploadedDate sha1Bytes:sha1Bytes stretchEncryptionKey:stretchEncryptionKey compressed:compressed]; } #pragma mark NSObject - (NSString *)description { - if (storageType == StorageTypeS3) { - return [NSString stringWithFormat:@"", sha1, (stretchEncryptionKey ? @"YES" : @"NO"), (compressed ? @"YES" : @"NO")]; + if (storageType == StorageTypeS3 || storageType == StorageTypeS3Glacier) { + NSString *type = storageType == StorageTypeS3 ? @"S3" : @"S3Glacier"; + return [NSString stringWithFormat:@"", [self sha1], type, (stretchEncryptionKey ? @"YES" : @"NO"), (compressed ? @"YES" : @"NO")]; } - return [NSString stringWithFormat:@"", archiveId, archiveUploadedDate, (stretchEncryptionKey ? @"YES" : @"NO"), (compressed ? @"YES" : @"NO")]; + return [NSString stringWithFormat:@"", [self sha1], archiveId, archiveSize, [self archiveUploadedDate], (stretchEncryptionKey ? @"YES" : @"NO"), (compressed ? @"YES" : @"NO")]; } - (BOOL)isEqual:(id)anObject { if (![anObject isKindOfClass:[BlobKey class]]) { @@ -104,7 +158,7 @@ } BlobKey *other = (BlobKey *)anObject; - return [NSObject equalObjects:sha1 and:[other sha1]] + return memcmp(sha1Bytes, [other sha1Bytes], 20) == 0 && stretchEncryptionKey == [other stretchEncryptionKey] && storageType == [other storageType] && [NSObject equalObjects:archiveId and:[other archiveId]] @@ -113,6 +167,25 @@ && compressed == [other compressed]; } - (NSUInteger)hash { - return [sha1 hash] + (stretchEncryptionKey ? 1 : 0) + (compressed ? 1 : 0); + return (NSUInteger)(*sha1Bytes); +} + + +#pragma mark internal +- (id)initWithStorageType:(StorageType)theStorageType archiveId:(NSString *)theArchiveId archiveSize:(uint64_t)theArchiveSize archiveUploadedDate:(NSDate *)theArchiveUploadedDate sha1Bytes:(unsigned char *)theSHA1Bytes stretchEncryptionKey:(BOOL)isStretchedKey compressed:(BOOL)isCompressed { + if (self = [super init]) { + storageType = theStorageType; + archiveId = [theArchiveId retain]; + archiveSize = theArchiveSize; + archiveUploadedDate = [theArchiveUploadedDate retain]; + + NSAssert(theSHA1Bytes != NULL, @"theSHA1Bytes may not be null"); + sha1Bytes = (unsigned char *)malloc(20); + memcpy(sha1Bytes, theSHA1Bytes, 20); + + stretchEncryptionKey = isStretchedKey; + compressed = isCompressed; + } + return self; } @end diff --git a/BlobKeyIO.h b/BlobKeyIO.h index 8129174..fdb0364 100644 --- a/BlobKeyIO.h +++ b/BlobKeyIO.h @@ -6,6 +6,7 @@ // @class BufferedInputStream; +@class BufferedOutputStream; @class BlobKey; @@ -13,5 +14,6 @@ } + (void)write:(BlobKey *)theBlobKey to:(NSMutableData *)data; ++ (BOOL)write:(BlobKey *)theBlobKey to:(BufferedOutputStream *)os error:(NSError **)error; + (BOOL)read:(BlobKey **)theBlobKey from:(BufferedInputStream *)is treeVersion:(int)theTreeVersion compressed:(BOOL)isCompressed error:(NSError **)error; @end diff --git a/BlobKeyIO.m b/BlobKeyIO.m index 342dfe2..1d6c324 100644 --- a/BlobKeyIO.m +++ b/BlobKeyIO.m @@ -23,6 +23,14 @@ [IntegerIO writeUInt64:[theBlobKey archiveSize] to:data]; [DateIO write:[theBlobKey archiveUploadedDate] to:data]; } ++ (BOOL)write:(BlobKey *)theBlobKey to:(BufferedOutputStream *)os error:(NSError **)error { + return [StringIO write:[theBlobKey sha1] to:os error:error] + && [BooleanIO write:[theBlobKey stretchEncryptionKey] to:os error:error] + && [IntegerIO writeUInt32:(uint32_t)[theBlobKey storageType] to:os error:error] + && [StringIO write:[theBlobKey archiveId] to:os error:error] + && [IntegerIO writeUInt64:[theBlobKey archiveSize] to:os error:error] + && [DateIO write:[theBlobKey archiveUploadedDate] to:os error:error]; +} + (BOOL)read:(BlobKey **)theBlobKey from:(BufferedInputStream *)is treeVersion:(int)theTreeVersion compressed:(BOOL)isCompressed error:(NSError **)error { NSString *dataSHA1; BOOL stretchEncryptionKey = NO; @@ -32,11 +40,9 @@ NSDate *archiveUploadedDate = nil; if (![StringIO read:&dataSHA1 from:is error:error]) { - [self release]; return NO; } if (theTreeVersion >= 14 && ![BooleanIO read:&stretchEncryptionKey from:is error:error]) { - [self release]; return NO; } if (theTreeVersion >= 17) { @@ -48,7 +54,17 @@ return NO; } } - *theBlobKey = [[[BlobKey alloc] initWithStorageType:storageType archiveId:archiveId archiveSize:archiveSize archiveUploadedDate:archiveUploadedDate sha1:dataSHA1 stretchEncryptionKey:stretchEncryptionKey compressed:isCompressed] autorelease]; + if (dataSHA1 == nil) { + // This BlobKeyIO class has been writing nil BlobKeys as if they weren't nil, + // and then reading the values in and creating bogus BlobKeys. + // If the sha1 is nil, it must have been a nil BlobKey, so we return nil here. + *theBlobKey = nil; + } else { + *theBlobKey = [[[BlobKey alloc] initWithStorageType:storageType archiveId:archiveId archiveSize:archiveSize archiveUploadedDate:archiveUploadedDate sha1:dataSHA1 stretchEncryptionKey:stretchEncryptionKey compressed:isCompressed error:error] autorelease]; + if (*theBlobKey == nil) { + return NO; + } + } return YES; } @end diff --git a/Bucket.h b/Bucket.h new file mode 100644 index 0000000..5036d19 --- /dev/null +++ b/Bucket.h @@ -0,0 +1,90 @@ +// +// BackupVolume.h +// +// Created by Stefan Reitshamer on 3/25/11. +// Copyright 2011 Haystack Software. All rights reserved. +// + + +#import "StorageType.h" +@class AWSRegion; +@class DictNode; +@class BucketExcludeSet; +@class BackupParamSet; +@class Target; +@class BufferedInputStream; +@class BufferedOutputStream; +@protocol TargetConnectionDelegate; + + +enum { + BucketPathMixedState = -1, + BucketPathOffState = 0, + BucketPathOnState = 1 +}; +typedef NSInteger BucketPathState; + +@interface Bucket : NSObject { + Target *target; + NSString *bucketUUID; + NSString *bucketName; + NSString *computerUUID; + NSString *localPath; + NSString *localMountPoint; + StorageType storageType; + NSMutableArray *ignoredRelativePaths; + BucketExcludeSet *excludeSet; + NSMutableArray *stringArrayPairs; + NSString *vaultName; + NSDate *vaultCreatedDate; + NSDate *plistDeletedDate; +} + ++ (NSArray *)bucketsWithTarget:(Target *)theTarget + computerUUID:(NSString *)theComputerUUID + encryptionPassword:(NSString *)theEncryptionPassword + targetConnectionDelegate:(id )theTCD + error:(NSError **)error; + ++ (NSArray *)bucketUUIDsWithTarget:(Target *)theTarget + computerUUID:(NSString *)theComputerUUID + encryptionPassword:(NSString *)theEncryptionPassword + targetConnectionDelegate:(id )theTCD + error:(NSError **)error; + ++ (NSArray *)deletedBucketsWithTarget:(Target *)theTarget + computerUUID:(NSString *)theComputerUUID + encryptionPassword:(NSString *)theEncryptionPassword + targetConnectionDelegate:(id )theTCD + error:(NSError **)error; + ++ (NSString *)errorDomain; + +- (id)initWithTarget:(Target *)theTarget + bucketUUID:(NSString *)theBucketUUID + bucketName:(NSString *)theBucketName + computerUUID:(NSString *)theComputerUUID + localPath:(NSString *)theLocalPath + localMountPoint:(NSString *)theLocalMountPoint + storageType:(int)theStorageType; +- (id)initWithBufferedInputStream:(BufferedInputStream *)theBIS error:(NSError **)error; + + +- (Target *)target; +- (NSString *)computerUUID; +- (NSString *)bucketUUID; +- (NSString *)bucketName; +- (NSString *)localPath; +- (StorageType)storageType; +- (NSString *)localMountPoint; +- (BucketExcludeSet *)bucketExcludeSet; +- (NSString *)vaultName; +- (NSDate *)vaultCreatedDate; +- (NSDate *)plistDeletedDate; +- (BucketPathState)stateForPath:(NSString *)thePath ignoreExcludes:(BOOL)ignoreExcludes; +- (void)setIgnoredRelativePaths:(NSSet *)theSet; +- (NSSet *)ignoredRelativePaths; +- (void)enteredPath:(NSString *)thePath; +- (void)leftPath:(NSString *)thePath; +- (NSData *)toXMLData; +@end diff --git a/Bucket.m b/Bucket.m new file mode 100644 index 0000000..36cf5f8 --- /dev/null +++ b/Bucket.m @@ -0,0 +1,482 @@ +// +// Bucket.m +// +// Created by Stefan Reitshamer on 3/25/11. +// Copyright 2011 Haystack Software. All rights reserved. +// + +#import "Bucket.h" +#import "DictNode.h" +#import "ArrayNode.h" +#import "StringNode.h" +#import "NSString_slashed.h" +#import "BucketExcludeSet.h" +#import "S3AuthorizationProvider.h" +#import "S3Service.h" +#import "UserLibrary_Arq.h" +#import "S3Service.h" +#import "StorageType.h" +#import "GlacierService.h" +#import "AWSRegion.h" +#import "RegexKitLite.h" +#import "AWSRegion.h" +#import "CryptoKey.h" +#import "Target.h" +#import "TargetConnection.h" +#import "GlacierAuthorizationProvider.h" +#import "GlacierService.h" +#import "ArqSalt.h" +#import "DataIO.h" +#import "StringIO.h" + + +#define BUCKET_PLIST_SALT "BucketPL" + + +@interface StringArrayPair : NSObject { + NSString *left; + NSArray *right; +} +- (id)initWithLeft:(NSString *)theLeft right:(NSArray *)theRight; +- (NSString *)left; +- (NSArray *)right; +@end + +@implementation StringArrayPair +- (id)initWithLeft:(NSString *)theLeft right:(NSArray *)theRight { + if (self = [super init]) { + left = [theLeft retain]; + right = [theRight retain]; + } + return self; +} +- (void)dealloc { + [left release]; + [right release]; + [super dealloc]; +} + +- (NSString *)left { + return left; +} +- (NSArray *)right { + return right; +} +@end + + + +@implementation Bucket ++ (NSArray *)bucketsWithTarget:(Target *)theTarget + computerUUID:(NSString *)theComputerUUID + encryptionPassword:(NSString *)theEncryptionPassword + targetConnectionDelegate:(id )theTCD + error:(NSError **)error { + id targetConnection = [theTarget newConnection]; + NSMutableArray *ret = [NSMutableArray array]; + do { + NSArray *bucketUUIDs = [targetConnection bucketUUIDsForComputerUUID:theComputerUUID deleted:NO delegate:theTCD error:error]; + if (bucketUUIDs == nil) { + if (error != NULL) { + HSLogDebug(@"failed to load bucketUUIDs for target %@ computer %@: %@", theTarget, theComputerUUID, *error); + } + ret = nil; + break; + } + for (NSString *bucketUUID in bucketUUIDs) { + NSError *myError = nil; + Bucket *bucket = [Bucket bucketWithTarget:theTarget targetConnection:targetConnection computerUUID:theComputerUUID bucketUUID:bucketUUID encryptionPassword:theEncryptionPassword error:&myError]; + if (bucket == nil) { + HSLogError(@"failed to load bucket plist for %@/%@: %@", theComputerUUID, bucketUUID, myError); + if ([myError code] != ERROR_INVALID_PLIST_XML) { + SETERRORFROMMYERROR; + ret = nil; + break; + } + } else { + [ret addObject:bucket]; + } + } + NSSortDescriptor *descriptor = [[[NSSortDescriptor alloc] initWithKey:@"bucketName" ascending:YES selector:@selector(caseInsensitiveCompare:)] autorelease]; + [ret sortUsingDescriptors:[NSArray arrayWithObject:descriptor]]; + } while(0); + [targetConnection release]; + return ret; +} ++ (NSArray *)bucketUUIDsWithTarget:(Target *)theTarget + computerUUID:(NSString *)theComputerUUID + encryptionPassword:(NSString *)theEncryptionPassword + targetConnectionDelegate:(id )theTCD + error:(NSError **)error { + id targetConnection = [[theTarget newConnection] autorelease]; + return [targetConnection bucketUUIDsForComputerUUID:theComputerUUID deleted:NO delegate:theTCD error:error]; +} ++ (NSArray *)deletedBucketsWithTarget:(Target *)theTarget + computerUUID:(NSString *)theComputerUUID + encryptionPassword:(NSString *)theEncryptionPassword + targetConnectionDelegate:(id )theTCD + error:(NSError **)error { + HSLogDebug(@"deletedBucketsWithTarget: theTarget=%@ endpoint=%@ path=%@", theTarget, [theTarget endpoint], [[theTarget endpoint] path]); + + NSData *salt = [NSData dataWithBytes:BUCKET_PLIST_SALT length:8]; + CryptoKey *cryptoKey = [[[CryptoKey alloc] initWithPassword:theEncryptionPassword salt:salt error:error] autorelease]; + if (cryptoKey == nil) { + return nil; + } + + NSMutableArray *ret = [NSMutableArray array]; + id targetConnection = [[theTarget newConnection] autorelease]; + NSArray *deletedBucketUUIDs = [targetConnection bucketUUIDsForComputerUUID:theComputerUUID deleted:YES delegate:theTCD error:error]; + if (deletedBucketUUIDs == nil) { + return nil; + } + for (NSString *bucketUUID in deletedBucketUUIDs) { + NSData *data = [targetConnection bucketPlistDataForComputerUUID:theComputerUUID bucketUUID:bucketUUID deleted:YES delegate:theTCD error:error]; + if (data == nil) { + return nil; + } + if (!strncmp([data bytes], "encrypted", 9)) { + NSData *encryptedData = [data subdataWithRange:NSMakeRange(9, [data length] - 9)]; + data = [cryptoKey decrypt:encryptedData error:error]; + if (data == nil) { + return nil; + } + } + NSError *myError = nil; + DictNode *plist = [DictNode dictNodeWithXMLData:data error:&myError]; + if (plist == nil) { + SETNSERROR(@"BucketErrorDomain", -1, @"error parsing bucket plist %@ %@: %@", theComputerUUID, bucketUUID, [myError localizedDescription]); + return nil; + } + Bucket *bucket = [[[Bucket alloc] initWithTarget:theTarget plist:plist] autorelease]; + [ret addObject:bucket]; + } + NSSortDescriptor *descriptor = [[[NSSortDescriptor alloc] initWithKey:@"bucketName" ascending:YES selector:@selector(caseInsensitiveCompare:)] autorelease]; + [ret sortUsingDescriptors:[NSArray arrayWithObject:descriptor]]; + return ret; +} + ++ (NSString *)errorDomain { + return @"BucketErrorDomain"; +} + +- (id)initWithTarget:(Target *)theTarget + bucketUUID:(NSString *)theBucketUUID + bucketName:(NSString *)theBucketName + computerUUID:(NSString *)theComputerUUID + localPath:(NSString *)theLocalPath + localMountPoint:(NSString *)theLocalMountPoint + storageType:(int)theStorageType { + if (self = [super init]) { + target = [theTarget retain]; + bucketUUID = [theBucketUUID retain]; + bucketName = [theBucketName retain]; + computerUUID = [theComputerUUID retain]; + localPath = [theLocalPath retain]; + localMountPoint = [theLocalMountPoint retain]; + storageType = theStorageType; + ignoredRelativePaths = [[NSMutableArray alloc] init]; + excludeSet = [[BucketExcludeSet alloc] init]; + stringArrayPairs = [[NSMutableArray alloc] init]; + } + return self; +} +- (id)initWithBufferedInputStream:(BufferedInputStream *)theBIS error:(NSError **)error { + Target *theTarget = [[[Target alloc] initWithBufferedInputStream:theBIS error:error] autorelease]; + if (theTarget == nil) { + [self release]; + return nil; + } + NSData *xmlData = nil; + if (![DataIO read:&xmlData from:theBIS error:error]) { + [self release]; + return nil; + } + if (xmlData == nil) { + SETNSERROR([Bucket errorDomain], -1, @"nil xmlData!"); + [self release]; + return nil; + } + DictNode *plist = [DictNode dictNodeWithXMLData:xmlData error:error]; + if (plist == nil) { + [self release]; + return nil; + } + return [self initWithTarget:theTarget plist:plist]; +} +- (void)dealloc { + [target release]; + [bucketUUID release]; + [bucketName release]; + [computerUUID release]; + [localPath release]; + [localMountPoint release]; + [ignoredRelativePaths release]; + [excludeSet release]; + [stringArrayPairs release]; + [vaultName release]; + [vaultCreatedDate release]; + [plistDeletedDate release]; + [super dealloc]; +} + +- (Target *)target { + return target; +} +- (NSString *)computerUUID { + return computerUUID; +} +- (NSString *)bucketUUID { + return bucketUUID; +} +- (NSString *)bucketName { + return bucketName; +} +- (NSString *)localPath { + return localPath; +} +- (NSString *)localMountPoint { + return localMountPoint; +} +- (StorageType)storageType { + return storageType; +} +- (BucketExcludeSet *)bucketExcludeSet { + return excludeSet; +} +- (NSString *)vaultName { + return vaultName; +} +- (NSDate *)vaultCreatedDate { + return vaultCreatedDate; +} +- (NSDate *)plistDeletedDate { + return plistDeletedDate; +} +- (BucketPathState)stateForPath:(NSString *)thePath ignoreExcludes:(BOOL)ignoreExcludes { + if ([ignoredRelativePaths containsObject:@""]) { + return BucketPathOffState; + } + + NSInteger ret = BucketPathOnState; + NSString *relativePath = [thePath substringFromIndex:[localPath length]]; + for (NSString *ignoredRelativePath in ignoredRelativePaths) { + if ([relativePath isEqualToString:ignoredRelativePath] + || ([relativePath hasPrefix:ignoredRelativePath] && ([relativePath length] > [ignoredRelativePath length]) && ([relativePath characterAtIndex:[ignoredRelativePath length]] == '/')) + ) { + ret = BucketPathOffState; + break; + } else if (([ignoredRelativePath hasPrefix:relativePath] || [relativePath length] == 0) + && ([ignoredRelativePath length] > [relativePath length]) + && ([relativePath isEqualToString:@""] || [relativePath isEqualToString:@"/"] || [ignoredRelativePath characterAtIndex:[relativePath length]] == '/')) { + ret = BucketPathMixedState; + break; + } + } + if (!ignoreExcludes && [excludeSet matchesFullPath:thePath filename:[thePath lastPathComponent]]) { + return BucketPathOffState; + } + return ret; +} +- (void)setIgnoredRelativePaths:(NSSet *)theSet { + [ignoredRelativePaths setArray:[theSet allObjects]]; +} +- (NSSet *)ignoredRelativePaths { + return [NSSet setWithArray:ignoredRelativePaths]; +} +- (void)enteredPath:(NSString *)thePath { + NSMutableArray *relativePathsToSkip = [NSMutableArray array]; + if (![thePath isEqualToString:localPath]) { + NSString *relativePath = [thePath substringFromIndex:[localPath length]]; + for (NSString *ignored in ignoredRelativePaths) { + BOOL applicable = NO; + if ([ignored hasPrefix:relativePath]) { + if ([ignored isEqualToString:relativePath] || ([ignored characterAtIndex:[relativePath length]] == '/')) { + applicable = YES; + } + } + if (!applicable) { + [relativePathsToSkip addObject:ignored]; + } + } + } + StringArrayPair *sap = [[StringArrayPair alloc] initWithLeft:thePath right:relativePathsToSkip]; + [stringArrayPairs addObject:sap]; + [sap release]; + [ignoredRelativePaths removeObjectsInArray:relativePathsToSkip]; +} +- (void)leftPath:(NSString *)thePath { + StringArrayPair *sap = [stringArrayPairs lastObject]; + NSAssert([[sap left] isEqualToString:thePath], @"must leave last path on the stack!"); + [ignoredRelativePaths addObjectsFromArray:[sap right]]; + [stringArrayPairs removeLastObject]; +} +- (NSData *)toXMLData { + DictNode *plist = [[[DictNode alloc] init] autorelease]; + [plist putString:[[target endpoint] description] forKey:@"Endpoint"]; + [plist putString:bucketUUID forKey:@"BucketUUID"]; + [plist putString:bucketName forKey:@"BucketName"]; + [plist putString:computerUUID forKey:@"ComputerUUID"]; + [plist putString:localPath forKey:@"LocalPath"]; + [plist putString:localMountPoint forKey:@"LocalMountPoint"]; + [plist putInt:storageType forKey:@"StorageType"]; + [plist putString:vaultName forKey:@"VaultName"]; + if (vaultCreatedDate != nil) { + [plist putDouble:[vaultCreatedDate timeIntervalSinceReferenceDate] forKey:@"VaultCreatedTime"]; + } + if (plistDeletedDate != nil) { + [plist putDouble:[plistDeletedDate timeIntervalSinceReferenceDate] forKey:@"PlistDeletedTime"]; + } + ArrayNode *ignoredRelativePathsNode = [[[ArrayNode alloc] init] autorelease]; + [plist put:ignoredRelativePathsNode forKey:@"IgnoredRelativePaths"]; + NSArray *sortedRelativePaths = [ignoredRelativePaths sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]; + for (NSString *ignoredRelativePath in sortedRelativePaths) { + [ignoredRelativePathsNode add:[[[StringNode alloc] initWithString:ignoredRelativePath] autorelease]]; + } + [plist put:[excludeSet toPlist] forKey:@"Excludes"]; + return [plist XMLData]; +} + + +#pragma mark NSCopying +- (id)copyWithZone:(NSZone *)zone { + BucketExcludeSet *excludeSetCopy = [excludeSet copyWithZone:zone]; + NSMutableArray *stringArrayPairsCopy = [stringArrayPairs copyWithZone:zone]; + NSMutableArray *ignoredRelativePathsCopy = [[NSMutableArray alloc] initWithArray:ignoredRelativePaths copyItems:YES]; + Bucket *ret = [[Bucket alloc] initWithTarget:target + bucketUUID:bucketUUID + bucketName:bucketName + computerUUID:computerUUID + localPath:localPath + localMountPoint:localMountPoint + storageType:storageType + ignoredRelativePaths:ignoredRelativePathsCopy + excludeSet:excludeSetCopy + stringArrayPairs:stringArrayPairsCopy + vaultName:vaultName + vaultCreatedDate:vaultCreatedDate + plistDeletedDate:plistDeletedDate]; + [excludeSetCopy release]; + [stringArrayPairsCopy release]; + [ignoredRelativePathsCopy release]; + return ret; +} + +#pragma mark NSObject +- (NSString *)description { + return [NSString stringWithFormat:@"", bucketUUID, localPath, (unsigned long)[ignoredRelativePaths count]]; +} + + +#pragma mark internal ++ (Bucket *)bucketWithTarget:(Target *)theTarget + targetConnection:(id )theTargetConnection + computerUUID:(NSString *)theComputerUUID + bucketUUID:(NSString *)theBucketUUID + encryptionPassword:(NSString *)theEncryptionPassword + error:(NSError **)error { + NSData *salt = [NSData dataWithBytes:BUCKET_PLIST_SALT length:8]; + CryptoKey *cryptoKey = [[[CryptoKey alloc] initWithPassword:theEncryptionPassword salt:salt error:error] autorelease]; + if (cryptoKey == nil) { + return nil; + } + + BOOL encrypted = NO; + NSData *data = [theTargetConnection bucketPlistDataForComputerUUID:theComputerUUID bucketUUID:theBucketUUID deleted:NO delegate:nil error:error]; + if (data == nil) { + return nil; + } + unsigned long length = 9; + if ([data length] < length) { + length = [data length]; + } + if (length >= 9 && !strncmp([data bytes], "encrypted", length)) { + encrypted = YES; + NSData *encryptedData = [data subdataWithRange:NSMakeRange(9, [data length] - 9)]; + data = [cryptoKey decrypt:encryptedData error:error]; + if (data == nil) { + return nil; + } + } + NSError *myError = nil; + DictNode *plist = [DictNode dictNodeWithXMLData:data error:&myError]; + if (plist == nil) { + HSLogDebug(@"error parsing XML data into DictNode: %@", myError); + NSString *str = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]; + HSLogDebug(@"invalid XML: %@", str); + SETNSERROR(@"BucketErrorDomain", ERROR_INVALID_PLIST_XML, @"%@ %@ not valid XML", theComputerUUID, theBucketUUID); + return nil; + } + Bucket *bucket = [[[Bucket alloc] initWithTarget:theTarget plist:plist] autorelease]; + return bucket; +} + +- (id)initWithTarget:(Target *)theTarget plist:(DictNode *)thePlist { + if (self = [super init]) { + target = [theTarget retain]; + bucketUUID = [[[thePlist stringNodeForKey:@"BucketUUID"] stringValue] retain]; + bucketName = [[[thePlist stringNodeForKey:@"BucketName"] stringValue] retain]; + computerUUID = [[[thePlist stringNodeForKey:@"ComputerUUID"] stringValue] retain]; + localPath = [[[thePlist stringNodeForKey:@"LocalPath"] stringValue] retain]; + localMountPoint = [[[thePlist stringNodeForKey:@"LocalMountPoint"] stringValue] retain]; + storageType = StorageTypeS3; + if ([thePlist containsKey:@"StorageType"]) { + storageType = [[thePlist integerNodeForKey:@"StorageType"] intValue]; + } + vaultName = [[[thePlist stringNodeForKey:@"VaultName"] stringValue] retain]; + if ([thePlist containsKey:@"VaultCreatedTime"]) { + vaultCreatedDate = [[NSDate alloc] initWithTimeIntervalSinceReferenceDate:[[thePlist realNodeForKey:@"VaultCreatedTime"] doubleValue]]; + } + if ([thePlist containsKey:@"PlistDeletedTime"]) { + plistDeletedDate = [[NSDate alloc] initWithTimeIntervalSinceReferenceDate:[[thePlist realNodeForKey:@"PlistDeletedTime"] doubleValue]]; + } + ignoredRelativePaths = [[NSMutableArray alloc] init]; + ArrayNode *ignoredPathsNode = [thePlist arrayNodeForKey:@"IgnoredRelativePaths"]; + for (NSUInteger index = 0; index < [ignoredPathsNode size]; index++) { + [ignoredRelativePaths addObject:[[ignoredPathsNode stringNodeAtIndex:(int)index] stringValue]]; + } + [self sortIgnoredRelativePaths]; + excludeSet = [[BucketExcludeSet alloc] init]; + [excludeSet loadFromPlist:[thePlist dictNodeForKey:@"Excludes"] localPath:localPath]; + stringArrayPairs = [[NSMutableArray alloc] init]; + } + return self; +} + +- (void)sortIgnoredRelativePaths { + // Filter out duplicates. + NSSet *set = [NSSet setWithArray:ignoredRelativePaths]; + [ignoredRelativePaths setArray:[set allObjects]]; + [ignoredRelativePaths sortUsingSelector:@selector(compareByLength:)]; +} +- (id)initWithTarget:(Target *)theTarget + bucketUUID:(NSString *)theBucketUUID + bucketName:(NSString *)theBucketName + computerUUID:(NSString *)theComputerUUID + localPath:(NSString *)theLocalPath + localMountPoint:(NSString *)theLocalMountPoint + storageType:(int)theStorageType +ignoredRelativePaths:(NSMutableArray *)theIgnoredRelativePaths + excludeSet:(BucketExcludeSet *)theExcludeSet + stringArrayPairs:(NSMutableArray *)theStringArrayPairs + vaultName:(NSString *)theVaultName + vaultCreatedDate:(NSDate *)theVaultCreatedDate + plistDeletedDate:(NSDate *)thePlistDeletedDate { + if (self = [super init]) { + target = [theTarget retain]; + bucketUUID = [theBucketUUID retain]; + bucketName = [theBucketName retain]; + computerUUID = [theComputerUUID retain]; + localPath = [theLocalPath retain]; + localMountPoint = [theLocalMountPoint retain]; + storageType = theStorageType; + ignoredRelativePaths = [theIgnoredRelativePaths retain]; + excludeSet = [theExcludeSet retain]; + stringArrayPairs = [theStringArrayPairs retain]; + vaultName = [theVaultName retain]; + vaultCreatedDate = [theVaultCreatedDate retain]; + plistDeletedDate = [thePlistDeletedDate retain]; + } + return self; +} +@end diff --git a/BucketExclude.h b/BucketExclude.h new file mode 100644 index 0000000..be91bcf --- /dev/null +++ b/BucketExclude.h @@ -0,0 +1,31 @@ +// +// BucketExclude.h +// +// Created by Stefan Reitshamer on 4/26/11. +// Copyright 2011 Haystack Software. All rights reserved. +// + + +@class DictNode; + +enum { + kBucketExcludeTypeFileNameIs = 1, + kBucketExcludeTypeFileNameContains = 2, + kBucketExcludeTypeFileNameStartsWith = 3, + kBucketExcludeTypeFileNameEndsWith = 4, + kBucketExcludeTypePathMatchesRegex = 5 +}; +typedef UInt32 BucketExcludeType; + +@interface BucketExclude : NSObject { + BucketExcludeType type; + NSString *text; +} +- (id)initWithType:(BucketExcludeType)theType text:(NSString *)theText; +- (id)initWithPlist:(DictNode *)thePlist; +- (DictNode *)toPlist; +- (BOOL)matchesFullPath:(NSString *)thePath filename:(NSString *)theFilename; + +@property (readonly) BucketExcludeType type; +@property (readonly, copy) NSString *text; +@end diff --git a/BucketExclude.m b/BucketExclude.m new file mode 100644 index 0000000..262382e --- /dev/null +++ b/BucketExclude.m @@ -0,0 +1,87 @@ +// +// BucketExclude.m +// +// Created by Stefan Reitshamer on 4/26/11. +// Copyright 2011 Haystack Software. All rights reserved. +// + +#import "BucketExclude.h" +#import "DictNode.h" +#import "IntegerNode.h" +#import "StringNode.h" +#import "RegexKitLite.h" + +@implementation BucketExclude +@synthesize type, text; + +- (id)initWithType:(BucketExcludeType)theType text:(NSString *)theText { + if (self = [super init]) { + type = theType; + text = [theText retain]; + } + return self; +} +- (id)initWithPlist:(DictNode *)thePlist { + if (self = [super init]) { + type = [[thePlist integerNodeForKey:@"type"] intValue]; + text = [[[thePlist stringNodeForKey:@"text"] stringValue] retain]; + } + return self; +} +- (void)dealloc { + [text release]; + [super dealloc]; +} + +- (DictNode *)toPlist { + DictNode *ret = [[[DictNode alloc] init] autorelease]; + [ret put:[[[IntegerNode alloc] initWithInt:type] autorelease] forKey:@"type"]; + [ret put:[[[StringNode alloc] initWithString:text] autorelease] forKey:@"text"]; + return ret; +} + +- (BOOL)matchesFullPath:(NSString *)thePath filename:(NSString *)theFilename { + switch(type) { + case kBucketExcludeTypeFileNameIs: + return [theFilename compare:text options:NSCaseInsensitiveSearch] == NSOrderedSame; + case kBucketExcludeTypeFileNameContains: + return [theFilename rangeOfString:text options:NSCaseInsensitiveSearch].location != NSNotFound; + case kBucketExcludeTypeFileNameStartsWith: + return [[theFilename lowercaseString] hasPrefix:[text lowercaseString]]; + case kBucketExcludeTypeFileNameEndsWith: + return [[theFilename lowercaseString] hasSuffix:[text lowercaseString]]; + case kBucketExcludeTypePathMatchesRegex: + return [thePath isMatchedByRegex:text options:RKLCaseless inRange:NSMakeRange(0, [thePath length]) error:NULL]; + } + return NO; +} + +#pragma mark NSObject +- (NSString *)description { + NSString *typeDesc = @""; + switch( type) { + case kBucketExcludeTypeFileNameIs: + typeDesc = @"kBucketExcludeTypeFileNameIs"; + break; + case kBucketExcludeTypeFileNameContains: + typeDesc = @"kBucketExcludeTypeFileNameContains"; + break; + case kBucketExcludeTypeFileNameStartsWith: + typeDesc = @"kBucketExcludeTypeFileNameStartsWith"; + break; + case kBucketExcludeTypeFileNameEndsWith: + typeDesc = @"kBucketExcludeTypeFileNameEndsWith"; + break; + case kBucketExcludeTypePathMatchesRegex: + typeDesc = @"kBucketExcludeTypePathMatchesRegex"; + break; + } + return [NSString stringWithFormat:@"", typeDesc, text]; +} + + +#pragma mark NSCopying +- (id)copyWithZone:(NSZone *)theZone { + return [[BucketExclude alloc] initWithType:type text:text]; +} +@end diff --git a/BucketExcludeSet.h b/BucketExcludeSet.h new file mode 100644 index 0000000..6561a20 --- /dev/null +++ b/BucketExcludeSet.h @@ -0,0 +1,25 @@ +// +// BucketExcludeSet.h +// +// Created by Stefan Reitshamer on 4/27/11. +// Copyright 2011 Haystack Software. All rights reserved. +// + + +#import "BucketExclude.h" +@class DictNode; + +@interface BucketExcludeSet : NSObject { + NSMutableArray *bucketExcludes; +} +- (void)loadFromPlist:(DictNode *)thePlist localPath:(NSString *)theLocalPath; +- (DictNode *)toPlist; +- (NSArray *)bucketExcludes; +- (BOOL)containsExcludeWithType:(BucketExcludeType)theType text:(NSString *)theText; +- (void)addExcludeWithType:(BucketExcludeType)theType text:(NSString *)theText; +- (void)removeExcludeWithType:(BucketExcludeType)theType text:(NSString *)theText; +- (void)setExcludes:(NSArray *)theBucketExcludes; +- (BOOL)removeEmptyExcludes; +- (BOOL)matchesFullPath:(NSString *)thePath filename:(NSString *)theFilename; +- (void)clearExcludes; +@end diff --git a/BucketExcludeSet.m b/BucketExcludeSet.m new file mode 100644 index 0000000..a643457 --- /dev/null +++ b/BucketExcludeSet.m @@ -0,0 +1,173 @@ +// +// BucketExcludeSet.m +// +// Created by Stefan Reitshamer on 4/27/11. +// Copyright 2011 Haystack Software. All rights reserved. +// + +#import "BucketExcludeSet.h" +#import "BucketExclude.h" +#import "DictNode.h" +#import "ArrayNode.h" + +@interface BucketExcludeSet (internal) +- (id)initWithBucketExcludes:(NSMutableArray *)theBucketExcludes; +@end + +@implementation BucketExcludeSet +- (id)init { + if (self = [super init]) { + bucketExcludes = [[NSMutableArray alloc] init]; + } + return self; +} +- (void)dealloc { + [bucketExcludes release]; + [super dealloc]; +} + +- (void)loadFromPlist:(DictNode *)thePlist localPath:(NSString *)theLocalPath { + [bucketExcludes removeAllObjects]; + if ([thePlist containsKey:@"Conditions"]) { + ArrayNode *conditions = [thePlist arrayNodeForKey:@"Conditions"]; + for (NSUInteger index = 0; index < [conditions size]; index++) { + DictNode *condition = [conditions dictNodeAtIndex:(int)index]; + NSString *matchText = [[condition stringNodeForKey:@"MatchText"] stringValue]; + NSString *escapedLocalPath = [theLocalPath stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"]; + escapedLocalPath = [escapedLocalPath stringByReplacingOccurrencesOfString:@"^" withString:@"\\^"]; + escapedLocalPath = [escapedLocalPath stringByReplacingOccurrencesOfString:@"$" withString:@"\\$"]; + escapedLocalPath = [escapedLocalPath stringByReplacingOccurrencesOfString:@"." withString:@"\\."]; + escapedLocalPath = [escapedLocalPath stringByReplacingOccurrencesOfString:@"*" withString:@"\\*"]; + escapedLocalPath = [escapedLocalPath stringByReplacingOccurrencesOfString:@"|" withString:@"\\|"]; + escapedLocalPath = [escapedLocalPath stringByReplacingOccurrencesOfString:@"+" withString:@"\\+"]; + escapedLocalPath = [escapedLocalPath stringByReplacingOccurrencesOfString:@"?" withString:@"\\?"]; + escapedLocalPath = [escapedLocalPath stringByReplacingOccurrencesOfString:@"{" withString:@"\\{"]; + escapedLocalPath = [escapedLocalPath stringByReplacingOccurrencesOfString:@"(" withString:@"\\("]; + NSString *escapedMatchText = [matchText stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"]; + escapedMatchText = [escapedMatchText stringByReplacingOccurrencesOfString:@"^" withString:@"\\^"]; + escapedMatchText = [escapedMatchText stringByReplacingOccurrencesOfString:@"$" withString:@"\\$"]; + escapedMatchText = [escapedMatchText stringByReplacingOccurrencesOfString:@"." withString:@"\\."]; + escapedMatchText = [escapedMatchText stringByReplacingOccurrencesOfString:@"*" withString:@"\\*"]; + escapedMatchText = [escapedMatchText stringByReplacingOccurrencesOfString:@"|" withString:@"\\|"]; + escapedMatchText = [escapedMatchText stringByReplacingOccurrencesOfString:@"+" withString:@"\\+"]; + escapedMatchText = [escapedMatchText stringByReplacingOccurrencesOfString:@"?" withString:@"\\?"]; + escapedMatchText = [escapedMatchText stringByReplacingOccurrencesOfString:@"{" withString:@"\\{"]; + escapedMatchText = [escapedMatchText stringByReplacingOccurrencesOfString:@"(" withString:@"\\("]; + NSString *regex = nil; + int matchTypeId = [[condition integerNodeForKey:@"MatchTypeId"] intValue]; + int objectTypeId = [[condition integerNodeForKey:@"ObjectTypeId"] intValue]; + BucketExcludeType excludeType = 0; + switch (matchTypeId) { + case 2: + excludeType = kBucketExcludeTypeFileNameContains; + regex = [NSString stringWithFormat:@"^%@.*/[^/]*%@[^/]*$", escapedLocalPath, escapedMatchText]; + break; + case 4: + excludeType = kBucketExcludeTypeFileNameIs; + regex = [NSString stringWithFormat:@"^%@.*/%@$", escapedLocalPath, escapedMatchText]; + break; + case 6: + excludeType = kBucketExcludeTypeFileNameStartsWith; + regex = [NSString stringWithFormat:@"^%@.*/%@[^/]*$", escapedLocalPath, escapedMatchText]; + break; + case 7: + excludeType = kBucketExcludeTypeFileNameEndsWith; + regex = [NSString stringWithFormat:@"^%@/.*%@$", escapedLocalPath, escapedMatchText]; + break; + } + if (objectTypeId == 2) { + excludeType = kBucketExcludeTypePathMatchesRegex; + matchText = regex; + } + BucketExclude *be = [[[BucketExclude alloc] initWithType:excludeType text:matchText] autorelease]; + [bucketExcludes addObject:be]; + } + } else { + ArrayNode *excludes = [thePlist arrayNodeForKey:@"excludes"]; + for (NSUInteger index = 0; index < [excludes size]; index++) { + BucketExclude *bce = [[[BucketExclude alloc] initWithPlist:[excludes dictNodeAtIndex:(int)index]] autorelease]; + [bucketExcludes addObject:bce]; + } + } +} +- (DictNode *)toPlist { + DictNode *plist = [[[DictNode alloc] init] autorelease]; + ArrayNode *array = [[[ArrayNode alloc] init] autorelease]; + [plist put:array forKey:@"excludes"]; + for (BucketExclude *bce in bucketExcludes) { + [array add:[bce toPlist]]; + } + return plist; +} +- (NSArray *)bucketExcludes { + return bucketExcludes; +} +- (BOOL)containsExcludeWithType:(BucketExcludeType)theType text:(NSString *)theText { + for (BucketExclude *bce in bucketExcludes) { + if (bce.type == theType && [bce.text isEqualToString:theText]) { + return YES; + } + } + return NO; +} +- (void)addExcludeWithType:(BucketExcludeType)theType text:(NSString *)theText { + if (![self containsExcludeWithType:theType text:theText]) { + BucketExclude *bce = [[[BucketExclude alloc] initWithType:theType text:theText] autorelease]; + [bucketExcludes addObject:bce]; + } +} +- (void)removeExcludeWithType:(BucketExcludeType)theType text:(NSString *)theText { + BucketExclude *found = nil; + for (BucketExclude *bce in bucketExcludes) { + if (bce.type == theType && [bce.text isEqualToString:theText]) { + found = bce; + break; + } + } + if (found != nil) { + [bucketExcludes removeObject:found]; + } +} +- (void)setExcludes:(NSArray *)theBucketExcludes { + [bucketExcludes setArray:theBucketExcludes]; +} +- (BOOL)removeEmptyExcludes { + NSMutableSet *itemsToRemove = [NSMutableSet set]; + for (BucketExclude *bce in bucketExcludes) { + if ([bce.text length] == 0) { + [itemsToRemove addObject:bce]; + } + } + [bucketExcludes removeObjectsInArray:[itemsToRemove allObjects]]; + return ([itemsToRemove count] > 0); +} +- (BOOL)matchesFullPath:(NSString *)thePath filename:(NSString *)theFilename { + for (BucketExclude *bce in bucketExcludes) { + if ([bce matchesFullPath:thePath filename:theFilename]) { + return YES; + } + } + return NO; +} +- (void)clearExcludes { + [bucketExcludes removeAllObjects]; +} + + +#pragma mark NSCopying +- (id)copyWithZone:(NSZone *)zone { + NSMutableArray *excludesCopy = [[NSMutableArray alloc] initWithArray:bucketExcludes copyItems:YES]; + BucketExcludeSet *ret = [[BucketExcludeSet alloc] initWithBucketExcludes:excludesCopy]; + [excludesCopy release]; + return ret; +} +@end + +@implementation BucketExcludeSet (internal) +- (id)initWithBucketExcludes:(NSMutableArray *)theBucketExcludes { + if (self = [super init]) { + bucketExcludes = [theBucketExcludes retain]; + } + return self; +} +@end diff --git a/DiskPack.h b/DiskPack.h deleted file mode 100644 index ff5f17a..0000000 --- a/DiskPack.h +++ /dev/null @@ -1,39 +0,0 @@ -// -// DiskPack.h -// Arq -// -// Created by Stefan Reitshamer on 12/30/09. -// Copyright 2009 __MyCompanyName__. All rights reserved. -// - - -@class S3Service; -@class ServerBlob; - -@interface DiskPack : NSObject { - S3Service *s3; - NSString *s3BucketName; - NSString *computerUUID; - NSString *packSetName; - NSString *packSHA1; - NSString *s3Path; - NSString *localPath; - uid_t targetUID; - gid_t targetGID; -} -+ (NSString *)s3PathWithS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID packSetName:(NSString *)thePackSetName packSHA1:(NSString *)thePackSHA1; -+ (NSString *)localPathWithS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID packSetName:(NSString *)thePackSetName packSHA1:(NSString *)thePackSHA1; -- (id)initWithS3Service:(S3Service *)theS3 - s3BucketName:(NSString *)theS3BucketName - computerUUID:(NSString *)theComputerUUID - packSetName:(NSString *)thePackSetName - packSHA1:(NSString *)thePackSHA1 - targetUID:(uid_t)theTargetUID - targetGID:(gid_t)theTargetGID; -- (BOOL)makeLocal:(NSError **)error; -- (BOOL)makeNotLocal:(NSError **)error; -- (ServerBlob *)newServerBlobForObjectAtOffset:(unsigned long long)offset error:(NSError **)error; -- (BOOL)fileLength:(unsigned long long *)length error:(NSError **)error; -- (BOOL)copyToPath:(NSString *)dest error:(NSError **)error; -- (NSArray *)sortedPackIndexEntries:(NSError **)error; -@end diff --git a/DiskPack.m b/DiskPack.m deleted file mode 100644 index 23f0e82..0000000 --- a/DiskPack.m +++ /dev/null @@ -1,262 +0,0 @@ -// -// DiskPack.m -// Arq -// -// Created by Stefan Reitshamer on 12/30/09. -// Copyright 2009 __MyCompanyName__. All rights reserved. -// - -#include -#import "DiskPack.h" -#import "SetNSError.h" -#import "FDInputStream.h" -#import "StringIO.h" -#import "IntegerIO.h" -#import "ServerBlob.h" -#import "NSFileManager_extra.h" -#import "S3Service.h" -#import "ServerBlob.h" -#import "FileInputStream.h" -#import "FileOutputStream.h" -#import "Streams.h" -#import "S3ObjectReceiver.h" -#import "S3ObjectMetadata.h" -#import "PackIndexEntry.h" -#import "SHA1Hash.h" -#import "UserLibrary_Arq.h" -#import "BufferedInputStream.h" -#import "NSError_extra.h" - - -@interface DiskPack (internal) -- (BOOL)savePack:(ServerBlob *)sb error:(NSError **)error; -- (NSArray *)sortedPackIndexEntriesFromStream:(BufferedInputStream *)fis error:(NSError **)error; -@end - -@implementation DiskPack -+ (NSString *)s3PathWithS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID packSetName:(NSString *)thePackSetName packSHA1:(NSString *)thePackSHA1 { - return [NSString stringWithFormat:@"/%@/%@/packsets/%@/%@.pack", theS3BucketName, theComputerUUID, thePackSetName, thePackSHA1]; -} -+ (NSString *)localPathWithS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID packSetName:(NSString *)thePackSetName packSHA1:(NSString *)thePackSHA1 { - return [NSString stringWithFormat:@"%@/%@/%@/packsets/%@/%@/%@.pack", [UserLibrary arqCachePath], theS3BucketName, theComputerUUID, thePackSetName, [thePackSHA1 substringToIndex:2], [thePackSHA1 substringFromIndex:2]]; -} -- (id)initWithS3Service:(S3Service *)theS3 - s3BucketName:(NSString *)theS3BucketName - computerUUID:(NSString *)theComputerUUID - packSetName:(NSString *)thePackSetName - packSHA1:(NSString *)thePackSHA1 - targetUID:(uid_t)theTargetUID - targetGID:(gid_t)theTargetGID { - if (self = [super init]) { - s3 = [theS3 retain]; - s3BucketName = [theS3BucketName retain]; - computerUUID = [theComputerUUID retain]; - packSetName = [thePackSetName retain]; - packSHA1 = [thePackSHA1 retain]; - s3Path = [[DiskPack s3PathWithS3BucketName:s3BucketName computerUUID:computerUUID packSetName:packSetName packSHA1:packSHA1] retain]; - localPath = [[DiskPack localPathWithS3BucketName:s3BucketName computerUUID:computerUUID packSetName:packSetName packSHA1:packSHA1] retain]; - targetUID = theTargetUID; - targetGID = theTargetGID; - } - return self; -} -- (void)dealloc { - [s3 release]; - [s3BucketName release]; - [computerUUID release]; - [packSetName release]; - [packSHA1 release]; - [s3Path release]; - [localPath release]; - [super dealloc]; -} -- (BOOL)makeLocal:(NSError **)error { - NSFileManager *fm = [NSFileManager defaultManager]; - BOOL ret = YES; - if (![fm fileExistsAtPath:localPath]) { - for (;;) { - HSLogDebug(@"packset %@: making pack %@ local", packSetName, packSHA1); - NSError *myError = nil; - ServerBlob *sb = [s3 newServerBlobAtPath:s3Path error:&myError]; - if (sb != nil) { - ret = [self savePack:sb error:error]; - [sb release]; - break; - } - if (![myError isTransientError]) { - HSLogError(@"error getting S3 pack %@: %@", s3Path, myError); - if (error != NULL) { - *error = myError; - } - ret = NO; - break; - } - HSLogWarn(@"network error making pack %@ local (retrying): %@", s3Path, myError); - NSError *rmError = nil; - if (![[NSFileManager defaultManager] removeItemAtPath:localPath error:&rmError]) { - HSLogError(@"error deleting incomplete downloaded pack file %@: %@", localPath, rmError); - } - } - } - return ret; -} -- (BOOL)makeNotLocal:(NSError **)error { - NSFileManager *fm = [NSFileManager defaultManager]; - HSLogDebug(@"removing disk pack %@", localPath); - BOOL ret = YES; - if ([fm fileExistsAtPath:localPath] && ![fm removeItemAtPath:localPath error:error]) { - ret = NO; - } - return ret; -} -- (ServerBlob *)newServerBlobForObjectAtOffset:(unsigned long long)offset error:(NSError **)error { - int fd = open([localPath fileSystemRepresentation], O_RDONLY); - if (fd == -1) { - int errnum = errno; - HSLogError(@"open(%@) error %d: %s", localPath, errnum, strerror(errnum)); - SETNSERROR(@"UnixErrorDomain", errnum, @"failed to open %@: %s", localPath, strerror(errnum)); - return nil; - } - ServerBlob *ret = nil; - FDInputStream *fdis = [[FDInputStream alloc] initWithFD:fd label:localPath]; - BufferedInputStream *bis = [[BufferedInputStream alloc] initWithUnderlyingStream:fdis]; - do { - if (lseek(fd, offset, SEEK_SET) == -1) { - int errnum = errno; - HSLogError(@"lseek(%@, %qu) error %d: %s", localPath, offset, errnum, strerror(errnum)); - SETNSERROR(@"UnixErrorDomain", errnum, @"failed to seek to %qu in %@: %s", offset, localPath, strerror(errnum)); - break; - } - NSString *mimeType; - NSString *downloadName; - if (![StringIO read:&mimeType from:bis error:error] || ![StringIO read:&downloadName from:bis error:error]) { - break; - } - uint64_t dataLen = 0; - if (![IntegerIO readUInt64:&dataLen from:bis error:error]) { - break; - } - NSData *data = nil; - if (dataLen > 0) { - unsigned char *buf = (unsigned char *)malloc(dataLen); - if (![bis readExactly:dataLen into:buf error:error]) { - free(buf); - break; - } - data = [NSData dataWithBytesNoCopy:buf length:dataLen]; - } else { - data = [NSData data]; - } - ret = [[ServerBlob alloc] initWithData:data mimeType:mimeType downloadName:downloadName]; - } while (0); - close(fd); - [bis release]; - [fdis release]; - return ret; -} -- (BOOL)fileLength:(unsigned long long *)length error:(NSError **)error { - struct stat st; - if (lstat([localPath fileSystemRepresentation], &st) == -1) { - int errnum = errno; - HSLogError(@"lstat(%@) error %d: %s", localPath, errnum, strerror(errnum)); - SETNSERROR(@"UnixErrorDomain", errnum, @"%@: %s", localPath, strerror(errnum)); - return NO; - } - *length = st.st_size; - return YES; -} -- (BOOL)copyToPath:(NSString *)dest error:(NSError **)error { - NSFileManager *fm = [NSFileManager defaultManager]; - if ([fm fileExistsAtPath:dest] && ![fm removeItemAtPath:dest error:error]) { - HSLogError(@"error removing old mutable pack at %@", dest); - return NO; - } - if (![fm ensureParentPathExistsForPath:dest targetUID:targetUID targetGID:targetGID error:error] || ![fm copyItemAtPath:localPath toPath:dest error:error]) { - HSLogError(@"error copying pack %@ to %@", localPath, dest); - return NO; - } - if (chown([localPath fileSystemRepresentation], targetUID, targetGID) == -1) { - int errnum = errno; - SETNSERROR(@"UnixErrorDomain", errnum, @"chown(%@): %s", localPath, strerror(errnum)); - return NO; - } - HSLogDebug(@"copied %@ to %@", localPath, dest); - return YES; -} -- (NSArray *)sortedPackIndexEntries:(NSError **)error { - unsigned long long length; - if (![self fileLength:&length error:error]) { - return NO; - } - FileInputStream *fis = [[FileInputStream alloc] initWithPath:localPath offset:0 length:length]; - BufferedInputStream *bis = [[BufferedInputStream alloc] initWithUnderlyingStream:fis]; - NSArray *ret = [self sortedPackIndexEntriesFromStream:bis error:error]; - [bis release]; - [fis release]; - return ret; -} -@end - -@implementation DiskPack (internal) -- (BOOL)savePack:(ServerBlob *)sb error:(NSError **)error { - if (![[NSFileManager defaultManager] ensureParentPathExistsForPath:localPath targetUID:targetUID targetGID:targetGID error:error]) { - return NO; - } - id is = [sb newInputStream]; - NSError *myError; - unsigned long long written; - BOOL ret = [Streams transferFrom:is atomicallyToFile:localPath targetUID:targetUID targetGID:targetGID bytesWritten:&written error:&myError]; - if (ret) { - HSLogDebug(@"wrote %qu bytes to %@", written, localPath); - } else { - if (error != NULL) { - *error = myError; - } - HSLogError(@"error making pack %@ local at %@: %@", packSHA1, localPath, [myError localizedDescription]); - } - [is release]; - return ret; -} -- (NSArray *)sortedPackIndexEntriesFromStream:(BufferedInputStream *)is error:(NSError **)error { - uint32_t packSig; - uint32_t packVersion; - if (![IntegerIO readUInt32:&packSig from:is error:error] || ![IntegerIO readUInt32:&packVersion from:is error:error]) { - return nil; - } - if (packSig != 0x5041434b) { // "PACK" - SETNSERROR(@"PackErrorDomain", -1, @"invalid pack signature"); - return nil; - } - if (packVersion != 2) { - SETNSERROR(@"PackErrorDomain", -1, @"invalid pack version"); - } - uint32_t objectCount; - if (![IntegerIO readUInt32:&objectCount from:is error:error]) { - return nil; - } - - NSMutableArray *ret = [NSMutableArray array]; - for (uint32_t index = 0; index < objectCount; index++) { - uint64_t offset = [is bytesReceived]; - NSString *mimeType; - NSString *name; - uint64_t length; - if (![StringIO read:&mimeType from:is error:error] || ![StringIO read:&name from:is error:error] || ![IntegerIO readUInt64:&length from:is error:error]) { - return NO; - } - NSString *objectSHA1 = [SHA1Hash hashStream:is withLength:length error:error]; - if (objectSHA1 == nil) { - return NO; - } - PackIndexEntry *pie = [[PackIndexEntry alloc] initWithPackSHA1:packSHA1 offset:offset dataLength:length objectSHA1:objectSHA1]; - [ret addObject:pie]; - [pie release]; - } - return ret; -} - -#pragma mark NSObject -- (NSString *)description { - return [NSString stringWithFormat:@"", s3BucketName, computerUUID, packSetName, packSHA1, localPath]; -} -@end diff --git a/DiskPackIndex.h b/DiskPackIndex.h deleted file mode 100644 index 49fcd8f..0000000 --- a/DiskPackIndex.h +++ /dev/null @@ -1,46 +0,0 @@ -// -// DiskPackIndex.h -// Arq -// -// Created by Stefan Reitshamer on 12/30/09. -// Copyright 2009 __MyCompanyName__. All rights reserved. -// - - -@class PackIndexEntry; -@class S3Service; - -@interface DiskPackIndex : NSObject { - S3Service *s3; - NSString *s3BucketName; - NSString *computerUUID; - NSString *packSetName; - NSString *packSHA1; - NSString *s3Path; - NSString *localPath; - uid_t targetUID; - gid_t targetGID; -} -+ (NSString *)s3PathWithS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID packSetName:(NSString *)thePackSetName packSHA1:(NSString *)thePackSHA1; -+ (NSString *)localPathWithS3BucketName:theS3BucketName computerUUID:(NSString *)theComputerUUID packSetName:(NSString *)thePackSetName packSHA1:(NSString *)thePackSHA1; -+ (NSArray *)diskPackIndexesForS3Service:(S3Service *)theS3 - s3BucketName:theS3BucketName - computerUUID:(NSString *)theComputerUUID - packSetName:(NSString *)thePackSetName - targetUID:(uid_t)theTargetUID - targetGID:(gid_t)theTargetGID - error:(NSError **)error; - -- (id)initWithS3Service:(S3Service *)theS3 - s3BucketName:(NSString *)theS3BucketName - computerUUID:(NSString *)theComputerUUID - packSetName:(NSString *)thePackSetName - packSHA1:(NSString *)thePackSHA1 - targetUID:(uid_t)theTargetUID - targetGID:(gid_t)theTargetGID; -- (BOOL)makeLocal:(NSError **)error; -- (NSArray *)allPackIndexEntries:(NSError **)error; -- (PackIndexEntry *)entryForSHA1:(NSString *)sha1 error:(NSError **)error; -- (NSString *)packSetName; -- (NSString *)packSHA1; -@end diff --git a/DiskPackIndex.m b/DiskPackIndex.m deleted file mode 100644 index a164576..0000000 --- a/DiskPackIndex.m +++ /dev/null @@ -1,358 +0,0 @@ -// -// DiskPackIndex.m -// Arq -// -// Created by Stefan Reitshamer on 12/30/09. -// Copyright 2009 __MyCompanyName__. All rights reserved. -// - -#include -#include -#include -#import "DiskPackIndex.h" -#import "NSString_extra.h" -#import "SetNSError.h" -#import "BinarySHA1.h" -#import "PackIndexEntry.h" -#import "NSErrorCodes.h" -#import "S3Service.h" -#import "FileOutputStream.h" -#import "Streams.h" -#import "NSFileManager_extra.h" -#import "ServerBlob.h" -#import "S3ObjectReceiver.h" -#import "DiskPack.h" -#import "BlobACL.h" -#import "FileInputStreamFactory.h" -#import "PackIndexWriter.h" -#import "UserLibrary_Arq.h" -#import "NSError_extra.h" -#import "RegexKitLite.h" - -typedef struct index_object { - uint64_t nbo_offset; - uint64_t nbo_datalength; - unsigned char sha1[20]; - unsigned char filler[4]; -} index_object; - -typedef struct pack_index { - uint32_t magic_number; - uint32_t nbo_version; - uint32_t nbo_fanout[256]; - index_object first_index_object; -} pack_index; - -@interface DiskPackIndex (internal) -- (BOOL)savePackIndex:(ServerBlob *)sb error:(NSError **)error; -- (PackIndexEntry *)doEntryForSHA1:(NSString *)sha1 error:(NSError **)error; -- (PackIndexEntry *)findEntryForSHA1:(NSString *)sha1 fd:(int)fd betweenStartIndex:(uint32_t)startIndex andEndIndex:(uint32_t)endIndex error:(NSError **)error; -- (BOOL)readFanoutStartIndex:(uint32_t *)start fanoutEndIndex:(uint32_t *)end fromFD:(int)fd forSHA1FirstByte:(unsigned int)firstByte error:(NSError **)error; -@end - -@implementation DiskPackIndex -+ (NSString *)s3PathWithS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID packSetName:(NSString *)thePackSetName packSHA1:(NSString *)thePackSHA1 { - return [NSString stringWithFormat:@"/%@/%@/packsets/%@/%@.index", theS3BucketName, theComputerUUID, thePackSetName, thePackSHA1]; -} -+ (NSString *)localPathWithS3BucketName:theS3BucketName computerUUID:(NSString *)theComputerUUID packSetName:(NSString *)thePackSetName packSHA1:(NSString *)thePackSHA1 { - return [NSString stringWithFormat:@"%@/%@/%@/packsets/%@/%@/%@.index", [UserLibrary arqCachePath], theS3BucketName, theComputerUUID, thePackSetName, [thePackSHA1 substringToIndex:2], [thePackSHA1 substringFromIndex:2]]; -} -+ (NSArray *)diskPackIndexesForS3Service:(S3Service *)theS3 - s3BucketName:theS3BucketName - computerUUID:(NSString *)theComputerUUID - packSetName:(NSString *)thePackSetName - targetUID:(uid_t)theTargetUID - targetGID:(gid_t)theTargetGID - error:(NSError **)error { - NSMutableArray *diskPackIndexes = [NSMutableArray array]; - NSString *packSetsPrefix = [NSString stringWithFormat:@"/%@/%@/packsets/%@/", theS3BucketName, theComputerUUID, thePackSetName]; - NSArray *paths = [theS3 pathsWithPrefix:packSetsPrefix error:error]; - if (paths == nil) { - return nil; - } - for (NSString *thePath in paths) { - NSRange sha1Range = [thePath rangeOfRegex:@"/(\\w+)\\.pack$" capture:1]; - if (sha1Range.location != NSNotFound) { - NSString *thePackSHA1 = [thePath substringWithRange:sha1Range]; - DiskPackIndex *index = [[DiskPackIndex alloc] initWithS3Service:theS3 - s3BucketName:theS3BucketName - computerUUID:theComputerUUID - packSetName:thePackSetName - packSHA1:thePackSHA1 - targetUID:theTargetUID - targetGID:theTargetGID]; - [diskPackIndexes addObject:index]; - [index release]; - } - } - return diskPackIndexes; -} - - -- (id)initWithS3Service:(S3Service *)theS3 - s3BucketName:(NSString *)theS3BucketName - computerUUID:(NSString *)theComputerUUID - packSetName:(NSString *)thePackSetName - packSHA1:(NSString *)thePackSHA1 - targetUID:(uid_t)theTargetUID - targetGID:(gid_t)theTargetGID { - if (self = [super init]) { - s3 = [theS3 retain]; - s3BucketName = [theS3BucketName retain]; - computerUUID = [theComputerUUID retain]; - packSetName = [thePackSetName retain]; - packSHA1 = [thePackSHA1 retain]; - s3Path = [[DiskPackIndex s3PathWithS3BucketName:s3BucketName computerUUID:computerUUID packSetName:packSetName packSHA1:packSHA1] retain]; - localPath = [[DiskPackIndex localPathWithS3BucketName:s3BucketName computerUUID:computerUUID packSetName:packSetName packSHA1:packSHA1] retain]; - targetUID = theTargetUID; - targetGID = theTargetGID; - } - return self; -} -- (void)dealloc { - [s3 release]; - [s3BucketName release]; - [computerUUID release]; - [packSetName release]; - [packSHA1 release]; - [s3Path release]; - [localPath release]; - [super dealloc]; -} -- (BOOL)makeLocal:(NSError **)error { - NSFileManager *fm = [NSFileManager defaultManager]; - BOOL ret = YES; - if (![fm fileExistsAtPath:localPath]) { - for (;;) { - HSLogDebug(@"packset %@: making pack index %@ local", packSetName, packSHA1); - NSError *myError = nil; - ServerBlob *sb = [s3 newServerBlobAtPath:s3Path error:&myError]; - if (sb != nil) { - ret = [self savePackIndex:sb error:error]; - [sb release]; - break; - } - if (![myError isTransientError]) { - HSLogError(@"error getting S3 pack index %@: %@", s3Path, myError); - if (error != NULL) { - *error = myError; - } - ret = NO; - break; - } - HSLogWarn(@"network error making pack index %@ local (retrying): %@", s3Path, myError); - NSError *rmError = nil; - if (![[NSFileManager defaultManager] removeItemAtPath:localPath error:&rmError]) { - HSLogError(@"error deleting incomplete downloaded pack index %@: %@", localPath, rmError); - } - } - } - return ret; -} -- (NSArray *)allPackIndexEntries:(NSError **)error { - int fd = open([localPath fileSystemRepresentation], O_RDONLY); - if (fd == -1) { - int errnum = errno; - HSLogError(@"open(%@) error %d: %s", localPath, errnum, strerror(errnum)); - SETNSERROR(@"UnixErrorDomain", errnum, @"failed to open %@: %s", localPath, strerror(errnum)); - return nil; - } - struct stat st; - if (fstat(fd, &st) == -1) { - int errnum = errno; - HSLogError(@"fstat(%@) error %d: %s", localPath, errnum, strerror(errnum)); - SETNSERROR(@"UnixErrorDomain", errnum, @"%@: %s", localPath, strerror(errnum)); - close(fd); - return nil; - } - pack_index *the_pack_index = mmap(0, st.st_size, PROT_READ, MAP_SHARED, fd, 0); - if (the_pack_index == MAP_FAILED) { - int errnum = errno; - HSLogError(@"mmap(%@) error %d: %s", localPath, errnum, strerror(errnum)); - SETNSERROR(@"UnixErrorDomain", errnum, @"error mapping %@ to memory: %s", localPath, strerror(errnum)); - close(fd); - return NO; - } - NSMutableArray *ret = [NSMutableArray array]; - uint32_t count = OSSwapBigToHostInt32(the_pack_index->nbo_fanout[255]); - index_object *indexObjects = &(the_pack_index->first_index_object); - for (uint32_t i = 0; i < count; i++) { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - uint64_t offset = OSSwapBigToHostInt64(indexObjects[i].nbo_offset); - uint64_t dataLength = OSSwapBigToHostInt64(indexObjects[i].nbo_datalength); - NSString *objectSHA1 = [NSString hexStringWithBytes:indexObjects[i].sha1 length:20]; - PackIndexEntry *pie = [[[PackIndexEntry alloc] initWithPackSHA1:packSHA1 offset:offset dataLength:dataLength objectSHA1:objectSHA1] autorelease]; - [ret addObject:pie]; - [pool drain]; - } - if (munmap(the_pack_index, st.st_size) == -1) { - int errnum = errno; - HSLogError(@"munmap: %s", strerror(errnum)); - } - close(fd); - return ret; -} -- (PackIndexEntry *)entryForSHA1:(NSString *)sha1 error:(NSError **)error { - if (error != NULL) { - *error = nil; - } - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - PackIndexEntry *ret = [self doEntryForSHA1:sha1 error:error]; - [ret retain]; - if (ret == nil && error != NULL) { - [*error retain]; - } - [pool drain]; - [ret autorelease]; - if (ret == nil && error != NULL) { - [*error autorelease]; - } - return ret; -} -- (NSString *)packSetName { - return packSetName; -} -- (NSString *)packSHA1 { - return packSHA1; -} - -#pragma mark NSObject -- (NSString *)description { - return [NSString stringWithFormat:@"", computerUUID, packSetName, packSHA1]; -} -@end - -@implementation DiskPackIndex (internal) -- (BOOL)savePackIndex:(ServerBlob *)sb error:(NSError **)error { - if (![[NSFileManager defaultManager] ensureParentPathExistsForPath:localPath targetUID:targetUID targetGID:targetGID error:error]) { - return NO; - } - id is = [sb newInputStream]; - NSError *myError = nil; - unsigned long long written = 0; - BOOL ret = [Streams transferFrom:is atomicallyToFile:localPath targetUID:targetUID targetGID:targetGID bytesWritten:&written error:&myError]; - if (ret) { - HSLogDebug(@"wrote %qu bytes to %@", written, localPath); - } else { - if (error != NULL) { - *error = myError; - } - HSLogError(@"error making pack %@ local at %@: %@", packSHA1, localPath, [myError localizedDescription]); - } - [is release]; - return ret; -} -- (PackIndexEntry *)doEntryForSHA1:(NSString *)sha1 error:(NSError **)error { - NSData *sha1Hex = [sha1 hexStringToData]; - unsigned char *sha1Bytes = (unsigned char *)[sha1Hex bytes]; - HSLogTrace(@"looking for sha1 %@ in packindex %@", sha1, packSHA1); - int fd = open([localPath fileSystemRepresentation], O_RDONLY); - if (fd == -1) { - int errnum = errno; - HSLogError(@"open(%@) error %d: %s", localPath, errnum, strerror(errnum)); - SETNSERROR(@"UnixErrorDomain", errnum, @"failed to open %@: %s", localPath, strerror(errnum)); - return nil; - } - uint32_t startIndex; - uint32_t endIndex; - if (![self readFanoutStartIndex:&startIndex fanoutEndIndex:&endIndex fromFD:fd forSHA1FirstByte:(unsigned int)sha1Bytes[0] error:error]) { - close(fd); - return nil; - } - close(fd); - if (endIndex == 0) { - SETNSERROR(@"PacksErrorDomain", ERROR_NOT_FOUND, @"sha1 %@ not found in pack", sha1); - return NO; - } - fd = open([localPath fileSystemRepresentation], O_RDONLY); - if (fd == -1) { - int errnum = errno; - HSLogError(@"open(%@) error %d: %s", localPath, errnum, strerror(errnum)); - SETNSERROR(@"UnixErrorDomain", errnum, @"failed to open %@: %s", localPath, strerror(errnum)); - return nil; - } - PackIndexEntry *ret = [self findEntryForSHA1:sha1 fd:fd betweenStartIndex:startIndex andEndIndex:endIndex error:error]; - close(fd); - if (ret != nil) { - HSLogTrace(@"found sha1 %@ in packindex %@", sha1, packSHA1); - } - return ret; -} -- (PackIndexEntry *)findEntryForSHA1:(NSString *)sha1 fd:(int)fd betweenStartIndex:(uint32_t)startIndex andEndIndex:(uint32_t)endIndex error:(NSError **)error { - NSData *sha1Data = [sha1 hexStringToData]; - const void *sha1Bytes = [sha1Data bytes]; - uint32_t lengthToMap = 4 + 4 + 256*4 + endIndex * sizeof(index_object); - pack_index *the_pack_index = mmap(0, lengthToMap, PROT_READ, MAP_SHARED, fd, 0); - if (the_pack_index == MAP_FAILED) { - int errnum = errno; - HSLogError(@"mmap(%@) error %d: %s", localPath, errnum, strerror(errnum)); - SETNSERROR(@"UnixErrorDomain", errnum, @"error mapping %@ to memory: %s", localPath, strerror(errnum)); - return NO; - } - int64_t left = startIndex; - int64_t right = endIndex - 1; - int64_t middle; - int64_t offset; - int64_t dataLength; - PackIndexEntry *pie = nil; - while (left <= right) { - middle = (left + right)/2; - index_object *firstIndexObject = &(the_pack_index->first_index_object); - index_object *middleIndexObject = &firstIndexObject[middle]; - void *middleSHA1 = middleIndexObject->sha1; - NSComparisonResult result = [BinarySHA1 compare:middleSHA1 to:sha1Bytes]; - switch (result) { - case NSOrderedAscending: - left = middle + 1; - break; - case NSOrderedDescending: - right = middle - 1; - break; - default: - offset = OSSwapBigToHostInt64(middleIndexObject->nbo_offset); - dataLength = OSSwapBigToHostInt64(middleIndexObject->nbo_datalength); - pie = [[[PackIndexEntry alloc] initWithPackSHA1:packSHA1 offset:offset dataLength:dataLength objectSHA1:sha1] autorelease]; - } - if (pie != nil) { - break; - } - } - if (munmap(the_pack_index, lengthToMap) == -1) { - int errnum = errno; - HSLogError(@"munmap: %s", strerror(errnum)); - } - if (pie == nil) { - SETNSERROR(@"PackErrorDomain", ERROR_NOT_FOUND, @"sha1 %@ not found in pack %@", sha1, packSHA1); - } - return pie; -} -- (BOOL)readFanoutStartIndex:(uint32_t *)start fanoutEndIndex:(uint32_t *)end fromFD:(int)fd forSHA1FirstByte:(unsigned int)firstByte error:(NSError **)error { - size_t len = 4 + 4 + 4*256; - uint32_t *map = mmap(0, len, PROT_READ, MAP_SHARED, fd, 0); - if (map == MAP_FAILED) { - int errnum = errno; - HSLogError(@"mmap(%@) error %d: %s", localPath, errnum, strerror(errnum)); - SETNSERROR(@"UnixErrorDomain", errnum, @"error mapping %@ to memory: %s", localPath, strerror(errnum)); - return NO; - } - BOOL ret = YES; - uint32_t magicNumber = OSSwapBigToHostInt32(map[0]); - uint32_t version = OSSwapBigToHostInt32(map[1]); - if (magicNumber != 0xff744f63 || version != 2) { - SETNSERROR(@"PackErrorDomain", -1, @"invalid pack index header"); - ret = NO; - } else { - uint32_t *fanoutTable = map + 2; - *start = 0; - if (firstByte > 0) { - *start = OSSwapBigToHostInt32(fanoutTable[firstByte - 1]); - } - *end = OSSwapBigToHostInt32(fanoutTable[firstByte]); - } - if (munmap(map, len) == -1) { - int errnum = errno; - HSLogError(@"munmap: %s", strerror(errnum)); - } - return ret; -} -@end diff --git a/FarkPath.h b/FarkPath.h deleted file mode 100644 index ceca10d..0000000 --- a/FarkPath.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// FarkPath.h -// Arq -// -// Created by Stefan Reitshamer on 6/29/10. -// Copyright 2010 __MyCompanyName__. All rights reserved. -// - - - - -@interface FarkPath : NSObject { - -} -+ (NSString *)s3PathForS3BucketName:(NSString *)s3BucketName computerUUID:(NSString *)computerUUID sha1:(NSString *)sha1; -+ (NSString *)s3PathForBucketDataRelativePath:(NSString *)bucketDataRelativePath s3BucketName:(NSString *)s3BucketName computerUUID:(NSString *)computerUUID; -@end diff --git a/FarkPath.m b/FarkPath.m deleted file mode 100644 index 39c2a7d..0000000 --- a/FarkPath.m +++ /dev/null @@ -1,19 +0,0 @@ -// -// FarkPath.m -// Arq -// -// Created by Stefan Reitshamer on 6/29/10. -// Copyright 2010 __MyCompanyName__. All rights reserved. -// - -#import "FarkPath.h" - - -@implementation FarkPath -+ (NSString *)s3PathForS3BucketName:(NSString *)s3BucketName computerUUID:(NSString *)computerUUID sha1:(NSString *)sha1 { - return [NSString stringWithFormat:@"/%@/%@/objects/%@", s3BucketName, computerUUID, sha1]; -} -+ (NSString *)s3PathForBucketDataRelativePath:(NSString *)bucketDataRelativePath s3BucketName:(NSString *)s3BucketName computerUUID:(NSString *)computerUUID { - return [[NSString stringWithFormat:@"/%@/%@/bucketdata", s3BucketName, computerUUID] stringByAppendingPathComponent:bucketDataRelativePath]; -} -@end diff --git a/FileAttributes.h b/FileAttributes.h deleted file mode 100644 index 4d0a50e..0000000 --- a/FileAttributes.h +++ /dev/null @@ -1,63 +0,0 @@ -// -// FileAttributes.h -// Backup -// -// Created by Stefan Reitshamer on 4/22/09. -// Copyright 2009 PhotoMinds LLC. All rights reserved. -// - -#include - - -@interface FileAttributes : NSObject { - BOOL targetExists; - NSString *path; - const char *cPath; - struct stat st; - struct timespec createTime; - int finderFlags; - int extendedFinderFlags; - NSString *finderFileType; - NSString *finderFileCreator; -} -- (id)initWithPath:(NSString *)thePath error:(NSError **)error; -- (id)initWithPath:(NSString *)thePath stat:(struct stat *)st error:(NSError **)error; - -- (unsigned long long)fileSize; -- (int)uid; -- (int)gid; -- (int)mode; -- (long)mtime_sec; -- (long)mtime_nsec; -- (long)flags; -- (int)finderFlags; -- (int)extendedFinderFlags; -- (NSString *)finderFileType; -- (NSString *)finderFileCreator; -- (BOOL)isExtensionHidden; -- (BOOL)isFifo; -- (BOOL)isDevice; -- (BOOL)isSymbolicLink; -- (BOOL)isRegularFile; -- (BOOL)isSocket; -- (int)st_dev; -- (int)st_ino; -- (uint32_t)st_nlink; -- (int)st_rdev; -- (int64_t)ctime_sec; -- (int64_t)ctime_nsec; -- (int64_t)createTime_sec; -- (int64_t)createTime_nsec; -- (int64_t)st_blocks; -- (uint32_t)st_blksize; - -- (BOOL)applyFinderFileType:(NSString *)finderFileType finderFileCreator:(NSString *)finderFileCreator error:(NSError **)error; -- (BOOL)applyFlags:(int)flags error:(NSError **)error; -- (BOOL)applyFinderFlags:(int)finderFlags error:(NSError **)error; -- (BOOL)applyExtendedFinderFlags:(int)extendedFinderFlags error:(NSError **)error; -- (BOOL)applyExtensionHidden:(BOOL)isExtensionHidden error:(NSError **)error; -- (BOOL)applyUID:(int)uid gid:(int)gid error:(NSError **)error; -- (BOOL)applyMode:(int)mode error:(NSError **)error; -- (BOOL)applyMTimeSec:(int64_t)mtime_sec mTimeNSec:(int64_t)mtime_nsec error:(NSError **)error; -- (BOOL)applyCreateTimeSec:(int64_t)createTime_sec createTimeNSec:(int64_t)createTime_sec error:(NSError **)error; -@end diff --git a/FileAttributes.m b/FileAttributes.m deleted file mode 100644 index 9cc5955..0000000 --- a/FileAttributes.m +++ /dev/null @@ -1,541 +0,0 @@ -// -// FileAttributes.m -// Backup -// -// Created by Stefan Reitshamer on 4/22/09. -// Copyright 2009 PhotoMinds LLC. All rights reserved. -// - -#include -#include -#include -#include -#include -#import "FileAttributes.h" -#import "SHA1Hash.h" -#import "FileACL.h" -#import "SetNSError.h" -#import "OSStatusDescription.h" -#import "NSError_extra.h" - -#define kCouldNotCreateCFString 4 -#define kCouldNotGetStringData 5 -#define MAX_PATH 1024 - -struct createDateBuf { - u_int32_t length; - struct timespec createTime; -}; - -static OSStatus ConvertCStringToHFSUniStr(const char* cStr, HFSUniStr255 *uniStr) { - OSStatus oss = noErr; - CFStringRef tmpStringRef = CFStringCreateWithCString(kCFAllocatorDefault, cStr, kCFStringEncodingMacRoman); - if (tmpStringRef != NULL) { - if (CFStringGetCString(tmpStringRef, (char*)uniStr->unicode, sizeof(uniStr->unicode), kCFStringEncodingUnicode)) { - uniStr->length = CFStringGetLength(tmpStringRef); - } else { - oss = kCouldNotGetStringData; - } - CFRelease(tmpStringRef); - } else { - oss = kCouldNotCreateCFString; - } - return oss; -} -static OSStatus SymlinkPathMakeRef(const UInt8 *path, FSRef *ref, Boolean *isDirectory) { - FSRef tmpFSRef; - char tmpPath[MAX_PATH]; - char *tmpNamePtr; - OSStatus oss; - - strcpy(tmpPath, (char *)path); - tmpNamePtr = strrchr(tmpPath, '/'); - if (*(tmpNamePtr + 1) == '\0') { - // Last character in the path is a '/'. - *tmpNamePtr = '\0'; - tmpNamePtr = strrchr(tmpPath, '/'); - } - *tmpNamePtr = '\0'; - tmpNamePtr++; - - // Get FSRef for parent directory. - oss = FSPathMakeRef((const UInt8 *)tmpPath, &tmpFSRef, NULL); - if (oss == noErr) { - HFSUniStr255 uniName; - oss = ConvertCStringToHFSUniStr(tmpNamePtr, &uniName); - if (oss == noErr) { - FSRef newFSRef; - oss = FSMakeFSRefUnicode(&tmpFSRef, uniName.length, uniName.unicode, kTextEncodingUnknown, &newFSRef); - tmpFSRef = newFSRef; - } - } - if (oss == noErr) { - *ref = tmpFSRef; - } - return oss; -} - -@implementation FileAttributes -- (id)initWithPath:(NSString *)thePath error:(NSError **)error { - struct stat theStat; - int ret = lstat([thePath fileSystemRepresentation], &theStat); - if (ret == -1) { - int errnum = errno; - HSLogError(@"lstat(%@) error %d: %s", thePath, errnum, strerror(errnum)); - SETNSERROR(@"UnixErrorDomain", errnum, @"%@: %s", thePath, strerror(errnum)); - return nil; - } - return [self initWithPath:thePath stat:&theStat error:error]; -} -- (id)initWithPath:(NSString *)thePath stat:(struct stat *)theStat error:(NSError **)error { - if (self = [super init]) { - path = [thePath copy]; - cPath = [path fileSystemRepresentation]; - memcpy(&st, theStat, sizeof(st)); - targetExists = YES; - if (S_ISLNK(st.st_mode)) { - struct stat targetSt; - int ret = stat(cPath, &targetSt); - if (ret == -1 && errno == ENOENT) { - targetExists = NO; - } - } - if (targetExists) { - FSRef fsRef; - Boolean isDirectory; - OSStatus oss = 0; - if (S_ISLNK(st.st_mode)) { - oss = SymlinkPathMakeRef((UInt8*)cPath, &fsRef, &isDirectory); - } else { - oss = FSPathMakeRef((UInt8*)cPath, &fsRef, &isDirectory); - } - if (oss == bdNamErr) { - HSLogInfo(@"skipping finder flags for %s: %@", cPath, [OSStatusDescription descriptionForMacOSStatus:oss]); - }else if (oss != noErr) { - SETNSERROR(@"MacFilesErrorDomain", oss, @"error making FSRef for %@: %@", thePath, [OSStatusDescription descriptionForMacOSStatus:oss]); - [self release]; - self = nil; - return self; - } else { - FSCatalogInfo catalogInfo; - OSErr oserr = FSGetCatalogInfo(&fsRef, kFSCatInfoCreateDate | kFSCatInfoFinderInfo | kFSCatInfoFinderXInfo, &catalogInfo, NULL, NULL, NULL); - if (oserr) { - SETNSERROR(@"MacFilesErrorDomain", oss, @"FSGetCatalogInfo(%@): %@", thePath, [OSStatusDescription descriptionForMacOSStatus:(OSStatus)oserr]); - [self release]; - self = nil; - return self; - } - - CFTimeInterval theCreateTime; // double: seconds since reference date - if (UCConvertUTCDateTimeToCFAbsoluteTime(&catalogInfo.createDate, &theCreateTime) != noErr) { - HSLogError(@"error converting create time to CFAbsoluteTime"); - } else { - createTime.tv_sec = (int64_t)(theCreateTime + NSTimeIntervalSince1970); - CFTimeInterval subsecond = theCreateTime - (double)((int64_t)theCreateTime); - createTime.tv_nsec = (int64_t)(subsecond * 1000000000.0); - } - - finderFlags = 0; - extendedFinderFlags = 0; - if (isDirectory) { - FolderInfo *folderInfo = (FolderInfo *)&catalogInfo.finderInfo; - finderFlags = folderInfo->finderFlags; - ExtendedFolderInfo *extFolderInfo = (ExtendedFolderInfo *)&catalogInfo.extFinderInfo; - extendedFinderFlags = extFolderInfo->extendedFinderFlags; - finderFileType = [[NSString alloc] initWithString:@""]; - finderFileCreator = [[NSString alloc] initWithString:@""]; - } else { - FileInfo *fileInfo = (FileInfo *)&catalogInfo.finderInfo; - finderFlags = fileInfo->finderFlags; - ExtendedFileInfo *extFileInfo = (ExtendedFileInfo *)&catalogInfo.extFinderInfo; - extendedFinderFlags = extFileInfo->extendedFinderFlags; - - char fileType[5]; - fileType[0] = *((const char *)&fileInfo->fileType + 3); - fileType[1] = *((const char *)&fileInfo->fileType + 2); - fileType[2] = *((const char *)&fileInfo->fileType + 1); - fileType[3] = *((const char *)&fileInfo->fileType); - fileType[4] = 0; - finderFileType = [[NSString alloc] initWithCString:fileType encoding:NSUTF8StringEncoding]; - char fileCreator[5]; - fileCreator[0] = *((const char *)&fileInfo->fileCreator + 3); - fileCreator[1] = *((const char *)&fileInfo->fileCreator + 2); - fileCreator[2] = *((const char *)&fileInfo->fileCreator + 1); - fileCreator[3] = *((const char *)&fileInfo->fileCreator); - fileCreator[4] = 0; - finderFileCreator = [[NSString alloc] initWithCString:fileCreator encoding:NSUTF8StringEncoding]; - } - } - } - } - return self; -} -- (void)dealloc { - [path release]; - [finderFileType release]; - [finderFileCreator release]; - [super dealloc]; -} -- (unsigned long long)fileSize { - return (unsigned long long)st.st_size; -} -- (int)uid { - return st.st_uid; -} -- (int)gid { - return st.st_gid; -} -- (int)mode { - return st.st_mode; -} -- (long)mtime_sec { - return st.st_mtimespec.tv_sec; -} -- (long)mtime_nsec { - return st.st_mtimespec.tv_nsec; -} -- (long)flags { - return st.st_flags; -} - -- (int)finderFlags { - return finderFlags; -} -- (int)extendedFinderFlags { - return extendedFinderFlags; -} -- (NSString *)finderFileType { - return finderFileType; -} -- (NSString *)finderFileCreator { - return finderFileCreator; -} -- (BOOL)isExtensionHidden { - return st.st_flags & UF_HIDDEN; -} -- (BOOL)isFifo { - return S_ISFIFO(st.st_mode); -} -- (BOOL)isDevice { - return S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode); -} -- (BOOL)isSymbolicLink { - return S_ISLNK(st.st_mode); -} -- (BOOL)isRegularFile { - return S_ISREG(st.st_mode); -} -- (BOOL)isSocket { - return S_ISSOCK(st.st_mode); -} -- (int)st_dev { - return st.st_dev; -} -- (int)st_ino { - return st.st_ino; -} -- (uint32_t)st_nlink { - return st.st_nlink; -} -- (int)st_rdev { - return st.st_rdev; -} -- (int64_t)ctime_sec { - return st.st_ctimespec.tv_sec; -} -- (int64_t)ctime_nsec { - return st.st_ctimespec.tv_nsec; -} -- (int64_t)createTime_sec { - return createTime.tv_sec; -} -- (int64_t)createTime_nsec { - return createTime.tv_nsec; -} -- (int64_t)st_blocks { - return st.st_blocks; -} -- (uint32_t)st_blksize { - return st.st_blksize; -} -- (BOOL)applyFinderFileType:(NSString *)fft finderFileCreator:(NSString *)ffc error:(NSError **)error { - if (targetExists && (![fft isEqualToString:finderFileType] || ![ffc isEqualToString:finderFileCreator])) { - if ([fft length] != 4) { - HSLogTrace(@"not applying finder file type '%@' to %@: invalid length (must be 4 characters)", fft, path); - } else if ([ffc length] != 4) { - HSLogTrace(@"not applying finder file type '%@' to %@: invalid length (must be 4 characters)", ffc, path); - } else { - FSRef fsRef; - Boolean isDirectory; - OSStatus oss = 0; - if (S_ISLNK(st.st_mode)) { - oss = SymlinkPathMakeRef((UInt8*)cPath, &fsRef, &isDirectory); - } else { - oss = FSPathMakeRef((UInt8*)cPath, &fsRef, &isDirectory); - } - if (oss != noErr) { - if (oss == bdNamErr) { - HSLogInfo(@"not setting finder file type/creator on %s: bad name", cPath); - return YES; - } else { - SETNSERROR(@"FileManagerErrorDomain", -1, @"%@", [OSStatusDescription descriptionForMacOSStatus:oss]); - return NO; - } - } - if (isDirectory) { - SETNSERROR(@"FileManagerErrorDomain", -1, @"cannot apply finderFileType to a directory"); - return NO; - } - - FSCatalogInfo catalogInfo; - OSErr oserr = FSGetCatalogInfo(&fsRef, kFSCatInfoFinderInfo, &catalogInfo, NULL, NULL, NULL); - if (oserr) { - SETNSERROR(@"FileManagerErrorDomain", -1, @"%@", [OSStatusDescription descriptionForMacOSStatus:(OSStatus)oserr]); - return NO; - } - FileInfo *fileInfo = (FileInfo *)&catalogInfo.finderInfo; - const char *fileType = [fft UTF8String]; - char *destFileType = (char *)&fileInfo->fileType; - destFileType[3] = fileType[0]; - destFileType[2] = fileType[1]; - destFileType[1] = fileType[2]; - destFileType[0] = fileType[3]; - - const char *fileCreator = [ffc UTF8String]; - char *destFileCreator = (char *)&fileInfo->fileCreator; - destFileCreator[3] = fileCreator[0]; - destFileCreator[2] = fileCreator[1]; - destFileCreator[1] = fileCreator[2]; - destFileCreator[0] = fileCreator[3]; - - oserr = FSSetCatalogInfo(&fsRef, kFSCatInfoFinderInfo, &catalogInfo); - if (oserr) { - SETNSERROR(@"FileManagerErrorDomain", -1, @"%@", [OSStatusDescription descriptionForMacOSStatus:(OSStatus)oserr]); - return NO; - } - [finderFileType release]; - finderFileType = [fft copy]; - [finderFileCreator release]; - finderFileCreator = [ffc copy]; - } - } - return YES; -} -- (BOOL)applyFlags:(int)flags error:(NSError **)error { - if (targetExists && flags != st.st_flags) { - HSLogTrace(@"chflags(%s, %d)", cPath, flags); - if (chflags(cPath, flags) == -1) { - int errnum = errno; - HSLogError(@"chflags(%s, %d) error %d: %s", cPath, flags, errnum, strerror(errnum)); - SETNSERROR(@"UnixErrorDomain", errnum, @"error changing flags of %s: %s", cPath, strerror(errnum)); - return NO; - } - st.st_flags = flags; - } - return YES; -} -- (BOOL)applyFinderFlags:(int)ff error:(NSError **)error { - if (targetExists && ff != finderFlags) { - FSRef fsRef; - Boolean isDirectory; - OSStatus oss = 0; - if (S_ISLNK(st.st_mode)) { - oss = SymlinkPathMakeRef((UInt8*)cPath, &fsRef, &isDirectory); - } else { - oss = FSPathMakeRef((UInt8*)cPath, &fsRef, &isDirectory); - } - if (oss != noErr) { - if (oss == bdNamErr) { - HSLogInfo(@"not setting finder file type/creator on %s: bad name", cPath); - return YES; - } else { - SETNSERROR(@"FileManagerErrorDomain", -1, @"%@", [OSStatusDescription descriptionForMacOSStatus:oss]); - return NO; - } - } - FSCatalogInfo catalogInfo; - OSErr oserr = FSGetCatalogInfo(&fsRef, kFSCatInfoFinderInfo, &catalogInfo, NULL, NULL, NULL); - if (oserr) { - SETNSERROR(@"FileManagerErrorDomain", -1, @"%@", [OSStatusDescription descriptionForMacOSStatus:(OSStatus)oserr]); - return NO; - } - if (isDirectory) { - FolderInfo *folderInfo = (FolderInfo *)&catalogInfo.finderInfo; - folderInfo->finderFlags = ff; - } else { - FileInfo *fileInfo = (FileInfo *)&catalogInfo.finderInfo; - fileInfo->finderFlags = ff; - } - oserr = FSSetCatalogInfo(&fsRef, kFSCatInfoFinderInfo, &catalogInfo); - if (oserr) { - SETNSERROR(@"FileManagerErrorDomain", -1, @"%@", [OSStatusDescription descriptionForMacOSStatus:(OSStatus)oserr]); - return NO; - } - finderFlags = ff; - } - return YES; -} -- (BOOL)applyExtendedFinderFlags:(int)eff error:(NSError **)error { - if (targetExists && extendedFinderFlags != eff) { - FSRef fsRef; - Boolean isDirectory; - OSStatus oss = 0; - if (S_ISLNK(st.st_mode)) { - oss = SymlinkPathMakeRef((UInt8*)cPath, &fsRef, &isDirectory); - } else { - oss = FSPathMakeRef((UInt8*)cPath, &fsRef, &isDirectory); - } - if (oss != noErr) { - if (oss == bdNamErr) { - HSLogInfo(@"not setting finder file type/creator on %s: bad name", cPath); - return YES; - } else { - SETNSERROR(@"FileManagerErrorDomain", -1, @"%@", [OSStatusDescription descriptionForMacOSStatus:oss]); - return NO; - } - } - FSCatalogInfo catalogInfo; - OSErr oserr = FSGetCatalogInfo(&fsRef, kFSCatInfoFinderXInfo, &catalogInfo, NULL, NULL, NULL); - if (oserr) { - SETNSERROR(@"FileManagerErrorDomain", -1, @"%@", [OSStatusDescription descriptionForMacOSStatus:(OSStatus)oserr]); - return NO; - } - if (isDirectory) { - ExtendedFolderInfo *extFolderInfo = (ExtendedFolderInfo *)&catalogInfo.extFinderInfo; - extFolderInfo->extendedFinderFlags = eff; - } else { - ExtendedFileInfo *extFileInfo = (ExtendedFileInfo *)&catalogInfo.extFinderInfo; - extFileInfo->extendedFinderFlags = eff; - } - oserr = FSSetCatalogInfo(&fsRef, kFSCatInfoFinderXInfo, &catalogInfo); - if (oserr) { - SETNSERROR(@"FileManagerErrorDomain", -1, @"%@", [OSStatusDescription descriptionForMacOSStatus:(OSStatus)oserr]); - return NO; - } - extendedFinderFlags = eff; - } - return YES; -} -- (BOOL)applyExtensionHidden:(BOOL)hidden error:(NSError **)error { - BOOL ret = YES; - if (hidden != [self isExtensionHidden]) { - NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:hidden], NSFileExtensionHidden, nil]; - ret = [[NSFileManager defaultManager] setAttributes:attribs ofItemAtPath:path error:error]; - if (ret) { - if (hidden) { - st.st_flags = st.st_flags & UF_HIDDEN; - } else { - st.st_flags = st.st_flags & (0xffffffff ^ UF_HIDDEN); - } - } - } - return ret; -} -- (BOOL)applyUID:(int)uid gid:(int)gid error:(NSError **)error { - if (uid != st.st_uid || gid != st.st_gid) { - if (lchown(cPath, uid, gid) == -1) { - int errnum = errno; - HSLogError(@"lchown(%s) error %d: %s", cPath, errnum, strerror(errnum)); - SETNSERROR(@"UnixErrorDomain", errnum, @"error changing ownership of %s: %s", cPath, strerror(errnum)); - return NO; - } - HSLogDebug(@"lchown(%s, %d, %d); euid=%d", cPath, uid, gid, geteuid()); - st.st_uid = uid; - st.st_gid = gid; - } - return YES; -} -- (BOOL)applyMode:(int)mode error:(NSError **)error { - if (mode != st.st_mode) { - if (S_ISDIR(st.st_mode)) { - int ret = chmod(cPath, mode); - if (ret == -1) { - int errnum = errno; - HSLogError(@"chmod(%s, %d) error %d: %s", cPath, mode, errnum, strerror(errnum)); - SETNSERROR(@"UnixErrorDomain", errnum, @"failed to set permissions on %@: %s", path, strerror(errnum)); - return NO; - } - HSLogDebug(@"chmod(%s, 0%6o)", cPath, mode); - } else { - int fd = open(cPath, O_RDWR|O_SYMLINK); - if (fd == -1) { - int errnum = errno; - HSLogError(@"open(%s) error %d: %s", cPath, errnum, strerror(errnum)); - SETNSERROR(@"UnixErrorDomain", errnum, @"failed to open %@: %s", path, strerror(errnum)); - return NO; - } - int ret = fchmod(fd, mode); - close(fd); - if (ret == -1) { - int errnum = errno; - HSLogError(@"fchmod(%@) error %d: %s", path, errnum, strerror(errnum)); - SETNSERROR(@"UnixErrorDomain", errnum, @"failed to set permissions on %@: %s", path, strerror(errnum)); - return NO; - } - HSLogDebug(@"fchmod(%s, 0%6o)", cPath, mode); - } - st.st_mode = mode; - } - return YES; -} -- (BOOL)applyMTimeSec:(int64_t)mtime_sec mTimeNSec:(int64_t)mtime_nsec error:(NSError **)error { - if (st.st_mtimespec.tv_sec != mtime_sec - || st.st_mtimespec.tv_nsec != mtime_nsec) { - struct timespec mtimeSpec = { mtime_sec, mtime_nsec }; - struct timeval atimeVal; - struct timeval mtimeVal; - TIMESPEC_TO_TIMEVAL(&atimeVal, &mtimeSpec); // Just use mtime because we don't have atime, nor do we care about atime. - TIMESPEC_TO_TIMEVAL(&mtimeVal, &mtimeSpec); - struct timeval timevals[2]; - timevals[0] = atimeVal; - timevals[1] = mtimeVal; - if (utimes(cPath, timevals) == -1) { - int errnum = errno; - HSLogError(@"utimes(%@) error %d: %s", path, errnum, strerror(errnum)); - SETNSERROR(@"UnixErrorDomain", errnum, @"failed to set timestamps on %@: %s", path, strerror(errnum)); - return NO; - } - } - return YES; -} -- (BOOL)applyCreateTimeSec:(int64_t)theCreateTime_sec createTimeNSec:(int64_t)theCreateTime_nsec error:(NSError **)error { - if (createTime.tv_sec != theCreateTime_sec || createTime.tv_nsec != theCreateTime_nsec) { - FSRef fsRef; - Boolean isDirectory; - OSStatus oss = 0; - if (S_ISLNK(st.st_mode)) { - oss = SymlinkPathMakeRef((UInt8*)cPath, &fsRef, &isDirectory); - } else { - oss = FSPathMakeRef((UInt8*)cPath, &fsRef, &isDirectory); - } - if (oss != noErr) { - if (oss == bdNamErr) { - HSLogInfo(@"not setting create time on %s: bad name", cPath); - return YES; - } else { - SETNSERROR(@"FileManagerErrorDomain", -1, @"%@", [OSStatusDescription descriptionForMacOSStatus:oss]); - return NO; - } - } - FSCatalogInfo catalogInfo; - OSErr oserr = FSGetCatalogInfo(&fsRef, kFSCatInfoCreateDate, &catalogInfo, NULL, NULL, NULL); - if (oserr) { - SETNSERROR(@"FileManagerErrorDomain", -1, @"%@", [OSStatusDescription descriptionForMacOSStatus:(OSStatus)oserr]); - return NO; - } - CFTimeInterval theCreateTime = (double)theCreateTime_sec - NSTimeIntervalSince1970 + (double)theCreateTime_nsec / 1000000000.0; - if (UCConvertCFAbsoluteTimeToUTCDateTime(theCreateTime, &catalogInfo.createDate) != noErr) { - SETNSERROR(@"FileManagerErrorDomain", -1, @"unable to convert CFAbsoluteTime %f to UTCDateTime", theCreateTime); - return NO; - } - oserr = FSSetCatalogInfo(&fsRef, kFSCatInfoCreateDate, &catalogInfo); - if (oserr) { - SETNSERROR(@"FileManagerErrorDomain", -1, @"%@", [OSStatusDescription descriptionForMacOSStatus:(OSStatus)oserr]); - return NO; - } - createTime.tv_sec = theCreateTime_sec; - createTime.tv_nsec = theCreateTime_nsec; - } - return YES; -} -@end diff --git a/GoogleDriveTargetConnection.h b/GoogleDriveTargetConnection.h new file mode 100644 index 0000000..d72e25c --- /dev/null +++ b/GoogleDriveTargetConnection.h @@ -0,0 +1,22 @@ +// +// GoogleDriveTargetConnection.h +// Arq +// +// Created by Stefan Reitshamer on 7/18/14. +// Copyright (c) 2014 Stefan Reitshamer. All rights reserved. +// + +#import "TargetConnection.h" +@class BaseTargetConnection; +@class Target; +@class GoogleDriveRemoteFS; + + +@interface GoogleDriveTargetConnection : NSObject { + GoogleDriveRemoteFS *googleDriveRemoteFS; + BaseTargetConnection *base; +} + +- (id)initWithTarget:(Target *)theTarget; + +@end diff --git a/GoogleDriveTargetConnection.m b/GoogleDriveTargetConnection.m new file mode 100644 index 0000000..0b3b81c --- /dev/null +++ b/GoogleDriveTargetConnection.m @@ -0,0 +1,102 @@ +// +// GoogleDriveTargetConnection.m +// Arq +// +// Created by Stefan Reitshamer on 7/18/14. +// Copyright (c) 2014 Stefan Reitshamer. All rights reserved. +// + +#import "GoogleDriveTargetConnection.h" +#import "GoogleDriveRemoteFS.h" +#import "BaseTargetConnection.h" +#import "S3ObjectMetadata.h" + + +@implementation GoogleDriveTargetConnection +- (id)initWithTarget:(Target *)theTarget { + if (self = [super init]) { + googleDriveRemoteFS = [[GoogleDriveRemoteFS alloc] initWithTarget:theTarget]; + base = [[BaseTargetConnection alloc] initWithTarget:theTarget remoteFS:googleDriveRemoteFS]; + } + return self; +} +- (void)dealloc { + [googleDriveRemoteFS release]; + [base release]; + [super dealloc]; +} + + +#pragma mark TargetConnection +- (NSArray *)computerUUIDsWithDelegate:(id)theDelegate error:(NSError **)error { + return [base computerUUIDsWithDelegate:theDelegate error:error]; +} +- (NSArray *)bucketUUIDsForComputerUUID:(NSString *)theComputerUUID deleted:(BOOL)deleted delegate:(id)theDelegate error:(NSError **)error { + return [base bucketUUIDsForComputerUUID:theComputerUUID deleted:deleted delegate:theDelegate error:error]; +} +- (NSData *)bucketPlistDataForComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID deleted:(BOOL)deleted delegate:(id)theDelegate error:(NSError **)error { + return [base bucketPlistDataForComputerUUID:theComputerUUID bucketUUID:theBucketUUID deleted:deleted delegate:theDelegate error:error]; +} +- (BOOL)saveBucketPlistData:(NSData *)theData forComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID deleted:(BOOL)deleted delegate:(id)theDelegate error:(NSError **)error { + return [base saveBucketPlistData:theData forComputerUUID:theComputerUUID bucketUUID:theBucketUUID deleted:deleted delegate:theDelegate error:error]; +} +- (BOOL)deleteBucketPlistDataForComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID deleted:(BOOL)deleted delegate:(id)theDelegate error:(NSError **)error { + return [base deleteBucketPlistDataForComputerUUID:theComputerUUID bucketUUID:theBucketUUID deleted:deleted delegate:theDelegate error:error]; +} +- (NSData *)computerInfoForComputerUUID:(NSString *)theComputerUUID delegate:(id)theDelegate error:(NSError **)error { + return [base computerInfoForComputerUUID:theComputerUUID delegate:theDelegate error:error]; +} +- (BOOL)saveComputerInfo:(NSData *)theData forComputerUUID:(NSString *)theComputerUUID delegate:(id)theDelegate error:(NSError **)error { + return [base saveComputerInfo:theData forComputerUUID:theComputerUUID delegate:theDelegate error:error]; +} +- (NSDictionary *)objectsBySHA1ForTargetEndpoint:(NSURL *)theEndpoint isGlacier:(BOOL)theIsGlacier computerUUID:(NSString *)theComputerUUID delegate:(id)theDelegate error:(NSError **)error { + NSString *prefix = [NSString stringWithFormat:@"%@/%@/objects/", [theEndpoint path], theComputerUUID]; + + NSArray *objects = [base objectsWithPrefix:prefix delegate:theDelegate error:error]; + if (objects == nil) { + return nil; + } + NSMutableDictionary *ret = [NSMutableDictionary dictionary]; + for (S3ObjectMetadata *md in objects) { + [ret setObject:md forKey:[[md path] lastPathComponent]]; + } + return ret; +} +- (NSArray *)pathsWithPrefix:(NSString *)thePrefix delegate:(id)theDelegate error:(NSError **)error { + return [base pathsWithPrefix:thePrefix delegate:theDelegate error:error]; +} +- (BOOL)deleteObjectsForComputerUUID:(NSString *)theComputerUUID delegate:(id)theDelegate error:(NSError **)error { + return [base deleteObjectsForComputerUUID:theComputerUUID delegate:theDelegate error:error]; +} +- (BOOL)deletePaths:(NSArray *)thePaths delegate:(id)theDelegate error:(NSError **)error { + return [base deletePaths:thePaths delegate:theDelegate error:error]; +} +- (NSNumber *)fileExistsAtPath:(NSString *)thePath dataSize:(unsigned long long *)theDataSize delegate:(id)theDelegate error:(NSError **)error { + return [base fileExistsAtPath:thePath dataSize:theDataSize delegate:theDelegate error:error]; +} +- (NSData *)contentsOfFileAtPath:(NSString *)thePath delegate:(id)theDelegate error:(NSError **)error { + return [base contentsOfFileAtPath:thePath delegate:theDelegate error:error]; +} +- (BOOL)writeData:(NSData *)theData toFileAtPath:(NSString *)thePath dataTransferDelegate:(id )theDataTransferDelegate targetConnectionDelegate:(id)theTargetConnectionDelegate error:(NSError **)error { + return [base writeData:theData toFileAtPath:thePath dataTransferDelegate:theDataTransferDelegate targetConnectionDelegate:theTargetConnectionDelegate error:error]; +} +- (BOOL)removeItemAtPath:(NSString *)thePath delegate:(id)theDelegate error:(NSError **)error { + return [base removeItemAtPath:thePath delegate:theDelegate error:error]; +} +- (NSNumber *)sizeOfItemAtPath:(NSString *)thePath delegate:(id)theDelegate error:(NSError **)error { + return [base sizeOfItemAtPath:thePath delegate:theDelegate error:error]; +} +- (NSNumber *)isObjectRestoredAtPath:(NSString *)thePath delegate:(id)theDelegate error:(NSError **)error { + return [base isObjectRestoredAtPath:thePath delegate:theDelegate error:error]; +} +- (BOOL)restoreObjectAtPath:(NSString *)thePath forDays:(NSUInteger)theDays alreadyRestoredOrRestoring:(BOOL *)alreadyRestoredOrRestoring delegate:(id)theDelegate error:(NSError **)error { + return [base restoreObjectAtPath:thePath forDays:theDays alreadyRestoredOrRestoring:alreadyRestoredOrRestoring delegate:theDelegate error:error]; +} +- (NSData *)saltDataForComputerUUID:(NSString *)theComputerUUID delegate:(id)theDelegate error:(NSError **)error { + return [base saltDataForComputerUUID:theComputerUUID delegate:theDelegate error:error]; +} +- (BOOL)setSaltData:(NSData *)theData forComputerUUID:(NSString *)theComputerUUID delegate:(id)theDelegate error:(NSError **)error { + return [base setSaltData:theData forComputerUUID:theComputerUUID delegate:theDelegate error:error]; +} + +@end diff --git a/NSString_slashed.h b/NSString_slashed.h new file mode 100644 index 0000000..98651a3 --- /dev/null +++ b/NSString_slashed.h @@ -0,0 +1,13 @@ +// +// NSString_slashed.h +// Arq +// +// Created by Stefan Reitshamer on 4/22/11. +// Copyright 2011 Haystack Software. All rights reserved. +// + + + +@interface NSString (slashed) +- (NSString *)slashed; +@end diff --git a/NSString_slashed.m b/NSString_slashed.m new file mode 100644 index 0000000..5a0793c --- /dev/null +++ b/NSString_slashed.m @@ -0,0 +1,18 @@ +// +// NSString_slashed.m +// Arq +// +// Created by Stefan Reitshamer on 4/22/11. +// Copyright 2011 Haystack Software. All rights reserved. +// + +#import "NSString_slashed.h" + +@implementation NSString (slashed) +- (NSString *)slashed { + if ([self isEqualToString:@"/"]) { + return self; + } + return [self stringByAppendingString:@"/"]; +} +@end diff --git a/PackIndexEntry.h b/PackIndexEntry.h deleted file mode 100644 index ced1f16..0000000 --- a/PackIndexEntry.h +++ /dev/null @@ -1,23 +0,0 @@ -// -// PackIndexEntry.h -// Arq -// -// Created by Stefan Reitshamer on 12/30/09. -// Copyright 2009 __MyCompanyName__. All rights reserved. -// - - - - -@interface PackIndexEntry : NSObject { - NSString *packSHA1; - unsigned long long offset; - unsigned long long dataLength; - NSString *objectSHA1; -} -- (id)initWithPackSHA1:(NSString *)thePackSHA1 offset:(unsigned long long)theOffset dataLength:(unsigned long long)theDataLength objectSHA1:(NSString *)theObjectSHA1; -- (NSString *)packSHA1; -- (unsigned long long)offset; -- (unsigned long long)dataLength; -- (NSString *)objectSHA1; -@end diff --git a/PackIndexEntry.m b/PackIndexEntry.m deleted file mode 100644 index b1b9d41..0000000 --- a/PackIndexEntry.m +++ /dev/null @@ -1,44 +0,0 @@ -// -// PackIndexEntry.m -// Arq -// -// Created by Stefan Reitshamer on 12/30/09. -// Copyright 2009 __MyCompanyName__. All rights reserved. -// - -#import "PackIndexEntry.h" - - -@implementation PackIndexEntry -- (id)initWithPackSHA1:(NSString *)thePackSHA1 offset:(unsigned long long)theOffset dataLength:(unsigned long long)theDataLength objectSHA1:(NSString *)theObjectSHA1 { - if (self = [super init]) { - packSHA1 = [thePackSHA1 copy]; - offset = theOffset; - dataLength = theDataLength; - objectSHA1 = [theObjectSHA1 copy]; - } - return self; -} -- (void)dealloc { - [packSHA1 release]; - [objectSHA1 release]; - [super dealloc]; -} -- (NSString *)packSHA1 { - return packSHA1; -} -- (unsigned long long)offset { - return offset; -} -- (unsigned long long)dataLength { - return dataLength; -} -- (NSString *)objectSHA1 { - return objectSHA1; -} - -#pragma mark NSObject -- (NSString *)description { - return [NSString stringWithFormat:@"", packSHA1, offset, dataLength, objectSHA1]; -} -@end diff --git a/PackIndexWriter.h b/PackIndexWriter.h deleted file mode 100644 index 6ddae0a..0000000 --- a/PackIndexWriter.h +++ /dev/null @@ -1,22 +0,0 @@ -// -// PackIndexWriter.h -// Arq -// -// Created by Stefan Reitshamer on 3/3/10. -// Copyright 2010 __MyCompanyName__. All rights reserved. -// - - -@class DiskPack; - -@interface PackIndexWriter : NSObject { - DiskPack *diskPack; - NSString *destination; - uid_t targetUID; - gid_t targetGID; -} -- (id)initWithPack:(DiskPack *)theDiskPack destination:(NSString *)theDestination - targetUID:(uid_t)theTargetUID - targetGID:(gid_t)theTargetGID; -- (BOOL)writeIndex:(NSError **)error; -@end diff --git a/PackIndexWriter.m b/PackIndexWriter.m deleted file mode 100644 index 147672a..0000000 --- a/PackIndexWriter.m +++ /dev/null @@ -1,121 +0,0 @@ -// -// PackIndexWriter.m -// Arq -// -// Created by Stefan Reitshamer on 3/3/10. -// Copyright 2010 __MyCompanyName__. All rights reserved. -// - -#import "PackIndexWriter.h" -#import "DiskPack.h" -#import "FileInputStream.h" -#import "FileOutputStream.h" -#import "IntegerIO.h" -#import "StringIO.h" -#import "SetNSError.h" -#import "SHA1Hash.h" -#import "NSString_extra.h" -#import "PackIndexEntry.h" -#import "BufferedOutputStream.h" - -@interface PackIndexWriter (internal) -- (BOOL)appendSHA1:(NSString *)theSHA1 error:(NSError **)error; -- (BOOL)writeEntries:(NSArray *)entries toStream:(BufferedOutputStream *)bos error:(NSError **)error; -@end - -@implementation PackIndexWriter -- (id)initWithPack:(DiskPack *)theDiskPack destination:(NSString *)theDestination - targetUID:(uid_t)theTargetUID - targetGID:(gid_t)theTargetGID { - if (self = [super init]) { - diskPack = [theDiskPack retain]; - destination = [theDestination copy]; - targetUID = theTargetUID; - targetGID = theTargetGID; - } - return self; -} -- (void)dealloc { - [diskPack release]; - [destination release]; - [super dealloc]; -} -- (BOOL)writeIndex:(NSError **)error { - NSArray *entries = [diskPack sortedPackIndexEntries:error]; - if (entries == nil) { - return NO; - } - BufferedOutputStream *bos = [[BufferedOutputStream alloc] initWithPath:destination targetUID:targetUID targetGID:targetGID append:NO]; - BOOL ret = [self writeEntries:entries toStream:bos error:error]; - if (![bos flush:error]) { - ret = NO; - } - [bos release]; - if (!ret) { - return NO; - } - NSString *indexSHA1 = [SHA1Hash hashFile:destination error:error]; - if (![self appendSHA1:indexSHA1 error:error]) { - return NO; - } - return ret; -} -@end - -@implementation PackIndexWriter (internal) -- (BOOL)appendSHA1:(NSString *)theSHA1 error:(NSError **)error { - NSData *sha1Data = [theSHA1 hexStringToData]; - BufferedOutputStream *bos = [[BufferedOutputStream alloc] initWithPath:destination targetUID:targetUID targetGID:targetGID append:YES]; - BOOL ret = [bos writeFully:[sha1Data bytes] length:[sha1Data length] error:error]; - if (![bos flush:error]) { - ret = NO; - } - [bos release]; - return ret; -} -- (BOOL)writeEntries:(NSArray *)entries toStream:(BufferedOutputStream *)bos error:(NSError **)error { - // Write header to index. - if (![IntegerIO writeUInt32:0xff744f63 to:bos error:error]) { // Magic number. - return NO; - } - if (![IntegerIO writeUInt32:0x00000002 to:bos error:error]) { // Version 2. - return NO; - } - unsigned int firstByte = 0; - NSUInteger index = 0; - for (index = 0; index < [entries count]; index++) { - PackIndexEntry *pie = [entries objectAtIndex:index]; - NSData *sha1Hex = [[pie objectSHA1] hexStringToData]; - unsigned char myFirstByte = ((unsigned char *)[sha1Hex bytes])[0]; - while ((unsigned int)myFirstByte > firstByte) { - if (![IntegerIO writeUInt32:index to:bos error:error]) { - return NO; - } - firstByte++; - } - } - while (firstByte <= 0xff) { - if (![IntegerIO writeUInt32:index to:bos error:error]) { - return NO; - } - firstByte++; - } - for (index = 0; index < [entries count]; index++) { - PackIndexEntry *pie = [entries objectAtIndex:index]; - if (![IntegerIO writeUInt64:[pie offset] to:bos error:error] - || ![IntegerIO writeUInt64:[pie dataLength] to:bos error:error]) { - return NO; - } - // Write sha1 to index. - NSData *sha1Data = [[pie objectSHA1] hexStringToData]; - if (![bos writeFully:[sha1Data bytes] length:[sha1Data length] error:error]) { - break; - } - // Write 4 bytes (for alignment) to index. - if (![IntegerIO writeUInt32:0 to:bos error:error]) { - return NO; - } - } - return YES; -} -@end diff --git a/ReflogEntry.h b/ReflogEntry.h deleted file mode 100644 index 56cda61..0000000 --- a/ReflogEntry.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// ReflogEntry.h -// arq_restore -// -// Created by Stefan Reitshamer on 11/20/11. -// Copyright 2011 __MyCompanyName__. All rights reserved. -// - - -@class BlobKey; - -@interface ReflogEntry : NSObject { - BlobKey *oldHeadBlobKey; - BlobKey *newHeadBlobKey; -} -- (id)initWithData:(NSData *)theData error:(NSError **)error; - -- (BlobKey *)oldHeadBlobKey; -- (BlobKey *)newHeadBlobKey; -@end diff --git a/ReflogEntry.m b/ReflogEntry.m deleted file mode 100644 index a3cf941..0000000 --- a/ReflogEntry.m +++ /dev/null @@ -1,55 +0,0 @@ -// -// ReflogEntry.m -// arq_restore -// -// Created by Stefan Reitshamer on 11/20/11. -// Copyright 2011 __MyCompanyName__. All rights reserved. -// - -#import "ReflogEntry.h" -#import "BlobKey.h" -#import "DictNode.h" -#import "SetNSError.h" - - -@implementation ReflogEntry -- (id)initWithData:(NSData *)theData error:(NSError **)error { - if (self = [super init]) { - DictNode *dictNode = [DictNode dictNodeWithXMLData:theData error:error]; - if (dictNode == nil) { - [self release]; - return nil; - } - if (![dictNode containsKey:@"oldHeadSHA1"] - || ![dictNode containsKey:@"oldHeadStretchKey"] - || ![dictNode containsKey:@"newHeadSHA1"] - || ![dictNode containsKey:@"newHeadStretchKey"]) { - SETNSERROR(@"ReflogEntryErrorDomain", -1, @"missing values in reflog entry"); - [self release]; - return nil; - } - oldHeadBlobKey = [[BlobKey alloc] initWithSHA1:[[dictNode stringNodeForKey:@"oldHeadSHA1"] stringValue] - storageType:StorageTypeS3 - stretchEncryptionKey:[[dictNode booleanNodeForKey:@"oldHeadStretchKey"] booleanValue] - compressed:NO]; - - newHeadBlobKey = [[BlobKey alloc] initWithSHA1:[[dictNode stringNodeForKey:@"newHeadSHA1"] stringValue] - storageType:StorageTypeS3 - stretchEncryptionKey:[[dictNode booleanNodeForKey:@"newHeadStretchKey"] booleanValue] - compressed:NO]; - } - return self; -} -- (void)dealloc { - [oldHeadBlobKey release]; - [newHeadBlobKey release]; - [super dealloc]; -} - -- (BlobKey *)oldHeadBlobKey { - return oldHeadBlobKey; -} -- (BlobKey *)newHeadBlobKey { - return newHeadBlobKey; -} -@end diff --git a/ReflogPrinter.h b/ReflogPrinter.h deleted file mode 100644 index c3505e4..0000000 --- a/ReflogPrinter.h +++ /dev/null @@ -1,22 +0,0 @@ -// -// ReflogPrinter.h -// arq_restore -// -// Created by Stefan Reitshamer on 11/21/11. -// Copyright 2011 __MyCompanyName__. All rights reserved. -// - - -@class S3Service; -@class ArqRepo; - -@interface ReflogPrinter : NSObject { - NSString *s3BucketName; - NSString *computerUUID; - NSString *bucketUUID; - S3Service *s3; - ArqRepo *repo; -} -- (id)initWithS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID s3:(S3Service *)theS3 repo:(ArqRepo *)theRepo; -- (BOOL)printReflog:(NSError **)error; -@end diff --git a/ReflogPrinter.m b/ReflogPrinter.m deleted file mode 100644 index 55d616c..0000000 --- a/ReflogPrinter.m +++ /dev/null @@ -1,99 +0,0 @@ -// -// ReflogPrinter.m -// arq_restore -// -// Created by Stefan Reitshamer on 11/21/11. -// Copyright 2011 __MyCompanyName__. All rights reserved. -// - -#import "ReflogPrinter.h" -#import "S3Service.h" -#import "ArqRepo.h" -#import "ReflogEntry.h" -#import "Commit.h" -#import "BlobKey.h" - - -@interface ReflogPrinter (internal) -- (BOOL)printEntry:(NSString *)path error:(NSError **)error; -@end - - -@implementation ReflogPrinter -- (id)initWithS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID s3:(S3Service *)theS3 repo:(ArqRepo *)theRepo { - if (self = [super init]) { - s3BucketName = [theS3BucketName retain]; - computerUUID = [theComputerUUID retain]; - bucketUUID = [theBucketUUID retain]; - s3 = [theS3 retain]; - repo = [theRepo retain]; - } - return self; -} -- (void)dealloc { - [s3BucketName release]; - [computerUUID release]; - [bucketUUID release]; - [s3 release]; - [repo release]; - [super dealloc]; -} - -- (BOOL)printReflog:(NSError **)error { - NSString *prefix = [NSString stringWithFormat:@"/%@/%@/bucketdata/%@/refs/logs/master/", s3BucketName, computerUUID, bucketUUID]; - NSArray *paths = [s3 pathsWithPrefix:prefix error:error]; - if (paths == nil) { - return NO; - } - NSSortDescriptor *descriptor = [[[NSSortDescriptor alloc] initWithKey:@"description" ascending:NO] autorelease]; - NSArray *sortedPaths = [paths sortedArrayUsingDescriptors:[NSArray arrayWithObject:descriptor]]; - - BOOL ret = YES; - NSAutoreleasePool *pool = nil; - for (NSString *path in sortedPaths) { - [pool drain]; - pool = [[NSAutoreleasePool alloc] init]; - if (![self printEntry:path error:error]) { - ret = NO; - break; - } - } - if (!ret && error != NULL) { - [*error retain]; - } - [pool drain]; - if (!ret && error != NULL) { - [*error autorelease]; - } - return ret; -} -@end - -@implementation ReflogPrinter (internal) -- (BOOL)printEntry:(NSString *)path error:(NSError **)error { - printf("reflog %s\n", [path UTF8String]); - - NSData *data = [s3 dataAtPath:path error:error]; - if (data == nil) { - return NO; - } - NSError *myError = nil; - ReflogEntry *entry = [[[ReflogEntry alloc] initWithData:data error:&myError] autorelease]; - if (entry == nil) { - printf("\terror reading reflog entry: %s\n\n", [[myError description] UTF8String]); - } else { - Commit *commit = [repo commitForBlobKey:[entry newHeadBlobKey] error:&myError]; - if (commit == nil) { - printf("\t%s\n\n", [[myError localizedDescription] UTF8String]); - } else { - printf("\tblobkey: %s\n", [[[entry newHeadBlobKey] description] UTF8String]); - printf("\tauthor: %s\n", [[commit author] UTF8String]); - printf("\tdate: %s\n", [[[commit creationDate] description] UTF8String]); - printf("\tlocation: %s\n", [[commit location] UTF8String]); - printf("\trestore command: arq_restore /%s/%s/buckets/%s %s\n\n", [s3BucketName UTF8String], [computerUUID UTF8String], [bucketUUID UTF8String], - [[[entry newHeadBlobKey] sha1] UTF8String]); - } - } - return YES; -} -@end diff --git a/RestoreNode.h b/RestoreNode.h deleted file mode 100644 index f9d607a..0000000 --- a/RestoreNode.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - - -#import "InputStream.h" -#import "OutputStream.h" -@class Tree; -@class Node; - -@interface RestoreNode : NSObject { - Tree *tree; - NSString *nodeName; - NSString *relativePath; -} -- (id)initWithTree:(Tree *)theTree nodeName:(NSString *)theNodeName relativePath:(NSString *)theRelativePath; -- (Tree *)tree; -- (Node *)node; -- (NSString *)relativePath; -@end diff --git a/RestoreNode.m b/RestoreNode.m deleted file mode 100644 index 586eaa2..0000000 --- a/RestoreNode.m +++ /dev/null @@ -1,63 +0,0 @@ -/* - Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#import "RestoreNode.h" -#import "Tree.h" -#import "Node.h" -#import "StringIO.h" - -@implementation RestoreNode -- (id)initWithTree:(Tree *)theTree nodeName:(NSString *)theNodeName relativePath:(NSString *)theRelativePath { - if (self = [super init]) { - tree = [theTree retain]; - nodeName = [theNodeName copy]; - NSAssert((nodeName == nil) || ([tree childNodeWithName:nodeName] != nil), @"node doesn't exist in Tree!"); - relativePath = [theRelativePath copy]; - } - return self; -} -- (void)dealloc { - [tree release]; - [nodeName release]; - [relativePath release]; - [super dealloc]; -} -- (Tree *)tree { - return tree; -} -- (Node *)node { - return [tree childNodeWithName:nodeName]; -} -- (NSString *)relativePath { - return relativePath; -} -@end diff --git a/Restorer.h b/Restorer.h deleted file mode 100644 index e1fcd7c..0000000 --- a/Restorer.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - - -@class S3Service; -@class ArqRepo; -@class BlobKey; -@class Commit; -@class Tree; - -@interface Restorer : NSObject { - ArqRepo *repo; - NSString *bucketName; - NSString *commitSHA1; - NSString *rootPath; - NSUInteger superUserNodeCount; - NSMutableArray *restoreNodes; - NSMutableDictionary *hardlinks; - unsigned long long writtenToCurrentFile; - NSMutableDictionary *errorsByPath; - int myUID; - int myGID; - unsigned long long transferred; - unsigned long long total; - - Tree *rootTree; -} -- (id)initWithRepo:(ArqRepo *)theRepo bucketName:(NSString *)theBucketName commitSHA1:(NSString *)theCommitSHA1; -- (BOOL)restore:(NSError **)error; -- (NSDictionary *)errorsByPath; -@end diff --git a/Restorer.m b/Restorer.m deleted file mode 100644 index d098f4e..0000000 --- a/Restorer.m +++ /dev/null @@ -1,700 +0,0 @@ -/* - Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#import "Restorer.h" -#import "ArqRepo.h" -#import "SetNSError.h" -#import "Tree.h" -#import "Node.h" -#import "RestoreNode.h" -#import "FileAttributes.h" -#import "NSData-InputStream.h" -#import "DataInputStream.h" -#import "XAttrSet.h" -#import "FileOutputStream.h" -#import "NSFileManager_extra.h" -#import "NSErrorCodes.h" -#import "BufferedInputStream.h" -#import "BufferedOutputStream.h" -#import "NSData-Gzip.h" -#import "GunzipInputStream.h" -#import "FileACL.h" -#import "BlobKey.h" - - -#define MAX_RETRIES (10) -#define MY_BUF_SIZE (8192) - -@interface Restorer (internal) -+ (NSString *)errorDomain; - -- (BOOL)restoreTree:(Tree *)theTree toPath:(NSString *)thePath error:(NSError **)error; -- (BOOL)restoreNode:(Node *)theNode ofTree:(Tree *)theTree toPath:(NSString *)thePath error:(NSError **)error; -- (BOOL)needSuperUserForTree:(Tree *)theTree; -- (BOOL)needSuperUserForTree:(Tree *)theTree node:(Node *)theNode; -- (BOOL)chownNode:(Node *)theNode ofTree:(Tree *)theTree atPath:(NSString *)thePath error:(NSError **)error; -- (BOOL)chownTree:(Tree *)theTree atPath:(NSString *)thePath error:(NSError **)error; -- (BOOL)applyUID:(int)theUID gid:(int)theGID mode:(int)theMode rdev:(int)theRdev toPath:(NSString *)thePath error:(NSError **)error; -- (BOOL)applyTree:(Tree *)tree toPath:(NSString *)restorePath error:(NSError **)error; -- (BOOL)applyNode:(Node *)node toPath:(NSString *)restorePath error:(NSError **)error; -- (BOOL)createFile:(Node *)node atPath:(NSString *)path error:(NSError **)error; -- (BOOL)createFileAtPath:(NSString *)path fromBlobKeys:(NSArray *)dataBlobKeys error:(NSError **)error; -- (BOOL)appendBlobForBlobKey:(BlobKey *)theBlobKey to:(FileOutputStream *)fos error:(NSError **)error; -- (BOOL)doAppendBlobForBlobKey:(BlobKey *)theBlobKey to:(BufferedOutputStream *)bos error:(NSError **)error; -- (BOOL)createSymLink:(Node *)node path:(NSString *)symLinkFile target:(NSString *)target error:(NSError **)error; -- (BOOL)applyACLBlobKey:(BlobKey *)aclBlobKey uncompress:(BOOL)uncompress toPath:(NSString *)path error:(NSError **)error; -- (BOOL)applyXAttrsBlobKey:(BlobKey *)xattrsBlobKey uncompress:(BOOL)uncompress toFile:(NSString *)path error:(NSError **)error; -- (void)addError:(NSError *)theError forPath:(NSString *)thePath; -@end - -@implementation Restorer -- (id)initWithRepo:(ArqRepo *)theArqRepo bucketName:(NSString *)theBucketName commitSHA1:(NSString *)theCommitSHA1 { - if (self = [super init]) { - repo = [theArqRepo retain]; - bucketName = [theBucketName copy]; - commitSHA1 = [theCommitSHA1 copy]; - rootPath = [[[[NSFileManager defaultManager] currentDirectoryPath] stringByAppendingPathComponent:theBucketName] copy]; - restoreNodes = [[NSMutableArray alloc] init]; - hardlinks = [[NSMutableDictionary alloc] init]; - errorsByPath = [[NSMutableDictionary alloc] init]; - myUID = geteuid(); - myGID = getgid(); - } - return self; -} -- (void)dealloc { - [repo release]; - [bucketName release]; - [commitSHA1 release]; - [rootPath release]; - [restoreNodes release]; - [hardlinks release]; - [errorsByPath release]; - [rootTree release]; - [super dealloc]; -} -- (BOOL)restore:(NSError **)error { - if ([[NSFileManager defaultManager] fileExistsAtPath:rootPath]) { - SETNSERROR([Restorer errorDomain], -1, @"%@ already exists", rootPath); - return NO; - } - if (![[NSFileManager defaultManager] createDirectoryAtPath:rootPath withIntermediateDirectories:YES attributes:nil error:error]) { - HSLogError(@"failed to create directory %@", rootPath); - return NO; - } - BlobKey *commitBlobKey = nil; - Commit *commit = nil; - NSError *myError = nil; - if (commitSHA1 != nil) { - commitBlobKey = [[[BlobKey alloc] initWithSHA1:commitSHA1 storageType:StorageTypeS3 stretchEncryptionKey:YES compressed:NO] autorelease]; - commit = [repo commitForBlobKey:commitBlobKey error:&myError]; - if (commit == nil) { - HSLogError(@"error attempting to read commit for %@", commitBlobKey); - - // Try without stretched encryption key. - commitBlobKey = [[[BlobKey alloc] initWithSHA1:commitSHA1 storageType:StorageTypeS3 stretchEncryptionKey:NO compressed:NO] autorelease]; - commit = [repo commitForBlobKey:commitBlobKey error:&myError]; - if (commit == nil) { - HSLogError(@"error attempting to read commit for %@", commitBlobKey); - if (error != NULL) { - *error = myError; - } - return NO; - } - } - } else { - commitBlobKey = [[repo headBlobKey:error] retain]; - if (commitBlobKey == nil) { - SETNSERROR([Restorer errorDomain], -1, @"no backup found"); - return NO; - } - commit = [repo commitForBlobKey:commitBlobKey error:error]; - if (commit == nil) { - return NO; - } - } - printf("restoring %scommit %s\n", (commitSHA1 == nil ? "head " : ""), [[commitBlobKey description] UTF8String]); - - rootTree = [[repo treeForBlobKey:[commit treeBlobKey] error:error] retain]; - if (rootTree == nil) { - return NO; - } - if (![self restoreTree:rootTree toPath:rootPath error:error]) { - return NO; - } - return YES; -} -- (NSDictionary *)errorsByPath { - return errorsByPath; -} -@end - -@implementation Restorer (internal) -+ (NSString *)errorDomain { - return @"RestorerErrorDomain"; -} - -- (BOOL)restoreTree:(Tree *)theTree toPath:(NSString *)thePath error:(NSError **)error { - NSNumber *inode = [NSNumber numberWithInt:[theTree st_ino]]; - NSString *existing = nil; - if ([theTree st_nlink] > 1) { - existing = [hardlinks objectForKey:inode]; - } - if (existing != nil) { - // Link. - if (link([existing fileSystemRepresentation], [thePath fileSystemRepresentation]) == -1) { - int errnum = errno; - SETNSERROR([Restorer errorDomain], -1, @"link(%@,%@): %s", existing, thePath, strerror(errnum)); - HSLogError(@"link() failed"); - return NO; - } - } else { - if (![[NSFileManager defaultManager] fileExistsAtPath:thePath] - && ![[NSFileManager defaultManager] createDirectoryAtPath:thePath withIntermediateDirectories:YES attributes:nil error:error]) { - return NO; - } - NSAutoreleasePool *pool = nil; - BOOL ret = YES; - for (NSString *childNodeName in [theTree childNodeNames]) { - [pool drain]; - pool = [[NSAutoreleasePool alloc] init]; - Node *childNode = [theTree childNodeWithName:childNodeName]; - NSString *childPath = [thePath stringByAppendingPathComponent:childNodeName]; - if ([childNode isTree]) { - Tree *childTree = [repo treeForBlobKey:[childNode treeBlobKey] error:error]; - if (childTree == nil) { - ret = NO; - break; - } - NSError *restoreError = nil; - if (![self restoreTree:childTree toPath:childPath error:&restoreError]) { - HSLogDebug(@"error restoring %@: %@", childPath, restoreError); - if ([restoreError isErrorWithDomain:[Restorer errorDomain] code:ERROR_ABORT_REQUESTED]) { - ret = NO; - if (error != NULL) { - *error = restoreError; - } - break; - } - [self addError:restoreError forPath:childPath]; - } - } else { - NSError *restoreError = nil; - if (![self restoreNode:childNode ofTree:theTree toPath:childPath error:&restoreError]) { - if ([restoreError isErrorWithDomain:[Restorer errorDomain] code:ERROR_ABORT_REQUESTED]) { - ret = NO; - if (error != NULL) { - *error = restoreError; - } - break; - } - HSLogDebug(@"error restoring %@: %@", childPath, restoreError); - [self addError:restoreError forPath:childPath]; - } - } - } - if (error != NULL) { [*error retain]; } - [pool drain]; - if (error != NULL) { [*error autorelease]; } - if (!ret) { - return NO; - } - - if (![self applyTree:theTree toPath:thePath error:error]) { - return NO; - } - [hardlinks setObject:thePath forKey:inode]; - if ([self needSuperUserForTree:theTree]) { - superUserNodeCount++; - } - } - return YES; -} -- (BOOL)restoreNode:(Node *)theNode ofTree:(Tree *)theTree toPath:(NSString *)thePath error:(NSError **)error { - NSAssert(theNode != nil, @"theNode can't be nil"); - NSAssert(theTree != nil, @"theTree can't be nil"); - - NSNumber *inode = [NSNumber numberWithInt:[theNode st_ino]]; - NSString *existing = nil; - if ([theNode st_nlink] > 1) { - existing = [hardlinks objectForKey:inode]; - } - if (existing != nil) { - // Link. - if (link([existing fileSystemRepresentation], [thePath fileSystemRepresentation]) == -1) { - int errnum = errno; - SETNSERROR([Restorer errorDomain], -1, @"link(%@,%@): %s", existing, thePath, strerror(errnum)); - HSLogError(@"link() failed"); - return NO; - } - } else { - int mode = [theNode mode]; - if (S_ISFIFO(mode)) { - if (mkfifo([thePath fileSystemRepresentation], mode) == -1) { - int errnum = errno; - SETNSERROR([Restorer errorDomain], errnum, @"mkfifo(%@): %s", thePath, strerror(errnum)); - return NO; - } - if (![self applyNode:theNode toPath:thePath error:error]) { - HSLogError(@"applyNode error"); - return NO; - } - } else if (S_ISSOCK(mode)) { - // Skip socket -- restoring it doesn't make any sense. - } else if (S_ISCHR(mode)) { - // character device: needs to be done as super-user. - } else if (S_ISBLK(mode)) { - // block device: needs to be done as super-user. - } else { - if (![self createFile:theNode atPath:thePath error:error]) { - HSLogError(@"createFile error"); - return NO; - } - if (![self applyNode:theNode toPath:thePath error:error]) { - HSLogError(@"applyNode error"); - return NO; - } - } - [hardlinks setObject:thePath forKey:inode]; - if ([self needSuperUserForTree:theTree node:theNode]) { - superUserNodeCount++; - } - } - return YES; -} -- (BOOL)needSuperUserForTree:(Tree *)theTree { - NSAssert(theTree != nil, @"theTree can't be nil"); - - int uid = [theTree uid]; - int gid = [theTree gid]; - int mode = [theTree mode]; - if ((uid != myUID) || (gid != myGID)) { - return YES; - } - if (mode & (S_ISUID|S_ISGID|S_ISVTX)) { - return YES; - } - return NO; -} -- (BOOL)needSuperUserForTree:(Tree *)theTree node:(Node *)theNode { - NSAssert(theNode != nil, @"theNode can't be nil"); - NSAssert(theTree != nil, @"theTree can't be nil"); - - int uid = [theNode uid]; - int gid = [theNode gid]; - int mode = [theNode mode]; - if ([theTree treeVersion] >= 7 && (S_ISCHR(mode) || S_ISBLK(mode))) { - return YES; - } - if ((uid != myUID) || (gid != myGID)) { - return YES; - } - if (mode & (S_ISUID|S_ISGID|S_ISVTX)) { - return YES; - } - return NO; -} -- (BOOL)chownNode:(Node *)theNode ofTree:(Tree *)theTree atPath:(NSString *)thePath error:(NSError **)error { - if ([[errorsByPath allKeys] containsObject:thePath]) { - HSLogDebug(@"error restoring %@; skipping chownNode", thePath); - return YES; - } - if ([self needSuperUserForTree:theTree node:theNode]) { - if (![self applyUID:[theNode uid] gid:[theNode gid] mode:[theNode mode] rdev:[theNode st_rdev] toPath:thePath error:error]) { - return NO; - } - } - return YES; -} -- (BOOL)chownTree:(Tree *)theTree atPath:(NSString *)thePath error:(NSError **)error { - if ([[errorsByPath allKeys] containsObject:thePath]) { - HSLogDebug(@"error restoring %@; skipping chownTree", thePath); - return YES; - } - - NSAutoreleasePool *pool = nil; - BOOL ret = YES; - for (NSString *childNodeName in [theTree childNodeNames]) { - [pool drain]; - pool = [[NSAutoreleasePool alloc] init]; - Node *childNode = [theTree childNodeWithName:childNodeName]; - NSString *childPath = [thePath stringByAppendingPathComponent:childNodeName]; - if ([childNode isTree]) { - Tree *childTree = [repo treeForBlobKey:[childNode treeBlobKey] error:error]; - if (childTree == nil) { - ret = NO; - break; - } - if (![self chownTree:childTree atPath:childPath error:error]) { - ret = NO; - break; - } - } else { - if (![self chownNode:childNode ofTree:theTree atPath:childPath error:error]) { - ret = NO; - break; - } - } - } - if (error != NULL) { [*error retain]; } - [pool drain]; - if (error != NULL) { [*error autorelease]; } - if (!ret) { - return NO; - } - - if ([self needSuperUserForTree:theTree]) { - if (![self applyUID:[theTree uid] gid:[theTree gid] mode:[theTree mode] rdev:[theTree st_rdev] toPath:thePath error:error]) { - return NO; - } - } - return YES; -} -- (BOOL)applyUID:(int)theUID gid:(int)theGID mode:(int)theMode rdev:(int)theRdev toPath:(NSString *)thePath error:(NSError **)error { - if (S_ISCHR(theMode) || S_ISBLK(theMode)) { - if (![[NSFileManager defaultManager] ensureParentPathExistsForPath:thePath error:error]) { - return NO; - } - HSLogDebug(@"mknod(%@, %d, %d)", thePath, theMode, theRdev); - if (mknod([thePath fileSystemRepresentation], theMode, theRdev) == -1) { - int errnum = errno; - HSLogError(@"mknod(%@) error %d: %s", thePath, errnum, strerror(errnum)); - SETNSERROR([Restorer errorDomain], -1, @"failed to make device node %@: %s", thePath, strerror(errnum)); - return NO; - } - } - FileAttributes *fa = [[[FileAttributes alloc] initWithPath:thePath error:error] autorelease]; - if (fa == nil) { - return NO; - } - int flags = [fa flags]; - if (flags) { - // Clear the flags temporarily so we can change ownership of the file. - if (![fa applyFlags:0 error:error]) { - return NO; - } - } - if (![fa applyMode:theMode error:error]) { - return NO; - } - if (![fa applyUID:theUID gid:theGID error:error]) { - return NO; - } - if (flags) { - if (![fa applyFlags:flags error:error]) { - return NO; - } - } - return YES; -} -- (BOOL)applyTree:(Tree *)tree toPath:(NSString *)path error:(NSError **)error { - FileAttributes *fa = [[[FileAttributes alloc] initWithPath:path error:error] autorelease]; - if (!fa) { - return NO; - } - if (![self applyXAttrsBlobKey:[tree xattrsBlobKey] uncompress:[tree xattrsAreCompressed] toFile:path error:error]) { - return NO; - } - if (![fa applyFinderFlags:[tree finderFlags] error:error] - || ![fa applyExtendedFinderFlags:[tree extendedFinderFlags] error:error]) { - return NO; - } - if (([tree mode] & (S_ISUID|S_ISGID|S_ISVTX)) && ![fa applyMode:[tree mode] error:error]) { - return NO; - } - if (!S_ISLNK([tree mode]) && [tree treeVersion] >= 7 && ![fa applyMTimeSec:tree.mtime_sec mTimeNSec:tree.mtime_nsec error:error]) { - return NO; - } - if (([tree treeVersion] >= 7) && ![fa applyCreateTimeSec:tree.createTime_sec createTimeNSec:tree.createTime_nsec error:error]) { - return NO; - } - if (![fa applyFlags:[tree flags] error:error]) { - return NO; - } - if (![self applyACLBlobKey:[tree aclBlobKey] uncompress:[tree aclIsCompressed] toPath:path error:error]) { - return NO; - } - return YES; -} -- (BOOL)applyNode:(Node *)node toPath:(NSString *)path error:(NSError **)error { - FileAttributes *fa = [[[FileAttributes alloc] initWithPath:path error:error] autorelease]; - if (!fa) { - return NO; - } - if (![self applyXAttrsBlobKey:[node xattrsBlobKey] uncompress:[[node xattrsBlobKey] compressed] toFile:path error:error]) { - return NO; - } - if (![self applyACLBlobKey:[node aclBlobKey] uncompress:[[node aclBlobKey] compressed] toPath:path error:error]) { - return NO; - } - if (!S_ISFIFO([node mode])) { - if (![fa applyFinderFlags:[node finderFlags] error:error] - || ![fa applyExtendedFinderFlags:[node extendedFinderFlags] error:error] - || ![fa applyFinderFileType:[node finderFileType] finderFileCreator:[node finderFileCreator] error:error]) { - return NO; - } - } - if (!([node mode] & (S_ISUID|S_ISGID|S_ISVTX))) { - if (![fa applyMode:[node mode] error:error]) { - return NO; - } - } - if (!S_ISLNK([node mode]) && [node treeVersion] >= 7 && ![fa applyMTimeSec:node.mtime_sec mTimeNSec:node.mtime_nsec error:error]) { - return NO; - } - if (([node treeVersion] >= 7) && ![fa applyCreateTimeSec:node.createTime_sec createTimeNSec:node.createTime_nsec error:error]) { - return NO; - } - if (!S_ISFIFO([node mode])) { - if (![fa applyFlags:[node flags] error:error]) { - return NO; - } - } - return YES; -} -- (BOOL)createFile:(Node *)node atPath:(NSString *)path error:(NSError **)error { - if (![[NSFileManager defaultManager] ensureParentPathExistsForPath:path error:error]) { - HSLogError(@"error ensuring path %@ exists", path); - return NO; - } - if ([[NSFileManager defaultManager] fileExistsAtPath:path]) { - if (![[NSFileManager defaultManager] removeItemAtPath:path error:error]) { - HSLogError(@"error removing existing file %@", path); - return NO; - } - } - HSLogTrace(@"%qu bytes -> %@", [node uncompressedDataSize], path); - if (S_ISLNK([node mode])) { - NSMutableData *data = [NSMutableData data]; - for (BlobKey *dataBlobKey in [node dataBlobKeys]) { - NSData *blobData = [repo blobDataForBlobKey:dataBlobKey error:error]; - if (blobData == nil) { - HSLogError(@"error getting data for %@", dataBlobKey); - return NO; - } - if ([dataBlobKey compressed]) { - blobData = [blobData gzipInflate]; - } - [data appendData:blobData]; - } - NSString *target = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]; - if (![self createSymLink:node path:path target:target error:error]) { - HSLogError(@"error creating sym link %@", path); - return NO; - } - } else if ([node uncompressedDataSize] > 0) { - if (![self createFileAtPath:path fromBlobKeys:[node dataBlobKeys] error:error]) { - NSError *myError = nil; - if ([[NSFileManager defaultManager] fileExistsAtPath:path] && ![[NSFileManager defaultManager] removeItemAtPath:path error:&myError]) { - HSLogError(@"error deleting incorrectly-restored file %@: %@", path, myError); - } - return NO; - } - } else { - // It's a zero-byte file. - int fd = open([path fileSystemRepresentation], O_CREAT|O_EXCL, S_IRWXU); - if (fd == -1) { - int errnum = errno; - HSLogError(@"open(%@) error %d: %s", path, errnum, strerror(errnum)); - SETNSERROR(@"UnixErrorDomain", errnum, @"failed to open %@: %s", path, strerror(errnum)); - return NO; - } - close(fd); - } - HSLogDetail(@"restored %@", path); - return YES; -} -- (BOOL)createFileAtPath:(NSString *)path fromBlobKeys:(NSArray *)dataBlobKeys error:(NSError **)error { - FileOutputStream *fos = [[FileOutputStream alloc] initWithPath:path append:NO]; - BOOL ret = YES; - writtenToCurrentFile = 0; - for (BlobKey *dataBlobKey in dataBlobKeys) { - if (![self appendBlobForBlobKey:dataBlobKey to:fos error:error]) { - ret = NO; - break; - } - } - [fos release]; - return ret; -} -- (BOOL)appendBlobForBlobKey:(BlobKey *)theBlobKey to:(FileOutputStream *)fos error:(NSError **)error { - BOOL ret = NO; - NSError *myError = nil; - NSAutoreleasePool *pool = nil; - unsigned long long transferredSoFar = transferred; - unsigned long long writtenToCurrentFileSoFar = writtenToCurrentFile; - for (;;) { - [pool drain]; - pool = [[NSAutoreleasePool alloc] init]; - BufferedOutputStream *bos = [[[BufferedOutputStream alloc] initWithUnderlyingOutputStream:fos] autorelease]; - if ([self doAppendBlobForBlobKey:theBlobKey to:bos error:&myError] && [bos flush:&myError]) { - ret = YES; - break; - } - if ([myError isErrorWithDomain:[Restorer errorDomain] code:ERROR_ABORT_REQUESTED]) { - HSLogInfo(@"restore canceled"); - break; - } - if (![myError isTransientError]) { - HSLogError(@"error getting appending blob %@ to %@: %@", theBlobKey, bos, myError); - ret = NO; - break; - } - HSLogWarn(@"error appending blob %@ to %@ (retrying): %@", theBlobKey, bos, [myError localizedDescription]); - // Reset transferred: - transferred = transferredSoFar; - writtenToCurrentFile = writtenToCurrentFileSoFar; - // Seek back to the starting offset for this blob: - if (![fos seekTo:writtenToCurrentFile error:&myError]) { - ret = NO; - break; - } - } - [myError retain]; - [pool drain]; - [myError autorelease]; - if (error != NULL) { - *error = myError; - } - return ret; -} -- (BOOL)doAppendBlobForBlobKey:(BlobKey *)theBlobKey to:(BufferedOutputStream *)bos error:(NSError **)error { - ServerBlob *sb = [[repo newServerBlobForBlobKey:theBlobKey error:error] autorelease]; - if (sb == nil) { - return NO; - } - id is = [[sb newInputStream] autorelease]; - if ([theBlobKey compressed]) { - is = [[[GunzipInputStream alloc] initWithUnderlyingStream:is] autorelease]; - } - HSLogDebug(@"writing %@ to %@", is, bos); - BOOL ret = YES; - NSError *myError = nil; - NSAutoreleasePool *pool = nil; - unsigned char *buf = (unsigned char *)malloc(MY_BUF_SIZE); - for (;;) { - [pool drain]; - pool = [[NSAutoreleasePool alloc] init]; - NSInteger received = [is read:buf bufferLength:MY_BUF_SIZE error:&myError]; - if (received < 0) { - ret = NO; - break; - } - if (received == 0) { - break; - } - if (![bos writeFully:buf length:received error:error]) { - ret = NO; - break; - } - - transferred += received; - writtenToCurrentFile += received; - } - free(buf); - [myError retain]; - [pool drain]; - [myError autorelease]; - if (error != NULL) { - *error = myError; - } - return ret; -} -- (BOOL)createSymLink:(Node *)node path:(NSString *)symLinkFile target:(NSString *)target error:(NSError **)error { - struct stat st; - if (lstat([symLinkFile fileSystemRepresentation], &st) == 0) { - if (![[NSFileManager defaultManager] removeItemAtPath:symLinkFile error:error]) { - return NO; - } - } - if (symlink([target fileSystemRepresentation], [symLinkFile fileSystemRepresentation]) == -1) { - int errnum = errno; - HSLogError(@"symlink(%@, %@) error %d: %s", target, symLinkFile, errnum, strerror(errnum)); - SETNSERROR(@"UnixErrorDomain", errnum, @"failed to create symlink %@ to %@: %s", symLinkFile, target, strerror(errnum)); - return NO; - } - return YES; -} -- (BOOL)applyACLBlobKey:(BlobKey *)aclBlobKey uncompress:(BOOL)uncompress toPath:(NSString *)path error:(NSError **)error { - if (aclBlobKey != nil) { - NSData *data = [repo blobDataForBlobKey:aclBlobKey error:error]; - if (data == nil) { - return NO; - } - if (uncompress) { - data = [data gzipInflate]; - } - NSString *aclString = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]; - - NSString *currentAclString = nil; - if (![FileACL aclText:¤tAclString forFile:path error:error]) { - return NO; - } - if (![currentAclString isEqualToString:aclString] && [aclString length] > 0) { - if (![FileACL writeACLText:aclString toFile:path error:error]) { - return NO; - } - } - } - return YES; -} -- (BOOL)applyXAttrsBlobKey:(BlobKey *)xattrsBlobKey uncompress:(BOOL)uncompress toFile:(NSString *)path error:(NSError **)error { - if (xattrsBlobKey != nil) { - NSData *xattrsData = [repo blobDataForBlobKey:xattrsBlobKey error:error]; - if (xattrsData == nil) { - return NO; - } - id is = [xattrsData newInputStream]; - if (uncompress) { - id uncompressed = [[GunzipInputStream alloc] initWithUnderlyingStream:is]; - [is release]; - is = uncompressed; - } - BufferedInputStream *bis = [[BufferedInputStream alloc] initWithUnderlyingStream:is]; - [is release]; - XAttrSet *set = [[[XAttrSet alloc] initWithBufferedInputStream:bis error:error] autorelease]; - [bis release]; - if (!set) { - return NO; - } - if (![set applyToFile:path error:error]) { - return NO; - } - } - return YES; -} -- (void)addError:(NSError *)theError forPath:(NSString *)thePath { - [errorsByPath setObject:theError forKey:thePath]; -} -@end diff --git a/S3TargetConnection.h b/S3TargetConnection.h new file mode 100644 index 0000000..96f3cb8 --- /dev/null +++ b/S3TargetConnection.h @@ -0,0 +1,21 @@ +// +// S3TargetConnection.h +// Arq +// +// Created by Stefan Reitshamer on 4/21/14. +// Copyright (c) 2014 Stefan Reitshamer. All rights reserved. +// + +#import "TargetConnection.h" +@class BaseTargetConnection; +@class Target; +@class S3RemoteFS; + + +@interface S3TargetConnection : NSObject { + S3RemoteFS *s3RemoteFS; + BaseTargetConnection *base; +} +- (id)initWithTarget:(Target *)theTarget; + +@end diff --git a/S3TargetConnection.m b/S3TargetConnection.m new file mode 100644 index 0000000..cecb5fd --- /dev/null +++ b/S3TargetConnection.m @@ -0,0 +1,101 @@ +// +// S3TargetConnection.m +// Arq +// +// Created by Stefan Reitshamer on 4/21/14. +// Copyright (c) 2014 Stefan Reitshamer. All rights reserved. +// + +#import "S3TargetConnection.h" +#import "BaseTargetConnection.h" +#import "S3RemoteFS.h" +#import "S3ObjectMetadata.h" + + +@implementation S3TargetConnection +- (id)initWithTarget:(Target *)theTarget { + if (self = [super init]) { + s3RemoteFS = [[S3RemoteFS alloc] initWithTarget:theTarget]; + base = [[BaseTargetConnection alloc] initWithTarget:theTarget remoteFS:s3RemoteFS]; + } + return self; +} +- (void)dealloc { + [s3RemoteFS release]; + [base release]; + [super dealloc]; +} + + +#pragma mark TargetConnection +- (NSArray *)computerUUIDsWithDelegate:(id)theDelegate error:(NSError **)error { + return [base computerUUIDsWithDelegate:theDelegate error:error]; +} +- (NSArray *)bucketUUIDsForComputerUUID:(NSString *)theComputerUUID deleted:(BOOL)deleted delegate:(id)theDelegate error:(NSError **)error { + return [base bucketUUIDsForComputerUUID:theComputerUUID deleted:deleted delegate:theDelegate error:error]; +} +- (NSData *)bucketPlistDataForComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID deleted:(BOOL)deleted delegate:(id)theDelegate error:(NSError **)error { + return [base bucketPlistDataForComputerUUID:theComputerUUID bucketUUID:theBucketUUID deleted:deleted delegate:theDelegate error:error]; +} +- (BOOL)saveBucketPlistData:(NSData *)theData forComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID deleted:(BOOL)deleted delegate:(id)theDelegate error:(NSError **)error { + return [base saveBucketPlistData:theData forComputerUUID:theComputerUUID bucketUUID:theBucketUUID deleted:deleted delegate:theDelegate error:error]; +} +- (BOOL)deleteBucketPlistDataForComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID deleted:(BOOL)deleted delegate:(id)theDelegate error:(NSError **)error { + return [base deleteBucketPlistDataForComputerUUID:theComputerUUID bucketUUID:theBucketUUID deleted:deleted delegate:theDelegate error:error]; +} +- (NSData *)computerInfoForComputerUUID:(NSString *)theComputerUUID delegate:(id)theDelegate error:(NSError **)error { + return [base computerInfoForComputerUUID:theComputerUUID delegate:theDelegate error:error]; +} +- (BOOL)saveComputerInfo:(NSData *)theData forComputerUUID:(NSString *)theComputerUUID delegate:(id)theDelegate error:(NSError **)error { + return [base saveComputerInfo:theData forComputerUUID:theComputerUUID delegate:theDelegate error:error]; +} +- (NSDictionary *)objectsBySHA1ForTargetEndpoint:(NSURL *)theEndpoint isGlacier:(BOOL)theIsGlacier computerUUID:(NSString *)theComputerUUID delegate:(id)theDelegate error:(NSError **)error { + NSString *prefix = [NSString stringWithFormat:@"%@/%@%@/objects/", [theEndpoint path], (theIsGlacier ? @"glacier/" : @""), theComputerUUID]; + + NSArray *objects = [base objectsWithPrefix:prefix delegate:theDelegate error:error]; + if (objects == nil) { + return nil; + } + NSMutableDictionary *ret = [NSMutableDictionary dictionary]; + for (S3ObjectMetadata *md in objects) { + [ret setObject:md forKey:[[md path] lastPathComponent]]; + } + return ret; +} +- (NSArray *)pathsWithPrefix:(NSString *)thePrefix delegate:(id)theDelegate error:(NSError **)error { + return [base pathsWithPrefix:thePrefix delegate:theDelegate error:error]; +} +- (BOOL)deleteObjectsForComputerUUID:(NSString *)theComputerUUID delegate:(id)theDelegate error:(NSError **)error { + return [base deleteObjectsForComputerUUID:theComputerUUID delegate:theDelegate error:error]; +} +- (BOOL)deletePaths:(NSArray *)thePaths delegate:(id)theDelegate error:(NSError **)error { + return [base deletePaths:thePaths delegate:theDelegate error:error]; +} +- (NSNumber *)fileExistsAtPath:(NSString *)thePath dataSize:(unsigned long long *)theDataSize delegate:(id)theDelegate error:(NSError **)error { + return [base fileExistsAtPath:thePath dataSize:theDataSize delegate:theDelegate error:error]; +} +- (NSData *)contentsOfFileAtPath:(NSString *)thePath delegate:(id)theDelegate error:(NSError **)error { + return [base contentsOfFileAtPath:thePath delegate:theDelegate error:error]; +} +- (BOOL)writeData:(NSData *)theData toFileAtPath:(NSString *)thePath dataTransferDelegate:(id )theDataTransferDelegate targetConnectionDelegate:(id)theTargetConnectionDelegate error:(NSError **)error { + return [base writeData:theData toFileAtPath:thePath dataTransferDelegate:theDataTransferDelegate targetConnectionDelegate:theTargetConnectionDelegate error:error]; +} +- (BOOL)removeItemAtPath:(NSString *)thePath delegate:(id)theDelegate error:(NSError **)error { + return [base removeItemAtPath:thePath delegate:theDelegate error:error]; +} +- (NSNumber *)sizeOfItemAtPath:(NSString *)thePath delegate:(id)theDelegate error:(NSError **)error { + return [base sizeOfItemAtPath:thePath delegate:theDelegate error:error]; +} +- (NSNumber *)isObjectRestoredAtPath:(NSString *)thePath delegate:(id)theDelegate error:(NSError **)error { + return [base isObjectRestoredAtPath:thePath delegate:theDelegate error:error]; +} +- (BOOL)restoreObjectAtPath:(NSString *)thePath forDays:(NSUInteger)theDays alreadyRestoredOrRestoring:(BOOL *)alreadyRestoredOrRestoring delegate:(id)theDelegate error:(NSError **)error { + return [base restoreObjectAtPath:thePath forDays:theDays alreadyRestoredOrRestoring:alreadyRestoredOrRestoring delegate:theDelegate error:error]; +} +- (NSData *)saltDataForComputerUUID:(NSString *)theComputerUUID delegate:(id)theDelegate error:(NSError **)error { + return [base saltDataForComputerUUID:theComputerUUID delegate:theDelegate error:error]; +} +- (BOOL)setSaltData:(NSData *)theData forComputerUUID:(NSString *)theComputerUUID delegate:(id)theDelegate error:(NSError **)error { + return [base setSaltData:theData forComputerUUID:theComputerUUID delegate:theDelegate error:error]; +} +@end diff --git a/SFTPTargetConnection.h b/SFTPTargetConnection.h new file mode 100644 index 0000000..c67e769 --- /dev/null +++ b/SFTPTargetConnection.h @@ -0,0 +1,21 @@ +// +// SFTPTargetConnection.h +// Arq +// +// Created by Stefan Reitshamer on 4/21/14. +// Copyright (c) 2014 Stefan Reitshamer. All rights reserved. +// + +#import "TargetConnection.h" +@class SFTPRemoteFS; +@class BaseTargetConnection; +@class Target; + + +@interface SFTPTargetConnection : NSObject { + SFTPRemoteFS *sftpRemoteFS; + BaseTargetConnection *base; +} +- (id)initWithTarget:(Target *)theTarget; + +@end diff --git a/SFTPTargetConnection.m b/SFTPTargetConnection.m new file mode 100644 index 0000000..06beb30 --- /dev/null +++ b/SFTPTargetConnection.m @@ -0,0 +1,130 @@ +// +// SFTPTargetConnection.m +// Arq +// +// Created by Stefan Reitshamer on 4/21/14. +// Copyright (c) 2014 Stefan Reitshamer. All rights reserved. +// + +#import "SFTPTargetConnection.h" +#import "SFTPRemoteFS.h" +#import "Target.h" +#import "BaseTargetConnection.h" +#import "S3ObjectMetadata.h" + + +@implementation SFTPTargetConnection +- (id)initWithTarget:(Target *)theTarget { + if (self = [super init]) { + NSString *pathPrefix = [[theTarget endpoint] path]; + if ([pathPrefix isEqualToString:@"/"]) { + pathPrefix = @""; + } + sftpRemoteFS = [[SFTPRemoteFS alloc] initWithTarget:theTarget tempDir:[pathPrefix stringByAppendingString:@"/temp"]]; + base = [[BaseTargetConnection alloc] initWithTarget:theTarget remoteFS:sftpRemoteFS]; + } + return self; +} +- (void)dealloc { + [sftpRemoteFS release]; + [base release]; + [super dealloc]; +} + + + +#pragma mark TargetConnection +- (NSArray *)computerUUIDsWithDelegate:(id)theDelegate error:(NSError **)error { + return [base computerUUIDsWithDelegate:theDelegate error:error]; +} +- (NSArray *)bucketUUIDsForComputerUUID:(NSString *)theComputerUUID deleted:(BOOL)deleted delegate:(id)theDelegate error:(NSError **)error { + return [base bucketUUIDsForComputerUUID:theComputerUUID deleted:deleted delegate:theDelegate error:error]; +} +- (NSData *)bucketPlistDataForComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID deleted:(BOOL)deleted delegate:(id)theDelegate error:(NSError **)error { + return [base bucketPlistDataForComputerUUID:theComputerUUID bucketUUID:theBucketUUID deleted:deleted delegate:theDelegate error:error]; +} +- (BOOL)saveBucketPlistData:(NSData *)theData forComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID deleted:(BOOL)deleted delegate:(id)theDelegate error:(NSError **)error { + return [base saveBucketPlistData:theData forComputerUUID:theComputerUUID bucketUUID:theBucketUUID deleted:deleted delegate:theDelegate error:error]; +} +- (BOOL)deleteBucketPlistDataForComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID deleted:(BOOL)deleted delegate:(id)theDelegate error:(NSError **)error { + return [base deleteBucketPlistDataForComputerUUID:theComputerUUID bucketUUID:theBucketUUID deleted:deleted delegate:theDelegate error:error]; +} +- (NSData *)computerInfoForComputerUUID:(NSString *)theComputerUUID delegate:(id)theDelegate error:(NSError **)error { + return [base computerInfoForComputerUUID:theComputerUUID delegate:theDelegate error:error]; +} +- (BOOL)saveComputerInfo:(NSData *)theData forComputerUUID:(NSString *)theComputerUUID delegate:(id)theDelegate error:(NSError **)error { + return [base saveComputerInfo:theData forComputerUUID:theComputerUUID delegate:theDelegate error:error]; +} +- (NSDictionary *)objectsBySHA1ForTargetEndpoint:(NSURL *)theEndpoint isGlacier:(BOOL)theIsGlacier computerUUID:(NSString *)theComputerUUID delegate:(id)theDelegate error:(NSError **)error { + NSString *endpointPath = [theEndpoint path]; + if ([endpointPath isEqualToString:@"/"]) { + endpointPath = @""; + } + NSString *prefix = [NSString stringWithFormat:@"%@/%@/objects/", endpointPath, theComputerUUID]; + NSArray *objects = [base objectsWithPrefix:prefix delegate:theDelegate error:error]; + if (objects == nil) { + return nil; + } + NSMutableDictionary *ret = [NSMutableDictionary dictionary]; + for (S3ObjectMetadata *md in objects) { + NSArray *pathComponents = [[md path] pathComponents]; + NSString *lastPathComponent = [pathComponents lastObject]; + + if ([lastPathComponent length] == 40) { + [ret setObject:md forKey:lastPathComponent]; + } else if ([lastPathComponent length] == 38) { + NSString *fragment1 = [pathComponents objectAtIndex:([pathComponents count] - 2)]; + NSString *fragment2 = [pathComponents objectAtIndex:([pathComponents count] - 1)]; + NSString *theSHA1 = [fragment1 stringByAppendingString:fragment2]; + [ret setObject:md forKey:theSHA1]; + } else if ([lastPathComponent length] == 36) { + NSString *fragment1 = [pathComponents objectAtIndex:([pathComponents count] - 3)]; + NSString *fragment2 = [pathComponents objectAtIndex:([pathComponents count] - 2)]; + NSString *fragment3 = [pathComponents objectAtIndex:([pathComponents count] - 1)]; + NSString *theSHA1 = [[fragment1 stringByAppendingString:fragment2] stringByAppendingString:fragment3]; + [ret setObject:md forKey:theSHA1]; + } else { + HSLogWarn(@"unexpected lastpathcomponent %@ of path %@", lastPathComponent, [md path]); + } + } + + return ret; +} +- (NSArray *)pathsWithPrefix:(NSString *)thePrefix delegate:(id)theDelegate error:(NSError **)error { + return [base pathsWithPrefix:thePrefix delegate:theDelegate error:error]; +} +- (BOOL)deleteObjectsForComputerUUID:(NSString *)theComputerUUID delegate:(id)theDelegate error:(NSError **)error { + return [base deleteObjectsForComputerUUID:theComputerUUID delegate:theDelegate error:error]; +} +- (BOOL)deletePaths:(NSArray *)thePaths delegate:(id)theDelegate error:(NSError **)error { + return [base deletePaths:thePaths delegate:theDelegate error:error]; +} +- (NSNumber *)fileExistsAtPath:(NSString *)thePath dataSize:(unsigned long long *)theDataSize delegate:(id)theDelegate error:(NSError **)error { + return [base fileExistsAtPath:thePath dataSize:theDataSize delegate:theDelegate error:error]; +} +- (NSData *)contentsOfFileAtPath:(NSString *)thePath delegate:(id)theDelegate error:(NSError **)error { + return [base contentsOfFileAtPath:thePath delegate:theDelegate error:error]; +} +- (BOOL)writeData:(NSData *)theData toFileAtPath:(NSString *)thePath dataTransferDelegate:(id)theDTD targetConnectionDelegate:(id)theTCD error:(NSError **)error { + return [base writeData:theData toFileAtPath:thePath dataTransferDelegate:theDTD targetConnectionDelegate:theTCD error:error]; +} +- (BOOL)removeItemAtPath:(NSString *)thePath delegate:(id)theDelegate error:(NSError **)error { + return [base removeItemAtPath:thePath delegate:theDelegate error:error]; +} +- (NSNumber *)sizeOfItemAtPath:(NSString *)thePath delegate:(id)theDelegate error:(NSError **)error { + return [base sizeOfItemAtPath:thePath delegate:theDelegate error:error]; +} +- (NSNumber *)isObjectRestoredAtPath:(NSString *)thePath delegate:(id)theDelegate error:(NSError **)error { + return [base isObjectRestoredAtPath:thePath delegate:theDelegate error:error]; +} +- (BOOL)restoreObjectAtPath:(NSString *)thePath forDays:(NSUInteger)theDays alreadyRestoredOrRestoring:(BOOL *)alreadyRestoredOrRestoring delegate:(id)theDelegate error:(NSError **)error { + return [base restoreObjectAtPath:thePath forDays:theDays alreadyRestoredOrRestoring:alreadyRestoredOrRestoring delegate:theDelegate error:error]; +} +- (NSData *)saltDataForComputerUUID:(NSString *)theComputerUUID delegate:(id)theDelegate error:(NSError **)error { + return [base saltDataForComputerUUID:theComputerUUID delegate:theDelegate error:error]; +} +- (BOOL)setSaltData:(NSData *)theData forComputerUUID:(NSString *)theComputerUUID delegate:(id)theDelegate error:(NSError **)error { + return [base setSaltData:theData forComputerUUID:theComputerUUID delegate:theDelegate error:error]; +} + +@end diff --git a/Target.h b/Target.h new file mode 100644 index 0000000..a631417 --- /dev/null +++ b/Target.h @@ -0,0 +1,50 @@ +// +// Target.h +// arq_restore +// +// Created by Stefan Reitshamer on 7/28/14. +// +// + +@protocol TargetConnection; +@class S3Service; +@class BufferedInputStream; + + +enum TargetType { + kTargetAWS = 0, + kTargetSFTP = 1, + kTargetGreenQloud = 2, + kTargetDreamObjects = 3, + kTargetGoogleCloudStorage = 4, + kTargetS3Compatible = 5, + kTargetGoogleDrive = 6 +}; +typedef int TargetType; + + +@interface Target : NSObject { + NSString *uuid; + NSURL *endpoint; + TargetType targetType; + NSString *secret; + NSString *passphrase; + + BOOL budgetEnabled; + double budgetDollars; + uint32_t budgetGB; + BOOL useRRS; +} + +- (id)initWithEndpoint:(NSURL *)theEndpoint secret:(NSString *)theSecret passphrase:(NSString *)thePassphrase; +- (id)initWithBufferedInputStream:(BufferedInputStream *)theBIS error:(NSError **)error; + +- (NSString *)targetUUID; +- (NSURL *)endpoint; +- (NSString *)endpointDisplayName; +- (NSString *)secret:(NSError **)error; +- (NSString *)passphrase:(NSError **)error; +- (TargetType)targetType; +- (id )newConnection; +- (S3Service *)s3:(NSError **)error; +@end diff --git a/Target.m b/Target.m new file mode 100644 index 0000000..ac80355 --- /dev/null +++ b/Target.m @@ -0,0 +1,169 @@ +// +// Target.m +// arq_restore +// +// Created by Stefan Reitshamer on 7/28/14. +// +// + +#import "Target.h" +#import "NSString_extra.h" +#import "AWSRegion.h" +#import "SFTPTargetConnection.h" +#import "GoogleDriveTargetConnection.h" +#import "S3TargetConnection.h" +#import "S3Service.h" +#import "S3AuthorizationProvider.h" +#import "TargetSchedule.h" +#import "DoubleIO.h" +#import "BooleanIO.h" +#import "IntegerIO.h" +#import "StringIO.h" + + +@implementation Target +- (id)initWithEndpoint:(NSURL *)theEndpoint secret:(NSString *)theSecret passphrase:(NSString *)thePassphrase { + if (self = [super init]) { + uuid = [[NSString stringWithRandomUUID] retain]; + endpoint = [theEndpoint retain]; + secret = [theSecret retain]; + targetType = [self targetTypeForEndpoint]; + passphrase = [thePassphrase retain]; + } + return self; +} +- (id)initWithBufferedInputStream:(BufferedInputStream *)theBIS error:(NSError **)error { + if (self = [super init]) { + if (![StringIO read:&uuid from:theBIS error:error]) { + [self release]; + return nil; + } + [uuid retain]; + NSString *theEndpointDescription = nil; + if (![StringIO read:&theEndpointDescription from:theBIS error:error]) { + [self release]; + return nil; + } + TargetSchedule *targetSchedule = [[TargetSchedule alloc] initWithBufferedInputStream:theBIS error:error]; + if (targetSchedule == nil) { + [self release]; + return nil; + } + if (![BooleanIO read:&budgetEnabled from:theBIS error:error] + || ![DoubleIO read:&budgetDollars from:theBIS error:error] + || ![BooleanIO read:&useRRS from:theBIS error:error] + || ![IntegerIO readUInt32:&budgetGB from:theBIS error:error]) { + [self release]; + return nil; + } + + endpoint = [[NSURL URLWithString:theEndpointDescription] copy]; + targetType = [self targetTypeForEndpoint]; + NSAssert(endpoint != nil, @"endpoint may not be nil"); + } + return self; +} + +- (void)dealloc { + [uuid release]; + [endpoint release]; + [secret release]; + [passphrase release]; + [super dealloc]; +} + +- (NSString *)errorDomain { + return @"TargetErrorDomain"; +} +- (NSString *)targetUUID { + return uuid; +} +- (NSURL *)endpoint { + return endpoint; +} +- (NSString *)endpointDisplayName { + TargetType theTargetType = [self targetType]; + switch (theTargetType) { + case kTargetAWS: + return @"Amazon"; + case kTargetGreenQloud: + return @"GreenQloud"; + case kTargetDreamObjects: + return @"DreamObjects"; + case kTargetGoogleCloudStorage: + return @"Google Cloud Storage"; + case kTargetGoogleDrive: + return @"Google Drive Storage"; + } + return [endpoint host]; +} +- (NSString *)secret:(NSError **)error { + return secret; +} +- (NSString *)passphrase:(NSError **)error { + if (passphrase == nil) { + SETNSERROR([self errorDomain], ERROR_NOT_FOUND, @"passphrase not given"); + return nil; + } + return passphrase; +} +- (TargetType)targetType { + return targetType; +} +- (id )newConnection { + id ret = nil; + if (targetType == kTargetSFTP) { + ret = [[SFTPTargetConnection alloc] initWithTarget:self]; + } else if (targetType == kTargetGoogleDrive) { + ret = [[GoogleDriveTargetConnection alloc] initWithTarget:self]; + } else { + ret = [[S3TargetConnection alloc] initWithTarget:self]; + } + return ret; +} +- (S3Service *)s3:(NSError **)error { + if ([self targetType] == kTargetSFTP || [self targetType] == kTargetGoogleDrive) { + SETNSERROR([self errorDomain], -1, @"cannot create S3Service for endpoint %@", endpoint); + return nil; + } + + S3AuthorizationProvider *sap = [[[S3AuthorizationProvider alloc] initWithAccessKey:[endpoint user] secretKey:secret] autorelease]; + NSString *portString = @""; + if ([[endpoint port] intValue] != 0) { + portString = [NSString stringWithFormat:@":%d", [[endpoint port] intValue]]; + } + NSURL *s3Endpoint = [NSURL URLWithString:[NSString stringWithFormat:@"%@://%@%@", [endpoint scheme], [endpoint host], portString]]; + return [[[S3Service alloc] initWithS3AuthorizationProvider:sap endpoint:s3Endpoint useAmazonRRS:NO] autorelease]; +} + + +#pragma mark NSObject +- (NSString *)description { + return [NSString stringWithFormat:@"%@:%@", [endpoint host], [endpoint path]]; +} + + +#pragma mark internal +- (TargetType)targetTypeForEndpoint { + if ([[[self endpoint] scheme] isEqualToString:@"sftp"]) { + return kTargetSFTP; + } + if ([[[self endpoint] scheme] isEqualToString:@"googledrive"]) { + return kTargetGoogleDrive; + } + AWSRegion *awsRegion = [AWSRegion regionWithS3Endpoint:[self endpoint]]; + if (awsRegion != nil) { + return kTargetAWS; + } + if ([[[self endpoint] host] isEqualToString:@"w.greenqloud.com"]) { + return kTargetGreenQloud; + } + if ([[[self endpoint] host] isEqualToString:@"objects.dreamhost.com"]) { + return kTargetDreamObjects; + } + if ([[[self endpoint] host] isEqualToString:@"storage.googleapis.com"]) { + return kTargetGoogleCloudStorage; + } + return kTargetS3Compatible; +} +@end diff --git a/TargetConnection.h b/TargetConnection.h new file mode 100644 index 0000000..f6e80ac --- /dev/null +++ b/TargetConnection.h @@ -0,0 +1,43 @@ +// +// TargetConnection.h +// Arq +// +// Created by Stefan Reitshamer on 4/21/14. +// Copyright (c) 2014 Stefan Reitshamer. All rights reserved. +// + +@protocol DataTransferDelegate; + +@protocol TargetConnectionDelegate +- (BOOL)targetConnectionShouldRetryOnTransientError:(NSError **)error; +@end + +@protocol TargetConnection + +- (NSArray *)computerUUIDsWithDelegate:(id )theDelegate error:(NSError **)error; +- (NSArray *)bucketUUIDsForComputerUUID:(NSString *)theComputerUUID deleted:(BOOL)deleted delegate:(id )theDelegate error:(NSError **)error; + +- (NSData *)bucketPlistDataForComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID deleted:(BOOL)deleted delegate:(id )theDelegate error:(NSError **)error; +- (BOOL)saveBucketPlistData:(NSData *)theData forComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID deleted:(BOOL)deleted delegate:(id )theDelegate error:(NSError **)error; +- (BOOL)deleteBucketPlistDataForComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID deleted:(BOOL)deleted delegate:(id )theDelegate error:(NSError **)error; + +- (NSData *)computerInfoForComputerUUID:(NSString *)theComputerUUID delegate:(id )theDelegate error:(NSError **)error; +- (BOOL)saveComputerInfo:(NSData *)theData forComputerUUID:(NSString *)theComputerUUID delegate:(id )theDelegate error:(NSError **)error; + +- (NSDictionary *)objectsBySHA1ForTargetEndpoint:(NSURL *)theEndpoint isGlacier:(BOOL)theIsGlacier computerUUID:(NSString *)theComputerUUID delegate:(id )theDelegate error:(NSError **)error; +- (NSArray *)pathsWithPrefix:(NSString *)thePrefix delegate:(id )theDelegate error:(NSError **)error; +- (BOOL)deleteObjectsForComputerUUID:(NSString *)theComputerUUID delegate:(id )theDelegate error:(NSError **)error; +- (BOOL)deletePaths:(NSArray *)thePaths delegate:(id )theDelegate error:(NSError **)error; + +- (NSNumber *)fileExistsAtPath:(NSString *)thePath dataSize:(unsigned long long *)theDataSize delegate:(id )theDelegate error:(NSError **)error; +- (NSData *)contentsOfFileAtPath:(NSString *)thePath delegate:(id )theDelegate error:(NSError **)error; +- (BOOL)writeData:(NSData *)theData toFileAtPath:(NSString *)thePath dataTransferDelegate:(id )theDelegate targetConnectionDelegate:(id )theDelegate error:(NSError **)error; +- (BOOL)removeItemAtPath:(NSString *)thePath delegate:(id )theDelegate error:(NSError **)error; +- (NSNumber *)sizeOfItemAtPath:(NSString *)thePath delegate:(id )theDelegate error:(NSError **)error; +- (NSNumber *)isObjectRestoredAtPath:(NSString *)thePath delegate:(id )theDelegate error:(NSError **)error; +- (BOOL)restoreObjectAtPath:(NSString *)thePath forDays:(NSUInteger)theDays alreadyRestoredOrRestoring:(BOOL *)alreadyRestoredOrRestoring delegate:(id )theDelegate error:(NSError **)error; + +- (NSData *)saltDataForComputerUUID:(NSString *)theComputerUUID delegate:(id )theDelegate error:(NSError **)error; +- (BOOL)setSaltData:(NSData *)theData forComputerUUID:(NSString *)theComputerUUID delegate:(id )theDelegate error:(NSError **)error; + +@end diff --git a/TargetSchedule.h b/TargetSchedule.h new file mode 100644 index 0000000..bfde264 --- /dev/null +++ b/TargetSchedule.h @@ -0,0 +1,53 @@ +// +// TargetSchedule.h +// Arq +// +// Created by Stefan Reitshamer on 12/10/13. +// Copyright (c) 2013 Stefan Reitshamer. All rights reserved. +// + +enum { + TargetScheduleTypeHourly = 0, + TargetScheduleTypeDaily = 1, + TargetScheduleTypeManual = 2 +}; +typedef uint32_t TargetScheduleType; + + +@class DictNode; +@class BufferedInputStream; +@class BufferedOutputStream; + +@interface TargetSchedule : NSObject { + TargetScheduleType type; + uint32_t numberOfHours; + uint32_t minutesAfterHour; + uint32_t backupHour; + uint32_t budgetEnforcementIntervalHours; + BOOL pauseDuringWindow; + uint32_t pauseFromHour; + uint32_t pauseToHour; +} +- (id)initWithScheduleType:(TargetScheduleType)theType + numberOfHours:(int)theNumberOfHours + minutesAfterHour:(int)theMinutesAfterHour + backupHour:(int)theBackupHour +budgetEnforcementIntervalHours:(int)theBudgetEnforcementIntervalHours + pauseDuringWindow:(BOOL)thePauseDuringWindow + pauseFromHour:(NSUInteger)thePauseFromHour + pauseToHour:(NSUInteger)thePauseToHour; +- (id)initWithPlist:(DictNode *)thePlist; +- (id)initWithBufferedInputStream:(BufferedInputStream *)theBIS error:(NSError **)error; + +- (TargetScheduleType)type; +- (uint32_t)numberOfHours; +- (uint32_t)minutesAfterHour; +- (uint32_t)backupHour; +- (uint32_t)budgetEnforcementIntervalHours; +- (BOOL)pauseDuringWindow; +- (uint32_t)pauseFromHour; +- (uint32_t)pauseToHour; +- (DictNode *)toPlist; +- (BOOL)writeTo:(BufferedOutputStream *)theBOS error:(NSError **)error; + +@end diff --git a/TargetSchedule.m b/TargetSchedule.m new file mode 100644 index 0000000..1969c6a --- /dev/null +++ b/TargetSchedule.m @@ -0,0 +1,122 @@ +// +// TargetSchedule.m +// Arq +// +// Created by Stefan Reitshamer on 12/10/13. +// Copyright (c) 2013 Stefan Reitshamer. All rights reserved. +// + +#import "TargetSchedule.h" +#import "DictNode.h" +#import "IntegerNode.h" +#import "IntegerIO.h" +#import "BufferedOutputStream.h" +#import "BooleanNode.h" +#import "BooleanIO.h" + + +#define TARGET_DATA_VERSION (1) + + +@implementation TargetSchedule +- (id)initWithScheduleType:(TargetScheduleType)theType + numberOfHours:(int)theNumberOfHours + minutesAfterHour:(int)theMinutesAfterHour + backupHour:(int)theBackupHour +budgetEnforcementIntervalHours:(int)theBudgetEnforcementIntervalHours + pauseDuringWindow:(BOOL)thePauseDuringWindow + pauseFromHour:(NSUInteger)thePauseFromHour + pauseToHour:(NSUInteger)thePauseToHour { + if (self = [super init]) { + type = theType; + numberOfHours = theNumberOfHours; + minutesAfterHour = theMinutesAfterHour; + backupHour = theBackupHour; + budgetEnforcementIntervalHours = theBudgetEnforcementIntervalHours; + pauseDuringWindow = thePauseDuringWindow; + pauseFromHour = (uint32_t)thePauseFromHour; + pauseToHour = (uint32_t)thePauseToHour; + } + return self; +} +- (id)initWithPlist:(DictNode *)thePlist { + if (self = [super init]) { + type = [[thePlist integerNodeForKey:@"type"] intValue]; + numberOfHours = [[thePlist integerNodeForKey:@"numberOfHours"] intValue]; + minutesAfterHour = [[thePlist integerNodeForKey:@"minutesAfterHour"] intValue]; + backupHour = [[thePlist integerNodeForKey:@"backupHour"] intValue]; + budgetEnforcementIntervalHours = [[thePlist integerNodeForKey:@"budgetEnforcementIntervalHours"] intValue]; + pauseDuringWindow = [[thePlist booleanNodeForKey:@"pauseDuringWindow"] booleanValue]; + pauseFromHour = [[thePlist integerNodeForKey:@"pauseFromHour"] intValue]; + pauseToHour = [[thePlist integerNodeForKey:@"pauseToHour"] intValue]; + } + return self; +} +- (id)initWithBufferedInputStream:(BufferedInputStream *)theBIS error:(NSError **)error { + if (self = [super init]) { + uint32_t version = 0; + if (![IntegerIO readUInt32:&version from:theBIS error:error] + || ![IntegerIO readUInt32:&type from:theBIS error:error] + || ![IntegerIO readUInt32:&numberOfHours from:theBIS error:error] + || ![IntegerIO readUInt32:&minutesAfterHour from:theBIS error:error] + || ![IntegerIO readUInt32:&backupHour from:theBIS error:error] + || ![IntegerIO readUInt32:&budgetEnforcementIntervalHours from:theBIS error:error] + || ![BooleanIO read:&pauseDuringWindow from:theBIS error:error] + || ![IntegerIO readUInt32:&pauseFromHour from:theBIS error:error] + || ![IntegerIO readUInt32:&pauseToHour from:theBIS error:error]) { + [self release]; + return nil; + } + } + return self; +} + +- (TargetScheduleType)type { + return type; +} +- (uint32_t)numberOfHours { + return numberOfHours; +} +- (uint32_t)minutesAfterHour { + return minutesAfterHour; +} +- (uint32_t)backupHour { + return backupHour; +} +- (uint32_t)budgetEnforcementIntervalHours { + return budgetEnforcementIntervalHours; +} +- (BOOL)pauseDuringWindow { + return pauseDuringWindow; +} +- (uint32_t)pauseFromHour { + return pauseFromHour; +} +- (uint32_t)pauseToHour { + return pauseToHour; +} +- (DictNode *)toPlist { + DictNode *ret = [[[DictNode alloc] init] autorelease]; + [ret putInt:TARGET_DATA_VERSION forKey:@"dataVersion"]; + [ret putInt:type forKey:@"type"]; + [ret putInt:numberOfHours forKey:@"numberOfHours"]; + [ret putInt:minutesAfterHour forKey:@"minutesAfterHour"]; + [ret putInt:backupHour forKey:@"backupHour"]; + [ret putInt:budgetEnforcementIntervalHours forKey:@"budgetEnforcementIntervalHours"]; + [ret putBoolean:pauseDuringWindow forKey:@"pauseDuringWindow"]; + [ret putInt:pauseFromHour forKey:@"pauseFromHour"]; + [ret putInt:pauseToHour forKey:@"pauseToHour"]; + return ret; +} +- (BOOL)writeTo:(BufferedOutputStream *)theBOS error:(NSError **)error { + return [IntegerIO writeUInt32:TARGET_DATA_VERSION to:theBOS error:error] + && [IntegerIO writeUInt32:type to:theBOS error:error] + && [IntegerIO writeUInt32:numberOfHours to:theBOS error:error] + && [IntegerIO writeUInt32:minutesAfterHour to:theBOS error:error] + && [IntegerIO writeUInt32:backupHour to:theBOS error:error] + && [IntegerIO writeUInt32:budgetEnforcementIntervalHours to:theBOS error:error] + && [BooleanIO write:pauseDuringWindow to:theBOS error:error] + && [IntegerIO writeUInt32:pauseFromHour to:theBOS error:error] + && [IntegerIO writeUInt32:pauseToHour to:theBOS error:error]; +} +@end diff --git a/UserAndComputer.h b/UserAndComputer.h index be9bd40..5e13cd5 100644 --- a/UserAndComputer.h +++ b/UserAndComputer.h @@ -3,7 +3,7 @@ // Arq // // Created by Stefan Reitshamer on 7/9/10. -// Copyright 2010 __MyCompanyName__. All rights reserved. +// Copyright 2010 Haystack Software. All rights reserved. // diff --git a/UserAndComputer.m b/UserAndComputer.m index 4bda18a..71dc6ee 100644 --- a/UserAndComputer.m +++ b/UserAndComputer.m @@ -3,7 +3,7 @@ // Arq // // Created by Stefan Reitshamer on 7/9/10. -// Copyright 2010 __MyCompanyName__. All rights reserved. +// Copyright 2010 Haystack Software. All rights reserved. // #import "UserAndComputer.h" diff --git a/UserLibrary_Arq.m b/UserLibrary_Arq.m index 7003cd7..519b3c3 100644 --- a/UserLibrary_Arq.m +++ b/UserLibrary_Arq.m @@ -11,7 +11,7 @@ @implementation UserLibrary (Arq) + (NSString *)arqUserLibraryPath { - return [NSHomeDirectory() stringByAppendingString:@"/Library/Arq"]; + return [[NSHomeDirectory() stringByAppendingPathComponent:@"Library"] stringByAppendingPathComponent:@"Arq"]; } + (NSString *)arqCachePath { return [NSString stringWithFormat:@"%@/Cache.noindex", [UserLibrary arqUserLibraryPath]]; diff --git a/XAttrSet.h b/XAttrSet.h deleted file mode 100644 index 78dbb5f..0000000 --- a/XAttrSet.h +++ /dev/null @@ -1,24 +0,0 @@ -// -// XAttrSet.h -// Backup -// -// Created by Stefan Reitshamer on 4/27/09. -// Copyright 2009 PhotoMinds LLC. All rights reserved. -// - - -#import "Blob.h" -#import "BufferedInputStream.h" - -@interface XAttrSet : NSObject { - NSMutableDictionary *xattrs; - NSString *path; -} -- (id)initWithPath:(NSString *)thePath error:(NSError **)error; -- (id)initWithBufferedInputStream:(BufferedInputStream *)is error:(NSError **)error; -- (NSData *)toData; -- (NSUInteger)count; -- (unsigned long long)dataLength; -- (NSArray *)names; -- (BOOL)applyToFile:(NSString *)path error:(NSError **)error; -@end diff --git a/XAttrSet.m b/XAttrSet.m deleted file mode 100644 index 44ee21c..0000000 --- a/XAttrSet.m +++ /dev/null @@ -1,214 +0,0 @@ -// -// XAttrSet.m -// Backup -// -// Created by Stefan Reitshamer on 4/27/09. -// Copyright 2009 PhotoMinds LLC. All rights reserved. -// - -#include -#include -#import "XAttrSet.h" -#import "StringIO.h" -#import "DataIO.h" -#import "IntegerIO.h" -#import "Blob.h" -#import "DataInputStream.h" -#import "SetNSError.h" -#import "NSErrorCodes.h" -#import "Streams.h" -#import "NSError_extra.h" -#import "BufferedInputStream.h" -#import "NSData-Gzip.h" - -#define HEADER_LENGTH (12) - -@interface XAttrSet (internal) -- (BOOL)loadFromPath:(NSString *)thePath error:(NSError **)error; -- (BOOL)loadFromInputStream:(BufferedInputStream *)is error:(NSError **)error; -@end - -@implementation XAttrSet -- (id)initWithPath:(NSString *)thePath error:(NSError **)error { - if (self = [super init]) { - xattrs = [[NSMutableDictionary alloc] init]; - NSError *myError = nil; - if (![self loadFromPath:thePath error:&myError]) { - if ([myError isErrorWithDomain:@"UnixErrorDomain" code:EPERM]) { - HSLogDebug(@"%@ doesn't support extended attributes; skipping", thePath); - } else { - if (error != NULL) { - *error = myError; - } - [self release]; - return nil; - } - } - path = [thePath retain]; - } - return self; -} -- (id)initWithBufferedInputStream:(BufferedInputStream *)is error:(NSError **)error { - if (self = [super init]) { - xattrs = [[NSMutableDictionary alloc] init]; - if (![self loadFromInputStream:is error:error]) { - [self release]; - self = nil; - } - } - return self; -} -- (void)dealloc { - [xattrs release]; - [path release]; - [super dealloc]; -} -- (NSData *)toData { - NSMutableData *mutableData = [[[NSMutableData alloc] init] autorelease]; - [mutableData appendBytes:"XAttrSetV002" length:HEADER_LENGTH]; - uint64_t count = (uint64_t)[xattrs count]; - [IntegerIO writeUInt64:count to:mutableData]; - for (NSString *name in [xattrs allKeys]) { - [StringIO write:name to:mutableData]; - [DataIO write:[xattrs objectForKey:name] to:mutableData]; - } - return mutableData; -} -- (NSUInteger)count { - return [xattrs count]; -} -- (unsigned long long)dataLength { - unsigned long long total = 0; - for (NSString *key in [xattrs allKeys]) { - NSData *value = [xattrs objectForKey:key]; - total += [value length]; - } - return total; -} -- (NSArray *)names { - return [xattrs allKeys]; -} -- (BOOL)applyToFile:(NSString *)thePath error:(NSError **)error { - XAttrSet *current = [[[XAttrSet alloc] initWithPath:thePath error:error] autorelease]; - if (!current) { - return NO; - } - const char *pathChars = [thePath fileSystemRepresentation]; - for (NSString *name in [current names]) { - if (removexattr(pathChars, [name UTF8String], XATTR_NOFOLLOW) == -1) { - int errnum = errno; - HSLogError(@"removexattr(%@, %@) error %d: %s", thePath, name, errnum, strerror(errnum)); - SETNSERROR(@"UnixErrorDomain", errnum, @"failed to remove extended attribute %@ from %@: %s", name, thePath, strerror(errnum)); - return NO; - } - } - for (NSString *key in [xattrs allKeys]) { - NSData *value = [xattrs objectForKey:key]; - if (setxattr(pathChars, - [key UTF8String], - [value bytes], - [value length], - 0, - XATTR_NOFOLLOW) == -1) { - int errnum = errno; - HSLogError(@"setxattr(%@, %@) error %d: %s", thePath, key, errnum, strerror(errnum)); - SETNSERROR(@"UnixErrorDomain", errnum, @"failed to set extended attribute %@ on %@: %s", key, thePath, strerror(errnum)); - return NO; - } - } - return YES; -} -@end - -@implementation XAttrSet (internal) -- (BOOL)loadFromPath:(NSString *)thePath error:(NSError **)error { - struct stat st; - if (lstat([thePath fileSystemRepresentation], &st) == -1) { - int errnum = errno; - HSLogError(@"lstat(%@) error %d: %s", thePath, errnum, strerror(errnum)); - SETNSERROR(@"UnixErrorDomain", errnum, @"%@: %s", thePath, strerror(errnum)); - return NO; - } - if (S_ISREG(st.st_mode) || S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode)) { - const char *cpath = [thePath fileSystemRepresentation]; - ssize_t xattrsize = listxattr(cpath, NULL, 0, XATTR_NOFOLLOW); - if (xattrsize == -1) { - int errnum = errno; - HSLogError(@"listxattr(%@) error %d: %s", thePath, errnum, strerror(errnum)); - SETNSERROR(@"UnixErrorDomain", errnum, @"failed to list extended attributes of %@: %s", thePath, strerror(errnum)); - return NO; - } - if (xattrsize > 0) { - char *xattrbuf = (char *)malloc(xattrsize); - xattrsize = listxattr(cpath, xattrbuf, xattrsize, XATTR_NOFOLLOW); - if (xattrsize == -1) { - int errnum = errno; - HSLogError(@"listxattr(%@) error %d: %s", thePath, errnum, strerror(errnum)); - SETNSERROR(@"UnixErrorDomain", errnum, @"failed to list extended attributes of %@: %s", thePath, strerror(errnum)); - free(xattrbuf); - return NO; - } - for (char *name = xattrbuf; name < (xattrbuf + xattrsize); name += strlen(name) + 1) { - NSString *theName = [NSString stringWithUTF8String:name]; - ssize_t valuesize = getxattr(cpath, name, NULL, 0, 0, XATTR_NOFOLLOW); - NSData *xattrData = nil; - if (valuesize == -1) { - int errnum = errno; - HSLogError(@"getxattr(%s, %s) error %d: %s", cpath, name, errnum, strerror(errnum)); - SETNSERROR(@"UnixErrorDomain", errnum, @"failed to read extended attribute %s of %@: %s", name, thePath, strerror(errnum)); - free(xattrbuf); - return NO; - } - if (valuesize > 0) { - void *value = malloc(valuesize); - if (getxattr(cpath, name, value, valuesize, 0, XATTR_NOFOLLOW) == -1) { - int errnum = errno; - HSLogError(@"getxattr(%s, %s) error %d: %s", cpath, name, errnum, strerror(errnum)); - SETNSERROR(@"UnixErrorDomain", errnum, @"failed to read extended attribute %s of %@: %s", name, thePath, strerror(errnum)); - free(value); - free(xattrbuf); - return NO; - } - xattrData = [NSData dataWithBytes:value length:valuesize]; - free(value); - } else { - xattrData = [NSData data]; - } - [xattrs setObject:xattrData forKey:theName]; - } - free(xattrbuf); - } - } - return YES; -} -- (BOOL)loadFromInputStream:(BufferedInputStream *)is error:(NSError **)error { - BOOL ret = NO; - unsigned char *buf = (unsigned char *)malloc(HEADER_LENGTH); - if (![is readExactly:HEADER_LENGTH into:buf error:error]) { - goto load_error; - } - if (strncmp((const char *)buf, "XAttrSetV002", HEADER_LENGTH)) { - SETNSERROR(@"XAttrSetErrorDomain", ERROR_INVALID_OBJECT_VERSION, @"invalid XAttrSet header"); - goto load_error; - } - uint64_t count; - if (![IntegerIO readUInt64:&count from:is error:error]) { - goto load_error; - } - for (uint64_t i = 0; i < count; i++) { - NSString *name; - if (![StringIO read:&name from:is error:error]) { - goto load_error; - } - NSData *value; - if (![DataIO read:&value from:is error:error]) { - goto load_error; - } - [xattrs setObject:value forKey:name]; - } - ret = YES; -load_error: - free(buf); - return ret; -} -@end diff --git a/arq_restore.m b/arq_restore.m index e9d6e76..374db93 100644 --- a/arq_restore.m +++ b/arq_restore.m @@ -37,26 +37,34 @@ static void printUsage(const char *exeName) { fprintf(stderr, "Usage:\n"); - fprintf(stderr, "\t%s -v\n", exeName); - fprintf(stderr, "\t%s [-l log_level]\n", exeName); - fprintf(stderr, "\t%s [-l log_level] /s3bucket/computerUUID/folderUUID\n", exeName); - fprintf(stderr, "\t%s [-l log_level] /s3bucket/computerUUID/folderUUID reflog\n", exeName); - fprintf(stderr, "\t%s [-l log_level] /s3bucket/computerUUID/folderUUID \n", exeName); + fprintf(stderr, "\t%s [-l log_level] listcomputers \n", exeName); + fprintf(stderr, "\t%s [-l log_level] listfolders \n", exeName); + fprintf(stderr, "\t%s [-l log_level] restore \n", exeName); + fprintf(stderr, "\t\ntarget_params by target type:\n"); + fprintf(stderr, "\taws: access_key secret_key bucket_name\n"); + fprintf(stderr, "\tsftp: hostname port path username password_or_keyfile [keyfile_passphrase]\n"); + fprintf(stderr, "\tgreenqloud: access_key secret_key bucket_name\n"); + fprintf(stderr, "\tdreamobjects: public_key secret_key bucket_name\n"); + fprintf(stderr, "\tgooglecloudstorage: public_key secret_key bucket_name\n"); + fprintf(stderr, "\ts3compatible: service_url access_key secret_key bucket_name\n"); + fprintf(stderr, "\tgoogledrive: refresh_token path\n"); } int main (int argc, const char **argv) { - setHSLogLevel(HSLOG_LEVEL_WARN); char *exePath = strdup(argv[0]); char *exeName = basename(exePath); NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; ArqRestoreCommand *cmd = [[[ArqRestoreCommand alloc] init] autorelease]; int ret = 0; - if (![cmd readArgc:argc argv:argv]) { + if (argc == 2 && !strcmp(argv[1], "-h")) { printUsage(exeName); - ret = 1; } else { NSError *myError = nil; - if (![cmd execute:&myError]) { - fprintf(stderr, "restore error: %s\n", [[myError localizedDescription] UTF8String]); + if (![cmd executeWithArgc:argc argv:argv error:&myError]) { + fprintf(stderr, "%s: %s\n", exeName, [[myError localizedDescription] UTF8String]); + + if ([myError isErrorWithDomain:[cmd errorDomain] code:ERROR_USAGE]) { + printUsage(exeName); + } ret = 1; } } diff --git a/arq_restore.xcodeproj/project.pbxproj b/arq_restore.xcodeproj/project.pbxproj index ad3e261..9c353ba 100644 --- a/arq_restore.xcodeproj/project.pbxproj +++ b/arq_restore.xcodeproj/project.pbxproj @@ -8,41 +8,6 @@ /* Begin PBXBuildFile section */ 8DD76F9C0486AA7600D96B5E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 08FB779EFE84155DC02AAC07 /* Foundation.framework */; }; - F805B54D1160D3E6007EC01E /* ArqRestoreCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B54C1160D3E6007EC01E /* ArqRestoreCommand.m */; }; - F805B7531160DCFE007EC01E /* ArrayNode.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7421160DCFE007EC01E /* ArrayNode.m */; }; - F805B7541160DCFE007EC01E /* BooleanNode.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7441160DCFE007EC01E /* BooleanNode.m */; }; - F805B7551160DCFE007EC01E /* DictNode.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7461160DCFE007EC01E /* DictNode.m */; }; - F805B7561160DCFE007EC01E /* IntegerNode.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7481160DCFE007EC01E /* IntegerNode.m */; }; - F805B7571160DCFE007EC01E /* RealNode.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B74C1160DCFE007EC01E /* RealNode.m */; }; - F805B7581160DCFE007EC01E /* StringNode.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B74E1160DCFE007EC01E /* StringNode.m */; }; - F805B7591160DCFE007EC01E /* XMLPListReader.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7501160DCFE007EC01E /* XMLPListReader.m */; }; - F805B75A1160DCFE007EC01E /* XMLPListWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7521160DCFE007EC01E /* XMLPListWriter.m */; }; - F805B7831160DD60007EC01E /* NSError_S3.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B76B1160DD60007EC01E /* NSError_S3.m */; }; - F805B7841160DD60007EC01E /* PathReceiver.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B76D1160DD60007EC01E /* PathReceiver.m */; }; - F805B7861160DD60007EC01E /* S3AuthorizationProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7711160DD60007EC01E /* S3AuthorizationProvider.m */; }; - F805B7871160DD60007EC01E /* S3Lister.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7731160DD60007EC01E /* S3Lister.m */; }; - F805B7881160DD60007EC01E /* S3ObjectMetadata.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7751160DD60007EC01E /* S3ObjectMetadata.m */; }; - F805B7891160DD60007EC01E /* S3ObjectReceiver.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7771160DD60007EC01E /* S3ObjectReceiver.m */; }; - F805B78B1160DD60007EC01E /* S3Request.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B77C1160DD60007EC01E /* S3Request.m */; }; - F805B78C1160DD60007EC01E /* S3Service.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B77E1160DD60007EC01E /* S3Service.m */; }; - F805B7A91160DEF2007EC01E /* RegexKitLite.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7A81160DEF2007EC01E /* RegexKitLite.m */; }; - F805B7BA1160E3AF007EC01E /* Blob.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7B91160E3AF007EC01E /* Blob.m */; }; - F805B7D81160E456007EC01E /* BlobACL.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7D71160E456007EC01E /* BlobACL.m */; }; - F805B7E11160E48B007EC01E /* ServerBlob.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7E01160E48B007EC01E /* ServerBlob.m */; }; - F805B7FB1160E73D007EC01E /* RFC2616DateFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7FA1160E73D007EC01E /* RFC2616DateFormatter.m */; }; - F805B7FE1160E764007EC01E /* RFC822.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7FD1160E764007EC01E /* RFC822.m */; }; - F805B81B1160E838007EC01E /* InputStreams.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B81A1160E838007EC01E /* InputStreams.m */; }; - F805B8251160E857007EC01E /* ChunkedInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B8221160E857007EC01E /* ChunkedInputStream.m */; }; - F805B8261160E857007EC01E /* FixedLengthInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B8241160E857007EC01E /* FixedLengthInputStream.m */; }; - F805B82B1160E861007EC01E /* FDInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B8281160E861007EC01E /* FDInputStream.m */; }; - F805B82C1160E861007EC01E /* FDOutputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B82A1160E861007EC01E /* FDOutputStream.m */; }; - F805B82F1160E86E007EC01E /* DataInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B82E1160E86E007EC01E /* DataInputStream.m */; }; - F805B8321160E878007EC01E /* Writer.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B8311160E878007EC01E /* Writer.m */; }; - F805B83B1160E8DD007EC01E /* NSData-InputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B83A1160E8DD007EC01E /* NSData-InputStream.m */; }; - F805B8421160E90F007EC01E /* Streams.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B8411160E90F007EC01E /* Streams.m */; }; - F805B8651160EA15007EC01E /* NSXMLNode_extra.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B8641160EA15007EC01E /* NSXMLNode_extra.m */; }; - F805B86A1160EA83007EC01E /* NSData-Base64Extensions.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B8691160EA83007EC01E /* NSData-Base64Extensions.m */; }; - F805B86F1160EAC1007EC01E /* DataInputStreamFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B86E1160EAC1007EC01E /* DataInputStreamFactory.m */; }; F805B8891160EB39007EC01E /* libcrypto.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F805B8881160EB39007EC01E /* libcrypto.dylib */; }; F805B88B1160EB3A007EC01E /* libicucore.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F805B88A1160EB39007EC01E /* libicucore.dylib */; }; F805B88F1160EB45007EC01E /* libssl.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F805B88E1160EB45007EC01E /* libssl.dylib */; }; @@ -50,199 +15,172 @@ F805B8A11160EBAA007EC01E /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F805B8A01160EBAA007EC01E /* CoreFoundation.framework */; }; F805B8C21160EC41007EC01E /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F805B8C11160EC41007EC01E /* Security.framework */; }; F805B8CE1160ECD7007EC01E /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F805B8CD1160ECD7007EC01E /* CoreServices.framework */; }; - F81426D714541A6C00D7E50A /* BackupSet.m in Sources */ = {isa = PBXBuildFile; fileRef = F81426D614541A6C00D7E50A /* BackupSet.m */; }; - F8146BEB16EB70EE006AD471 /* BlobKeyIO.m in Sources */ = {isa = PBXBuildFile; fileRef = F8146BEA16EB70EE006AD471 /* BlobKeyIO.m */; }; - F8373E0F14794D01005AFBE6 /* ReflogEntry.m in Sources */ = {isa = PBXBuildFile; fileRef = F8373E0E14794D01005AFBE6 /* ReflogEntry.m */; }; - F8373E5A147A8DEC005AFBE6 /* ReflogPrinter.m in Sources */ = {isa = PBXBuildFile; fileRef = F8373E59147A8DEC005AFBE6 /* ReflogPrinter.m */; }; - F83C1A7411CA7C170001958F /* ArqRestoreCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B54C1160D3E6007EC01E /* ArqRestoreCommand.m */; }; - F83C1A7711CA7C170001958F /* ArrayNode.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7421160DCFE007EC01E /* ArrayNode.m */; }; - F83C1A7811CA7C170001958F /* BooleanNode.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7441160DCFE007EC01E /* BooleanNode.m */; }; - F83C1A7911CA7C170001958F /* DictNode.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7461160DCFE007EC01E /* DictNode.m */; }; - F83C1A7A11CA7C170001958F /* IntegerNode.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7481160DCFE007EC01E /* IntegerNode.m */; }; - F83C1A7B11CA7C170001958F /* RealNode.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B74C1160DCFE007EC01E /* RealNode.m */; }; - F83C1A7C11CA7C170001958F /* StringNode.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B74E1160DCFE007EC01E /* StringNode.m */; }; - F83C1A7D11CA7C170001958F /* XMLPListReader.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7501160DCFE007EC01E /* XMLPListReader.m */; }; - F83C1A7E11CA7C170001958F /* XMLPListWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7521160DCFE007EC01E /* XMLPListWriter.m */; }; - F83C1A8011CA7C170001958F /* NSError_S3.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B76B1160DD60007EC01E /* NSError_S3.m */; }; - F83C1A8111CA7C170001958F /* PathReceiver.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B76D1160DD60007EC01E /* PathReceiver.m */; }; - F83C1A8311CA7C170001958F /* S3AuthorizationProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7711160DD60007EC01E /* S3AuthorizationProvider.m */; }; - F83C1A8411CA7C170001958F /* S3Lister.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7731160DD60007EC01E /* S3Lister.m */; }; - F83C1A8511CA7C170001958F /* S3ObjectMetadata.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7751160DD60007EC01E /* S3ObjectMetadata.m */; }; - F83C1A8611CA7C170001958F /* S3ObjectReceiver.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7771160DD60007EC01E /* S3ObjectReceiver.m */; }; - F83C1A8711CA7C170001958F /* S3Request.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B77C1160DD60007EC01E /* S3Request.m */; }; - F83C1A8811CA7C170001958F /* S3Service.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B77E1160DD60007EC01E /* S3Service.m */; }; - F83C1A8A11CA7C170001958F /* RegexKitLite.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7A81160DEF2007EC01E /* RegexKitLite.m */; }; - F83C1A8B11CA7C170001958F /* Blob.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7B91160E3AF007EC01E /* Blob.m */; }; - F83C1A8F11CA7C170001958F /* BlobACL.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7D71160E456007EC01E /* BlobACL.m */; }; - F83C1A9011CA7C170001958F /* ServerBlob.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7E01160E48B007EC01E /* ServerBlob.m */; }; - F83C1A9111CA7C170001958F /* RFC2616DateFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7FA1160E73D007EC01E /* RFC2616DateFormatter.m */; }; - F83C1A9211CA7C170001958F /* RFC822.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7FD1160E764007EC01E /* RFC822.m */; }; - F83C1A9311CA7C170001958F /* InputStreams.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B81A1160E838007EC01E /* InputStreams.m */; }; - F83C1A9411CA7C170001958F /* ChunkedInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B8221160E857007EC01E /* ChunkedInputStream.m */; }; - F83C1A9511CA7C170001958F /* FixedLengthInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B8241160E857007EC01E /* FixedLengthInputStream.m */; }; - F83C1A9611CA7C170001958F /* FDInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B8281160E861007EC01E /* FDInputStream.m */; }; - F83C1A9711CA7C170001958F /* FDOutputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B82A1160E861007EC01E /* FDOutputStream.m */; }; - F83C1A9811CA7C170001958F /* DataInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B82E1160E86E007EC01E /* DataInputStream.m */; }; - F83C1A9911CA7C170001958F /* Writer.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B8311160E878007EC01E /* Writer.m */; }; - F83C1A9A11CA7C170001958F /* NSData-InputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B83A1160E8DD007EC01E /* NSData-InputStream.m */; }; - F83C1A9C11CA7C170001958F /* Streams.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B8411160E90F007EC01E /* Streams.m */; }; - F83C1AA111CA7C170001958F /* NSXMLNode_extra.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B8641160EA15007EC01E /* NSXMLNode_extra.m */; }; - F83C1AA211CA7C170001958F /* NSData-Base64Extensions.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B8691160EA83007EC01E /* NSData-Base64Extensions.m */; }; - F83C1AA311CA7C170001958F /* DataInputStreamFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B86E1160EAC1007EC01E /* DataInputStreamFactory.m */; }; - F83C1AA411CA7C170001958F /* Restorer.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D677FF1160F26A00CC270E /* Restorer.m */; }; - F83C1AA711CA7C170001958F /* SHA1Hash.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D6781C1160F4FD00CC270E /* SHA1Hash.m */; }; - F83C1AAB11CA7C170001958F /* PackIndexEntry.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D6783B1160F70100CC270E /* PackIndexEntry.m */; }; - F83C1AAC11CA7C170001958F /* DiskPack.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678421160F74A00CC270E /* DiskPack.m */; }; - F83C1AAD11CA7C170001958F /* DiskPackIndex.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678441160F74A00CC270E /* DiskPackIndex.m */; }; - F83C1AAE11CA7C170001958F /* Commit.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D6785C1160F7CF00CC270E /* Commit.m */; }; - F83C1AAF11CA7C170001958F /* Tree.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D6785E1160F7CF00CC270E /* Tree.m */; }; - F83C1AB011CA7C170001958F /* NSData-Encrypt.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678641160F7FE00CC270E /* NSData-Encrypt.m */; }; - F83C1AB111CA7C170001958F /* OpenSSL.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678691160F81100CC270E /* OpenSSL.m */; }; - F83C1AB211CA7C170001958F /* DecryptedInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D6786E1160F84600CC270E /* DecryptedInputStream.m */; }; - F83C1AB311CA7C170001958F /* CryptInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678731160F85D00CC270E /* CryptInputStream.m */; }; - F83C1AB411CA7C170001958F /* FileInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678761160F86E00CC270E /* FileInputStream.m */; }; - F83C1AB511CA7C170001958F /* DataIO.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678791160F8A000CC270E /* DataIO.m */; }; - F83C1AB611CA7C170001958F /* DateIO.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D6787B1160F8A000CC270E /* DateIO.m */; }; - F83C1AB711CA7C170001958F /* DoubleIO.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D6787D1160F8A000CC270E /* DoubleIO.m */; }; - F83C1AB811CA7C170001958F /* IntegerIO.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D6787F1160F8A000CC270E /* IntegerIO.m */; }; - F83C1AB911CA7C170001958F /* StringIO.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678811160F8A000CC270E /* StringIO.m */; }; - F83C1ABA11CA7C170001958F /* NSString_extra.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678881160F8CD00CC270E /* NSString_extra.m */; }; - F83C1ABB11CA7C170001958F /* BinarySHA1.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D6788B1160F8E500CC270E /* BinarySHA1.m */; }; - F83C1ABC11CA7C170001958F /* NSFileManager_extra.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678991160FA2A00CC270E /* NSFileManager_extra.m */; }; - F83C1ABD11CA7C170001958F /* FileOutputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D6789C1160FA3900CC270E /* FileOutputStream.m */; }; - F83C1ABE11CA7C170001958F /* FileInputStreamFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D6789F1160FA4800CC270E /* FileInputStreamFactory.m */; }; - F83C1ABF11CA7C170001958F /* BooleanIO.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678A41160FA5F00CC270E /* BooleanIO.m */; }; - F83C1AC011CA7C170001958F /* EncryptedInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678A71160FA6A00CC270E /* EncryptedInputStream.m */; }; - F83C1AC111CA7C170001958F /* CommitFailedFile.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678AE1160FAD900CC270E /* CommitFailedFile.m */; }; - F83C1AC211CA7C170001958F /* Node.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678B71160FB2100CC270E /* Node.m */; }; - F83C1AC311CA7C170001958F /* FileAttributes.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D67CEA1161363A00CC270E /* FileAttributes.m */; }; - F83C1AC411CA7C170001958F /* XAttrSet.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D67CF11161366100CC270E /* XAttrSet.m */; }; - F83C1AC511CA7C170001958F /* FileACL.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D67D041161384100CC270E /* FileACL.m */; }; - F83C1AC611CA7C170001958F /* OSStatusDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D67D061161384100CC270E /* OSStatusDescription.m */; }; - F83C1AC711CA7C170001958F /* RestoreNode.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D67F6F1161443600CC270E /* RestoreNode.m */; }; - F83C1AC811CA7C170001958F /* arq_verify.m in Sources */ = {isa = PBXBuildFile; fileRef = F83C1A5F11CA7A6B0001958F /* arq_verify.m */; }; - F83C1AC911CA7C170001958F /* ArqVerifyCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = F83C1A6211CA7BD20001958F /* ArqVerifyCommand.m */; }; - F83C1ACB11CA7C170001958F /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 08FB779EFE84155DC02AAC07 /* Foundation.framework */; }; - F83C1ACC11CA7C170001958F /* libcrypto.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F805B8881160EB39007EC01E /* libcrypto.dylib */; }; - F83C1ACD11CA7C170001958F /* libicucore.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F805B88A1160EB39007EC01E /* libicucore.dylib */; }; - F83C1ACE11CA7C170001958F /* libssl.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F805B88E1160EB45007EC01E /* libssl.dylib */; }; - F83C1ACF11CA7C170001958F /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F805B8921160EB4E007EC01E /* SystemConfiguration.framework */; }; - F83C1AD011CA7C170001958F /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F805B8A01160EBAA007EC01E /* CoreFoundation.framework */; }; - F83C1AD111CA7C170001958F /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F805B8C11160EC41007EC01E /* Security.framework */; }; - F83C1AD211CA7C170001958F /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F805B8CD1160ECD7007EC01E /* CoreServices.framework */; }; + F829512419868345001DC91B /* HSLog.m in Sources */ = {isa = PBXBuildFile; fileRef = F829512319868345001DC91B /* HSLog.m */; }; + F829512919868394001DC91B /* NSError_extra.m in Sources */ = {isa = PBXBuildFile; fileRef = F829512819868394001DC91B /* NSError_extra.m */; }; + F8295157198683D5001DC91B /* agent.c in Sources */ = {isa = PBXBuildFile; fileRef = F8295140198683D5001DC91B /* agent.c */; settings = {COMPILER_FLAGS = "-w"; }; }; + F8295158198683D5001DC91B /* channel.c in Sources */ = {isa = PBXBuildFile; fileRef = F8295141198683D5001DC91B /* channel.c */; settings = {COMPILER_FLAGS = "-w"; }; }; + F8295159198683D5001DC91B /* comp.c in Sources */ = {isa = PBXBuildFile; fileRef = F8295142198683D5001DC91B /* comp.c */; settings = {COMPILER_FLAGS = "-w"; }; }; + F829515A198683D5001DC91B /* crypt.c in Sources */ = {isa = PBXBuildFile; fileRef = F8295143198683D5001DC91B /* crypt.c */; settings = {COMPILER_FLAGS = "-w"; }; }; + F829515B198683D5001DC91B /* global.c in Sources */ = {isa = PBXBuildFile; fileRef = F8295144198683D5001DC91B /* global.c */; settings = {COMPILER_FLAGS = "-w"; }; }; + F829515C198683D5001DC91B /* hostkey.c in Sources */ = {isa = PBXBuildFile; fileRef = F8295145198683D5001DC91B /* hostkey.c */; settings = {COMPILER_FLAGS = "-w"; }; }; + F829515D198683D5001DC91B /* keepalive.c in Sources */ = {isa = PBXBuildFile; fileRef = F8295146198683D5001DC91B /* keepalive.c */; settings = {COMPILER_FLAGS = "-w"; }; }; + F829515E198683D5001DC91B /* kex.c in Sources */ = {isa = PBXBuildFile; fileRef = F8295147198683D5001DC91B /* kex.c */; settings = {COMPILER_FLAGS = "-w"; }; }; + F829515F198683D5001DC91B /* knownhost.c in Sources */ = {isa = PBXBuildFile; fileRef = F8295148198683D5001DC91B /* knownhost.c */; settings = {COMPILER_FLAGS = "-w"; }; }; + F8295160198683D5001DC91B /* libgcrypt.c in Sources */ = {isa = PBXBuildFile; fileRef = F8295149198683D5001DC91B /* libgcrypt.c */; settings = {COMPILER_FLAGS = "-w"; }; }; + F8295161198683D5001DC91B /* mac.c in Sources */ = {isa = PBXBuildFile; fileRef = F829514A198683D5001DC91B /* mac.c */; settings = {COMPILER_FLAGS = "-w"; }; }; + F8295162198683D5001DC91B /* misc.c in Sources */ = {isa = PBXBuildFile; fileRef = F829514B198683D5001DC91B /* misc.c */; settings = {COMPILER_FLAGS = "-w"; }; }; + F8295163198683D5001DC91B /* openssl.c in Sources */ = {isa = PBXBuildFile; fileRef = F829514C198683D5001DC91B /* openssl.c */; settings = {COMPILER_FLAGS = "-w"; }; }; + F8295164198683D5001DC91B /* packet.c in Sources */ = {isa = PBXBuildFile; fileRef = F829514D198683D5001DC91B /* packet.c */; settings = {COMPILER_FLAGS = "-w"; }; }; + F8295165198683D5001DC91B /* pem.c in Sources */ = {isa = PBXBuildFile; fileRef = F829514E198683D5001DC91B /* pem.c */; settings = {COMPILER_FLAGS = "-w"; }; }; + F8295166198683D5001DC91B /* publickey.c in Sources */ = {isa = PBXBuildFile; fileRef = F829514F198683D5001DC91B /* publickey.c */; settings = {COMPILER_FLAGS = "-w"; }; }; + F8295167198683D5001DC91B /* scp.c in Sources */ = {isa = PBXBuildFile; fileRef = F8295150198683D5001DC91B /* scp.c */; settings = {COMPILER_FLAGS = "-w"; }; }; + F8295168198683D5001DC91B /* session.c in Sources */ = {isa = PBXBuildFile; fileRef = F8295151198683D5001DC91B /* session.c */; settings = {COMPILER_FLAGS = "-w"; }; }; + F8295169198683D5001DC91B /* sftp.c in Sources */ = {isa = PBXBuildFile; fileRef = F8295152198683D5001DC91B /* sftp.c */; settings = {COMPILER_FLAGS = "-w"; }; }; + F829516A198683D5001DC91B /* transport.c in Sources */ = {isa = PBXBuildFile; fileRef = F8295153198683D5001DC91B /* transport.c */; settings = {COMPILER_FLAGS = "-w"; }; }; + F829516B198683D5001DC91B /* userauth.c in Sources */ = {isa = PBXBuildFile; fileRef = F8295154198683D5001DC91B /* userauth.c */; settings = {COMPILER_FLAGS = "-w"; }; }; + F829516C198683D5001DC91B /* version.c in Sources */ = {isa = PBXBuildFile; fileRef = F8295155198683D5001DC91B /* version.c */; settings = {COMPILER_FLAGS = "-w"; }; }; + F829518E198683F9001DC91B /* LifecycleConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = F829516F198683F9001DC91B /* LifecycleConfiguration.m */; }; + F829518F198683F9001DC91B /* LocalS3Signer.m in Sources */ = {isa = PBXBuildFile; fileRef = F8295171198683F9001DC91B /* LocalS3Signer.m */; }; + F8295190198683F9001DC91B /* PathReceiver.m in Sources */ = {isa = PBXBuildFile; fileRef = F8295173198683F9001DC91B /* PathReceiver.m */; }; + F8295191198683F9001DC91B /* RemoteS3Signer.m in Sources */ = {isa = PBXBuildFile; fileRef = F8295175198683F9001DC91B /* RemoteS3Signer.m */; }; + F8295192198683F9001DC91B /* S3AuthorizationProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = F8295177198683F9001DC91B /* S3AuthorizationProvider.m */; }; + F8295193198683F9001DC91B /* S3DeleteReceiver.m in Sources */ = {isa = PBXBuildFile; fileRef = F8295179198683F9001DC91B /* S3DeleteReceiver.m */; }; + F8295194198683F9001DC91B /* S3ErrorResult.m in Sources */ = {isa = PBXBuildFile; fileRef = F829517B198683F9001DC91B /* S3ErrorResult.m */; }; + F8295195198683F9001DC91B /* S3Lister.m in Sources */ = {isa = PBXBuildFile; fileRef = F829517D198683F9001DC91B /* S3Lister.m */; }; + F8295196198683F9001DC91B /* S3MultiDeleteResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = F829517F198683F9001DC91B /* S3MultiDeleteResponse.m */; }; + F8295197198683F9001DC91B /* S3ObjectMetadata.m in Sources */ = {isa = PBXBuildFile; fileRef = F8295181198683F9001DC91B /* S3ObjectMetadata.m */; }; + F8295198198683F9001DC91B /* S3ObjectReceiver.m in Sources */ = {isa = PBXBuildFile; fileRef = F8295183198683F9001DC91B /* S3ObjectReceiver.m */; }; + F8295199198683F9001DC91B /* S3Owner.m in Sources */ = {isa = PBXBuildFile; fileRef = F8295185198683F9001DC91B /* S3Owner.m */; }; + F829519A198683F9001DC91B /* S3Request.m in Sources */ = {isa = PBXBuildFile; fileRef = F8295188198683F9001DC91B /* S3Request.m */; }; + F829519B198683F9001DC91B /* S3Service.m in Sources */ = {isa = PBXBuildFile; fileRef = F829518A198683F9001DC91B /* S3Service.m */; }; + F82951EC19868D90001DC91B /* NSData-Base64Extensions.m in Sources */ = {isa = PBXBuildFile; fileRef = F829519F19868D90001DC91B /* NSData-Base64Extensions.m */; }; + F82951ED19868D90001DC91B /* HTTPConnectionFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = F82951A419868D90001DC91B /* HTTPConnectionFactory.m */; }; + F82951EE19868D90001DC91B /* HTTPInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F82951A619868D90001DC91B /* HTTPInputStream.m */; }; + F82951EF19868D90001DC91B /* HTTPThrottle.m in Sources */ = {isa = PBXBuildFile; fileRef = F82951A819868D90001DC91B /* HTTPThrottle.m */; }; + F82951F119868D90001DC91B /* NSDictionary_HTTP.m in Sources */ = {isa = PBXBuildFile; fileRef = F82951AC19868D90001DC91B /* NSDictionary_HTTP.m */; }; + F82951F219868D90001DC91B /* RFC2616DateFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = F82951AE19868D90001DC91B /* RFC2616DateFormatter.m */; }; + F82951F519868D90001DC91B /* URLConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = F82951B419868D90001DC91B /* URLConnection.m */; }; + F829521319868DE6001DC91B /* RegexKitLite.m in Sources */ = {isa = PBXBuildFile; fileRef = F829521219868DE6001DC91B /* RegexKitLite.m */; }; + F829522019868E26001DC91B /* GoogleDrive.m in Sources */ = {isa = PBXBuildFile; fileRef = F829521719868E26001DC91B /* GoogleDrive.m */; }; + F829522119868E26001DC91B /* GoogleDriveErrorResult.m in Sources */ = {isa = PBXBuildFile; fileRef = F829521919868E26001DC91B /* GoogleDriveErrorResult.m */; }; + F829522219868E26001DC91B /* GoogleDriveFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = F829521B19868E26001DC91B /* GoogleDriveFactory.m */; }; + F829522319868E26001DC91B /* GoogleDriveRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = F829521D19868E26001DC91B /* GoogleDriveRequest.m */; }; + F829522419868E26001DC91B /* GoogleDriveFolderLister.m in Sources */ = {isa = PBXBuildFile; fileRef = F829521F19868E26001DC91B /* GoogleDriveFolderLister.m */; }; + F829523019868E59001DC91B /* NSObject+SBJSON.m in Sources */ = {isa = PBXBuildFile; fileRef = F829522719868E59001DC91B /* NSObject+SBJSON.m */; }; + F829523119868E59001DC91B /* NSString+SBJSON.m in Sources */ = {isa = PBXBuildFile; fileRef = F829522919868E59001DC91B /* NSString+SBJSON.m */; }; + F829523219868E59001DC91B /* SBJsonBase.m in Sources */ = {isa = PBXBuildFile; fileRef = F829522B19868E59001DC91B /* SBJsonBase.m */; }; + F829523319868E59001DC91B /* SBJsonParser.m in Sources */ = {isa = PBXBuildFile; fileRef = F829522D19868E59001DC91B /* SBJsonParser.m */; }; + F829523419868E59001DC91B /* SBJsonWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = F829522F19868E59001DC91B /* SBJsonWriter.m */; }; + F829523819868E83001DC91B /* AWSRegion.m in Sources */ = {isa = PBXBuildFile; fileRef = F829523719868E83001DC91B /* AWSRegion.m */; }; + F829523C19868EA4001DC91B /* NetMonitor.m in Sources */ = {isa = PBXBuildFile; fileRef = F829523B19868EA4001DC91B /* NetMonitor.m */; }; + F829524019868EC7001DC91B /* NSString_extra.m in Sources */ = {isa = PBXBuildFile; fileRef = F829523F19868EC7001DC91B /* NSString_extra.m */; }; + F829524319868ED6001DC91B /* NSData-InputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F829524219868ED6001DC91B /* NSData-InputStream.m */; }; + F829524719868F02001DC91B /* ISO8601Date.m in Sources */ = {isa = PBXBuildFile; fileRef = F829524619868F02001DC91B /* ISO8601Date.m */; }; + F829524A19868F0F001DC91B /* DataInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F829524919868F0F001DC91B /* DataInputStream.m */; }; + F829524D19868F1E001DC91B /* ChunkedInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F829524C19868F1E001DC91B /* ChunkedInputStream.m */; }; + F829525019868F31001DC91B /* InputStreams.m in Sources */ = {isa = PBXBuildFile; fileRef = F829524F19868F31001DC91B /* InputStreams.m */; }; + F829525319868F3C001DC91B /* BufferedInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F829525219868F3C001DC91B /* BufferedInputStream.m */; }; + F829DBFC19868FCA00D637E0 /* RFC822.m in Sources */ = {isa = PBXBuildFile; fileRef = F829DBFB19868FCA00D637E0 /* RFC822.m */; }; + F829DBFF1986901300D637E0 /* Streams.m in Sources */ = {isa = PBXBuildFile; fileRef = F829DBFE1986901300D637E0 /* Streams.m */; }; + F829DC10198691CB00D637E0 /* BooleanIO.m in Sources */ = {isa = PBXBuildFile; fileRef = F829DC03198691CB00D637E0 /* BooleanIO.m */; }; + F829DC11198691CB00D637E0 /* DataIO.m in Sources */ = {isa = PBXBuildFile; fileRef = F829DC05198691CB00D637E0 /* DataIO.m */; }; + F829DC12198691CB00D637E0 /* DateIO.m in Sources */ = {isa = PBXBuildFile; fileRef = F829DC07198691CB00D637E0 /* DateIO.m */; }; + F829DC13198691CB00D637E0 /* DoubleIO.m in Sources */ = {isa = PBXBuildFile; fileRef = F829DC09198691CB00D637E0 /* DoubleIO.m */; }; + F829DC14198691CB00D637E0 /* IntegerIO.m in Sources */ = {isa = PBXBuildFile; fileRef = F829DC0B198691CB00D637E0 /* IntegerIO.m */; }; + F829DC15198691CB00D637E0 /* NSErrorIO.m in Sources */ = {isa = PBXBuildFile; fileRef = F829DC0D198691CB00D637E0 /* NSErrorIO.m */; }; + F829DC16198691CB00D637E0 /* StringIO.m in Sources */ = {isa = PBXBuildFile; fileRef = F829DC0F198691CB00D637E0 /* StringIO.m */; }; + F829DC19198691D900D637E0 /* BufferedOutputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F829DC18198691D900D637E0 /* BufferedOutputStream.m */; }; + F829DC1C1986921E00D637E0 /* MD5Hash.m in Sources */ = {isa = PBXBuildFile; fileRef = F829DC1B1986921E00D637E0 /* MD5Hash.m */; }; + F829DC1F1986923100D637E0 /* Sysctl.m in Sources */ = {isa = PBXBuildFile; fileRef = F829DC1E1986923100D637E0 /* Sysctl.m */; }; + F829DC241986924100D637E0 /* FDInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F829DC211986924100D637E0 /* FDInputStream.m */; }; + F829DC251986924100D637E0 /* FDOutputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F829DC231986924100D637E0 /* FDOutputStream.m */; }; + F829DC281986924E00D637E0 /* DataOutputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F829DC271986924E00D637E0 /* DataOutputStream.m */; }; + F829DC2D1986927000D637E0 /* FileInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F829DC2A1986927000D637E0 /* FileInputStream.m */; }; + F829DC2E1986927000D637E0 /* FileOutputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F829DC2C1986927000D637E0 /* FileOutputStream.m */; }; + F829DC311986930300D637E0 /* OpenSSL.m in Sources */ = {isa = PBXBuildFile; fileRef = F829DC301986930300D637E0 /* OpenSSL.m */; }; + F829DC35198697EB00D637E0 /* Target.m in Sources */ = {isa = PBXBuildFile; fileRef = F829DC34198697EB00D637E0 /* Target.m */; }; F83C1AE311CA7C7C0001958F /* arq_restore.m in Sources */ = {isa = PBXBuildFile; fileRef = 08FB7796FE84155DC02AAC07 /* arq_restore.m */; }; - F83C1D1D11CA95AF0001958F /* BucketVerifier.m in Sources */ = {isa = PBXBuildFile; fileRef = F83C1D0911CA929D0001958F /* BucketVerifier.m */; }; - F84166D815E2782600B6ECED /* HTTPConnectionFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = F84166D515E2782600B6ECED /* HTTPConnectionFactory.m */; }; - F84166D915E2782600B6ECED /* HTTPTimeoutSetting.m in Sources */ = {isa = PBXBuildFile; fileRef = F84166D715E2782600B6ECED /* HTTPTimeoutSetting.m */; }; - F84166DA15E2782600B6ECED /* HTTPConnectionFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = F84166D515E2782600B6ECED /* HTTPConnectionFactory.m */; }; - F84166DB15E2782600B6ECED /* HTTPTimeoutSetting.m in Sources */ = {isa = PBXBuildFile; fileRef = F84166D715E2782600B6ECED /* HTTPTimeoutSetting.m */; }; - F84166E215E2785100B6ECED /* CFHTTPConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = F84166DF15E2785100B6ECED /* CFHTTPConnection.m */; }; - F84166E315E2785100B6ECED /* CFHTTPInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F84166E115E2785100B6ECED /* CFHTTPInputStream.m */; }; - F84166E415E2785100B6ECED /* CFHTTPConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = F84166DF15E2785100B6ECED /* CFHTTPConnection.m */; }; - F84166E515E2785100B6ECED /* CFHTTPInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F84166E115E2785100B6ECED /* CFHTTPInputStream.m */; }; - F84166ED15E278BC00B6ECED /* CFNetwork.m in Sources */ = {isa = PBXBuildFile; fileRef = F84166EC15E278BC00B6ECED /* CFNetwork.m */; }; - F84166EE15E278BC00B6ECED /* CFNetwork.m in Sources */ = {isa = PBXBuildFile; fileRef = F84166EC15E278BC00B6ECED /* CFNetwork.m */; }; - F84166F515E278F500B6ECED /* S3Owner.m in Sources */ = {isa = PBXBuildFile; fileRef = F84166F215E278F500B6ECED /* S3Owner.m */; }; - F84166F615E278F500B6ECED /* S3Region.m in Sources */ = {isa = PBXBuildFile; fileRef = F84166F415E278F500B6ECED /* S3Region.m */; }; - F84166F715E278F500B6ECED /* S3Owner.m in Sources */ = {isa = PBXBuildFile; fileRef = F84166F215E278F500B6ECED /* S3Owner.m */; }; - F84166F815E278F500B6ECED /* S3Region.m in Sources */ = {isa = PBXBuildFile; fileRef = F84166F415E278F500B6ECED /* S3Region.m */; }; - F84166FD15E2792500B6ECED /* NetMonitor.m in Sources */ = {isa = PBXBuildFile; fileRef = F84166FC15E2792500B6ECED /* NetMonitor.m */; }; - F84166FE15E2792500B6ECED /* NetMonitor.m in Sources */ = {isa = PBXBuildFile; fileRef = F84166FC15E2792500B6ECED /* NetMonitor.m */; }; - F841670515E279BB00B6ECED /* Sysctl.m in Sources */ = {isa = PBXBuildFile; fileRef = F841670415E279BB00B6ECED /* Sysctl.m */; }; - F841670615E279BB00B6ECED /* Sysctl.m in Sources */ = {isa = PBXBuildFile; fileRef = F841670415E279BB00B6ECED /* Sysctl.m */; }; - F841670B15E279DE00B6ECED /* DNS_SDErrors.m in Sources */ = {isa = PBXBuildFile; fileRef = F841670A15E279DE00B6ECED /* DNS_SDErrors.m */; }; - F841670C15E279DE00B6ECED /* DNS_SDErrors.m in Sources */ = {isa = PBXBuildFile; fileRef = F841670A15E279DE00B6ECED /* DNS_SDErrors.m */; }; - F841672615E27A5200B6ECED /* RemoteS3Signer.m in Sources */ = {isa = PBXBuildFile; fileRef = F841672515E27A5200B6ECED /* RemoteS3Signer.m */; }; - F841672715E27A5200B6ECED /* RemoteS3Signer.m in Sources */ = {isa = PBXBuildFile; fileRef = F841672515E27A5200B6ECED /* RemoteS3Signer.m */; }; - F8987235121EB68900F07D76 /* BinaryPListReader.m in Sources */ = {isa = PBXBuildFile; fileRef = F8987232121EB68900F07D76 /* BinaryPListReader.m */; }; - F8987236121EB68900F07D76 /* BinaryPListWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = F8987234121EB68900F07D76 /* BinaryPListWriter.m */; }; - F8987560121EBD9600F07D76 /* BufferedInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F898755F121EBD9600F07D76 /* BufferedInputStream.m */; }; - F8987588121EBDF300F07D76 /* BinaryPListReader.m in Sources */ = {isa = PBXBuildFile; fileRef = F8987232121EB68900F07D76 /* BinaryPListReader.m */; }; - F8987589121EBDF400F07D76 /* BinaryPListWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = F8987234121EB68900F07D76 /* BinaryPListWriter.m */; }; - F898758A121EBDFA00F07D76 /* BufferedInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F898755F121EBD9600F07D76 /* BufferedInputStream.m */; }; - F898758C121EBE0600F07D76 /* UserAndComputer.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F4D67D121DA542002D09C1 /* UserAndComputer.m */; }; - F89A1EEC13FAC4E30071D321 /* URLConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = F89A1EEB13FAC4E30071D321 /* URLConnection.m */; }; - F89A1EED13FAC4E30071D321 /* URLConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = F89A1EEB13FAC4E30071D321 /* URLConnection.m */; }; - F89A1EFB13FAC5970071D321 /* EncryptedInputStreamFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = F89A1EFA13FAC5970071D321 /* EncryptedInputStreamFactory.m */; }; - F89A1EFC13FAC5970071D321 /* EncryptedInputStreamFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = F89A1EFA13FAC5970071D321 /* EncryptedInputStreamFactory.m */; }; - F89A1F2B13FAC6700071D321 /* UserLibrary.m in Sources */ = {isa = PBXBuildFile; fileRef = F89A1F2A13FAC6700071D321 /* UserLibrary.m */; }; - F89A1F2C13FAC6700071D321 /* UserLibrary.m in Sources */ = {isa = PBXBuildFile; fileRef = F89A1F2A13FAC6700071D321 /* UserLibrary.m */; }; - F89A1F3E13FAC6820071D321 /* UserLibrary_Arq.m in Sources */ = {isa = PBXBuildFile; fileRef = F89A1F3D13FAC6820071D321 /* UserLibrary_Arq.m */; }; - F89A1F3F13FAC6820071D321 /* UserLibrary_Arq.m in Sources */ = {isa = PBXBuildFile; fileRef = F89A1F3D13FAC6820071D321 /* UserLibrary_Arq.m */; }; - F89A1F4513FAC6C40071D321 /* BlobKey.m in Sources */ = {isa = PBXBuildFile; fileRef = F89A1F4413FAC6C40071D321 /* BlobKey.m */; }; - F89A1F4613FAC6C40071D321 /* BlobKey.m in Sources */ = {isa = PBXBuildFile; fileRef = F89A1F4413FAC6C40071D321 /* BlobKey.m */; }; - F89A1F4D13FAC73D0071D321 /* Computer.m in Sources */ = {isa = PBXBuildFile; fileRef = F89A1F4C13FAC73D0071D321 /* Computer.m */; }; - F89A1F4E13FAC73D0071D321 /* Computer.m in Sources */ = {isa = PBXBuildFile; fileRef = F89A1F4C13FAC73D0071D321 /* Computer.m */; }; - F89A1F5313FAC78F0071D321 /* BufferedOutputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F89A1F5213FAC78F0071D321 /* BufferedOutputStream.m */; }; - F89A1F5413FAC78F0071D321 /* BufferedOutputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F89A1F5213FAC78F0071D321 /* BufferedOutputStream.m */; }; - F89A1F5B13FAC7D50071D321 /* Encryption.m in Sources */ = {isa = PBXBuildFile; fileRef = F89A1F5A13FAC7D50071D321 /* Encryption.m */; }; - F89A1F5C13FAC7D50071D321 /* Encryption.m in Sources */ = {isa = PBXBuildFile; fileRef = F89A1F5A13FAC7D50071D321 /* Encryption.m */; }; - F89A1F5F13FAC8020071D321 /* NSObject_extra.m in Sources */ = {isa = PBXBuildFile; fileRef = F89A1F5E13FAC8020071D321 /* NSObject_extra.m */; }; - F89A1F6013FAC8020071D321 /* NSObject_extra.m in Sources */ = {isa = PBXBuildFile; fileRef = F89A1F5E13FAC8020071D321 /* NSObject_extra.m */; }; - F89A1F6313FAC8270071D321 /* CryptoKey.m in Sources */ = {isa = PBXBuildFile; fileRef = F89A1F6213FAC8270071D321 /* CryptoKey.m */; }; - F89A1F6413FAC8270071D321 /* CryptoKey.m in Sources */ = {isa = PBXBuildFile; fileRef = F89A1F6213FAC8270071D321 /* CryptoKey.m */; }; - F89A1F6713FAC83E0071D321 /* GunzipInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F89A1F6613FAC83E0071D321 /* GunzipInputStream.m */; }; - F89A1F6813FAC83E0071D321 /* GunzipInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F89A1F6613FAC83E0071D321 /* GunzipInputStream.m */; }; - F89A1F7713FAC8930071D321 /* NSData-GZip.m in Sources */ = {isa = PBXBuildFile; fileRef = F89A1F7613FAC8930071D321 /* NSData-GZip.m */; }; - F89A1F7813FAC8930071D321 /* NSData-GZip.m in Sources */ = {isa = PBXBuildFile; fileRef = F89A1F7613FAC8930071D321 /* NSData-GZip.m */; }; - F89A1FD213FAD6BE0071D321 /* HSLog.m in Sources */ = {isa = PBXBuildFile; fileRef = F89A1FD113FAD6BE0071D321 /* HSLog.m */; }; - F89A1FD313FAD6BE0071D321 /* HSLog.m in Sources */ = {isa = PBXBuildFile; fileRef = F89A1FD113FAD6BE0071D321 /* HSLog.m */; }; - F89A200B13FADAD70071D321 /* ArqSalt.m in Sources */ = {isa = PBXBuildFile; fileRef = F89A200A13FADAD70071D321 /* ArqSalt.m */; }; - F89A200C13FADAD70071D321 /* ArqSalt.m in Sources */ = {isa = PBXBuildFile; fileRef = F89A200A13FADAD70071D321 /* ArqSalt.m */; }; + F83F9B2D1983303F007CBFB4 /* ArqRestoreCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = F83F9B2C1983303F007CBFB4 /* ArqRestoreCommand.m */; }; F89A204613FAE29E0071D321 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F89A204513FAE29E0071D321 /* libz.dylib */; }; - F89A205313FAE2DA0071D321 /* LocalS3Signer.m in Sources */ = {isa = PBXBuildFile; fileRef = F89A205213FAE2DA0071D321 /* LocalS3Signer.m */; }; - F89A205413FAE2DA0071D321 /* LocalS3Signer.m in Sources */ = {isa = PBXBuildFile; fileRef = F89A205213FAE2DA0071D321 /* LocalS3Signer.m */; }; - F89A205913FAE3010071D321 /* DataOutputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F89A205813FAE3010071D321 /* DataOutputStream.m */; }; - F89A205A13FAE3010071D321 /* DataOutputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F89A205813FAE3010071D321 /* DataOutputStream.m */; }; - F89A20A513FAE5300071D321 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F89A20A413FAE5300071D321 /* libz.dylib */; }; - F89A20CF13FAE7170071D321 /* NSError_extra.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F4D19C121D77E1002D09C1 /* NSError_extra.m */; }; - F8D678001160F26A00CC270E /* Restorer.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D677FF1160F26A00CC270E /* Restorer.m */; }; - F8D6781D1160F4FD00CC270E /* SHA1Hash.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D6781C1160F4FD00CC270E /* SHA1Hash.m */; }; - F8D6783C1160F70100CC270E /* PackIndexEntry.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D6783B1160F70100CC270E /* PackIndexEntry.m */; }; - F8D678451160F74A00CC270E /* DiskPack.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678421160F74A00CC270E /* DiskPack.m */; }; - F8D678461160F74A00CC270E /* DiskPackIndex.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678441160F74A00CC270E /* DiskPackIndex.m */; }; - F8D6785F1160F7CF00CC270E /* Commit.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D6785C1160F7CF00CC270E /* Commit.m */; }; - F8D678601160F7CF00CC270E /* Tree.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D6785E1160F7CF00CC270E /* Tree.m */; }; - F8D678651160F7FE00CC270E /* NSData-Encrypt.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678641160F7FE00CC270E /* NSData-Encrypt.m */; }; - F8D6786A1160F81100CC270E /* OpenSSL.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678691160F81100CC270E /* OpenSSL.m */; }; - F8D6786F1160F84600CC270E /* DecryptedInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D6786E1160F84600CC270E /* DecryptedInputStream.m */; }; - F8D678741160F85D00CC270E /* CryptInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678731160F85D00CC270E /* CryptInputStream.m */; }; - F8D678771160F86E00CC270E /* FileInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678761160F86E00CC270E /* FileInputStream.m */; }; - F8D678821160F8A000CC270E /* DataIO.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678791160F8A000CC270E /* DataIO.m */; }; - F8D678831160F8A000CC270E /* DateIO.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D6787B1160F8A000CC270E /* DateIO.m */; }; - F8D678841160F8A000CC270E /* DoubleIO.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D6787D1160F8A000CC270E /* DoubleIO.m */; }; - F8D678851160F8A000CC270E /* IntegerIO.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D6787F1160F8A000CC270E /* IntegerIO.m */; }; - F8D678861160F8A000CC270E /* StringIO.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678811160F8A000CC270E /* StringIO.m */; }; - F8D678891160F8CD00CC270E /* NSString_extra.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678881160F8CD00CC270E /* NSString_extra.m */; }; - F8D6788C1160F8E500CC270E /* BinarySHA1.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D6788B1160F8E500CC270E /* BinarySHA1.m */; }; - F8D6789A1160FA2A00CC270E /* NSFileManager_extra.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678991160FA2A00CC270E /* NSFileManager_extra.m */; }; - F8D6789D1160FA3900CC270E /* FileOutputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D6789C1160FA3900CC270E /* FileOutputStream.m */; }; - F8D678A01160FA4800CC270E /* FileInputStreamFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D6789F1160FA4800CC270E /* FileInputStreamFactory.m */; }; - F8D678A51160FA5F00CC270E /* BooleanIO.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678A41160FA5F00CC270E /* BooleanIO.m */; }; - F8D678A81160FA6A00CC270E /* EncryptedInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678A71160FA6A00CC270E /* EncryptedInputStream.m */; }; - F8D678AF1160FAD900CC270E /* CommitFailedFile.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678AE1160FAD900CC270E /* CommitFailedFile.m */; }; - F8D678B81160FB2100CC270E /* Node.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D678B71160FB2100CC270E /* Node.m */; }; - F8D67CEB1161363A00CC270E /* FileAttributes.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D67CEA1161363A00CC270E /* FileAttributes.m */; }; - F8D67CF21161366100CC270E /* XAttrSet.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D67CF11161366100CC270E /* XAttrSet.m */; }; - F8D67D071161384100CC270E /* FileACL.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D67D041161384100CC270E /* FileACL.m */; }; - F8D67D081161384100CC270E /* OSStatusDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D67D061161384100CC270E /* OSStatusDescription.m */; }; - F8D67F701161443600CC270E /* RestoreNode.m in Sources */ = {isa = PBXBuildFile; fileRef = F8D67F6F1161443600CC270E /* RestoreNode.m */; }; - F8F4D19D121D77E1002D09C1 /* NSError_extra.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F4D19C121D77E1002D09C1 /* NSError_extra.m */; }; - F8F4D1A0121D78AD002D09C1 /* MonitoredInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F4D19F121D78AD002D09C1 /* MonitoredInputStream.m */; }; - F8F4D1B0121D7990002D09C1 /* ArqPackSet.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F4D1AD121D7990002D09C1 /* ArqPackSet.m */; }; - F8F4D1B1121D7990002D09C1 /* ArqRepo.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F4D1AF121D7990002D09C1 /* ArqRepo.m */; }; - F8F4D1C3121D79AC002D09C1 /* ArqFark.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F4D1C2121D79AC002D09C1 /* ArqFark.m */; }; - F8F4D1E2121D7BC3002D09C1 /* AppKeychain.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F4D1E1121D7BC3002D09C1 /* AppKeychain.m */; }; - F8F4D1E7121D7DA2002D09C1 /* FarkPath.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F4D1E6121D7DA2002D09C1 /* FarkPath.m */; }; - F8F4D1FE121D8409002D09C1 /* PackIndexWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F4D1FD121D8409002D09C1 /* PackIndexWriter.m */; }; - F8F4D59A121D9FA7002D09C1 /* MonitoredInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F4D19F121D78AD002D09C1 /* MonitoredInputStream.m */; }; - F8F4D59B121D9FAD002D09C1 /* ArqFark.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F4D1C2121D79AC002D09C1 /* ArqFark.m */; }; - F8F4D59C121D9FAE002D09C1 /* ArqPackSet.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F4D1AD121D7990002D09C1 /* ArqPackSet.m */; }; - F8F4D59D121D9FAF002D09C1 /* ArqRepo.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F4D1AF121D7990002D09C1 /* ArqRepo.m */; }; - F8F4D5A1121D9FC2002D09C1 /* AppKeychain.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F4D1E1121D7BC3002D09C1 /* AppKeychain.m */; }; - F8F4D5A2121D9FC9002D09C1 /* FarkPath.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F4D1E6121D7DA2002D09C1 /* FarkPath.m */; }; - F8F4D5A3121D9FCF002D09C1 /* PackIndexWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F4D1FD121D8409002D09C1 /* PackIndexWriter.m */; }; - F8F4D67E121DA542002D09C1 /* UserAndComputer.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F4D67D121DA542002D09C1 /* UserAndComputer.m */; }; + F8F2D8681986B58000997A15 /* BackupSet.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D8671986B58000997A15 /* BackupSet.m */; }; + F8F2D86D1986B5CC00997A15 /* S3TargetConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D86A1986B5CC00997A15 /* S3TargetConnection.m */; }; + F8F2D86E1986B5CC00997A15 /* SFTPTargetConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D86C1986B5CC00997A15 /* SFTPTargetConnection.m */; }; + F8F2D8711986B5D900997A15 /* GoogleDriveTargetConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D8701986B5D900997A15 /* GoogleDriveTargetConnection.m */; }; + F8F2D87A1986B61800997A15 /* S3RemoteFS.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D8751986B61800997A15 /* S3RemoteFS.m */; }; + F8F2D87B1986B61800997A15 /* SFTPRemoteFS.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D8771986B61800997A15 /* SFTPRemoteFS.m */; }; + F8F2D87C1986B61800997A15 /* GoogleDriveRemoteFS.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D8791986B61800997A15 /* GoogleDriveRemoteFS.m */; }; + F8F2D8821986B63400997A15 /* BaseTargetConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D8811986B63400997A15 /* BaseTargetConnection.m */; }; + F8F2D8851986B64D00997A15 /* GlacierAuthorizationProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D8841986B64D00997A15 /* GlacierAuthorizationProvider.m */; }; + F8F2D8921986B66000997A15 /* GlacierJob.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D8871986B66000997A15 /* GlacierJob.m */; }; + F8F2D8931986B66000997A15 /* GlacierJobLister.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D8891986B66000997A15 /* GlacierJobLister.m */; }; + F8F2D8941986B66000997A15 /* GlacierRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D88B1986B66000997A15 /* GlacierRequest.m */; }; + F8F2D8951986B66000997A15 /* GlacierResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D88D1986B66000997A15 /* GlacierResponse.m */; }; + F8F2D8961986B66000997A15 /* GlacierService.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D88F1986B66000997A15 /* GlacierService.m */; }; + F8F2D8971986B66000997A15 /* LocalGlacierSigner.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D8911986B66000997A15 /* LocalGlacierSigner.m */; }; + F8F2D8A31986B67500997A15 /* NSError_Glacier.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D8991986B67500997A15 /* NSError_Glacier.m */; }; + F8F2D8A41986B67500997A15 /* SHA256TreeHash.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D89B1986B67500997A15 /* SHA256TreeHash.m */; }; + F8F2D8A51986B67500997A15 /* Vault.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D89D1986B67500997A15 /* Vault.m */; }; + F8F2D8A61986B67500997A15 /* VaultDeleter.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D89F1986B67500997A15 /* VaultDeleter.m */; }; + F8F2D8A71986B67500997A15 /* VaultLister.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D8A21986B67500997A15 /* VaultLister.m */; }; + F8F2D8AA1986B68800997A15 /* NSXMLNode_extra.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D8A91986B68800997A15 /* NSXMLNode_extra.m */; }; + F8F2D8AD1986B6A300997A15 /* SHA256Hash.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D8AC1986B6A300997A15 /* SHA256Hash.m */; }; + F8F2D8B81986B6E000997A15 /* CreateTopicResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D8B11986B6E000997A15 /* CreateTopicResponse.m */; }; + F8F2D8B91986B6E000997A15 /* ListTopicsResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D8B31986B6E000997A15 /* ListTopicsResponse.m */; }; + F8F2D8BA1986B6E000997A15 /* SNS.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D8B51986B6E000997A15 /* SNS.m */; }; + F8F2D8BB1986B6E000997A15 /* SubscribeResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D8B71986B6E000997A15 /* SubscribeResponse.m */; }; + F8F2D8C81986B6E900997A15 /* CreateQueueResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D8BD1986B6E900997A15 /* CreateQueueResponse.m */; }; + F8F2D8C91986B6E900997A15 /* GetQueueAttributesResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D8BF1986B6E900997A15 /* GetQueueAttributesResponse.m */; }; + F8F2D8CA1986B6E900997A15 /* ListQueuesResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D8C11986B6E900997A15 /* ListQueuesResponse.m */; }; + F8F2D8CB1986B6E900997A15 /* ReceiveMessageResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D8C31986B6E900997A15 /* ReceiveMessageResponse.m */; }; + F8F2D8CC1986B6E900997A15 /* SQS.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D8C51986B6E900997A15 /* SQS.m */; }; + F8F2D8CD1986B6E900997A15 /* SQSMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D8C71986B6E900997A15 /* SQSMessage.m */; }; + F8F2D8D01986B6FD00997A15 /* SignatureV2Provider.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D8CF1986B6FD00997A15 /* SignatureV2Provider.m */; }; + F8F2D8D71986B70300997A15 /* AWSQueryError.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D8D21986B70300997A15 /* AWSQueryError.m */; }; + F8F2D8D81986B70300997A15 /* AWSQueryRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D8D41986B70300997A15 /* AWSQueryRequest.m */; }; + F8F2D8D91986B70300997A15 /* AWSQueryResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D8D61986B70300997A15 /* AWSQueryResponse.m */; }; + F8F2D8DD1986B72900997A15 /* GlacierAuthorization.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D8DC1986B72900997A15 /* GlacierAuthorization.m */; }; + F8F2D8E01986B74400997A15 /* UserAndComputer.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D8DF1986B74400997A15 /* UserAndComputer.m */; }; + F8F2D8E31986B75C00997A15 /* Computer.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D8E21986B75C00997A15 /* Computer.m */; }; + F8F2D8FD1986B78600997A15 /* ArrayNode.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D8E61986B78600997A15 /* ArrayNode.m */; }; + F8F2D8FE1986B78600997A15 /* BinaryPListReader.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D8E81986B78600997A15 /* BinaryPListReader.m */; }; + F8F2D8FF1986B78600997A15 /* BinaryPListWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D8EA1986B78600997A15 /* BinaryPListWriter.m */; }; + F8F2D9001986B78600997A15 /* BooleanNode.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D8EC1986B78600997A15 /* BooleanNode.m */; }; + F8F2D9011986B78600997A15 /* DictNode.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D8EE1986B78600997A15 /* DictNode.m */; }; + F8F2D9021986B78600997A15 /* IntegerNode.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D8F01986B78600997A15 /* IntegerNode.m */; }; + F8F2D9031986B78600997A15 /* RealNode.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D8F41986B78600997A15 /* RealNode.m */; }; + F8F2D9041986B78600997A15 /* StringNode.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D8F61986B78600997A15 /* StringNode.m */; }; + F8F2D9051986B78600997A15 /* XMLPlistParser.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D8F81986B78600997A15 /* XMLPlistParser.m */; }; + F8F2D9061986B78600997A15 /* XMLPListReader.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D8FA1986B78600997A15 /* XMLPListReader.m */; }; + F8F2D9071986B78600997A15 /* XMLPListWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D8FC1986B78600997A15 /* XMLPListWriter.m */; }; + F8F2D90D1986B7AF00997A15 /* NSObject_extra.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D90C1986B7AF00997A15 /* NSObject_extra.m */; }; + F8F2D9121986B80800997A15 /* CryptoKey.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D90F1986B80800997A15 /* CryptoKey.m */; }; + F8F2D9131986B80800997A15 /* OpenSSLCryptoKey.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D9111986B80800997A15 /* OpenSSLCryptoKey.m */; }; + F8F2D9171986B92100997A15 /* SFTPServer.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D9161986B92100997A15 /* SFTPServer.m */; }; + F8F2D9251986B98600997A15 /* BlobKey.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D9241986B98600997A15 /* BlobKey.m */; }; + F8F2D92A1986B9DF00997A15 /* SHA1Hash.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D9291986B9DF00997A15 /* SHA1Hash.m */; }; + F8F2D92E1986BA1400997A15 /* Commit.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D92D1986BA1400997A15 /* Commit.m */; }; + F8F2D9311986BA2200997A15 /* CommitFailedFile.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D9301986BA2200997A15 /* CommitFailedFile.m */; }; + F8F2D9341986BA2B00997A15 /* ArqSalt.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D9331986BA2B00997A15 /* ArqSalt.m */; }; + F8F2D9371986BA4900997A15 /* NSFileManager_extra.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D9361986BA4900997A15 /* NSFileManager_extra.m */; }; + F8F2D93A1986BA6900997A15 /* UserLibrary_Arq.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D9391986BA6900997A15 /* UserLibrary_Arq.m */; }; + F8F2D93D1986BA7900997A15 /* UserLibrary.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D93C1986BA7900997A15 /* UserLibrary.m */; }; + F8F2D9441986BA9B00997A15 /* Bucket.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D93F1986BA9B00997A15 /* Bucket.m */; }; + F8F2D9451986BA9B00997A15 /* BucketExclude.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D9411986BA9B00997A15 /* BucketExclude.m */; }; + F8F2D9461986BA9B00997A15 /* BucketExcludeSet.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D9431986BA9B00997A15 /* BucketExcludeSet.m */; }; + F8F2D9491986BAD200997A15 /* Repo.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D9481986BAD200997A15 /* Repo.m */; }; + F8F2D94C1986BB4900997A15 /* NSString_slashed.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D94B1986BB4900997A15 /* NSString_slashed.m */; }; + F8F2D94F1986BD1600997A15 /* TargetSchedule.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D94E1986BD1600997A15 /* TargetSchedule.m */; }; + F8F2D9531986BE2A00997A15 /* FarkImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D9521986BE2A00997A15 /* FarkImpl.m */; }; + F8F2D9561986BE3C00997A15 /* PackId.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D9551986BE3C00997A15 /* PackId.m */; }; + F8F2D95B1986BE4E00997A15 /* PackIndex.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D9581986BE4E00997A15 /* PackIndex.m */; }; + F8F2D95C1986BE4E00997A15 /* PackIndexEntry.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D95A1986BE4E00997A15 /* PackIndexEntry.m */; }; + F8F2D9611986BE6100997A15 /* Node.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D95E1986BE6100997A15 /* Node.m */; }; + F8F2D9621986BE6100997A15 /* Tree.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D9601986BE6100997A15 /* Tree.m */; }; + F8F2D9651986BE7600997A15 /* NSData-GZip.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D9641986BE7600997A15 /* NSData-GZip.m */; }; + F8F2D9681986BEA300997A15 /* BlobKeyIO.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D9671986BEA300997A15 /* BlobKeyIO.m */; }; + F8F2D96B1986BF5100997A15 /* GunzipInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D96A1986BF5100997A15 /* GunzipInputStream.m */; }; + F8F2D96E1986BF6800997A15 /* PackSet.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D96D1986BF6800997A15 /* PackSet.m */; }; + F8F2D9701986C09300997A15 /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8F2D96F1986C09300997A15 /* IOKit.framework */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -255,15 +193,6 @@ ); runOnlyForDeploymentPostprocessing = 1; }; - F83C1AD311CA7C170001958F /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 8; - dstPath = /usr/share/man/man1/; - dstSubfolderSpec = 0; - files = ( - ); - runOnlyForDeploymentPostprocessing = 1; - }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ @@ -271,87 +200,6 @@ 08FB779EFE84155DC02AAC07 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; 32A70AAB03705E1F00C91783 /* arq_restore_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = arq_restore_Prefix.pch; sourceTree = ""; }; 8DD76FA10486AA7600D96B5E /* arq_restore */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = arq_restore; sourceTree = BUILT_PRODUCTS_DIR; }; - F805B54B1160D3E6007EC01E /* ArqRestoreCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ArqRestoreCommand.h; sourceTree = ""; }; - F805B54C1160D3E6007EC01E /* ArqRestoreCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ArqRestoreCommand.m; sourceTree = ""; }; - F805B7411160DCFE007EC01E /* ArrayNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ArrayNode.h; sourceTree = ""; }; - F805B7421160DCFE007EC01E /* ArrayNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ArrayNode.m; sourceTree = ""; }; - F805B7431160DCFE007EC01E /* BooleanNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BooleanNode.h; sourceTree = ""; }; - F805B7441160DCFE007EC01E /* BooleanNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BooleanNode.m; sourceTree = ""; }; - F805B7451160DCFE007EC01E /* DictNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DictNode.h; sourceTree = ""; }; - F805B7461160DCFE007EC01E /* DictNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DictNode.m; sourceTree = ""; }; - F805B7471160DCFE007EC01E /* IntegerNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IntegerNode.h; sourceTree = ""; }; - F805B7481160DCFE007EC01E /* IntegerNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IntegerNode.m; sourceTree = ""; }; - F805B7491160DCFE007EC01E /* PListNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PListNode.h; sourceTree = ""; }; - F805B74A1160DCFE007EC01E /* PListNodeType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PListNodeType.h; sourceTree = ""; }; - F805B74B1160DCFE007EC01E /* RealNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RealNode.h; sourceTree = ""; }; - F805B74C1160DCFE007EC01E /* RealNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RealNode.m; sourceTree = ""; }; - F805B74D1160DCFE007EC01E /* StringNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StringNode.h; sourceTree = ""; }; - F805B74E1160DCFE007EC01E /* StringNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StringNode.m; sourceTree = ""; }; - F805B74F1160DCFE007EC01E /* XMLPListReader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMLPListReader.h; sourceTree = ""; }; - F805B7501160DCFE007EC01E /* XMLPListReader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XMLPListReader.m; sourceTree = ""; }; - F805B7511160DCFE007EC01E /* XMLPListWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMLPListWriter.h; sourceTree = ""; }; - F805B7521160DCFE007EC01E /* XMLPListWriter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XMLPListWriter.m; sourceTree = ""; }; - F805B76A1160DD60007EC01E /* NSError_S3.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSError_S3.h; sourceTree = ""; }; - F805B76B1160DD60007EC01E /* NSError_S3.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSError_S3.m; sourceTree = ""; }; - F805B76C1160DD60007EC01E /* PathReceiver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PathReceiver.h; sourceTree = ""; }; - F805B76D1160DD60007EC01E /* PathReceiver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PathReceiver.m; sourceTree = ""; }; - F805B7701160DD60007EC01E /* S3AuthorizationProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S3AuthorizationProvider.h; sourceTree = ""; }; - F805B7711160DD60007EC01E /* S3AuthorizationProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = S3AuthorizationProvider.m; sourceTree = ""; }; - F805B7721160DD60007EC01E /* S3Lister.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S3Lister.h; sourceTree = ""; }; - F805B7731160DD60007EC01E /* S3Lister.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = S3Lister.m; sourceTree = ""; }; - F805B7741160DD60007EC01E /* S3ObjectMetadata.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S3ObjectMetadata.h; sourceTree = ""; }; - F805B7751160DD60007EC01E /* S3ObjectMetadata.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = S3ObjectMetadata.m; sourceTree = ""; }; - F805B7761160DD60007EC01E /* S3ObjectReceiver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S3ObjectReceiver.h; sourceTree = ""; }; - F805B7771160DD60007EC01E /* S3ObjectReceiver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = S3ObjectReceiver.m; sourceTree = ""; }; - F805B77A1160DD60007EC01E /* S3Receiver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S3Receiver.h; sourceTree = ""; }; - F805B77B1160DD60007EC01E /* S3Request.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S3Request.h; sourceTree = ""; }; - F805B77C1160DD60007EC01E /* S3Request.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = S3Request.m; sourceTree = ""; }; - F805B77D1160DD60007EC01E /* S3Service.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S3Service.h; sourceTree = ""; }; - F805B77E1160DD60007EC01E /* S3Service.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = S3Service.m; sourceTree = ""; }; - F805B7A71160DEF2007EC01E /* RegexKitLite.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RegexKitLite.h; sourceTree = ""; }; - F805B7A81160DEF2007EC01E /* RegexKitLite.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RegexKitLite.m; sourceTree = ""; }; - F805B7B81160E3AF007EC01E /* Blob.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Blob.h; sourceTree = ""; }; - F805B7B91160E3AF007EC01E /* Blob.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Blob.m; sourceTree = ""; }; - F805B7CA1160E445007EC01E /* HTTP.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTP.h; sourceTree = ""; }; - F805B7CB1160E445007EC01E /* HTTPConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPConnection.h; sourceTree = ""; }; - F805B7D61160E456007EC01E /* BlobACL.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BlobACL.h; sourceTree = ""; }; - F805B7D71160E456007EC01E /* BlobACL.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BlobACL.m; sourceTree = ""; }; - F805B7DE1160E48B007EC01E /* NSErrorCodes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSErrorCodes.h; sourceTree = ""; }; - F805B7DF1160E48B007EC01E /* ServerBlob.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ServerBlob.h; sourceTree = ""; }; - F805B7E01160E48B007EC01E /* ServerBlob.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ServerBlob.m; sourceTree = ""; }; - F805B7F91160E73D007EC01E /* RFC2616DateFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RFC2616DateFormatter.h; sourceTree = ""; }; - F805B7FA1160E73D007EC01E /* RFC2616DateFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RFC2616DateFormatter.m; sourceTree = ""; }; - F805B7FC1160E764007EC01E /* RFC822.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RFC822.h; sourceTree = ""; }; - F805B7FD1160E764007EC01E /* RFC822.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RFC822.m; sourceTree = ""; }; - F805B80B1160E7C3007EC01E /* InputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InputStream.h; sourceTree = ""; }; - F805B8191160E838007EC01E /* InputStreams.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InputStreams.h; sourceTree = ""; }; - F805B81A1160E838007EC01E /* InputStreams.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InputStreams.m; sourceTree = ""; }; - F805B8211160E857007EC01E /* ChunkedInputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ChunkedInputStream.h; sourceTree = ""; }; - F805B8221160E857007EC01E /* ChunkedInputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ChunkedInputStream.m; sourceTree = ""; }; - F805B8231160E857007EC01E /* FixedLengthInputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FixedLengthInputStream.h; sourceTree = ""; }; - F805B8241160E857007EC01E /* FixedLengthInputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FixedLengthInputStream.m; sourceTree = ""; }; - F805B8271160E861007EC01E /* FDInputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FDInputStream.h; sourceTree = ""; }; - F805B8281160E861007EC01E /* FDInputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FDInputStream.m; sourceTree = ""; }; - F805B8291160E861007EC01E /* FDOutputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FDOutputStream.h; sourceTree = ""; }; - F805B82A1160E861007EC01E /* FDOutputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FDOutputStream.m; sourceTree = ""; }; - F805B82D1160E86E007EC01E /* DataInputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DataInputStream.h; sourceTree = ""; }; - F805B82E1160E86E007EC01E /* DataInputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DataInputStream.m; sourceTree = ""; }; - F805B8301160E878007EC01E /* Writer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Writer.h; sourceTree = ""; }; - F805B8311160E878007EC01E /* Writer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Writer.m; sourceTree = ""; }; - F805B8331160E882007EC01E /* BufferedInputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BufferedInputStream.h; sourceTree = ""; }; - F805B8361160E8A0007EC01E /* OutputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OutputStream.h; sourceTree = ""; }; - F805B8391160E8DD007EC01E /* NSData-InputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData-InputStream.h"; sourceTree = ""; }; - F805B83A1160E8DD007EC01E /* NSData-InputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData-InputStream.m"; sourceTree = ""; }; - F805B83C1160E900007EC01E /* StreamPair.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StreamPair.h; sourceTree = ""; }; - F805B8401160E90F007EC01E /* Streams.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Streams.h; sourceTree = ""; }; - F805B8411160E90F007EC01E /* Streams.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Streams.m; sourceTree = ""; }; - F805B8631160EA15007EC01E /* NSXMLNode_extra.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSXMLNode_extra.h; sourceTree = ""; }; - F805B8641160EA15007EC01E /* NSXMLNode_extra.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSXMLNode_extra.m; sourceTree = ""; }; - F805B8661160EA36007EC01E /* InputStreamFactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InputStreamFactory.h; sourceTree = ""; }; - F805B8681160EA83007EC01E /* NSData-Base64Extensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData-Base64Extensions.h"; sourceTree = ""; }; - F805B8691160EA83007EC01E /* NSData-Base64Extensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData-Base64Extensions.m"; sourceTree = ""; }; - F805B86D1160EAC1007EC01E /* DataInputStreamFactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DataInputStreamFactory.h; sourceTree = ""; }; - F805B86E1160EAC1007EC01E /* DataInputStreamFactory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DataInputStreamFactory.m; sourceTree = ""; }; F805B8881160EB39007EC01E /* libcrypto.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libcrypto.dylib; path = usr/lib/libcrypto.dylib; sourceTree = SDKROOT; }; F805B88A1160EB39007EC01E /* libicucore.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libicucore.dylib; path = usr/lib/libicucore.dylib; sourceTree = SDKROOT; }; F805B88E1160EB45007EC01E /* libssl.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libssl.dylib; path = usr/lib/libssl.dylib; sourceTree = SDKROOT; }; @@ -362,165 +210,349 @@ F805B8C11160EC41007EC01E /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; F805B8C51160EC4E007EC01E /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; F805B8CD1160ECD7007EC01E /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; }; - F81426D514541A6C00D7E50A /* BackupSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BackupSet.h; sourceTree = ""; }; - F81426D614541A6C00D7E50A /* BackupSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BackupSet.m; sourceTree = ""; }; - F8146BE816EB7054006AD471 /* StorageType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = StorageType.h; path = storage/StorageType.h; sourceTree = ""; }; - F8146BE916EB70EE006AD471 /* BlobKeyIO.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BlobKeyIO.h; sourceTree = ""; }; - F8146BEA16EB70EE006AD471 /* BlobKeyIO.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BlobKeyIO.m; sourceTree = ""; }; - F8373E0D14794D01005AFBE6 /* ReflogEntry.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ReflogEntry.h; sourceTree = ""; }; - F8373E0E14794D01005AFBE6 /* ReflogEntry.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ReflogEntry.m; sourceTree = ""; }; - F8373E58147A8DEC005AFBE6 /* ReflogPrinter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ReflogPrinter.h; sourceTree = ""; }; - F8373E59147A8DEC005AFBE6 /* ReflogPrinter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ReflogPrinter.m; sourceTree = ""; }; - F83C1A5F11CA7A6B0001958F /* arq_verify.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = arq_verify.m; sourceTree = ""; }; - F83C1A6111CA7BD20001958F /* ArqVerifyCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ArqVerifyCommand.h; sourceTree = ""; }; - F83C1A6211CA7BD20001958F /* ArqVerifyCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ArqVerifyCommand.m; sourceTree = ""; }; - F83C1AD711CA7C170001958F /* arq_verify */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = arq_verify; sourceTree = BUILT_PRODUCTS_DIR; }; - F83C1D0811CA929D0001958F /* BucketVerifier.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BucketVerifier.h; path = s3/BucketVerifier.h; sourceTree = ""; }; - F83C1D0911CA929D0001958F /* BucketVerifier.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BucketVerifier.m; path = s3/BucketVerifier.m; sourceTree = ""; }; - F84166D415E2782600B6ECED /* HTTPConnectionFactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPConnectionFactory.h; sourceTree = ""; }; - F84166D515E2782600B6ECED /* HTTPConnectionFactory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTTPConnectionFactory.m; sourceTree = ""; }; - F84166D615E2782600B6ECED /* HTTPTimeoutSetting.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPTimeoutSetting.h; sourceTree = ""; }; - F84166D715E2782600B6ECED /* HTTPTimeoutSetting.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTTPTimeoutSetting.m; sourceTree = ""; }; - F84166DE15E2785100B6ECED /* CFHTTPConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CFHTTPConnection.h; sourceTree = ""; }; - F84166DF15E2785100B6ECED /* CFHTTPConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CFHTTPConnection.m; sourceTree = ""; }; - F84166E015E2785100B6ECED /* CFHTTPInputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CFHTTPInputStream.h; sourceTree = ""; }; - F84166E115E2785100B6ECED /* CFHTTPInputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CFHTTPInputStream.m; sourceTree = ""; }; - F84166E815E2787B00B6ECED /* HTTPConnectionDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPConnectionDelegate.h; sourceTree = ""; }; - F84166EB15E278BC00B6ECED /* CFNetwork.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CFNetwork.h; sourceTree = ""; }; - F84166EC15E278BC00B6ECED /* CFNetwork.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CFNetwork.m; sourceTree = ""; }; - F84166F115E278F500B6ECED /* S3Owner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S3Owner.h; sourceTree = ""; }; - F84166F215E278F500B6ECED /* S3Owner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = S3Owner.m; sourceTree = ""; }; - F84166F315E278F500B6ECED /* S3Region.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S3Region.h; sourceTree = ""; }; - F84166F415E278F500B6ECED /* S3Region.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = S3Region.m; sourceTree = ""; }; - F84166FB15E2792500B6ECED /* NetMonitor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NetMonitor.h; sourceTree = ""; }; - F84166FC15E2792500B6ECED /* NetMonitor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NetMonitor.m; sourceTree = ""; }; - F841670315E279BB00B6ECED /* Sysctl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Sysctl.h; sourceTree = ""; }; - F841670415E279BB00B6ECED /* Sysctl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Sysctl.m; sourceTree = ""; }; - F841670915E279DE00B6ECED /* DNS_SDErrors.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DNS_SDErrors.h; sourceTree = ""; }; - F841670A15E279DE00B6ECED /* DNS_SDErrors.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DNS_SDErrors.m; sourceTree = ""; }; - F841672415E27A5200B6ECED /* RemoteS3Signer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RemoteS3Signer.h; sourceTree = ""; }; - F841672515E27A5200B6ECED /* RemoteS3Signer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RemoteS3Signer.m; sourceTree = ""; }; - F8987231121EB68900F07D76 /* BinaryPListReader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BinaryPListReader.h; sourceTree = ""; }; - F8987232121EB68900F07D76 /* BinaryPListReader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BinaryPListReader.m; sourceTree = ""; }; - F8987233121EB68900F07D76 /* BinaryPListWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BinaryPListWriter.h; sourceTree = ""; }; - F8987234121EB68900F07D76 /* BinaryPListWriter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BinaryPListWriter.m; sourceTree = ""; }; - F898755F121EBD9600F07D76 /* BufferedInputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BufferedInputStream.m; sourceTree = ""; }; - F89A1EEA13FAC4E30071D321 /* URLConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = URLConnection.h; sourceTree = ""; }; - F89A1EEB13FAC4E30071D321 /* URLConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = URLConnection.m; sourceTree = ""; }; - F89A1EF913FAC5970071D321 /* EncryptedInputStreamFactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EncryptedInputStreamFactory.h; sourceTree = ""; }; - F89A1EFA13FAC5970071D321 /* EncryptedInputStreamFactory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EncryptedInputStreamFactory.m; sourceTree = ""; }; - F89A1F2913FAC6700071D321 /* UserLibrary.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UserLibrary.h; sourceTree = ""; }; - F89A1F2A13FAC6700071D321 /* UserLibrary.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UserLibrary.m; sourceTree = ""; }; - F89A1F3C13FAC6820071D321 /* UserLibrary_Arq.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UserLibrary_Arq.h; sourceTree = ""; }; - F89A1F3D13FAC6820071D321 /* UserLibrary_Arq.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UserLibrary_Arq.m; sourceTree = ""; }; - F89A1F4313FAC6C40071D321 /* BlobKey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BlobKey.h; sourceTree = ""; }; - F89A1F4413FAC6C40071D321 /* BlobKey.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BlobKey.m; sourceTree = ""; }; - F89A1F4B13FAC73D0071D321 /* Computer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Computer.h; sourceTree = ""; }; - F89A1F4C13FAC73D0071D321 /* Computer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Computer.m; sourceTree = ""; }; - F89A1F5113FAC78F0071D321 /* BufferedOutputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BufferedOutputStream.h; sourceTree = ""; }; - F89A1F5213FAC78F0071D321 /* BufferedOutputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BufferedOutputStream.m; sourceTree = ""; }; - F89A1F5913FAC7D50071D321 /* Encryption.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Encryption.h; sourceTree = ""; }; - F89A1F5A13FAC7D50071D321 /* Encryption.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Encryption.m; sourceTree = ""; }; - F89A1F5D13FAC8020071D321 /* NSObject_extra.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSObject_extra.h; sourceTree = ""; }; - F89A1F5E13FAC8020071D321 /* NSObject_extra.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSObject_extra.m; sourceTree = ""; }; - F89A1F6113FAC8270071D321 /* CryptoKey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CryptoKey.h; sourceTree = ""; }; - F89A1F6213FAC8270071D321 /* CryptoKey.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CryptoKey.m; sourceTree = ""; }; - F89A1F6513FAC83E0071D321 /* GunzipInputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GunzipInputStream.h; sourceTree = ""; }; - F89A1F6613FAC83E0071D321 /* GunzipInputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GunzipInputStream.m; sourceTree = ""; }; - F89A1F7513FAC8930071D321 /* NSData-GZip.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData-GZip.h"; sourceTree = ""; }; - F89A1F7613FAC8930071D321 /* NSData-GZip.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData-GZip.m"; sourceTree = ""; }; - F89A1FD013FAD6BE0071D321 /* HSLog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HSLog.h; sourceTree = ""; }; - F89A1FD113FAD6BE0071D321 /* HSLog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HSLog.m; sourceTree = ""; }; - F89A200913FADAD70071D321 /* ArqSalt.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ArqSalt.h; sourceTree = ""; }; - F89A200A13FADAD70071D321 /* ArqSalt.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ArqSalt.m; sourceTree = ""; }; + F829512219868345001DC91B /* HSLog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HSLog.h; sourceTree = ""; }; + F829512319868345001DC91B /* HSLog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HSLog.m; sourceTree = ""; }; + F829512519868379001DC91B /* NSErrorCodes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSErrorCodes.h; sourceTree = ""; }; + F829512619868379001DC91B /* SetNSError.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SetNSError.h; sourceTree = ""; }; + F829512719868394001DC91B /* NSError_extra.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSError_extra.h; sourceTree = ""; }; + F829512819868394001DC91B /* NSError_extra.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSError_extra.m; sourceTree = ""; }; + F829512C198683D5001DC91B /* channel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = channel.h; sourceTree = ""; }; + F829512D198683D5001DC91B /* comp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = comp.h; sourceTree = ""; }; + F829512E198683D5001DC91B /* crypto.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = crypto.h; sourceTree = ""; }; + F829512F198683D5001DC91B /* libgcrypt.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = libgcrypt.h; sourceTree = ""; }; + F8295130198683D5001DC91B /* libssh2.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = libssh2.h; sourceTree = ""; }; + F8295131198683D5001DC91B /* libssh2_config.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = libssh2_config.h; sourceTree = ""; }; + F8295132198683D5001DC91B /* libssh2_priv.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = libssh2_priv.h; sourceTree = ""; }; + F8295133198683D5001DC91B /* libssh2_publickey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = libssh2_publickey.h; sourceTree = ""; }; + F8295134198683D5001DC91B /* libssh2_sftp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = libssh2_sftp.h; sourceTree = ""; }; + F8295135198683D5001DC91B /* mac.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mac.h; sourceTree = ""; }; + F8295136198683D5001DC91B /* misc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = misc.h; sourceTree = ""; }; + F8295137198683D5001DC91B /* openssl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = openssl.h; sourceTree = ""; }; + F8295138198683D5001DC91B /* packet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = packet.h; sourceTree = ""; }; + F8295139198683D5001DC91B /* session.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = session.h; sourceTree = ""; }; + F829513A198683D5001DC91B /* sftp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sftp.h; sourceTree = ""; }; + F829513B198683D5001DC91B /* transport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = transport.h; sourceTree = ""; }; + F829513C198683D5001DC91B /* userauth.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = userauth.h; sourceTree = ""; }; + F829513E198683D5001DC91B /* libssh2.1.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libssh2.1.dylib; sourceTree = ""; }; + F8295140198683D5001DC91B /* agent.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = agent.c; sourceTree = ""; }; + F8295141198683D5001DC91B /* channel.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = channel.c; sourceTree = ""; }; + F8295142198683D5001DC91B /* comp.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = comp.c; sourceTree = ""; }; + F8295143198683D5001DC91B /* crypt.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = crypt.c; sourceTree = ""; }; + F8295144198683D5001DC91B /* global.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = global.c; sourceTree = ""; }; + F8295145198683D5001DC91B /* hostkey.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = hostkey.c; sourceTree = ""; }; + F8295146198683D5001DC91B /* keepalive.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = keepalive.c; sourceTree = ""; }; + F8295147198683D5001DC91B /* kex.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = kex.c; sourceTree = ""; }; + F8295148198683D5001DC91B /* knownhost.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = knownhost.c; sourceTree = ""; }; + F8295149198683D5001DC91B /* libgcrypt.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = libgcrypt.c; sourceTree = ""; }; + F829514A198683D5001DC91B /* mac.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mac.c; sourceTree = ""; }; + F829514B198683D5001DC91B /* misc.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = misc.c; sourceTree = ""; }; + F829514C198683D5001DC91B /* openssl.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = openssl.c; sourceTree = ""; }; + F829514D198683D5001DC91B /* packet.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = packet.c; sourceTree = ""; }; + F829514E198683D5001DC91B /* pem.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = pem.c; sourceTree = ""; }; + F829514F198683D5001DC91B /* publickey.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = publickey.c; sourceTree = ""; }; + F8295150198683D5001DC91B /* scp.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = scp.c; sourceTree = ""; }; + F8295151198683D5001DC91B /* session.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = session.c; sourceTree = ""; }; + F8295152198683D5001DC91B /* sftp.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sftp.c; sourceTree = ""; }; + F8295153198683D5001DC91B /* transport.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = transport.c; sourceTree = ""; }; + F8295154198683D5001DC91B /* userauth.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = userauth.c; sourceTree = ""; }; + F8295155198683D5001DC91B /* version.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = version.c; sourceTree = ""; }; + F829516E198683F9001DC91B /* LifecycleConfiguration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LifecycleConfiguration.h; sourceTree = ""; }; + F829516F198683F9001DC91B /* LifecycleConfiguration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LifecycleConfiguration.m; sourceTree = ""; }; + F8295170198683F9001DC91B /* LocalS3Signer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LocalS3Signer.h; sourceTree = ""; }; + F8295171198683F9001DC91B /* LocalS3Signer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LocalS3Signer.m; sourceTree = ""; }; + F8295172198683F9001DC91B /* PathReceiver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PathReceiver.h; sourceTree = ""; }; + F8295173198683F9001DC91B /* PathReceiver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PathReceiver.m; sourceTree = ""; }; + F8295174198683F9001DC91B /* RemoteS3Signer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RemoteS3Signer.h; sourceTree = ""; }; + F8295175198683F9001DC91B /* RemoteS3Signer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RemoteS3Signer.m; sourceTree = ""; }; + F8295176198683F9001DC91B /* S3AuthorizationProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S3AuthorizationProvider.h; sourceTree = ""; }; + F8295177198683F9001DC91B /* S3AuthorizationProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = S3AuthorizationProvider.m; sourceTree = ""; }; + F8295178198683F9001DC91B /* S3DeleteReceiver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S3DeleteReceiver.h; sourceTree = ""; }; + F8295179198683F9001DC91B /* S3DeleteReceiver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = S3DeleteReceiver.m; sourceTree = ""; }; + F829517A198683F9001DC91B /* S3ErrorResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S3ErrorResult.h; sourceTree = ""; }; + F829517B198683F9001DC91B /* S3ErrorResult.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = S3ErrorResult.m; sourceTree = ""; }; + F829517C198683F9001DC91B /* S3Lister.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S3Lister.h; sourceTree = ""; }; + F829517D198683F9001DC91B /* S3Lister.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = S3Lister.m; sourceTree = ""; }; + F829517E198683F9001DC91B /* S3MultiDeleteResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S3MultiDeleteResponse.h; sourceTree = ""; }; + F829517F198683F9001DC91B /* S3MultiDeleteResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = S3MultiDeleteResponse.m; sourceTree = ""; }; + F8295180198683F9001DC91B /* S3ObjectMetadata.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S3ObjectMetadata.h; sourceTree = ""; }; + F8295181198683F9001DC91B /* S3ObjectMetadata.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = S3ObjectMetadata.m; sourceTree = ""; }; + F8295182198683F9001DC91B /* S3ObjectReceiver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S3ObjectReceiver.h; sourceTree = ""; }; + F8295183198683F9001DC91B /* S3ObjectReceiver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = S3ObjectReceiver.m; sourceTree = ""; }; + F8295184198683F9001DC91B /* S3Owner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S3Owner.h; sourceTree = ""; }; + F8295185198683F9001DC91B /* S3Owner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = S3Owner.m; sourceTree = ""; }; + F8295186198683F9001DC91B /* S3Receiver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S3Receiver.h; sourceTree = ""; }; + F8295187198683F9001DC91B /* S3Request.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S3Request.h; sourceTree = ""; }; + F8295188198683F9001DC91B /* S3Request.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = S3Request.m; sourceTree = ""; }; + F8295189198683F9001DC91B /* S3Service.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S3Service.h; sourceTree = ""; }; + F829518A198683F9001DC91B /* S3Service.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = S3Service.m; sourceTree = ""; }; + F829518B198683F9001DC91B /* S3Signer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S3Signer.h; sourceTree = ""; }; + F829519E19868D90001DC91B /* NSData-Base64Extensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData-Base64Extensions.h"; sourceTree = ""; }; + F829519F19868D90001DC91B /* NSData-Base64Extensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData-Base64Extensions.m"; sourceTree = ""; }; + F82951A119868D90001DC91B /* HTTP.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTP.h; sourceTree = ""; }; + F82951A219868D90001DC91B /* HTTPConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPConnection.h; sourceTree = ""; }; + F82951A319868D90001DC91B /* HTTPConnectionFactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPConnectionFactory.h; sourceTree = ""; }; + F82951A419868D90001DC91B /* HTTPConnectionFactory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTTPConnectionFactory.m; sourceTree = ""; }; + F82951A519868D90001DC91B /* HTTPInputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPInputStream.h; sourceTree = ""; }; + F82951A619868D90001DC91B /* HTTPInputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTTPInputStream.m; sourceTree = ""; }; + F82951A719868D90001DC91B /* HTTPThrottle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPThrottle.h; sourceTree = ""; }; + F82951A819868D90001DC91B /* HTTPThrottle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTTPThrottle.m; sourceTree = ""; }; + F82951AB19868D90001DC91B /* NSDictionary_HTTP.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSDictionary_HTTP.h; sourceTree = ""; }; + F82951AC19868D90001DC91B /* NSDictionary_HTTP.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSDictionary_HTTP.m; sourceTree = ""; }; + F82951AD19868D90001DC91B /* RFC2616DateFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RFC2616DateFormatter.h; sourceTree = ""; }; + F82951AE19868D90001DC91B /* RFC2616DateFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RFC2616DateFormatter.m; sourceTree = ""; }; + F82951B319868D90001DC91B /* URLConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = URLConnection.h; sourceTree = ""; }; + F82951B419868D90001DC91B /* URLConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = URLConnection.m; sourceTree = ""; }; + F829521119868DD0001DC91B /* InputStream.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = InputStream.h; sourceTree = ""; }; + F829521219868DE6001DC91B /* RegexKitLite.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RegexKitLite.m; sourceTree = ""; }; + F829521419868DEA001DC91B /* RegexKitLite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RegexKitLite.h; sourceTree = ""; }; + F829521619868E26001DC91B /* GoogleDrive.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GoogleDrive.h; sourceTree = ""; }; + F829521719868E26001DC91B /* GoogleDrive.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GoogleDrive.m; sourceTree = ""; }; + F829521819868E26001DC91B /* GoogleDriveErrorResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GoogleDriveErrorResult.h; sourceTree = ""; }; + F829521919868E26001DC91B /* GoogleDriveErrorResult.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GoogleDriveErrorResult.m; sourceTree = ""; }; + F829521A19868E26001DC91B /* GoogleDriveFactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GoogleDriveFactory.h; sourceTree = ""; }; + F829521B19868E26001DC91B /* GoogleDriveFactory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GoogleDriveFactory.m; sourceTree = ""; }; + F829521C19868E26001DC91B /* GoogleDriveRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GoogleDriveRequest.h; sourceTree = ""; }; + F829521D19868E26001DC91B /* GoogleDriveRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GoogleDriveRequest.m; sourceTree = ""; }; + F829521E19868E26001DC91B /* GoogleDriveFolderLister.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GoogleDriveFolderLister.h; sourceTree = ""; }; + F829521F19868E26001DC91B /* GoogleDriveFolderLister.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GoogleDriveFolderLister.m; sourceTree = ""; }; + F829522619868E59001DC91B /* NSObject+SBJSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+SBJSON.h"; sourceTree = ""; }; + F829522719868E59001DC91B /* NSObject+SBJSON.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+SBJSON.m"; sourceTree = ""; }; + F829522819868E59001DC91B /* NSString+SBJSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+SBJSON.h"; sourceTree = ""; }; + F829522919868E59001DC91B /* NSString+SBJSON.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+SBJSON.m"; sourceTree = ""; }; + F829522A19868E59001DC91B /* SBJsonBase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonBase.h; sourceTree = ""; }; + F829522B19868E59001DC91B /* SBJsonBase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonBase.m; sourceTree = ""; }; + F829522C19868E59001DC91B /* SBJsonParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonParser.h; sourceTree = ""; }; + F829522D19868E59001DC91B /* SBJsonParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonParser.m; sourceTree = ""; }; + F829522E19868E59001DC91B /* SBJsonWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonWriter.h; sourceTree = ""; }; + F829522F19868E59001DC91B /* SBJsonWriter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonWriter.m; sourceTree = ""; }; + F829523619868E83001DC91B /* AWSRegion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AWSRegion.h; sourceTree = ""; }; + F829523719868E83001DC91B /* AWSRegion.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AWSRegion.m; sourceTree = ""; }; + F829523919868E94001DC91B /* CWLSynthesizeSingleton.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CWLSynthesizeSingleton.h; sourceTree = ""; }; + F829523A19868EA4001DC91B /* NetMonitor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NetMonitor.h; sourceTree = ""; }; + F829523B19868EA4001DC91B /* NetMonitor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NetMonitor.m; sourceTree = ""; }; + F829523D19868EBB001DC91B /* TargetConnection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TargetConnection.h; sourceTree = ""; }; + F829523E19868EC7001DC91B /* NSString_extra.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSString_extra.h; sourceTree = ""; }; + F829523F19868EC7001DC91B /* NSString_extra.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSString_extra.m; sourceTree = ""; }; + F829524119868ED6001DC91B /* NSData-InputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData-InputStream.h"; sourceTree = ""; }; + F829524219868ED6001DC91B /* NSData-InputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData-InputStream.m"; sourceTree = ""; }; + F829524519868F02001DC91B /* ISO8601Date.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ISO8601Date.h; sourceTree = ""; }; + F829524619868F02001DC91B /* ISO8601Date.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ISO8601Date.m; sourceTree = ""; }; + F829524819868F0F001DC91B /* DataInputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DataInputStream.h; sourceTree = ""; }; + F829524919868F0F001DC91B /* DataInputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DataInputStream.m; sourceTree = ""; }; + F829524B19868F1E001DC91B /* ChunkedInputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ChunkedInputStream.h; sourceTree = ""; }; + F829524C19868F1E001DC91B /* ChunkedInputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ChunkedInputStream.m; sourceTree = ""; }; + F829524E19868F31001DC91B /* InputStreams.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InputStreams.h; sourceTree = ""; }; + F829524F19868F31001DC91B /* InputStreams.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InputStreams.m; sourceTree = ""; }; + F829525119868F3C001DC91B /* BufferedInputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BufferedInputStream.h; sourceTree = ""; }; + F829525219868F3C001DC91B /* BufferedInputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BufferedInputStream.m; sourceTree = ""; }; + F829DBFA19868FCA00D637E0 /* RFC822.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RFC822.h; sourceTree = ""; }; + F829DBFB19868FCA00D637E0 /* RFC822.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RFC822.m; sourceTree = ""; }; + F829DBFD1986901300D637E0 /* Streams.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Streams.h; sourceTree = ""; }; + F829DBFE1986901300D637E0 /* Streams.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Streams.m; sourceTree = ""; }; + F829DC001986902100D637E0 /* OutputStream.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OutputStream.h; sourceTree = ""; }; + F829DC011986904C00D637E0 /* DataTransferDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DataTransferDelegate.h; sourceTree = ""; }; + F829DC02198691CB00D637E0 /* BooleanIO.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BooleanIO.h; sourceTree = ""; }; + F829DC03198691CB00D637E0 /* BooleanIO.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BooleanIO.m; sourceTree = ""; }; + F829DC04198691CB00D637E0 /* DataIO.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DataIO.h; sourceTree = ""; }; + F829DC05198691CB00D637E0 /* DataIO.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DataIO.m; sourceTree = ""; }; + F829DC06198691CB00D637E0 /* DateIO.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DateIO.h; sourceTree = ""; }; + F829DC07198691CB00D637E0 /* DateIO.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DateIO.m; sourceTree = ""; }; + F829DC08198691CB00D637E0 /* DoubleIO.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DoubleIO.h; sourceTree = ""; }; + F829DC09198691CB00D637E0 /* DoubleIO.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DoubleIO.m; sourceTree = ""; }; + F829DC0A198691CB00D637E0 /* IntegerIO.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IntegerIO.h; sourceTree = ""; }; + F829DC0B198691CB00D637E0 /* IntegerIO.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IntegerIO.m; sourceTree = ""; }; + F829DC0C198691CB00D637E0 /* NSErrorIO.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSErrorIO.h; sourceTree = ""; }; + F829DC0D198691CB00D637E0 /* NSErrorIO.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSErrorIO.m; sourceTree = ""; }; + F829DC0E198691CB00D637E0 /* StringIO.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StringIO.h; sourceTree = ""; }; + F829DC0F198691CB00D637E0 /* StringIO.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StringIO.m; sourceTree = ""; }; + F829DC17198691D900D637E0 /* BufferedOutputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BufferedOutputStream.h; sourceTree = ""; }; + F829DC18198691D900D637E0 /* BufferedOutputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BufferedOutputStream.m; sourceTree = ""; }; + F829DC1A1986921E00D637E0 /* MD5Hash.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MD5Hash.h; sourceTree = ""; }; + F829DC1B1986921E00D637E0 /* MD5Hash.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MD5Hash.m; sourceTree = ""; }; + F829DC1D1986923100D637E0 /* Sysctl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Sysctl.h; sourceTree = ""; }; + F829DC1E1986923100D637E0 /* Sysctl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Sysctl.m; sourceTree = ""; }; + F829DC201986924100D637E0 /* FDInputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FDInputStream.h; sourceTree = ""; }; + F829DC211986924100D637E0 /* FDInputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FDInputStream.m; sourceTree = ""; }; + F829DC221986924100D637E0 /* FDOutputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FDOutputStream.h; sourceTree = ""; }; + F829DC231986924100D637E0 /* FDOutputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FDOutputStream.m; sourceTree = ""; }; + F829DC261986924E00D637E0 /* DataOutputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DataOutputStream.h; sourceTree = ""; }; + F829DC271986924E00D637E0 /* DataOutputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DataOutputStream.m; sourceTree = ""; }; + F829DC291986927000D637E0 /* FileInputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FileInputStream.h; sourceTree = ""; }; + F829DC2A1986927000D637E0 /* FileInputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FileInputStream.m; sourceTree = ""; }; + F829DC2B1986927000D637E0 /* FileOutputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FileOutputStream.h; sourceTree = ""; }; + F829DC2C1986927000D637E0 /* FileOutputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FileOutputStream.m; sourceTree = ""; }; + F829DC2F1986930300D637E0 /* OpenSSL.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OpenSSL.h; sourceTree = ""; }; + F829DC301986930300D637E0 /* OpenSSL.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OpenSSL.m; sourceTree = ""; }; + F829DC33198697EB00D637E0 /* Target.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Target.h; sourceTree = ""; }; + F829DC34198697EB00D637E0 /* Target.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Target.m; sourceTree = ""; }; + F83F9B2B1983303F007CBFB4 /* ArqRestoreCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ArqRestoreCommand.h; sourceTree = ""; }; + F83F9B2C1983303F007CBFB4 /* ArqRestoreCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ArqRestoreCommand.m; sourceTree = ""; }; F89A204513FAE29E0071D321 /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; }; - F89A205113FAE2DA0071D321 /* LocalS3Signer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LocalS3Signer.h; sourceTree = ""; }; - F89A205213FAE2DA0071D321 /* LocalS3Signer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LocalS3Signer.m; sourceTree = ""; }; - F89A205713FAE3010071D321 /* DataOutputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DataOutputStream.h; sourceTree = ""; }; - F89A205813FAE3010071D321 /* DataOutputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DataOutputStream.m; sourceTree = ""; }; - F89A207F13FAE3810071D321 /* S3Signer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S3Signer.h; sourceTree = ""; }; F89A20A413FAE5300071D321 /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; }; - F8D6763E1160F22800CC270E /* SetNSError.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SetNSError.h; sourceTree = ""; }; - F8D677FE1160F26A00CC270E /* Restorer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Restorer.h; sourceTree = ""; }; - F8D677FF1160F26A00CC270E /* Restorer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Restorer.m; sourceTree = ""; }; - F8D6781B1160F4FD00CC270E /* SHA1Hash.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SHA1Hash.h; sourceTree = ""; }; - F8D6781C1160F4FD00CC270E /* SHA1Hash.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SHA1Hash.m; sourceTree = ""; }; - F8D6783A1160F70100CC270E /* PackIndexEntry.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PackIndexEntry.h; sourceTree = ""; }; - F8D6783B1160F70100CC270E /* PackIndexEntry.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PackIndexEntry.m; sourceTree = ""; }; - F8D678411160F74A00CC270E /* DiskPack.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DiskPack.h; sourceTree = ""; }; - F8D678421160F74A00CC270E /* DiskPack.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DiskPack.m; sourceTree = ""; }; - F8D678431160F74A00CC270E /* DiskPackIndex.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DiskPackIndex.h; sourceTree = ""; }; - F8D678441160F74A00CC270E /* DiskPackIndex.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DiskPackIndex.m; sourceTree = ""; }; - F8D6785B1160F7CE00CC270E /* Commit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Commit.h; sourceTree = ""; }; - F8D6785C1160F7CF00CC270E /* Commit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Commit.m; sourceTree = ""; }; - F8D6785D1160F7CF00CC270E /* Tree.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Tree.h; sourceTree = ""; }; - F8D6785E1160F7CF00CC270E /* Tree.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Tree.m; sourceTree = ""; }; - F8D678631160F7FE00CC270E /* NSData-Encrypt.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData-Encrypt.h"; sourceTree = ""; }; - F8D678641160F7FE00CC270E /* NSData-Encrypt.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData-Encrypt.m"; sourceTree = ""; }; - F8D678681160F81100CC270E /* OpenSSL.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OpenSSL.h; sourceTree = ""; }; - F8D678691160F81100CC270E /* OpenSSL.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OpenSSL.m; sourceTree = ""; }; - F8D6786D1160F84600CC270E /* DecryptedInputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DecryptedInputStream.h; sourceTree = ""; }; - F8D6786E1160F84600CC270E /* DecryptedInputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DecryptedInputStream.m; sourceTree = ""; }; - F8D678721160F85D00CC270E /* CryptInputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CryptInputStream.h; sourceTree = ""; }; - F8D678731160F85D00CC270E /* CryptInputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CryptInputStream.m; sourceTree = ""; }; - F8D678751160F86E00CC270E /* FileInputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FileInputStream.h; sourceTree = ""; }; - F8D678761160F86E00CC270E /* FileInputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FileInputStream.m; sourceTree = ""; }; - F8D678781160F8A000CC270E /* DataIO.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DataIO.h; sourceTree = ""; }; - F8D678791160F8A000CC270E /* DataIO.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DataIO.m; sourceTree = ""; }; - F8D6787A1160F8A000CC270E /* DateIO.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DateIO.h; sourceTree = ""; }; - F8D6787B1160F8A000CC270E /* DateIO.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DateIO.m; sourceTree = ""; }; - F8D6787C1160F8A000CC270E /* DoubleIO.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DoubleIO.h; sourceTree = ""; }; - F8D6787D1160F8A000CC270E /* DoubleIO.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DoubleIO.m; sourceTree = ""; }; - F8D6787E1160F8A000CC270E /* IntegerIO.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IntegerIO.h; sourceTree = ""; }; - F8D6787F1160F8A000CC270E /* IntegerIO.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IntegerIO.m; sourceTree = ""; }; - F8D678801160F8A000CC270E /* StringIO.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StringIO.h; sourceTree = ""; }; - F8D678811160F8A000CC270E /* StringIO.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StringIO.m; sourceTree = ""; }; - F8D678871160F8CD00CC270E /* NSString_extra.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSString_extra.h; sourceTree = ""; }; - F8D678881160F8CD00CC270E /* NSString_extra.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSString_extra.m; sourceTree = ""; }; - F8D6788A1160F8E500CC270E /* BinarySHA1.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BinarySHA1.h; sourceTree = ""; }; - F8D6788B1160F8E500CC270E /* BinarySHA1.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BinarySHA1.m; sourceTree = ""; }; - F8D678981160FA2A00CC270E /* NSFileManager_extra.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSFileManager_extra.h; sourceTree = ""; }; - F8D678991160FA2A00CC270E /* NSFileManager_extra.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSFileManager_extra.m; sourceTree = ""; }; - F8D6789B1160FA3900CC270E /* FileOutputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FileOutputStream.h; sourceTree = ""; }; - F8D6789C1160FA3900CC270E /* FileOutputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FileOutputStream.m; sourceTree = ""; }; - F8D6789E1160FA4800CC270E /* FileInputStreamFactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FileInputStreamFactory.h; sourceTree = ""; }; - F8D6789F1160FA4800CC270E /* FileInputStreamFactory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FileInputStreamFactory.m; sourceTree = ""; }; - F8D678A31160FA5F00CC270E /* BooleanIO.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BooleanIO.h; sourceTree = ""; }; - F8D678A41160FA5F00CC270E /* BooleanIO.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BooleanIO.m; sourceTree = ""; }; - F8D678A61160FA6A00CC270E /* EncryptedInputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EncryptedInputStream.h; sourceTree = ""; }; - F8D678A71160FA6A00CC270E /* EncryptedInputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EncryptedInputStream.m; sourceTree = ""; }; - F8D678AD1160FAD900CC270E /* CommitFailedFile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CommitFailedFile.h; sourceTree = ""; }; - F8D678AE1160FAD900CC270E /* CommitFailedFile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CommitFailedFile.m; sourceTree = ""; }; - F8D678B61160FB2100CC270E /* Node.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Node.h; sourceTree = ""; }; - F8D678B71160FB2100CC270E /* Node.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Node.m; sourceTree = ""; }; - F8D67CE91161363A00CC270E /* FileAttributes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FileAttributes.h; sourceTree = ""; }; - F8D67CEA1161363A00CC270E /* FileAttributes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FileAttributes.m; sourceTree = ""; }; - F8D67CF01161366100CC270E /* XAttrSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XAttrSet.h; sourceTree = ""; }; - F8D67CF11161366100CC270E /* XAttrSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XAttrSet.m; sourceTree = ""; }; - F8D67D031161384100CC270E /* FileACL.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FileACL.h; sourceTree = ""; }; - F8D67D041161384100CC270E /* FileACL.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FileACL.m; sourceTree = ""; }; - F8D67D051161384100CC270E /* OSStatusDescription.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OSStatusDescription.h; sourceTree = ""; }; - F8D67D061161384100CC270E /* OSStatusDescription.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OSStatusDescription.m; sourceTree = ""; }; - F8D67F6E1161443600CC270E /* RestoreNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RestoreNode.h; sourceTree = ""; }; - F8D67F6F1161443600CC270E /* RestoreNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RestoreNode.m; sourceTree = ""; }; - F8F4D19B121D77E1002D09C1 /* NSError_extra.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSError_extra.h; sourceTree = ""; }; - F8F4D19C121D77E1002D09C1 /* NSError_extra.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSError_extra.m; sourceTree = ""; }; - F8F4D19E121D78AD002D09C1 /* MonitoredInputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MonitoredInputStream.h; sourceTree = ""; }; - F8F4D19F121D78AD002D09C1 /* MonitoredInputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MonitoredInputStream.m; sourceTree = ""; }; - F8F4D1AC121D7990002D09C1 /* ArqPackSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ArqPackSet.h; sourceTree = ""; }; - F8F4D1AD121D7990002D09C1 /* ArqPackSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ArqPackSet.m; sourceTree = ""; }; - F8F4D1AE121D7990002D09C1 /* ArqRepo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ArqRepo.h; sourceTree = ""; }; - F8F4D1AF121D7990002D09C1 /* ArqRepo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ArqRepo.m; sourceTree = ""; }; - F8F4D1C1121D79AC002D09C1 /* ArqFark.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ArqFark.h; sourceTree = ""; }; - F8F4D1C2121D79AC002D09C1 /* ArqFark.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ArqFark.m; sourceTree = ""; }; - F8F4D1E0121D7BC3002D09C1 /* AppKeychain.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppKeychain.h; sourceTree = ""; }; - F8F4D1E1121D7BC3002D09C1 /* AppKeychain.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppKeychain.m; sourceTree = ""; }; - F8F4D1E5121D7DA2002D09C1 /* FarkPath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FarkPath.h; sourceTree = ""; }; - F8F4D1E6121D7DA2002D09C1 /* FarkPath.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FarkPath.m; sourceTree = ""; }; - F8F4D1FC121D8409002D09C1 /* PackIndexWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PackIndexWriter.h; sourceTree = ""; }; - F8F4D1FD121D8409002D09C1 /* PackIndexWriter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PackIndexWriter.m; sourceTree = ""; }; - F8F4D67C121DA542002D09C1 /* UserAndComputer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UserAndComputer.h; sourceTree = ""; }; - F8F4D67D121DA542002D09C1 /* UserAndComputer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UserAndComputer.m; sourceTree = ""; }; + F8F2D8661986B57D00997A15 /* BackupSet.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BackupSet.h; sourceTree = ""; }; + F8F2D8671986B58000997A15 /* BackupSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BackupSet.m; sourceTree = ""; }; + F8F2D8691986B5CC00997A15 /* S3TargetConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S3TargetConnection.h; sourceTree = ""; }; + F8F2D86A1986B5CC00997A15 /* S3TargetConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = S3TargetConnection.m; sourceTree = ""; }; + F8F2D86B1986B5CC00997A15 /* SFTPTargetConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SFTPTargetConnection.h; sourceTree = ""; }; + F8F2D86C1986B5CC00997A15 /* SFTPTargetConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SFTPTargetConnection.m; sourceTree = ""; }; + F8F2D86F1986B5D900997A15 /* GoogleDriveTargetConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GoogleDriveTargetConnection.h; sourceTree = ""; }; + F8F2D8701986B5D900997A15 /* GoogleDriveTargetConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GoogleDriveTargetConnection.m; sourceTree = ""; }; + F8F2D8731986B61800997A15 /* RemoteFS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RemoteFS.h; sourceTree = ""; }; + F8F2D8741986B61800997A15 /* S3RemoteFS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S3RemoteFS.h; sourceTree = ""; }; + F8F2D8751986B61800997A15 /* S3RemoteFS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = S3RemoteFS.m; sourceTree = ""; }; + F8F2D8761986B61800997A15 /* SFTPRemoteFS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SFTPRemoteFS.h; sourceTree = ""; }; + F8F2D8771986B61800997A15 /* SFTPRemoteFS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SFTPRemoteFS.m; sourceTree = ""; }; + F8F2D8781986B61800997A15 /* GoogleDriveRemoteFS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GoogleDriveRemoteFS.h; sourceTree = ""; }; + F8F2D8791986B61800997A15 /* GoogleDriveRemoteFS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GoogleDriveRemoteFS.m; sourceTree = ""; }; + F8F2D8801986B63400997A15 /* BaseTargetConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BaseTargetConnection.h; sourceTree = ""; }; + F8F2D8811986B63400997A15 /* BaseTargetConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BaseTargetConnection.m; sourceTree = ""; }; + F8F2D8831986B64D00997A15 /* GlacierAuthorizationProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GlacierAuthorizationProvider.h; sourceTree = ""; }; + F8F2D8841986B64D00997A15 /* GlacierAuthorizationProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GlacierAuthorizationProvider.m; sourceTree = ""; }; + F8F2D8861986B66000997A15 /* GlacierJob.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GlacierJob.h; sourceTree = ""; }; + F8F2D8871986B66000997A15 /* GlacierJob.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GlacierJob.m; sourceTree = ""; }; + F8F2D8881986B66000997A15 /* GlacierJobLister.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GlacierJobLister.h; sourceTree = ""; }; + F8F2D8891986B66000997A15 /* GlacierJobLister.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GlacierJobLister.m; sourceTree = ""; }; + F8F2D88A1986B66000997A15 /* GlacierRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GlacierRequest.h; sourceTree = ""; }; + F8F2D88B1986B66000997A15 /* GlacierRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GlacierRequest.m; sourceTree = ""; }; + F8F2D88C1986B66000997A15 /* GlacierResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GlacierResponse.h; sourceTree = ""; }; + F8F2D88D1986B66000997A15 /* GlacierResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GlacierResponse.m; sourceTree = ""; }; + F8F2D88E1986B66000997A15 /* GlacierService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GlacierService.h; sourceTree = ""; }; + F8F2D88F1986B66000997A15 /* GlacierService.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GlacierService.m; sourceTree = ""; }; + F8F2D8901986B66000997A15 /* LocalGlacierSigner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LocalGlacierSigner.h; sourceTree = ""; }; + F8F2D8911986B66000997A15 /* LocalGlacierSigner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LocalGlacierSigner.m; sourceTree = ""; }; + F8F2D8981986B67500997A15 /* NSError_Glacier.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSError_Glacier.h; sourceTree = ""; }; + F8F2D8991986B67500997A15 /* NSError_Glacier.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSError_Glacier.m; sourceTree = ""; }; + F8F2D89A1986B67500997A15 /* SHA256TreeHash.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SHA256TreeHash.h; sourceTree = ""; }; + F8F2D89B1986B67500997A15 /* SHA256TreeHash.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SHA256TreeHash.m; sourceTree = ""; }; + F8F2D89C1986B67500997A15 /* Vault.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Vault.h; sourceTree = ""; }; + F8F2D89D1986B67500997A15 /* Vault.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Vault.m; sourceTree = ""; }; + F8F2D89E1986B67500997A15 /* VaultDeleter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VaultDeleter.h; sourceTree = ""; }; + F8F2D89F1986B67500997A15 /* VaultDeleter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VaultDeleter.m; sourceTree = ""; }; + F8F2D8A01986B67500997A15 /* VaultDeleterDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VaultDeleterDelegate.h; sourceTree = ""; }; + F8F2D8A11986B67500997A15 /* VaultLister.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VaultLister.h; sourceTree = ""; }; + F8F2D8A21986B67500997A15 /* VaultLister.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VaultLister.m; sourceTree = ""; }; + F8F2D8A81986B68800997A15 /* NSXMLNode_extra.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSXMLNode_extra.h; sourceTree = ""; }; + F8F2D8A91986B68800997A15 /* NSXMLNode_extra.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSXMLNode_extra.m; sourceTree = ""; }; + F8F2D8AB1986B6A300997A15 /* SHA256Hash.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SHA256Hash.h; sourceTree = ""; }; + F8F2D8AC1986B6A300997A15 /* SHA256Hash.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SHA256Hash.m; sourceTree = ""; }; + F8F2D8B01986B6E000997A15 /* CreateTopicResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CreateTopicResponse.h; sourceTree = ""; }; + F8F2D8B11986B6E000997A15 /* CreateTopicResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CreateTopicResponse.m; sourceTree = ""; }; + F8F2D8B21986B6E000997A15 /* ListTopicsResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ListTopicsResponse.h; sourceTree = ""; }; + F8F2D8B31986B6E000997A15 /* ListTopicsResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ListTopicsResponse.m; sourceTree = ""; }; + F8F2D8B41986B6E000997A15 /* SNS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNS.h; sourceTree = ""; }; + F8F2D8B51986B6E000997A15 /* SNS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNS.m; sourceTree = ""; }; + F8F2D8B61986B6E000997A15 /* SubscribeResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SubscribeResponse.h; sourceTree = ""; }; + F8F2D8B71986B6E000997A15 /* SubscribeResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SubscribeResponse.m; sourceTree = ""; }; + F8F2D8BC1986B6E900997A15 /* CreateQueueResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CreateQueueResponse.h; sourceTree = ""; }; + F8F2D8BD1986B6E900997A15 /* CreateQueueResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CreateQueueResponse.m; sourceTree = ""; }; + F8F2D8BE1986B6E900997A15 /* GetQueueAttributesResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GetQueueAttributesResponse.h; sourceTree = ""; }; + F8F2D8BF1986B6E900997A15 /* GetQueueAttributesResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GetQueueAttributesResponse.m; sourceTree = ""; }; + F8F2D8C01986B6E900997A15 /* ListQueuesResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ListQueuesResponse.h; sourceTree = ""; }; + F8F2D8C11986B6E900997A15 /* ListQueuesResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ListQueuesResponse.m; sourceTree = ""; }; + F8F2D8C21986B6E900997A15 /* ReceiveMessageResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ReceiveMessageResponse.h; sourceTree = ""; }; + F8F2D8C31986B6E900997A15 /* ReceiveMessageResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ReceiveMessageResponse.m; sourceTree = ""; }; + F8F2D8C41986B6E900997A15 /* SQS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SQS.h; sourceTree = ""; }; + F8F2D8C51986B6E900997A15 /* SQS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SQS.m; sourceTree = ""; }; + F8F2D8C61986B6E900997A15 /* SQSMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SQSMessage.h; sourceTree = ""; }; + F8F2D8C71986B6E900997A15 /* SQSMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SQSMessage.m; sourceTree = ""; }; + F8F2D8CE1986B6FD00997A15 /* SignatureV2Provider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SignatureV2Provider.h; sourceTree = ""; }; + F8F2D8CF1986B6FD00997A15 /* SignatureV2Provider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SignatureV2Provider.m; sourceTree = ""; }; + F8F2D8D11986B70300997A15 /* AWSQueryError.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AWSQueryError.h; sourceTree = ""; }; + F8F2D8D21986B70300997A15 /* AWSQueryError.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AWSQueryError.m; sourceTree = ""; }; + F8F2D8D31986B70300997A15 /* AWSQueryRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AWSQueryRequest.h; sourceTree = ""; }; + F8F2D8D41986B70300997A15 /* AWSQueryRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AWSQueryRequest.m; sourceTree = ""; }; + F8F2D8D51986B70300997A15 /* AWSQueryResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AWSQueryResponse.h; sourceTree = ""; }; + F8F2D8D61986B70300997A15 /* AWSQueryResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AWSQueryResponse.m; sourceTree = ""; }; + F8F2D8DA1986B72000997A15 /* GlacierSigner.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GlacierSigner.h; sourceTree = ""; }; + F8F2D8DB1986B72900997A15 /* GlacierAuthorization.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GlacierAuthorization.h; sourceTree = ""; }; + F8F2D8DC1986B72900997A15 /* GlacierAuthorization.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GlacierAuthorization.m; sourceTree = ""; }; + F8F2D8DE1986B74400997A15 /* UserAndComputer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UserAndComputer.h; sourceTree = ""; }; + F8F2D8DF1986B74400997A15 /* UserAndComputer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UserAndComputer.m; sourceTree = ""; }; + F8F2D8E11986B75C00997A15 /* Computer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Computer.h; sourceTree = ""; }; + F8F2D8E21986B75C00997A15 /* Computer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Computer.m; sourceTree = ""; }; + F8F2D8E51986B78600997A15 /* ArrayNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ArrayNode.h; sourceTree = ""; }; + F8F2D8E61986B78600997A15 /* ArrayNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ArrayNode.m; sourceTree = ""; }; + F8F2D8E71986B78600997A15 /* BinaryPListReader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BinaryPListReader.h; sourceTree = ""; }; + F8F2D8E81986B78600997A15 /* BinaryPListReader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BinaryPListReader.m; sourceTree = ""; }; + F8F2D8E91986B78600997A15 /* BinaryPListWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BinaryPListWriter.h; sourceTree = ""; }; + F8F2D8EA1986B78600997A15 /* BinaryPListWriter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BinaryPListWriter.m; sourceTree = ""; }; + F8F2D8EB1986B78600997A15 /* BooleanNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BooleanNode.h; sourceTree = ""; }; + F8F2D8EC1986B78600997A15 /* BooleanNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BooleanNode.m; sourceTree = ""; }; + F8F2D8ED1986B78600997A15 /* DictNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DictNode.h; sourceTree = ""; }; + F8F2D8EE1986B78600997A15 /* DictNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DictNode.m; sourceTree = ""; }; + F8F2D8EF1986B78600997A15 /* IntegerNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IntegerNode.h; sourceTree = ""; }; + F8F2D8F01986B78600997A15 /* IntegerNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IntegerNode.m; sourceTree = ""; }; + F8F2D8F11986B78600997A15 /* PListNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PListNode.h; sourceTree = ""; }; + F8F2D8F21986B78600997A15 /* PListNodeType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PListNodeType.h; sourceTree = ""; }; + F8F2D8F31986B78600997A15 /* RealNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RealNode.h; sourceTree = ""; }; + F8F2D8F41986B78600997A15 /* RealNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RealNode.m; sourceTree = ""; }; + F8F2D8F51986B78600997A15 /* StringNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StringNode.h; sourceTree = ""; }; + F8F2D8F61986B78600997A15 /* StringNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StringNode.m; sourceTree = ""; }; + F8F2D8F71986B78600997A15 /* XMLPlistParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMLPlistParser.h; sourceTree = ""; }; + F8F2D8F81986B78600997A15 /* XMLPlistParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XMLPlistParser.m; sourceTree = ""; }; + F8F2D8F91986B78600997A15 /* XMLPListReader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMLPListReader.h; sourceTree = ""; }; + F8F2D8FA1986B78600997A15 /* XMLPListReader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XMLPListReader.m; sourceTree = ""; }; + F8F2D8FB1986B78600997A15 /* XMLPListWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMLPListWriter.h; sourceTree = ""; }; + F8F2D8FC1986B78600997A15 /* XMLPListWriter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XMLPListWriter.m; sourceTree = ""; }; + F8F2D90B1986B7AF00997A15 /* NSObject_extra.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSObject_extra.h; sourceTree = ""; }; + F8F2D90C1986B7AF00997A15 /* NSObject_extra.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSObject_extra.m; sourceTree = ""; }; + F8F2D90E1986B80800997A15 /* CryptoKey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CryptoKey.h; sourceTree = ""; }; + F8F2D90F1986B80800997A15 /* CryptoKey.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CryptoKey.m; sourceTree = ""; }; + F8F2D9101986B80800997A15 /* OpenSSLCryptoKey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OpenSSLCryptoKey.h; sourceTree = ""; }; + F8F2D9111986B80800997A15 /* OpenSSLCryptoKey.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OpenSSLCryptoKey.m; sourceTree = ""; }; + F8F2D9151986B92100997A15 /* SFTPServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SFTPServer.h; sourceTree = ""; }; + F8F2D9161986B92100997A15 /* SFTPServer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SFTPServer.m; sourceTree = ""; }; + F8F2D9231986B98600997A15 /* BlobKey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BlobKey.h; sourceTree = ""; }; + F8F2D9241986B98600997A15 /* BlobKey.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BlobKey.m; sourceTree = ""; }; + F8F2D9271986B9CE00997A15 /* StorageType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StorageType.h; sourceTree = ""; }; + F8F2D9281986B9DF00997A15 /* SHA1Hash.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SHA1Hash.h; sourceTree = ""; }; + F8F2D9291986B9DF00997A15 /* SHA1Hash.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SHA1Hash.m; sourceTree = ""; }; + F8F2D92C1986BA1400997A15 /* Commit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Commit.h; sourceTree = ""; }; + F8F2D92D1986BA1400997A15 /* Commit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Commit.m; sourceTree = ""; }; + F8F2D92F1986BA2200997A15 /* CommitFailedFile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CommitFailedFile.h; sourceTree = ""; }; + F8F2D9301986BA2200997A15 /* CommitFailedFile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CommitFailedFile.m; sourceTree = ""; }; + F8F2D9321986BA2B00997A15 /* ArqSalt.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ArqSalt.h; sourceTree = ""; }; + F8F2D9331986BA2B00997A15 /* ArqSalt.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ArqSalt.m; sourceTree = ""; }; + F8F2D9351986BA4900997A15 /* NSFileManager_extra.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSFileManager_extra.h; sourceTree = ""; }; + F8F2D9361986BA4900997A15 /* NSFileManager_extra.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSFileManager_extra.m; sourceTree = ""; }; + F8F2D9381986BA6900997A15 /* UserLibrary_Arq.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UserLibrary_Arq.h; sourceTree = ""; }; + F8F2D9391986BA6900997A15 /* UserLibrary_Arq.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UserLibrary_Arq.m; sourceTree = ""; }; + F8F2D93B1986BA7900997A15 /* UserLibrary.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UserLibrary.h; sourceTree = ""; }; + F8F2D93C1986BA7900997A15 /* UserLibrary.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UserLibrary.m; sourceTree = ""; }; + F8F2D93E1986BA9B00997A15 /* Bucket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Bucket.h; sourceTree = ""; }; + F8F2D93F1986BA9B00997A15 /* Bucket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Bucket.m; sourceTree = ""; }; + F8F2D9401986BA9B00997A15 /* BucketExclude.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BucketExclude.h; sourceTree = ""; }; + F8F2D9411986BA9B00997A15 /* BucketExclude.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BucketExclude.m; sourceTree = ""; }; + F8F2D9421986BA9B00997A15 /* BucketExcludeSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BucketExcludeSet.h; sourceTree = ""; }; + F8F2D9431986BA9B00997A15 /* BucketExcludeSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BucketExcludeSet.m; sourceTree = ""; }; + F8F2D9471986BAD200997A15 /* Repo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Repo.h; sourceTree = ""; }; + F8F2D9481986BAD200997A15 /* Repo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Repo.m; sourceTree = ""; }; + F8F2D94A1986BB4900997A15 /* NSString_slashed.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSString_slashed.h; sourceTree = ""; }; + F8F2D94B1986BB4900997A15 /* NSString_slashed.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSString_slashed.m; sourceTree = ""; }; + F8F2D94D1986BD1600997A15 /* TargetSchedule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TargetSchedule.h; sourceTree = ""; }; + F8F2D94E1986BD1600997A15 /* TargetSchedule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TargetSchedule.m; sourceTree = ""; }; + F8F2D9501986BE2A00997A15 /* Fark.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Fark.h; sourceTree = ""; }; + F8F2D9511986BE2A00997A15 /* FarkImpl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FarkImpl.h; sourceTree = ""; }; + F8F2D9521986BE2A00997A15 /* FarkImpl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FarkImpl.m; sourceTree = ""; }; + F8F2D9541986BE3C00997A15 /* PackId.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PackId.h; sourceTree = ""; }; + F8F2D9551986BE3C00997A15 /* PackId.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PackId.m; sourceTree = ""; }; + F8F2D9571986BE4E00997A15 /* PackIndex.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PackIndex.h; sourceTree = ""; }; + F8F2D9581986BE4E00997A15 /* PackIndex.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PackIndex.m; sourceTree = ""; }; + F8F2D9591986BE4E00997A15 /* PackIndexEntry.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PackIndexEntry.h; sourceTree = ""; }; + F8F2D95A1986BE4E00997A15 /* PackIndexEntry.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PackIndexEntry.m; sourceTree = ""; }; + F8F2D95D1986BE6100997A15 /* Node.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Node.h; sourceTree = ""; }; + F8F2D95E1986BE6100997A15 /* Node.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Node.m; sourceTree = ""; }; + F8F2D95F1986BE6100997A15 /* Tree.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Tree.h; sourceTree = ""; }; + F8F2D9601986BE6100997A15 /* Tree.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Tree.m; sourceTree = ""; }; + F8F2D9631986BE7600997A15 /* NSData-GZip.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData-GZip.h"; sourceTree = ""; }; + F8F2D9641986BE7600997A15 /* NSData-GZip.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData-GZip.m"; sourceTree = ""; }; + F8F2D9661986BEA300997A15 /* BlobKeyIO.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BlobKeyIO.h; sourceTree = ""; }; + F8F2D9671986BEA300997A15 /* BlobKeyIO.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BlobKeyIO.m; sourceTree = ""; }; + F8F2D9691986BF5100997A15 /* GunzipInputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GunzipInputStream.h; sourceTree = ""; }; + F8F2D96A1986BF5100997A15 /* GunzipInputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GunzipInputStream.m; sourceTree = ""; }; + F8F2D96C1986BF6800997A15 /* PackSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PackSet.h; sourceTree = ""; }; + F8F2D96D1986BF6800997A15 /* PackSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PackSet.m; sourceTree = ""; }; + F8F2D96F1986C09300997A15 /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = System/Library/Frameworks/IOKit.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -528,6 +560,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + F8F2D9701986C09300997A15 /* IOKit.framework in Frameworks */, 8DD76F9C0486AA7600D96B5E /* Foundation.framework in Frameworks */, F805B8891160EB39007EC01E /* libcrypto.dylib in Frameworks */, F805B88B1160EB3A007EC01E /* libicucore.dylib in Frameworks */, @@ -540,29 +573,17 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - F83C1ACA11CA7C170001958F /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - F83C1ACB11CA7C170001958F /* Foundation.framework in Frameworks */, - F83C1ACC11CA7C170001958F /* libcrypto.dylib in Frameworks */, - F83C1ACD11CA7C170001958F /* libicucore.dylib in Frameworks */, - F83C1ACE11CA7C170001958F /* libssl.dylib in Frameworks */, - F83C1ACF11CA7C170001958F /* SystemConfiguration.framework in Frameworks */, - F83C1AD011CA7C170001958F /* CoreFoundation.framework in Frameworks */, - F83C1AD111CA7C170001958F /* Security.framework in Frameworks */, - F83C1AD211CA7C170001958F /* CoreServices.framework in Frameworks */, - F89A20A513FAE5300071D321 /* libz.dylib in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 08FB7794FE84155DC02AAC07 /* arq_restore */ = { isa = PBXGroup; children = ( - 08FB7795FE84155DC02AAC07 /* Source */, + F8F2D96F1986C09300997A15 /* IOKit.framework */, + F829512A198683D5001DC91B /* libssh2 */, + F829512019868345001DC91B /* cocoastack */, + F8F2D92B1986BA0700997A15 /* repo */, + 08FB7795FE84155DC02AAC07 /* arq_restore */, 08FB779DFE84155DC02AAC07 /* External Frameworks and Libraries */, 1AB674ADFE9D54B511CA2CBB /* Products */, F89A204513FAE29E0071D321 /* libz.dylib */, @@ -571,48 +592,48 @@ name = arq_restore; sourceTree = ""; }; - 08FB7795FE84155DC02AAC07 /* Source */ = { + 08FB7795FE84155DC02AAC07 /* arq_restore */ = { isa = PBXGroup; children = ( - F805B8671160EA7C007EC01E /* crypto */, - F805B7C91160E445007EC01E /* http */, - F805B8081160E7A1007EC01E /* io */, - F805B7401160DCFE007EC01E /* plist */, - F89A1EB613FAC3750071D321 /* repo */, - F805B7651160DD60007EC01E /* s3 */, - F805B7A61160DEF2007EC01E /* shared */, - F8146BE716EB704A006AD471 /* storage */, - F8F4D1E0121D7BC3002D09C1 /* AppKeychain.h */, - F8F4D1E1121D7BC3002D09C1 /* AppKeychain.m */, - F805B54B1160D3E6007EC01E /* ArqRestoreCommand.h */, - F805B54C1160D3E6007EC01E /* ArqRestoreCommand.m */, - F83C1A6111CA7BD20001958F /* ArqVerifyCommand.h */, - F83C1A6211CA7BD20001958F /* ArqVerifyCommand.m */, 08FB7796FE84155DC02AAC07 /* arq_restore.m */, - F83C1A5F11CA7A6B0001958F /* arq_verify.m */, 32A70AAB03705E1F00C91783 /* arq_restore_Prefix.pch */, - F89A1F4313FAC6C40071D321 /* BlobKey.h */, - F89A1F4413FAC6C40071D321 /* BlobKey.m */, - F8146BE916EB70EE006AD471 /* BlobKeyIO.h */, - F8146BEA16EB70EE006AD471 /* BlobKeyIO.m */, - F83C1D0811CA929D0001958F /* BucketVerifier.h */, - F83C1D0911CA929D0001958F /* BucketVerifier.m */, - F8D67F6E1161443600CC270E /* RestoreNode.h */, - F8D67F6F1161443600CC270E /* RestoreNode.m */, - F8D677FE1160F26A00CC270E /* Restorer.h */, - F8D677FF1160F26A00CC270E /* Restorer.m */, - F8F4D67C121DA542002D09C1 /* UserAndComputer.h */, - F8F4D67D121DA542002D09C1 /* UserAndComputer.m */, - F89A1F3C13FAC6820071D321 /* UserLibrary_Arq.h */, - F89A1F3D13FAC6820071D321 /* UserLibrary_Arq.m */, - F81426D514541A6C00D7E50A /* BackupSet.h */, - F81426D614541A6C00D7E50A /* BackupSet.m */, - F8373E58147A8DEC005AFBE6 /* ReflogPrinter.h */, - F8373E59147A8DEC005AFBE6 /* ReflogPrinter.m */, - F8373E0D14794D01005AFBE6 /* ReflogEntry.h */, - F8373E0E14794D01005AFBE6 /* ReflogEntry.m */, + F83F9B2B1983303F007CBFB4 /* ArqRestoreCommand.h */, + F83F9B2C1983303F007CBFB4 /* ArqRestoreCommand.m */, + F8F2D9321986BA2B00997A15 /* ArqSalt.h */, + F8F2D9331986BA2B00997A15 /* ArqSalt.m */, + F8F2D8661986B57D00997A15 /* BackupSet.h */, + F8F2D8671986B58000997A15 /* BackupSet.m */, + F8F2D8801986B63400997A15 /* BaseTargetConnection.h */, + F8F2D8811986B63400997A15 /* BaseTargetConnection.m */, + F8F2D9231986B98600997A15 /* BlobKey.h */, + F8F2D9241986B98600997A15 /* BlobKey.m */, + F8F2D9661986BEA300997A15 /* BlobKeyIO.h */, + F8F2D9671986BEA300997A15 /* BlobKeyIO.m */, + F8F2D93E1986BA9B00997A15 /* Bucket.h */, + F8F2D93F1986BA9B00997A15 /* Bucket.m */, + F8F2D9401986BA9B00997A15 /* BucketExclude.h */, + F8F2D9411986BA9B00997A15 /* BucketExclude.m */, + F8F2D9421986BA9B00997A15 /* BucketExcludeSet.h */, + F8F2D9431986BA9B00997A15 /* BucketExcludeSet.m */, + F8F2D86F1986B5D900997A15 /* GoogleDriveTargetConnection.h */, + F8F2D8701986B5D900997A15 /* GoogleDriveTargetConnection.m */, + F8F2D94A1986BB4900997A15 /* NSString_slashed.h */, + F8F2D94B1986BB4900997A15 /* NSString_slashed.m */, + F8F2D8691986B5CC00997A15 /* S3TargetConnection.h */, + F8F2D86A1986B5CC00997A15 /* S3TargetConnection.m */, + F8F2D86B1986B5CC00997A15 /* SFTPTargetConnection.h */, + F8F2D86C1986B5CC00997A15 /* SFTPTargetConnection.m */, + F829DC33198697EB00D637E0 /* Target.h */, + F829DC34198697EB00D637E0 /* Target.m */, + F8F2D94D1986BD1600997A15 /* TargetSchedule.h */, + F8F2D94E1986BD1600997A15 /* TargetSchedule.m */, + F829523D19868EBB001DC91B /* TargetConnection.h */, + F8F2D8DE1986B74400997A15 /* UserAndComputer.h */, + F8F2D8DF1986B74400997A15 /* UserAndComputer.m */, + F8F2D9381986BA6900997A15 /* UserLibrary_Arq.h */, + F8F2D9391986BA6900997A15 /* UserLibrary_Arq.m */, ); - name = Source; + name = arq_restore; sourceTree = ""; }; 08FB779DFE84155DC02AAC07 /* External Frameworks and Libraries */ = { @@ -637,275 +658,478 @@ isa = PBXGroup; children = ( 8DD76FA10486AA7600D96B5E /* arq_restore */, - F83C1AD711CA7C170001958F /* arq_verify */, ); name = Products; sourceTree = ""; }; - F805B7401160DCFE007EC01E /* plist */ = { + F829512019868345001DC91B /* cocoastack */ = { isa = PBXGroup; children = ( - F8987231121EB68900F07D76 /* BinaryPListReader.h */, - F8987232121EB68900F07D76 /* BinaryPListReader.m */, - F8987233121EB68900F07D76 /* BinaryPListWriter.h */, - F8987234121EB68900F07D76 /* BinaryPListWriter.m */, - F805B7411160DCFE007EC01E /* ArrayNode.h */, - F805B7421160DCFE007EC01E /* ArrayNode.m */, - F805B7431160DCFE007EC01E /* BooleanNode.h */, - F805B7441160DCFE007EC01E /* BooleanNode.m */, - F805B7451160DCFE007EC01E /* DictNode.h */, - F805B7461160DCFE007EC01E /* DictNode.m */, - F805B7471160DCFE007EC01E /* IntegerNode.h */, - F805B7481160DCFE007EC01E /* IntegerNode.m */, - F805B7491160DCFE007EC01E /* PListNode.h */, - F805B74A1160DCFE007EC01E /* PListNodeType.h */, - F805B74B1160DCFE007EC01E /* RealNode.h */, - F805B74C1160DCFE007EC01E /* RealNode.m */, - F805B74D1160DCFE007EC01E /* StringNode.h */, - F805B74E1160DCFE007EC01E /* StringNode.m */, - F805B74F1160DCFE007EC01E /* XMLPListReader.h */, - F805B7501160DCFE007EC01E /* XMLPListReader.m */, - F805B7511160DCFE007EC01E /* XMLPListWriter.h */, - F805B7521160DCFE007EC01E /* XMLPListWriter.m */, + F829523519868E70001DC91B /* aws */, + F829519D19868D90001DC91B /* crypto */, + F829524419868EF8001DC91B /* glacier */, + F829521519868E0D001DC91B /* googledrive */, + F82951A019868D90001DC91B /* http */, + F829521019868DC7001DC91B /* io */, + F829522519868E4F001DC91B /* json */, + F8F2D8E41986B77600997A15 /* plist */, + F8F2D8721986B60200997A15 /* remotefs */, + F8F2D9141986B91200997A15 /* sftp */, + F829516D198683F9001DC91B /* s3 */, + F829512119868345001DC91B /* shared */, + F8F2D8AE1986B6BA00997A15 /* sns */, + F8F2D8AF1986B6BA00997A15 /* sqs */, + F8F2D9261986B9AB00997A15 /* storage */, ); - path = plist; + path = cocoastack; sourceTree = ""; }; - F805B7651160DD60007EC01E /* s3 */ = { + F829512119868345001DC91B /* shared */ = { isa = PBXGroup; children = ( - F841672415E27A5200B6ECED /* RemoteS3Signer.h */, - F841672515E27A5200B6ECED /* RemoteS3Signer.m */, - F84166F115E278F500B6ECED /* S3Owner.h */, - F84166F215E278F500B6ECED /* S3Owner.m */, - F84166F315E278F500B6ECED /* S3Region.h */, - F84166F415E278F500B6ECED /* S3Region.m */, - F89A205113FAE2DA0071D321 /* LocalS3Signer.h */, - F89A205213FAE2DA0071D321 /* LocalS3Signer.m */, - F805B76A1160DD60007EC01E /* NSError_S3.h */, - F805B76B1160DD60007EC01E /* NSError_S3.m */, - F805B76C1160DD60007EC01E /* PathReceiver.h */, - F805B76D1160DD60007EC01E /* PathReceiver.m */, - F805B7701160DD60007EC01E /* S3AuthorizationProvider.h */, - F805B7711160DD60007EC01E /* S3AuthorizationProvider.m */, - F805B7721160DD60007EC01E /* S3Lister.h */, - F805B7731160DD60007EC01E /* S3Lister.m */, - F805B7741160DD60007EC01E /* S3ObjectMetadata.h */, - F805B7751160DD60007EC01E /* S3ObjectMetadata.m */, - F805B7761160DD60007EC01E /* S3ObjectReceiver.h */, - F805B7771160DD60007EC01E /* S3ObjectReceiver.m */, - F805B77A1160DD60007EC01E /* S3Receiver.h */, - F805B77B1160DD60007EC01E /* S3Request.h */, - F805B77C1160DD60007EC01E /* S3Request.m */, - F805B77D1160DD60007EC01E /* S3Service.h */, - F805B77E1160DD60007EC01E /* S3Service.m */, - F89A207F13FAE3810071D321 /* S3Signer.h */, - ); - path = s3; - sourceTree = ""; - }; - F805B7A61160DEF2007EC01E /* shared */ = { - isa = PBXGroup; - children = ( - F841670915E279DE00B6ECED /* DNS_SDErrors.h */, - F841670A15E279DE00B6ECED /* DNS_SDErrors.m */, - F841670315E279BB00B6ECED /* Sysctl.h */, - F841670415E279BB00B6ECED /* Sysctl.m */, - F89A1FD013FAD6BE0071D321 /* HSLog.h */, - F89A1FD113FAD6BE0071D321 /* HSLog.m */, - F89A1F4B13FAC73D0071D321 /* Computer.h */, - F89A1F4C13FAC73D0071D321 /* Computer.m */, - F8D6788A1160F8E500CC270E /* BinarySHA1.h */, - F8D6788B1160F8E500CC270E /* BinarySHA1.m */, - F805B7B81160E3AF007EC01E /* Blob.h */, - F805B7B91160E3AF007EC01E /* Blob.m */, - F805B7D61160E456007EC01E /* BlobACL.h */, - F805B7D71160E456007EC01E /* BlobACL.m */, - F8D67D031161384100CC270E /* FileACL.h */, - F8D67D041161384100CC270E /* FileACL.m */, - F89A1F7513FAC8930071D321 /* NSData-GZip.h */, - F89A1F7613FAC8930071D321 /* NSData-GZip.m */, - F805B7DE1160E48B007EC01E /* NSErrorCodes.h */, - F8F4D19B121D77E1002D09C1 /* NSError_extra.h */, - F8F4D19C121D77E1002D09C1 /* NSError_extra.m */, - F89A1F5D13FAC8020071D321 /* NSObject_extra.h */, - F89A1F5E13FAC8020071D321 /* NSObject_extra.m */, - F8D678871160F8CD00CC270E /* NSString_extra.h */, - F8D678881160F8CD00CC270E /* NSString_extra.m */, - F805B8631160EA15007EC01E /* NSXMLNode_extra.h */, - F805B8641160EA15007EC01E /* NSXMLNode_extra.m */, - F8D67D051161384100CC270E /* OSStatusDescription.h */, - F8D67D061161384100CC270E /* OSStatusDescription.m */, - F805B7FC1160E764007EC01E /* RFC822.h */, - F805B7FD1160E764007EC01E /* RFC822.m */, - F805B7A71160DEF2007EC01E /* RegexKitLite.h */, - F805B7A81160DEF2007EC01E /* RegexKitLite.m */, - F805B7DF1160E48B007EC01E /* ServerBlob.h */, - F805B7E01160E48B007EC01E /* ServerBlob.m */, - F8D6763E1160F22800CC270E /* SetNSError.h */, - F89A1F2913FAC6700071D321 /* UserLibrary.h */, - F89A1F2A13FAC6700071D321 /* UserLibrary.m */, + F829523919868E94001DC91B /* CWLSynthesizeSingleton.h */, + F8F2D8E11986B75C00997A15 /* Computer.h */, + F8F2D8E21986B75C00997A15 /* Computer.m */, + F8F2D9631986BE7600997A15 /* NSData-GZip.h */, + F8F2D9641986BE7600997A15 /* NSData-GZip.m */, + F829512719868394001DC91B /* NSError_extra.h */, + F829512819868394001DC91B /* NSError_extra.m */, + F829512519868379001DC91B /* NSErrorCodes.h */, + F8F2D90B1986B7AF00997A15 /* NSObject_extra.h */, + F8F2D90C1986B7AF00997A15 /* NSObject_extra.m */, + F8F2D8A81986B68800997A15 /* NSXMLNode_extra.h */, + F8F2D8A91986B68800997A15 /* NSXMLNode_extra.m */, + F829512219868345001DC91B /* HSLog.h */, + F829512319868345001DC91B /* HSLog.m */, + F829523E19868EC7001DC91B /* NSString_extra.h */, + F829523F19868EC7001DC91B /* NSString_extra.m */, + F829DBFA19868FCA00D637E0 /* RFC822.h */, + F829DBFB19868FCA00D637E0 /* RFC822.m */, + F829521419868DEA001DC91B /* RegexKitLite.h */, + F829521219868DE6001DC91B /* RegexKitLite.m */, + F829512619868379001DC91B /* SetNSError.h */, + F829DC1D1986923100D637E0 /* Sysctl.h */, + F829DC1E1986923100D637E0 /* Sysctl.m */, + F8F2D93B1986BA7900997A15 /* UserLibrary.h */, + F8F2D93C1986BA7900997A15 /* UserLibrary.m */, ); path = shared; sourceTree = ""; }; - F805B7C91160E445007EC01E /* http */ = { + F829512A198683D5001DC91B /* libssh2 */ = { isa = PBXGroup; children = ( - F84166EB15E278BC00B6ECED /* CFNetwork.h */, - F84166EC15E278BC00B6ECED /* CFNetwork.m */, - F84166E815E2787B00B6ECED /* HTTPConnectionDelegate.h */, - F84166DE15E2785100B6ECED /* CFHTTPConnection.h */, - F84166DF15E2785100B6ECED /* CFHTTPConnection.m */, - F84166E015E2785100B6ECED /* CFHTTPInputStream.h */, - F84166E115E2785100B6ECED /* CFHTTPInputStream.m */, - F84166D415E2782600B6ECED /* HTTPConnectionFactory.h */, - F84166D515E2782600B6ECED /* HTTPConnectionFactory.m */, - F84166D615E2782600B6ECED /* HTTPTimeoutSetting.h */, - F84166D715E2782600B6ECED /* HTTPTimeoutSetting.m */, - F89A1EEA13FAC4E30071D321 /* URLConnection.h */, - F89A1EEB13FAC4E30071D321 /* URLConnection.m */, - F805B7CA1160E445007EC01E /* HTTP.h */, - F805B7CB1160E445007EC01E /* HTTPConnection.h */, - F805B7F91160E73D007EC01E /* RFC2616DateFormatter.h */, - F805B7FA1160E73D007EC01E /* RFC2616DateFormatter.m */, + F829512B198683D5001DC91B /* include */, + F829513D198683D5001DC91B /* lib */, + F829513F198683D5001DC91B /* src */, ); - path = http; + path = libssh2; sourceTree = ""; }; - F805B8081160E7A1007EC01E /* io */ = { + F829512B198683D5001DC91B /* include */ = { isa = PBXGroup; children = ( - F84166FB15E2792500B6ECED /* NetMonitor.h */, - F84166FC15E2792500B6ECED /* NetMonitor.m */, - F89A205713FAE3010071D321 /* DataOutputStream.h */, - F89A205813FAE3010071D321 /* DataOutputStream.m */, - F89A1F6113FAC8270071D321 /* CryptoKey.h */, - F89A1F6213FAC8270071D321 /* CryptoKey.m */, - F8D678A31160FA5F00CC270E /* BooleanIO.h */, - F8D678A41160FA5F00CC270E /* BooleanIO.m */, - F805B8331160E882007EC01E /* BufferedInputStream.h */, - F898755F121EBD9600F07D76 /* BufferedInputStream.m */, - F89A1F5113FAC78F0071D321 /* BufferedOutputStream.h */, - F89A1F5213FAC78F0071D321 /* BufferedOutputStream.m */, - F805B8211160E857007EC01E /* ChunkedInputStream.h */, - F805B8221160E857007EC01E /* ChunkedInputStream.m */, - F8D678721160F85D00CC270E /* CryptInputStream.h */, - F8D678731160F85D00CC270E /* CryptInputStream.m */, - F8D678781160F8A000CC270E /* DataIO.h */, - F8D678791160F8A000CC270E /* DataIO.m */, - F805B86D1160EAC1007EC01E /* DataInputStreamFactory.h */, - F805B86E1160EAC1007EC01E /* DataInputStreamFactory.m */, - F805B82D1160E86E007EC01E /* DataInputStream.h */, - F805B82E1160E86E007EC01E /* DataInputStream.m */, - F8D6787A1160F8A000CC270E /* DateIO.h */, - F8D6787B1160F8A000CC270E /* DateIO.m */, - F8D6786D1160F84600CC270E /* DecryptedInputStream.h */, - F8D6786E1160F84600CC270E /* DecryptedInputStream.m */, - F8D6787C1160F8A000CC270E /* DoubleIO.h */, - F8D6787D1160F8A000CC270E /* DoubleIO.m */, - F89A1EF913FAC5970071D321 /* EncryptedInputStreamFactory.h */, - F89A1EFA13FAC5970071D321 /* EncryptedInputStreamFactory.m */, - F8D678A61160FA6A00CC270E /* EncryptedInputStream.h */, - F8D678A71160FA6A00CC270E /* EncryptedInputStream.m */, - F805B8271160E861007EC01E /* FDInputStream.h */, - F805B8281160E861007EC01E /* FDInputStream.m */, - F805B8291160E861007EC01E /* FDOutputStream.h */, - F805B82A1160E861007EC01E /* FDOutputStream.m */, - F8D678751160F86E00CC270E /* FileInputStream.h */, - F8D678761160F86E00CC270E /* FileInputStream.m */, - F8D6789E1160FA4800CC270E /* FileInputStreamFactory.h */, - F8D6789F1160FA4800CC270E /* FileInputStreamFactory.m */, - F8D6789B1160FA3900CC270E /* FileOutputStream.h */, - F8D6789C1160FA3900CC270E /* FileOutputStream.m */, - F805B8231160E857007EC01E /* FixedLengthInputStream.h */, - F805B8241160E857007EC01E /* FixedLengthInputStream.m */, - F89A1F6513FAC83E0071D321 /* GunzipInputStream.h */, - F89A1F6613FAC83E0071D321 /* GunzipInputStream.m */, - F805B8661160EA36007EC01E /* InputStreamFactory.h */, - F805B8191160E838007EC01E /* InputStreams.h */, - F805B81A1160E838007EC01E /* InputStreams.m */, - F805B80B1160E7C3007EC01E /* InputStream.h */, - F8D6787E1160F8A000CC270E /* IntegerIO.h */, - F8D6787F1160F8A000CC270E /* IntegerIO.m */, - F8F4D19E121D78AD002D09C1 /* MonitoredInputStream.h */, - F8F4D19F121D78AD002D09C1 /* MonitoredInputStream.m */, - F805B8391160E8DD007EC01E /* NSData-InputStream.h */, - F805B83A1160E8DD007EC01E /* NSData-InputStream.m */, - F8D678981160FA2A00CC270E /* NSFileManager_extra.h */, - F8D678991160FA2A00CC270E /* NSFileManager_extra.m */, - F805B8361160E8A0007EC01E /* OutputStream.h */, - F805B83C1160E900007EC01E /* StreamPair.h */, - F805B8401160E90F007EC01E /* Streams.h */, - F805B8411160E90F007EC01E /* Streams.m */, - F8D678801160F8A000CC270E /* StringIO.h */, - F8D678811160F8A000CC270E /* StringIO.m */, - F805B8301160E878007EC01E /* Writer.h */, - F805B8311160E878007EC01E /* Writer.m */, + F829512C198683D5001DC91B /* channel.h */, + F829512D198683D5001DC91B /* comp.h */, + F829512E198683D5001DC91B /* crypto.h */, + F829512F198683D5001DC91B /* libgcrypt.h */, + F8295130198683D5001DC91B /* libssh2.h */, + F8295131198683D5001DC91B /* libssh2_config.h */, + F8295132198683D5001DC91B /* libssh2_priv.h */, + F8295133198683D5001DC91B /* libssh2_publickey.h */, + F8295134198683D5001DC91B /* libssh2_sftp.h */, + F8295135198683D5001DC91B /* mac.h */, + F8295136198683D5001DC91B /* misc.h */, + F8295137198683D5001DC91B /* openssl.h */, + F8295138198683D5001DC91B /* packet.h */, + F8295139198683D5001DC91B /* session.h */, + F829513A198683D5001DC91B /* sftp.h */, + F829513B198683D5001DC91B /* transport.h */, + F829513C198683D5001DC91B /* userauth.h */, ); - path = io; + path = include; sourceTree = ""; }; - F805B8671160EA7C007EC01E /* crypto */ = { + F829513D198683D5001DC91B /* lib */ = { isa = PBXGroup; children = ( - F89A1F5913FAC7D50071D321 /* Encryption.h */, - F89A1F5A13FAC7D50071D321 /* Encryption.m */, - F8D6781B1160F4FD00CC270E /* SHA1Hash.h */, - F8D6781C1160F4FD00CC270E /* SHA1Hash.m */, - F805B8681160EA83007EC01E /* NSData-Base64Extensions.h */, - F805B8691160EA83007EC01E /* NSData-Base64Extensions.m */, - F8D678631160F7FE00CC270E /* NSData-Encrypt.h */, - F8D678641160F7FE00CC270E /* NSData-Encrypt.m */, - F8D678681160F81100CC270E /* OpenSSL.h */, - F8D678691160F81100CC270E /* OpenSSL.m */, + F829513E198683D5001DC91B /* libssh2.1.dylib */, + ); + path = lib; + sourceTree = ""; + }; + F829513F198683D5001DC91B /* src */ = { + isa = PBXGroup; + children = ( + F8295140198683D5001DC91B /* agent.c */, + F8295141198683D5001DC91B /* channel.c */, + F8295142198683D5001DC91B /* comp.c */, + F8295143198683D5001DC91B /* crypt.c */, + F8295144198683D5001DC91B /* global.c */, + F8295145198683D5001DC91B /* hostkey.c */, + F8295146198683D5001DC91B /* keepalive.c */, + F8295147198683D5001DC91B /* kex.c */, + F8295148198683D5001DC91B /* knownhost.c */, + F8295149198683D5001DC91B /* libgcrypt.c */, + F829514A198683D5001DC91B /* mac.c */, + F829514B198683D5001DC91B /* misc.c */, + F829514C198683D5001DC91B /* openssl.c */, + F829514D198683D5001DC91B /* packet.c */, + F829514E198683D5001DC91B /* pem.c */, + F829514F198683D5001DC91B /* publickey.c */, + F8295150198683D5001DC91B /* scp.c */, + F8295151198683D5001DC91B /* session.c */, + F8295152198683D5001DC91B /* sftp.c */, + F8295153198683D5001DC91B /* transport.c */, + F8295154198683D5001DC91B /* userauth.c */, + F8295155198683D5001DC91B /* version.c */, + ); + path = src; + sourceTree = ""; + }; + F829516D198683F9001DC91B /* s3 */ = { + isa = PBXGroup; + children = ( + F829516E198683F9001DC91B /* LifecycleConfiguration.h */, + F829516F198683F9001DC91B /* LifecycleConfiguration.m */, + F8295170198683F9001DC91B /* LocalS3Signer.h */, + F8295171198683F9001DC91B /* LocalS3Signer.m */, + F8295172198683F9001DC91B /* PathReceiver.h */, + F8295173198683F9001DC91B /* PathReceiver.m */, + F8295174198683F9001DC91B /* RemoteS3Signer.h */, + F8295175198683F9001DC91B /* RemoteS3Signer.m */, + F8295176198683F9001DC91B /* S3AuthorizationProvider.h */, + F8295177198683F9001DC91B /* S3AuthorizationProvider.m */, + F8295178198683F9001DC91B /* S3DeleteReceiver.h */, + F8295179198683F9001DC91B /* S3DeleteReceiver.m */, + F829517A198683F9001DC91B /* S3ErrorResult.h */, + F829517B198683F9001DC91B /* S3ErrorResult.m */, + F829517C198683F9001DC91B /* S3Lister.h */, + F829517D198683F9001DC91B /* S3Lister.m */, + F829517E198683F9001DC91B /* S3MultiDeleteResponse.h */, + F829517F198683F9001DC91B /* S3MultiDeleteResponse.m */, + F8295180198683F9001DC91B /* S3ObjectMetadata.h */, + F8295181198683F9001DC91B /* S3ObjectMetadata.m */, + F8295182198683F9001DC91B /* S3ObjectReceiver.h */, + F8295183198683F9001DC91B /* S3ObjectReceiver.m */, + F8295184198683F9001DC91B /* S3Owner.h */, + F8295185198683F9001DC91B /* S3Owner.m */, + F8295186198683F9001DC91B /* S3Receiver.h */, + F8295187198683F9001DC91B /* S3Request.h */, + F8295188198683F9001DC91B /* S3Request.m */, + F8295189198683F9001DC91B /* S3Service.h */, + F829518A198683F9001DC91B /* S3Service.m */, + F829518B198683F9001DC91B /* S3Signer.h */, + ); + path = s3; + sourceTree = ""; + }; + F829519D19868D90001DC91B /* crypto */ = { + isa = PBXGroup; + children = ( + F8F2D90E1986B80800997A15 /* CryptoKey.h */, + F8F2D90F1986B80800997A15 /* CryptoKey.m */, + F8F2D9101986B80800997A15 /* OpenSSLCryptoKey.h */, + F8F2D9111986B80800997A15 /* OpenSSLCryptoKey.m */, + F829DC1A1986921E00D637E0 /* MD5Hash.h */, + F829DC1B1986921E00D637E0 /* MD5Hash.m */, + F829519E19868D90001DC91B /* NSData-Base64Extensions.h */, + F829519F19868D90001DC91B /* NSData-Base64Extensions.m */, + F829DC2F1986930300D637E0 /* OpenSSL.h */, + F829DC301986930300D637E0 /* OpenSSL.m */, + F8F2D9281986B9DF00997A15 /* SHA1Hash.h */, + F8F2D9291986B9DF00997A15 /* SHA1Hash.m */, + F8F2D8AB1986B6A300997A15 /* SHA256Hash.h */, + F8F2D8AC1986B6A300997A15 /* SHA256Hash.m */, ); path = crypto; sourceTree = ""; }; - F8146BE716EB704A006AD471 /* storage */ = { + F82951A019868D90001DC91B /* http */ = { isa = PBXGroup; children = ( - F8146BE816EB7054006AD471 /* StorageType.h */, + F82951A119868D90001DC91B /* HTTP.h */, + F82951A219868D90001DC91B /* HTTPConnection.h */, + F82951A319868D90001DC91B /* HTTPConnectionFactory.h */, + F82951A419868D90001DC91B /* HTTPConnectionFactory.m */, + F82951A519868D90001DC91B /* HTTPInputStream.h */, + F82951A619868D90001DC91B /* HTTPInputStream.m */, + F82951A719868D90001DC91B /* HTTPThrottle.h */, + F82951A819868D90001DC91B /* HTTPThrottle.m */, + F82951AB19868D90001DC91B /* NSDictionary_HTTP.h */, + F82951AC19868D90001DC91B /* NSDictionary_HTTP.m */, + F82951AD19868D90001DC91B /* RFC2616DateFormatter.h */, + F82951AE19868D90001DC91B /* RFC2616DateFormatter.m */, + F82951B319868D90001DC91B /* URLConnection.h */, + F82951B419868D90001DC91B /* URLConnection.m */, ); - name = storage; + path = http; sourceTree = ""; }; - F89A1EB613FAC3750071D321 /* repo */ = { + F829521019868DC7001DC91B /* io */ = { isa = PBXGroup; children = ( - F89A200913FADAD70071D321 /* ArqSalt.h */, - F89A200A13FADAD70071D321 /* ArqSalt.m */, - F8F4D1C1121D79AC002D09C1 /* ArqFark.h */, - F8F4D1C2121D79AC002D09C1 /* ArqFark.m */, - F8F4D1AC121D7990002D09C1 /* ArqPackSet.h */, - F8F4D1AD121D7990002D09C1 /* ArqPackSet.m */, - F8F4D1AE121D7990002D09C1 /* ArqRepo.h */, - F8F4D1AF121D7990002D09C1 /* ArqRepo.m */, - F8D6785B1160F7CE00CC270E /* Commit.h */, - F8D6785C1160F7CF00CC270E /* Commit.m */, - F8D678AD1160FAD900CC270E /* CommitFailedFile.h */, - F8D678AE1160FAD900CC270E /* CommitFailedFile.m */, - F8D678411160F74A00CC270E /* DiskPack.h */, - F8D678421160F74A00CC270E /* DiskPack.m */, - F8D678431160F74A00CC270E /* DiskPackIndex.h */, - F8D678441160F74A00CC270E /* DiskPackIndex.m */, - F8F4D1E5121D7DA2002D09C1 /* FarkPath.h */, - F8F4D1E6121D7DA2002D09C1 /* FarkPath.m */, - F8D67CE91161363A00CC270E /* FileAttributes.h */, - F8D67CEA1161363A00CC270E /* FileAttributes.m */, - F8D678B61160FB2100CC270E /* Node.h */, - F8D678B71160FB2100CC270E /* Node.m */, - F8D6783A1160F70100CC270E /* PackIndexEntry.h */, - F8D6783B1160F70100CC270E /* PackIndexEntry.m */, - F8F4D1FC121D8409002D09C1 /* PackIndexWriter.h */, - F8F4D1FD121D8409002D09C1 /* PackIndexWriter.m */, - F8D6785D1160F7CF00CC270E /* Tree.h */, - F8D6785E1160F7CF00CC270E /* Tree.m */, - F8D67CF01161366100CC270E /* XAttrSet.h */, - F8D67CF11161366100CC270E /* XAttrSet.m */, + F829DC02198691CB00D637E0 /* BooleanIO.h */, + F829DC03198691CB00D637E0 /* BooleanIO.m */, + F829DC17198691D900D637E0 /* BufferedOutputStream.h */, + F829DC18198691D900D637E0 /* BufferedOutputStream.m */, + F829525119868F3C001DC91B /* BufferedInputStream.h */, + F829525219868F3C001DC91B /* BufferedInputStream.m */, + F829524B19868F1E001DC91B /* ChunkedInputStream.h */, + F829524C19868F1E001DC91B /* ChunkedInputStream.m */, + F829DC04198691CB00D637E0 /* DataIO.h */, + F829DC05198691CB00D637E0 /* DataIO.m */, + F829524819868F0F001DC91B /* DataInputStream.h */, + F829524919868F0F001DC91B /* DataInputStream.m */, + F829DC261986924E00D637E0 /* DataOutputStream.h */, + F829DC271986924E00D637E0 /* DataOutputStream.m */, + F829DC011986904C00D637E0 /* DataTransferDelegate.h */, + F829DC06198691CB00D637E0 /* DateIO.h */, + F829DC07198691CB00D637E0 /* DateIO.m */, + F829DC08198691CB00D637E0 /* DoubleIO.h */, + F829DC09198691CB00D637E0 /* DoubleIO.m */, + F829DC201986924100D637E0 /* FDInputStream.h */, + F829DC211986924100D637E0 /* FDInputStream.m */, + F829DC221986924100D637E0 /* FDOutputStream.h */, + F829DC231986924100D637E0 /* FDOutputStream.m */, + F829DC291986927000D637E0 /* FileInputStream.h */, + F829DC2A1986927000D637E0 /* FileInputStream.m */, + F829DC2B1986927000D637E0 /* FileOutputStream.h */, + F829DC2C1986927000D637E0 /* FileOutputStream.m */, + F8F2D9691986BF5100997A15 /* GunzipInputStream.h */, + F8F2D96A1986BF5100997A15 /* GunzipInputStream.m */, + F829521119868DD0001DC91B /* InputStream.h */, + F829524E19868F31001DC91B /* InputStreams.h */, + F829524F19868F31001DC91B /* InputStreams.m */, + F829DC0A198691CB00D637E0 /* IntegerIO.h */, + F829DC0B198691CB00D637E0 /* IntegerIO.m */, + F829DC0C198691CB00D637E0 /* NSErrorIO.h */, + F829DC0D198691CB00D637E0 /* NSErrorIO.m */, + F829524119868ED6001DC91B /* NSData-InputStream.h */, + F829524219868ED6001DC91B /* NSData-InputStream.m */, + F8F2D9351986BA4900997A15 /* NSFileManager_extra.h */, + F8F2D9361986BA4900997A15 /* NSFileManager_extra.m */, + F829523A19868EA4001DC91B /* NetMonitor.h */, + F829523B19868EA4001DC91B /* NetMonitor.m */, + F829DC001986902100D637E0 /* OutputStream.h */, + F829DBFD1986901300D637E0 /* Streams.h */, + F829DBFE1986901300D637E0 /* Streams.m */, + F829DC0E198691CB00D637E0 /* StringIO.h */, + F829DC0F198691CB00D637E0 /* StringIO.m */, ); - name = repo; + path = io; + sourceTree = ""; + }; + F829521519868E0D001DC91B /* googledrive */ = { + isa = PBXGroup; + children = ( + F829521619868E26001DC91B /* GoogleDrive.h */, + F829521719868E26001DC91B /* GoogleDrive.m */, + F829521819868E26001DC91B /* GoogleDriveErrorResult.h */, + F829521919868E26001DC91B /* GoogleDriveErrorResult.m */, + F829521A19868E26001DC91B /* GoogleDriveFactory.h */, + F829521B19868E26001DC91B /* GoogleDriveFactory.m */, + F829521C19868E26001DC91B /* GoogleDriveRequest.h */, + F829521D19868E26001DC91B /* GoogleDriveRequest.m */, + F829521E19868E26001DC91B /* GoogleDriveFolderLister.h */, + F829521F19868E26001DC91B /* GoogleDriveFolderLister.m */, + ); + path = googledrive; + sourceTree = ""; + }; + F829522519868E4F001DC91B /* json */ = { + isa = PBXGroup; + children = ( + F829522619868E59001DC91B /* NSObject+SBJSON.h */, + F829522719868E59001DC91B /* NSObject+SBJSON.m */, + F829522819868E59001DC91B /* NSString+SBJSON.h */, + F829522919868E59001DC91B /* NSString+SBJSON.m */, + F829522A19868E59001DC91B /* SBJsonBase.h */, + F829522B19868E59001DC91B /* SBJsonBase.m */, + F829522C19868E59001DC91B /* SBJsonParser.h */, + F829522D19868E59001DC91B /* SBJsonParser.m */, + F829522E19868E59001DC91B /* SBJsonWriter.h */, + F829522F19868E59001DC91B /* SBJsonWriter.m */, + ); + path = json; + sourceTree = ""; + }; + F829523519868E70001DC91B /* aws */ = { + isa = PBXGroup; + children = ( + F829523619868E83001DC91B /* AWSRegion.h */, + F829523719868E83001DC91B /* AWSRegion.m */, + F8F2D8D11986B70300997A15 /* AWSQueryError.h */, + F8F2D8D21986B70300997A15 /* AWSQueryError.m */, + F8F2D8D31986B70300997A15 /* AWSQueryRequest.h */, + F8F2D8D41986B70300997A15 /* AWSQueryRequest.m */, + F8F2D8D51986B70300997A15 /* AWSQueryResponse.h */, + F8F2D8D61986B70300997A15 /* AWSQueryResponse.m */, + F8F2D8CE1986B6FD00997A15 /* SignatureV2Provider.h */, + F8F2D8CF1986B6FD00997A15 /* SignatureV2Provider.m */, + ); + path = aws; + sourceTree = ""; + }; + F829524419868EF8001DC91B /* glacier */ = { + isa = PBXGroup; + children = ( + F8F2D8DB1986B72900997A15 /* GlacierAuthorization.h */, + F8F2D8DC1986B72900997A15 /* GlacierAuthorization.m */, + F8F2D8831986B64D00997A15 /* GlacierAuthorizationProvider.h */, + F8F2D8841986B64D00997A15 /* GlacierAuthorizationProvider.m */, + F8F2D8861986B66000997A15 /* GlacierJob.h */, + F8F2D8871986B66000997A15 /* GlacierJob.m */, + F8F2D8881986B66000997A15 /* GlacierJobLister.h */, + F8F2D8891986B66000997A15 /* GlacierJobLister.m */, + F8F2D88A1986B66000997A15 /* GlacierRequest.h */, + F8F2D88B1986B66000997A15 /* GlacierRequest.m */, + F8F2D88C1986B66000997A15 /* GlacierResponse.h */, + F8F2D88D1986B66000997A15 /* GlacierResponse.m */, + F8F2D88E1986B66000997A15 /* GlacierService.h */, + F8F2D88F1986B66000997A15 /* GlacierService.m */, + F8F2D8DA1986B72000997A15 /* GlacierSigner.h */, + F829524519868F02001DC91B /* ISO8601Date.h */, + F829524619868F02001DC91B /* ISO8601Date.m */, + F8F2D8901986B66000997A15 /* LocalGlacierSigner.h */, + F8F2D8911986B66000997A15 /* LocalGlacierSigner.m */, + F8F2D8981986B67500997A15 /* NSError_Glacier.h */, + F8F2D8991986B67500997A15 /* NSError_Glacier.m */, + F8F2D89A1986B67500997A15 /* SHA256TreeHash.h */, + F8F2D89B1986B67500997A15 /* SHA256TreeHash.m */, + F8F2D89C1986B67500997A15 /* Vault.h */, + F8F2D89D1986B67500997A15 /* Vault.m */, + F8F2D89E1986B67500997A15 /* VaultDeleter.h */, + F8F2D89F1986B67500997A15 /* VaultDeleter.m */, + F8F2D8A01986B67500997A15 /* VaultDeleterDelegate.h */, + F8F2D8A11986B67500997A15 /* VaultLister.h */, + F8F2D8A21986B67500997A15 /* VaultLister.m */, + ); + path = glacier; + sourceTree = ""; + }; + F8F2D8721986B60200997A15 /* remotefs */ = { + isa = PBXGroup; + children = ( + F8F2D8731986B61800997A15 /* RemoteFS.h */, + F8F2D8741986B61800997A15 /* S3RemoteFS.h */, + F8F2D8751986B61800997A15 /* S3RemoteFS.m */, + F8F2D8761986B61800997A15 /* SFTPRemoteFS.h */, + F8F2D8771986B61800997A15 /* SFTPRemoteFS.m */, + F8F2D8781986B61800997A15 /* GoogleDriveRemoteFS.h */, + F8F2D8791986B61800997A15 /* GoogleDriveRemoteFS.m */, + ); + path = remotefs; + sourceTree = ""; + }; + F8F2D8AE1986B6BA00997A15 /* sns */ = { + isa = PBXGroup; + children = ( + F8F2D8B01986B6E000997A15 /* CreateTopicResponse.h */, + F8F2D8B11986B6E000997A15 /* CreateTopicResponse.m */, + F8F2D8B21986B6E000997A15 /* ListTopicsResponse.h */, + F8F2D8B31986B6E000997A15 /* ListTopicsResponse.m */, + F8F2D8B41986B6E000997A15 /* SNS.h */, + F8F2D8B51986B6E000997A15 /* SNS.m */, + F8F2D8B61986B6E000997A15 /* SubscribeResponse.h */, + F8F2D8B71986B6E000997A15 /* SubscribeResponse.m */, + ); + path = sns; + sourceTree = ""; + }; + F8F2D8AF1986B6BA00997A15 /* sqs */ = { + isa = PBXGroup; + children = ( + F8F2D8BC1986B6E900997A15 /* CreateQueueResponse.h */, + F8F2D8BD1986B6E900997A15 /* CreateQueueResponse.m */, + F8F2D8BE1986B6E900997A15 /* GetQueueAttributesResponse.h */, + F8F2D8BF1986B6E900997A15 /* GetQueueAttributesResponse.m */, + F8F2D8C01986B6E900997A15 /* ListQueuesResponse.h */, + F8F2D8C11986B6E900997A15 /* ListQueuesResponse.m */, + F8F2D8C21986B6E900997A15 /* ReceiveMessageResponse.h */, + F8F2D8C31986B6E900997A15 /* ReceiveMessageResponse.m */, + F8F2D8C41986B6E900997A15 /* SQS.h */, + F8F2D8C51986B6E900997A15 /* SQS.m */, + F8F2D8C61986B6E900997A15 /* SQSMessage.h */, + F8F2D8C71986B6E900997A15 /* SQSMessage.m */, + ); + path = sqs; + sourceTree = ""; + }; + F8F2D8E41986B77600997A15 /* plist */ = { + isa = PBXGroup; + children = ( + F8F2D8E51986B78600997A15 /* ArrayNode.h */, + F8F2D8E61986B78600997A15 /* ArrayNode.m */, + F8F2D8E71986B78600997A15 /* BinaryPListReader.h */, + F8F2D8E81986B78600997A15 /* BinaryPListReader.m */, + F8F2D8E91986B78600997A15 /* BinaryPListWriter.h */, + F8F2D8EA1986B78600997A15 /* BinaryPListWriter.m */, + F8F2D8EB1986B78600997A15 /* BooleanNode.h */, + F8F2D8EC1986B78600997A15 /* BooleanNode.m */, + F8F2D8ED1986B78600997A15 /* DictNode.h */, + F8F2D8EE1986B78600997A15 /* DictNode.m */, + F8F2D8EF1986B78600997A15 /* IntegerNode.h */, + F8F2D8F01986B78600997A15 /* IntegerNode.m */, + F8F2D8F11986B78600997A15 /* PListNode.h */, + F8F2D8F21986B78600997A15 /* PListNodeType.h */, + F8F2D8F31986B78600997A15 /* RealNode.h */, + F8F2D8F41986B78600997A15 /* RealNode.m */, + F8F2D8F51986B78600997A15 /* StringNode.h */, + F8F2D8F61986B78600997A15 /* StringNode.m */, + F8F2D8F71986B78600997A15 /* XMLPlistParser.h */, + F8F2D8F81986B78600997A15 /* XMLPlistParser.m */, + F8F2D8F91986B78600997A15 /* XMLPListReader.h */, + F8F2D8FA1986B78600997A15 /* XMLPListReader.m */, + F8F2D8FB1986B78600997A15 /* XMLPListWriter.h */, + F8F2D8FC1986B78600997A15 /* XMLPListWriter.m */, + ); + path = plist; + sourceTree = ""; + }; + F8F2D9141986B91200997A15 /* sftp */ = { + isa = PBXGroup; + children = ( + F8F2D9151986B92100997A15 /* SFTPServer.h */, + F8F2D9161986B92100997A15 /* SFTPServer.m */, + ); + path = sftp; + sourceTree = ""; + }; + F8F2D9261986B9AB00997A15 /* storage */ = { + isa = PBXGroup; + children = ( + F8F2D9271986B9CE00997A15 /* StorageType.h */, + ); + path = storage; + sourceTree = ""; + }; + F8F2D92B1986BA0700997A15 /* repo */ = { + isa = PBXGroup; + children = ( + F8F2D92C1986BA1400997A15 /* Commit.h */, + F8F2D92D1986BA1400997A15 /* Commit.m */, + F8F2D92F1986BA2200997A15 /* CommitFailedFile.h */, + F8F2D9301986BA2200997A15 /* CommitFailedFile.m */, + F8F2D9501986BE2A00997A15 /* Fark.h */, + F8F2D9511986BE2A00997A15 /* FarkImpl.h */, + F8F2D9521986BE2A00997A15 /* FarkImpl.m */, + F8F2D95D1986BE6100997A15 /* Node.h */, + F8F2D95E1986BE6100997A15 /* Node.m */, + F8F2D9541986BE3C00997A15 /* PackId.h */, + F8F2D9551986BE3C00997A15 /* PackId.m */, + F8F2D9571986BE4E00997A15 /* PackIndex.h */, + F8F2D9581986BE4E00997A15 /* PackIndex.m */, + F8F2D9591986BE4E00997A15 /* PackIndexEntry.h */, + F8F2D95A1986BE4E00997A15 /* PackIndexEntry.m */, + F8F2D96C1986BF6800997A15 /* PackSet.h */, + F8F2D96D1986BF6800997A15 /* PackSet.m */, + F8F2D9471986BAD200997A15 /* Repo.h */, + F8F2D9481986BAD200997A15 /* Repo.m */, + F8F2D95F1986BE6100997A15 /* Tree.h */, + F8F2D9601986BE6100997A15 /* Tree.m */, + ); + path = repo; sourceTree = ""; }; /* End PBXGroup section */ @@ -929,31 +1153,13 @@ productReference = 8DD76FA10486AA7600D96B5E /* arq_restore */; productType = "com.apple.product-type.tool"; }; - F83C1A7111CA7C170001958F /* arq_verify */ = { - isa = PBXNativeTarget; - buildConfigurationList = F83C1AD411CA7C170001958F /* Build configuration list for PBXNativeTarget "arq_verify" */; - buildPhases = ( - F83C1A7211CA7C170001958F /* Sources */, - F83C1ACA11CA7C170001958F /* Frameworks */, - F83C1AD311CA7C170001958F /* CopyFiles */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = arq_verify; - productInstallPath = "$(HOME)/bin"; - productName = arq_restore; - productReference = F83C1AD711CA7C170001958F /* arq_verify */; - productType = "com.apple.product-type.tool"; - }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 08FB7793FE84155DC02AAC07 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0450; + LastUpgradeCheck = 0510; }; buildConfigurationList = 1DEB927808733DD40010E9CD /* Build configuration list for PBXProject "arq_restore" */; compatibilityVersion = "Xcode 3.2"; @@ -970,7 +1176,6 @@ projectRoot = ""; targets = ( 8DD76F960486AA7600D96B5E /* arq_restore */, - F83C1A7111CA7C170001958F /* arq_verify */, ); }; /* End PBXProject section */ @@ -980,231 +1185,170 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - F805B54D1160D3E6007EC01E /* ArqRestoreCommand.m in Sources */, - F805B7531160DCFE007EC01E /* ArrayNode.m in Sources */, - F805B7541160DCFE007EC01E /* BooleanNode.m in Sources */, - F805B7551160DCFE007EC01E /* DictNode.m in Sources */, - F805B7561160DCFE007EC01E /* IntegerNode.m in Sources */, - F805B7571160DCFE007EC01E /* RealNode.m in Sources */, - F805B7581160DCFE007EC01E /* StringNode.m in Sources */, - F805B7591160DCFE007EC01E /* XMLPListReader.m in Sources */, - F805B75A1160DCFE007EC01E /* XMLPListWriter.m in Sources */, - F805B7831160DD60007EC01E /* NSError_S3.m in Sources */, - F805B7841160DD60007EC01E /* PathReceiver.m in Sources */, - F805B7861160DD60007EC01E /* S3AuthorizationProvider.m in Sources */, - F805B7871160DD60007EC01E /* S3Lister.m in Sources */, - F805B7881160DD60007EC01E /* S3ObjectMetadata.m in Sources */, - F805B7891160DD60007EC01E /* S3ObjectReceiver.m in Sources */, - F805B78B1160DD60007EC01E /* S3Request.m in Sources */, - F805B78C1160DD60007EC01E /* S3Service.m in Sources */, - F805B7A91160DEF2007EC01E /* RegexKitLite.m in Sources */, - F805B7BA1160E3AF007EC01E /* Blob.m in Sources */, - F805B7D81160E456007EC01E /* BlobACL.m in Sources */, - F805B7E11160E48B007EC01E /* ServerBlob.m in Sources */, - F805B7FB1160E73D007EC01E /* RFC2616DateFormatter.m in Sources */, - F805B7FE1160E764007EC01E /* RFC822.m in Sources */, - F805B81B1160E838007EC01E /* InputStreams.m in Sources */, - F805B8251160E857007EC01E /* ChunkedInputStream.m in Sources */, - F805B8261160E857007EC01E /* FixedLengthInputStream.m in Sources */, - F805B82B1160E861007EC01E /* FDInputStream.m in Sources */, - F805B82C1160E861007EC01E /* FDOutputStream.m in Sources */, - F805B82F1160E86E007EC01E /* DataInputStream.m in Sources */, - F805B8321160E878007EC01E /* Writer.m in Sources */, - F805B83B1160E8DD007EC01E /* NSData-InputStream.m in Sources */, - F805B8421160E90F007EC01E /* Streams.m in Sources */, - F805B8651160EA15007EC01E /* NSXMLNode_extra.m in Sources */, - F805B86A1160EA83007EC01E /* NSData-Base64Extensions.m in Sources */, - F805B86F1160EAC1007EC01E /* DataInputStreamFactory.m in Sources */, - F8D678001160F26A00CC270E /* Restorer.m in Sources */, - F8D6781D1160F4FD00CC270E /* SHA1Hash.m in Sources */, - F8D6783C1160F70100CC270E /* PackIndexEntry.m in Sources */, - F8D678451160F74A00CC270E /* DiskPack.m in Sources */, - F8D678461160F74A00CC270E /* DiskPackIndex.m in Sources */, - F8D6785F1160F7CF00CC270E /* Commit.m in Sources */, - F8D678601160F7CF00CC270E /* Tree.m in Sources */, - F8D678651160F7FE00CC270E /* NSData-Encrypt.m in Sources */, - F8D6786A1160F81100CC270E /* OpenSSL.m in Sources */, - F8D6786F1160F84600CC270E /* DecryptedInputStream.m in Sources */, - F8D678741160F85D00CC270E /* CryptInputStream.m in Sources */, - F8D678771160F86E00CC270E /* FileInputStream.m in Sources */, - F8D678821160F8A000CC270E /* DataIO.m in Sources */, - F8D678831160F8A000CC270E /* DateIO.m in Sources */, - F8D678841160F8A000CC270E /* DoubleIO.m in Sources */, - F8D678851160F8A000CC270E /* IntegerIO.m in Sources */, - F8D678861160F8A000CC270E /* StringIO.m in Sources */, - F8D678891160F8CD00CC270E /* NSString_extra.m in Sources */, - F8D6788C1160F8E500CC270E /* BinarySHA1.m in Sources */, - F8D6789A1160FA2A00CC270E /* NSFileManager_extra.m in Sources */, - F8D6789D1160FA3900CC270E /* FileOutputStream.m in Sources */, - F8D678A01160FA4800CC270E /* FileInputStreamFactory.m in Sources */, - F8D678A51160FA5F00CC270E /* BooleanIO.m in Sources */, - F8D678A81160FA6A00CC270E /* EncryptedInputStream.m in Sources */, - F8D678AF1160FAD900CC270E /* CommitFailedFile.m in Sources */, - F8D678B81160FB2100CC270E /* Node.m in Sources */, - F8D67CEB1161363A00CC270E /* FileAttributes.m in Sources */, - F8D67CF21161366100CC270E /* XAttrSet.m in Sources */, - F8D67D071161384100CC270E /* FileACL.m in Sources */, - F8D67D081161384100CC270E /* OSStatusDescription.m in Sources */, - F8D67F701161443600CC270E /* RestoreNode.m in Sources */, + F8F2D8711986B5D900997A15 /* GoogleDriveTargetConnection.m in Sources */, + F8F2D9681986BEA300997A15 /* BlobKeyIO.m in Sources */, + F8F2D8821986B63400997A15 /* BaseTargetConnection.m in Sources */, + F8F2D93D1986BA7900997A15 /* UserLibrary.m in Sources */, + F8F2D86E1986B5CC00997A15 /* SFTPTargetConnection.m in Sources */, + F829522019868E26001DC91B /* GoogleDrive.m in Sources */, + F8F2D9001986B78600997A15 /* BooleanNode.m in Sources */, + F8295157198683D5001DC91B /* agent.c in Sources */, + F829DBFC19868FCA00D637E0 /* RFC822.m in Sources */, + F8F2D9131986B80800997A15 /* OpenSSLCryptoKey.m in Sources */, + F8F2D94F1986BD1600997A15 /* TargetSchedule.m in Sources */, + F8F2D9011986B78600997A15 /* DictNode.m in Sources */, + F8F2D9121986B80800997A15 /* CryptoKey.m in Sources */, + F8F2D8B91986B6E000997A15 /* ListTopicsResponse.m in Sources */, + F829DC251986924100D637E0 /* FDOutputStream.m in Sources */, + F8F2D92E1986BA1400997A15 /* Commit.m in Sources */, + F82951EF19868D90001DC91B /* HTTPThrottle.m in Sources */, + F829516A198683D5001DC91B /* transport.c in Sources */, + F8295169198683D5001DC91B /* sftp.c in Sources */, + F829519A198683F9001DC91B /* S3Request.m in Sources */, + F8F2D8961986B66000997A15 /* GlacierService.m in Sources */, + F8F2D8E31986B75C00997A15 /* Computer.m in Sources */, + F829DC311986930300D637E0 /* OpenSSL.m in Sources */, + F829524D19868F1E001DC91B /* ChunkedInputStream.m in Sources */, + F829512919868394001DC91B /* NSError_extra.m in Sources */, + F82951F519868D90001DC91B /* URLConnection.m in Sources */, + F8F2D8D81986B70300997A15 /* AWSQueryRequest.m in Sources */, + F8F2D96E1986BF6800997A15 /* PackSet.m in Sources */, + F8F2D96B1986BF5100997A15 /* GunzipInputStream.m in Sources */, + F829523119868E59001DC91B /* NSString+SBJSON.m in Sources */, + F82951F119868D90001DC91B /* NSDictionary_HTTP.m in Sources */, + F8295194198683F9001DC91B /* S3ErrorResult.m in Sources */, + F829525019868F31001DC91B /* InputStreams.m in Sources */, + F8F2D9561986BE3C00997A15 /* PackId.m in Sources */, + F8295160198683D5001DC91B /* libgcrypt.c in Sources */, + F8295162198683D5001DC91B /* misc.c in Sources */, + F829515A198683D5001DC91B /* crypt.c in Sources */, + F829515B198683D5001DC91B /* global.c in Sources */, + F829523019868E59001DC91B /* NSObject+SBJSON.m in Sources */, + F8295192198683F9001DC91B /* S3AuthorizationProvider.m in Sources */, + F8F2D8DD1986B72900997A15 /* GlacierAuthorization.m in Sources */, + F829DC13198691CB00D637E0 /* DoubleIO.m in Sources */, + F8F2D9451986BA9B00997A15 /* BucketExclude.m in Sources */, + F829DC10198691CB00D637E0 /* BooleanIO.m in Sources */, + F8F2D9491986BAD200997A15 /* Repo.m in Sources */, + F829522119868E26001DC91B /* GoogleDriveErrorResult.m in Sources */, + F8295167198683D5001DC91B /* scp.c in Sources */, + F8F2D9531986BE2A00997A15 /* FarkImpl.m in Sources */, + F829DC241986924100D637E0 /* FDInputStream.m in Sources */, + F829DC2D1986927000D637E0 /* FileInputStream.m in Sources */, + F8F2D8CC1986B6E900997A15 /* SQS.m in Sources */, + F8F2D8BA1986B6E000997A15 /* SNS.m in Sources */, + F8F2D9371986BA4900997A15 /* NSFileManager_extra.m in Sources */, + F829512419868345001DC91B /* HSLog.m in Sources */, + F829516C198683D5001DC91B /* version.c in Sources */, + F829518F198683F9001DC91B /* LocalS3Signer.m in Sources */, + F8F2D8951986B66000997A15 /* GlacierResponse.m in Sources */, + F829524A19868F0F001DC91B /* DataInputStream.m in Sources */, + F8F2D8A31986B67500997A15 /* NSError_Glacier.m in Sources */, + F829522219868E26001DC91B /* GoogleDriveFactory.m in Sources */, + F829524319868ED6001DC91B /* NSData-InputStream.m in Sources */, + F8F2D8E01986B74400997A15 /* UserAndComputer.m in Sources */, + F829DC12198691CB00D637E0 /* DateIO.m in Sources */, + F8F2D8931986B66000997A15 /* GlacierJobLister.m in Sources */, + F8F2D9031986B78600997A15 /* RealNode.m in Sources */, + F8F2D87C1986B61800997A15 /* GoogleDriveRemoteFS.m in Sources */, + F829521319868DE6001DC91B /* RegexKitLite.m in Sources */, + F8F2D8FD1986B78600997A15 /* ArrayNode.m in Sources */, + F82951ED19868D90001DC91B /* HTTPConnectionFactory.m in Sources */, + F8F2D9251986B98600997A15 /* BlobKey.m in Sources */, + F829523319868E59001DC91B /* SBJsonParser.m in Sources */, + F8F2D8A51986B67500997A15 /* Vault.m in Sources */, + F829DC15198691CB00D637E0 /* NSErrorIO.m in Sources */, + F82951EE19868D90001DC91B /* HTTPInputStream.m in Sources */, + F829523419868E59001DC91B /* SBJsonWriter.m in Sources */, + F8F2D8CA1986B6E900997A15 /* ListQueuesResponse.m in Sources */, + F8295161198683D5001DC91B /* mac.c in Sources */, + F8295159198683D5001DC91B /* comp.c in Sources */, + F8295163198683D5001DC91B /* openssl.c in Sources */, + F8F2D9621986BE6100997A15 /* Tree.m in Sources */, + F829515F198683D5001DC91B /* knownhost.c in Sources */, + F8F2D8C91986B6E900997A15 /* GetQueueAttributesResponse.m in Sources */, + F829525319868F3C001DC91B /* BufferedInputStream.m in Sources */, + F829DC35198697EB00D637E0 /* Target.m in Sources */, + F8295195198683F9001DC91B /* S3Lister.m in Sources */, + F8F2D8FE1986B78600997A15 /* BinaryPListReader.m in Sources */, + F8F2D8C81986B6E900997A15 /* CreateQueueResponse.m in Sources */, + F8F2D9061986B78600997A15 /* XMLPListReader.m in Sources */, + F8295164198683D5001DC91B /* packet.c in Sources */, + F829516B198683D5001DC91B /* userauth.c in Sources */, + F829515E198683D5001DC91B /* kex.c in Sources */, + F8F2D8A61986B67500997A15 /* VaultDeleter.m in Sources */, + F8F2D8BB1986B6E000997A15 /* SubscribeResponse.m in Sources */, + F829518E198683F9001DC91B /* LifecycleConfiguration.m in Sources */, + F8F2D9171986B92100997A15 /* SFTPServer.m in Sources */, + F8F2D8D71986B70300997A15 /* AWSQueryError.m in Sources */, + F8295197198683F9001DC91B /* S3ObjectMetadata.m in Sources */, + F8F2D95C1986BE4E00997A15 /* PackIndexEntry.m in Sources */, + F8F2D8971986B66000997A15 /* LocalGlacierSigner.m in Sources */, + F829523219868E59001DC91B /* SBJsonBase.m in Sources */, + F82951F219868D90001DC91B /* RFC2616DateFormatter.m in Sources */, + F8295165198683D5001DC91B /* pem.c in Sources */, + F829DC16198691CB00D637E0 /* StringIO.m in Sources */, + F8F2D8D91986B70300997A15 /* AWSQueryResponse.m in Sources */, + F8F2D92A1986B9DF00997A15 /* SHA1Hash.m in Sources */, + F829524019868EC7001DC91B /* NSString_extra.m in Sources */, + F829523819868E83001DC91B /* AWSRegion.m in Sources */, + F8F2D8CD1986B6E900997A15 /* SQSMessage.m in Sources */, + F829524719868F02001DC91B /* ISO8601Date.m in Sources */, + F829515D198683D5001DC91B /* keepalive.c in Sources */, + F8F2D8CB1986B6E900997A15 /* ReceiveMessageResponse.m in Sources */, + F8295158198683D5001DC91B /* channel.c in Sources */, + F8F2D90D1986B7AF00997A15 /* NSObject_extra.m in Sources */, + F829522419868E26001DC91B /* GoogleDriveFolderLister.m in Sources */, + F829DC11198691CB00D637E0 /* DataIO.m in Sources */, + F8295166198683D5001DC91B /* publickey.c in Sources */, + F8F2D8D01986B6FD00997A15 /* SignatureV2Provider.m in Sources */, + F829519B198683F9001DC91B /* S3Service.m in Sources */, + F8F2D8A71986B67500997A15 /* VaultLister.m in Sources */, + F829515C198683D5001DC91B /* hostkey.c in Sources */, + F8F2D9041986B78600997A15 /* StringNode.m in Sources */, + F8295199198683F9001DC91B /* S3Owner.m in Sources */, + F8F2D94C1986BB4900997A15 /* NSString_slashed.m in Sources */, + F8F2D9461986BA9B00997A15 /* BucketExcludeSet.m in Sources */, + F8F2D8B81986B6E000997A15 /* CreateTopicResponse.m in Sources */, + F829DC1F1986923100D637E0 /* Sysctl.m in Sources */, + F8295198198683F9001DC91B /* S3ObjectReceiver.m in Sources */, + F8F2D9651986BE7600997A15 /* NSData-GZip.m in Sources */, + F8F2D8941986B66000997A15 /* GlacierRequest.m in Sources */, + F829DBFF1986901300D637E0 /* Streams.m in Sources */, + F8F2D8A41986B67500997A15 /* SHA256TreeHash.m in Sources */, + F8F2D9071986B78600997A15 /* XMLPListWriter.m in Sources */, + F829DC1C1986921E00D637E0 /* MD5Hash.m in Sources */, + F8F2D8681986B58000997A15 /* BackupSet.m in Sources */, + F8F2D87B1986B61800997A15 /* SFTPRemoteFS.m in Sources */, + F829523C19868EA4001DC91B /* NetMonitor.m in Sources */, + F8295191198683F9001DC91B /* RemoteS3Signer.m in Sources */, + F8F2D9051986B78600997A15 /* XMLPlistParser.m in Sources */, + F8F2D9441986BA9B00997A15 /* Bucket.m in Sources */, + F829DC2E1986927000D637E0 /* FileOutputStream.m in Sources */, + F8F2D9611986BE6100997A15 /* Node.m in Sources */, + F829DC19198691D900D637E0 /* BufferedOutputStream.m in Sources */, + F82951EC19868D90001DC91B /* NSData-Base64Extensions.m in Sources */, + F8295193198683F9001DC91B /* S3DeleteReceiver.m in Sources */, + F8F2D9311986BA2200997A15 /* CommitFailedFile.m in Sources */, + F8F2D8851986B64D00997A15 /* GlacierAuthorizationProvider.m in Sources */, F83C1AE311CA7C7C0001958F /* arq_restore.m in Sources */, - F8F4D19D121D77E1002D09C1 /* NSError_extra.m in Sources */, - F8F4D1A0121D78AD002D09C1 /* MonitoredInputStream.m in Sources */, - F8F4D1B0121D7990002D09C1 /* ArqPackSet.m in Sources */, - F8F4D1B1121D7990002D09C1 /* ArqRepo.m in Sources */, - F8F4D1C3121D79AC002D09C1 /* ArqFark.m in Sources */, - F8F4D1E2121D7BC3002D09C1 /* AppKeychain.m in Sources */, - F8F4D1E7121D7DA2002D09C1 /* FarkPath.m in Sources */, - F8F4D1FE121D8409002D09C1 /* PackIndexWriter.m in Sources */, - F8F4D67E121DA542002D09C1 /* UserAndComputer.m in Sources */, - F8987235121EB68900F07D76 /* BinaryPListReader.m in Sources */, - F8987236121EB68900F07D76 /* BinaryPListWriter.m in Sources */, - F8987560121EBD9600F07D76 /* BufferedInputStream.m in Sources */, - F89A1EEC13FAC4E30071D321 /* URLConnection.m in Sources */, - F89A1EFB13FAC5970071D321 /* EncryptedInputStreamFactory.m in Sources */, - F89A1F2B13FAC6700071D321 /* UserLibrary.m in Sources */, - F89A1F3E13FAC6820071D321 /* UserLibrary_Arq.m in Sources */, - F89A1F4513FAC6C40071D321 /* BlobKey.m in Sources */, - F89A1F4D13FAC73D0071D321 /* Computer.m in Sources */, - F89A1F5313FAC78F0071D321 /* BufferedOutputStream.m in Sources */, - F89A1F5B13FAC7D50071D321 /* Encryption.m in Sources */, - F89A1F5F13FAC8020071D321 /* NSObject_extra.m in Sources */, - F89A1F6313FAC8270071D321 /* CryptoKey.m in Sources */, - F89A1F6713FAC83E0071D321 /* GunzipInputStream.m in Sources */, - F89A1F7713FAC8930071D321 /* NSData-GZip.m in Sources */, - F89A1FD213FAD6BE0071D321 /* HSLog.m in Sources */, - F89A200B13FADAD70071D321 /* ArqSalt.m in Sources */, - F89A205413FAE2DA0071D321 /* LocalS3Signer.m in Sources */, - F89A205A13FAE3010071D321 /* DataOutputStream.m in Sources */, - F81426D714541A6C00D7E50A /* BackupSet.m in Sources */, - F8373E0F14794D01005AFBE6 /* ReflogEntry.m in Sources */, - F8373E5A147A8DEC005AFBE6 /* ReflogPrinter.m in Sources */, - F84166D815E2782600B6ECED /* HTTPConnectionFactory.m in Sources */, - F84166D915E2782600B6ECED /* HTTPTimeoutSetting.m in Sources */, - F84166E215E2785100B6ECED /* CFHTTPConnection.m in Sources */, - F84166E315E2785100B6ECED /* CFHTTPInputStream.m in Sources */, - F84166ED15E278BC00B6ECED /* CFNetwork.m in Sources */, - F84166F515E278F500B6ECED /* S3Owner.m in Sources */, - F84166F615E278F500B6ECED /* S3Region.m in Sources */, - F84166FD15E2792500B6ECED /* NetMonitor.m in Sources */, - F841670515E279BB00B6ECED /* Sysctl.m in Sources */, - F841670B15E279DE00B6ECED /* DNS_SDErrors.m in Sources */, - F841672615E27A5200B6ECED /* RemoteS3Signer.m in Sources */, - F8146BEB16EB70EE006AD471 /* BlobKeyIO.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - F83C1A7211CA7C170001958F /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - F83C1AC811CA7C170001958F /* arq_verify.m in Sources */, - F83C1A7411CA7C170001958F /* ArqRestoreCommand.m in Sources */, - F83C1A7711CA7C170001958F /* ArrayNode.m in Sources */, - F83C1A7811CA7C170001958F /* BooleanNode.m in Sources */, - F83C1A7911CA7C170001958F /* DictNode.m in Sources */, - F83C1A7A11CA7C170001958F /* IntegerNode.m in Sources */, - F83C1A7B11CA7C170001958F /* RealNode.m in Sources */, - F83C1A7C11CA7C170001958F /* StringNode.m in Sources */, - F83C1A7D11CA7C170001958F /* XMLPListReader.m in Sources */, - F83C1A7E11CA7C170001958F /* XMLPListWriter.m in Sources */, - F83C1A8011CA7C170001958F /* NSError_S3.m in Sources */, - F83C1A8111CA7C170001958F /* PathReceiver.m in Sources */, - F83C1A8311CA7C170001958F /* S3AuthorizationProvider.m in Sources */, - F83C1A8411CA7C170001958F /* S3Lister.m in Sources */, - F83C1A8511CA7C170001958F /* S3ObjectMetadata.m in Sources */, - F83C1A8611CA7C170001958F /* S3ObjectReceiver.m in Sources */, - F83C1A8711CA7C170001958F /* S3Request.m in Sources */, - F83C1A8811CA7C170001958F /* S3Service.m in Sources */, - F83C1A8A11CA7C170001958F /* RegexKitLite.m in Sources */, - F83C1A8B11CA7C170001958F /* Blob.m in Sources */, - F83C1A8F11CA7C170001958F /* BlobACL.m in Sources */, - F83C1A9011CA7C170001958F /* ServerBlob.m in Sources */, - F83C1A9111CA7C170001958F /* RFC2616DateFormatter.m in Sources */, - F83C1A9211CA7C170001958F /* RFC822.m in Sources */, - F83C1A9311CA7C170001958F /* InputStreams.m in Sources */, - F83C1A9411CA7C170001958F /* ChunkedInputStream.m in Sources */, - F83C1A9511CA7C170001958F /* FixedLengthInputStream.m in Sources */, - F83C1A9611CA7C170001958F /* FDInputStream.m in Sources */, - F83C1A9711CA7C170001958F /* FDOutputStream.m in Sources */, - F83C1A9811CA7C170001958F /* DataInputStream.m in Sources */, - F83C1A9911CA7C170001958F /* Writer.m in Sources */, - F83C1A9A11CA7C170001958F /* NSData-InputStream.m in Sources */, - F83C1A9C11CA7C170001958F /* Streams.m in Sources */, - F83C1AA111CA7C170001958F /* NSXMLNode_extra.m in Sources */, - F83C1AA211CA7C170001958F /* NSData-Base64Extensions.m in Sources */, - F83C1AA311CA7C170001958F /* DataInputStreamFactory.m in Sources */, - F83C1AA411CA7C170001958F /* Restorer.m in Sources */, - F83C1AA711CA7C170001958F /* SHA1Hash.m in Sources */, - F83C1AAB11CA7C170001958F /* PackIndexEntry.m in Sources */, - F83C1AAC11CA7C170001958F /* DiskPack.m in Sources */, - F83C1AAD11CA7C170001958F /* DiskPackIndex.m in Sources */, - F83C1AAE11CA7C170001958F /* Commit.m in Sources */, - F83C1AAF11CA7C170001958F /* Tree.m in Sources */, - F83C1AB011CA7C170001958F /* NSData-Encrypt.m in Sources */, - F83C1AB111CA7C170001958F /* OpenSSL.m in Sources */, - F83C1AB211CA7C170001958F /* DecryptedInputStream.m in Sources */, - F83C1AB311CA7C170001958F /* CryptInputStream.m in Sources */, - F83C1AB411CA7C170001958F /* FileInputStream.m in Sources */, - F83C1AB511CA7C170001958F /* DataIO.m in Sources */, - F83C1AB611CA7C170001958F /* DateIO.m in Sources */, - F83C1AB711CA7C170001958F /* DoubleIO.m in Sources */, - F83C1AB811CA7C170001958F /* IntegerIO.m in Sources */, - F83C1AB911CA7C170001958F /* StringIO.m in Sources */, - F83C1ABA11CA7C170001958F /* NSString_extra.m in Sources */, - F83C1ABB11CA7C170001958F /* BinarySHA1.m in Sources */, - F83C1ABC11CA7C170001958F /* NSFileManager_extra.m in Sources */, - F83C1ABD11CA7C170001958F /* FileOutputStream.m in Sources */, - F83C1ABE11CA7C170001958F /* FileInputStreamFactory.m in Sources */, - F83C1ABF11CA7C170001958F /* BooleanIO.m in Sources */, - F83C1AC011CA7C170001958F /* EncryptedInputStream.m in Sources */, - F83C1AC111CA7C170001958F /* CommitFailedFile.m in Sources */, - F83C1AC211CA7C170001958F /* Node.m in Sources */, - F83C1AC311CA7C170001958F /* FileAttributes.m in Sources */, - F83C1AC411CA7C170001958F /* XAttrSet.m in Sources */, - F83C1AC511CA7C170001958F /* FileACL.m in Sources */, - F83C1AC611CA7C170001958F /* OSStatusDescription.m in Sources */, - F83C1AC711CA7C170001958F /* RestoreNode.m in Sources */, - F83C1AC911CA7C170001958F /* ArqVerifyCommand.m in Sources */, - F83C1D1D11CA95AF0001958F /* BucketVerifier.m in Sources */, - F8F4D59A121D9FA7002D09C1 /* MonitoredInputStream.m in Sources */, - F8F4D59B121D9FAD002D09C1 /* ArqFark.m in Sources */, - F8F4D59C121D9FAE002D09C1 /* ArqPackSet.m in Sources */, - F8F4D59D121D9FAF002D09C1 /* ArqRepo.m in Sources */, - F8F4D5A1121D9FC2002D09C1 /* AppKeychain.m in Sources */, - F8F4D5A2121D9FC9002D09C1 /* FarkPath.m in Sources */, - F8F4D5A3121D9FCF002D09C1 /* PackIndexWriter.m in Sources */, - F8987588121EBDF300F07D76 /* BinaryPListReader.m in Sources */, - F8987589121EBDF400F07D76 /* BinaryPListWriter.m in Sources */, - F898758A121EBDFA00F07D76 /* BufferedInputStream.m in Sources */, - F898758C121EBE0600F07D76 /* UserAndComputer.m in Sources */, - F89A1EED13FAC4E30071D321 /* URLConnection.m in Sources */, - F89A1EFC13FAC5970071D321 /* EncryptedInputStreamFactory.m in Sources */, - F89A1F2C13FAC6700071D321 /* UserLibrary.m in Sources */, - F89A1F3F13FAC6820071D321 /* UserLibrary_Arq.m in Sources */, - F89A1F4613FAC6C40071D321 /* BlobKey.m in Sources */, - F89A1F4E13FAC73D0071D321 /* Computer.m in Sources */, - F89A1F5413FAC78F0071D321 /* BufferedOutputStream.m in Sources */, - F89A1F5C13FAC7D50071D321 /* Encryption.m in Sources */, - F89A1F6013FAC8020071D321 /* NSObject_extra.m in Sources */, - F89A1F6413FAC8270071D321 /* CryptoKey.m in Sources */, - F89A1F6813FAC83E0071D321 /* GunzipInputStream.m in Sources */, - F89A1F7813FAC8930071D321 /* NSData-GZip.m in Sources */, - F89A1FD313FAD6BE0071D321 /* HSLog.m in Sources */, - F89A200C13FADAD70071D321 /* ArqSalt.m in Sources */, - F89A205313FAE2DA0071D321 /* LocalS3Signer.m in Sources */, - F89A205913FAE3010071D321 /* DataOutputStream.m in Sources */, - F89A20CF13FAE7170071D321 /* NSError_extra.m in Sources */, - F84166DA15E2782600B6ECED /* HTTPConnectionFactory.m in Sources */, - F84166DB15E2782600B6ECED /* HTTPTimeoutSetting.m in Sources */, - F84166E415E2785100B6ECED /* CFHTTPConnection.m in Sources */, - F84166E515E2785100B6ECED /* CFHTTPInputStream.m in Sources */, - F84166EE15E278BC00B6ECED /* CFNetwork.m in Sources */, - F84166F715E278F500B6ECED /* S3Owner.m in Sources */, - F84166F815E278F500B6ECED /* S3Region.m in Sources */, - F84166FE15E2792500B6ECED /* NetMonitor.m in Sources */, - F841670615E279BB00B6ECED /* Sysctl.m in Sources */, - F841670C15E279DE00B6ECED /* DNS_SDErrors.m in Sources */, - F841672715E27A5200B6ECED /* RemoteS3Signer.m in Sources */, + F8295190198683F9001DC91B /* PathReceiver.m in Sources */, + F829DC281986924E00D637E0 /* DataOutputStream.m in Sources */, + F8295196198683F9001DC91B /* S3MultiDeleteResponse.m in Sources */, + F8F2D9021986B78600997A15 /* IntegerNode.m in Sources */, + F8F2D8921986B66000997A15 /* GlacierJob.m in Sources */, + F8F2D8AD1986B6A300997A15 /* SHA256Hash.m in Sources */, + F8295168198683D5001DC91B /* session.c in Sources */, + F8F2D86D1986B5CC00997A15 /* S3TargetConnection.m in Sources */, + F83F9B2D1983303F007CBFB4 /* ArqRestoreCommand.m in Sources */, + F8F2D9341986BA2B00997A15 /* ArqSalt.m in Sources */, + F829DC14198691CB00D637E0 /* IntegerIO.m in Sources */, + F8F2D8FF1986B78600997A15 /* BinaryPListWriter.m in Sources */, + F8F2D95B1986BE4E00997A15 /* PackIndex.m in Sources */, + F829522319868E26001DC91B /* GoogleDriveRequest.m in Sources */, + F8F2D87A1986B61800997A15 /* S3RemoteFS.m in Sources */, + F8F2D8AA1986B68800997A15 /* NSXMLNode_extra.m in Sources */, + F8F2D93A1986BA6900997A15 /* UserLibrary_Arq.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1215,17 +1359,25 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - ARCHS = "$(ARCHS_STANDARD_64_BIT)"; COPY_PHASE_STRIP = NO; + FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)"; GCC_DYNAMIC_NO_PIC = NO; GCC_MODEL_TUNING = G5; GCC_OPTIMIZATION_LEVEL = 0; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = arq_restore_Prefix.pch; + GCC_PREPROCESSOR_DEFINITIONS = "USE_OPENSSL=1"; GCC_TREAT_WARNINGS_AS_ERRORS = YES; GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO; - HEADER_SEARCH_PATHS = ""; + HEADER_SEARCH_PATHS = ( + "$(PROJECT_DIR)/libssh2/include", + /usr/local/ssl/include, + ); INSTALL_PATH = /usr/local/bin; + LIBRARY_SEARCH_PATHS = ( + /usr/local/ssl/lib, + "$(inherited)", + ); PRODUCT_NAME = arq_restore; SDKROOT = macosx; }; @@ -1235,15 +1387,23 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - ARCHS = "$(ARCHS_STANDARD_64_BIT)"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)"; GCC_MODEL_TUNING = G5; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = arq_restore_Prefix.pch; + GCC_PREPROCESSOR_DEFINITIONS = "USE_OPENSSL=1"; GCC_TREAT_WARNINGS_AS_ERRORS = YES; GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO; - HEADER_SEARCH_PATHS = ""; + HEADER_SEARCH_PATHS = ( + "$(PROJECT_DIR)/libssh2/include", + /usr/local/ssl/include, + ); INSTALL_PATH = /usr/local/bin; + LIBRARY_SEARCH_PATHS = ( + /usr/local/ssl/lib, + "$(inherited)", + ); PRODUCT_NAME = arq_restore; SDKROOT = macosx; }; @@ -1252,7 +1412,6 @@ 1DEB927908733DD40010E9CD /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_OPTIMIZATION_LEVEL = 0; GCC_WARN_ABOUT_RETURN_TYPE = YES; @@ -1265,7 +1424,6 @@ 1DEB927A08733DD40010E9CD /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNUSED_VARIABLE = YES; @@ -1273,44 +1431,6 @@ }; name = Release; }; - F83C1AD511CA7C170001958F /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ARCHS = "$(ARCHS_STANDARD_64_BIT)"; - COPY_PHASE_STRIP = NO; - GCC_DYNAMIC_NO_PIC = NO; - GCC_MODEL_TUNING = G5; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = arq_restore_Prefix.pch; - GCC_TREAT_WARNINGS_AS_ERRORS = YES; - HEADER_SEARCH_PATHS = ""; - INSTALL_PATH = /usr/local/bin; - PRODUCT_NAME = arq_verify; - SDKROOT = macosx; - VALID_ARCHS = x86_64; - }; - name = Debug; - }; - F83C1AD611CA7C170001958F /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ARCHS = "$(ARCHS_STANDARD_64_BIT)"; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - GCC_MODEL_TUNING = G5; - GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = arq_restore_Prefix.pch; - GCC_TREAT_WARNINGS_AS_ERRORS = YES; - HEADER_SEARCH_PATHS = ""; - INSTALL_PATH = /usr/local/bin; - PRODUCT_NAME = arq_verify; - SDKROOT = macosx; - VALID_ARCHS = x86_64; - }; - name = Release; - }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -1332,15 +1452,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - F83C1AD411CA7C170001958F /* Build configuration list for PBXNativeTarget "arq_verify" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - F83C1AD511CA7C170001958F /* Debug */, - F83C1AD611CA7C170001958F /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; /* End XCConfigurationList section */ }; rootObject = 08FB7793FE84155DC02AAC07 /* Project object */; diff --git a/arq_restore_Prefix.pch b/arq_restore_Prefix.pch index 76e4b43..ae5b291 100644 --- a/arq_restore_Prefix.pch +++ b/arq_restore_Prefix.pch @@ -1,6 +1,6 @@ #ifdef __OBJC__ - #import - #import "HSLog.h" +#import +#import "HSLog.h" #import "SetNSError.h" #import "NSErrorCodes.h" #endif diff --git a/arq_verify.m b/arq_verify.m deleted file mode 100644 index 99b7ee7..0000000 --- a/arq_verify.m +++ /dev/null @@ -1,110 +0,0 @@ -/* - Copyright (c) 2010, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#import -#import "ArqVerifyCommand.h" - -static void printUsage(const char *exeName) { - fprintf(stderr, "usage: %s [-v] all | s3_bucket_name [computer_uuid [folder_uuid]]]\n", exeName); -} -int main (int argc, const char * argv[]) { - NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; - setHSLogLevel(HSLOG_LEVEL_ERROR); - char *exePath = strdup(argv[0]); - char *exeName = basename(exePath); - - char *cAccessKey = getenv("ARQ_ACCESS_KEY"); - if (cAccessKey == NULL) { - fprintf(stderr, "%s: missing ARQ_ACCESS_KEY environment variable\n", exeName); - } - char *cSecretKey = getenv("ARQ_SECRET_KEY"); - if (cSecretKey == NULL) { - fprintf(stderr, "%s: missing ARQ_SECRET_KEY environment variable\n", exeName); - } - char *cEncryptionPassword = getenv("ARQ_ENCRYPTION_PASSWORD"); - if (cEncryptionPassword == NULL) { - fprintf(stderr, "%s: missing ARQ_ENCRYPTION_PASSWORD environment variable\n", exeName); - } - if (cAccessKey == NULL || cSecretKey == NULL || cEncryptionPassword == NULL) { - goto main_error; - } - - NSString *accessKey = [NSString stringWithUTF8String:cAccessKey]; - NSString *secretKey = [NSString stringWithUTF8String:cSecretKey]; - NSString *encryptionPassword = [[[NSString alloc] initWithUTF8String:cEncryptionPassword] autorelease]; - ArqVerifyCommand *cmd = [[[ArqVerifyCommand alloc] initWithAccessKey:accessKey secretKey:secretKey encryptionPassword:encryptionPassword] autorelease]; - NSError *error = nil; - BOOL verbose = NO; - int index = 1; - if (argc > 1 && !strcmp(argv[1], "-v")) { - verbose = YES; - index++; - } - [cmd setVerbose:verbose]; - BOOL ret = NO; - if ((argc - index) == 0) { - printUsage(exeName); - goto main_error; - } else if ((argc - index) == 1) { - if (!strcmp(argv[index], "-?") || !strcmp(argv[index], "-h")) { - printUsage(exeName); - goto main_error; - } else if (!strcmp(argv[index], "all")) { - if (![cmd verifyAll:&error]) { - NSLog(@"%@", [error localizedDescription]); - goto main_error; - } - } else if (![cmd verifyS3BucketName:[NSString stringWithUTF8String:argv[index]] error:&error]) { - NSLog(@"%@", [error localizedDescription]); - goto main_error; - } - } else if ((argc - index) == 2) { - if (![cmd verifyS3BucketName:[NSString stringWithUTF8String:argv[index]] computerUUID:[NSString stringWithUTF8String:argv[index+1]] error:&error]) { - NSLog(@"%@", [error localizedDescription]); - goto main_error; - } - } else if ((argc - index) == 3) { - if (![cmd verifyS3BucketName:[NSString stringWithUTF8String:argv[index]] computerUUID:[NSString stringWithUTF8String:argv[index+1]] bucketUUID:[NSString stringWithUTF8String:argv[index+2]] error:&error]) { - NSLog(@"%@", [error localizedDescription]); - goto main_error; - } - } else { - printUsage(exeName); - goto main_error; - } - ret = YES; -main_error: - [pool drain]; - free(exePath); - return ret ? 0 : 1; -} diff --git a/cocoastack/aws/AWSQueryError.h b/cocoastack/aws/AWSQueryError.h new file mode 100644 index 0000000..a0ccf13 --- /dev/null +++ b/cocoastack/aws/AWSQueryError.h @@ -0,0 +1,15 @@ +// +// Created by Stefan Reitshamer on 9/16/12. +// +// + + +@interface AWSQueryError : NSObject { + NSMutableDictionary *values; + NSMutableString *currentStringBuffer; + BOOL parseErrorOccurred; + NSError *nsError; +} +- (id)initWithDomain:(NSString *)theDomain httpStatusCode:(int)theCode responseBody:(NSData *)theBody; +- (NSError *)nsError; +@end diff --git a/cocoastack/aws/AWSQueryError.m b/cocoastack/aws/AWSQueryError.m new file mode 100644 index 0000000..0bd0579 --- /dev/null +++ b/cocoastack/aws/AWSQueryError.m @@ -0,0 +1,77 @@ +// +// Created by Stefan Reitshamer on 9/16/12. +// +// + +#import "AWSQueryError.h" + + + +@implementation AWSQueryError +- (id)initWithDomain:(NSString *)theDomain httpStatusCode:(int)theCode responseBody:(NSData *)theBody { + if (self = [super init]) { + values = [[NSMutableDictionary alloc] init]; + NSXMLParser *parser = [[NSXMLParser alloc] initWithData:theBody]; + [parser setDelegate:self]; + [parser parse]; + [parser release]; + if (parseErrorOccurred) { + nsError = [[NSError errorWithDomain:theDomain code:theCode description:@"SNS error"] retain]; + } else { + NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; + [userInfo setObject:[NSNumber numberWithInt:theCode] forKey:@"HTTPStatusCode"]; + for (NSString *key in [values allKeys]) { + [userInfo setObject:[values objectForKey:key] forKey:[@"Amazon" stringByAppendingString:key]]; + } + NSString *msg = [values objectForKey:@"Message"]; + if (msg == nil) { + msg = @"unknown AWS error"; + } + if ([[userInfo objectForKey:@"AmazonCode"] isEqualToString:@"SubscriptionRequiredException"]) { + msg = @"Your AWS account is not signed up all services. Please visit http://aws.amazon.com and sign up for S3, Glacier, SNS and SQS."; + } + [userInfo setObject:msg forKey:NSLocalizedDescriptionKey]; + nsError = [[NSError errorWithDomain:theDomain code:theCode userInfo:userInfo] retain]; + } + } + return self; +} +- (void)dealloc { + [values release]; + [currentStringBuffer release]; + [nsError release]; + [super dealloc]; +} + +- (NSError *)nsError { + return nsError; +} + + +#pragma mark - NSXMLParserDelegate +- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName + namespaceURI:(NSString *)namespaceURI + qualifiedName:(NSString *)qualifiedName + attributes:(NSDictionary *)attributeDict { + [currentStringBuffer release]; + currentStringBuffer = nil; +} +- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { + if (currentStringBuffer == nil) { + currentStringBuffer = [[NSMutableString alloc] init]; + } + [currentStringBuffer appendString:string]; +} +- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { + if (currentStringBuffer != nil) { + [values setObject:[NSString stringWithString:currentStringBuffer] forKey:elementName]; + } +} +- (void)parser:(NSXMLParser *)theParser parseErrorOccurred:(NSError *)parseError { + parseErrorOccurred = YES; + HSLogError(@"error parsing amazon error response: %@", parseError); +} +- (void)parserDidEndDocument:(NSXMLParser *)parser { +} + +@end diff --git a/cocoastack/aws/AWSQueryRequest.h b/cocoastack/aws/AWSQueryRequest.h new file mode 100644 index 0000000..68e1951 --- /dev/null +++ b/cocoastack/aws/AWSQueryRequest.h @@ -0,0 +1,18 @@ +// +// Created by Stefan Reitshamer on 9/16/12. +// +// + +@class AWSQueryResponse; + + +@interface AWSQueryRequest : NSObject { + NSString *method; + NSURL *url; + BOOL retryOnTransientError; +} +- (id)initWithMethod:(NSString *)theMethod url:(NSURL *)theURL retryOnTransientError:(BOOL)theRetryOnTransientError; + +- (NSString *)errorDomain; +- (AWSQueryResponse *)execute:(NSError **)error; +@end diff --git a/cocoastack/aws/AWSQueryRequest.m b/cocoastack/aws/AWSQueryRequest.m new file mode 100644 index 0000000..86e1d6d --- /dev/null +++ b/cocoastack/aws/AWSQueryRequest.m @@ -0,0 +1,133 @@ +// +// Created by Stefan Reitshamer on 9/16/12. +// +// + +#import "AWSQueryRequest.h" +#import "AWSQueryResponse.h" +#import "HTTPConnectionFactory.h" +#import "HTTPConnection.h" +#import "InputStream.h" +#import "HTTP.h" +#import "AWSQueryError.h" + + +#define INITIAL_RETRY_SLEEP (0.5) +#define RETRY_SLEEP_GROWTH_FACTOR (1.5) +#define MAX_RETRY_SLEEP (5.0) + + +@interface AWSQueryRequest () +- (AWSQueryResponse *)executeOnce:(NSError **)error; +@end + + +@implementation AWSQueryRequest +- (id)initWithMethod:(NSString *)theMethod url:(NSURL *)theURL retryOnTransientError:(BOOL)theRetryOnTransientError { + if (self = [super init]) { + method = [theMethod retain]; + url = [theURL retain]; + retryOnTransientError = theRetryOnTransientError; + } + return self; +} +- (void)dealloc { + [method release]; + [url release]; + [super dealloc]; +} + +- (NSString *)errorDomain { + return @"AWSQueryRequestErrorDomain"; +} +- (AWSQueryResponse *)execute:(NSError **)error { + NSAutoreleasePool *pool = nil; + NSTimeInterval sleepTime = INITIAL_RETRY_SLEEP; + AWSQueryResponse *theResponse = nil; + NSError *myError = nil; + for (;;) { + [pool drain]; + pool = [[NSAutoreleasePool alloc] init]; + BOOL transientError = NO; + BOOL needSleep = NO; + myError = nil; + theResponse = [self executeOnce:&myError]; + if (theResponse != nil) { + break; + } + if ([myError isErrorWithDomain:[self errorDomain] code:ERROR_NOT_FOUND]) { + break; + } else if ([[[myError userInfo] objectForKey:@"HTTPStatusCode"] intValue] == HTTP_INTERNAL_SERVER_ERROR) { + transientError = YES; + } else if ([myError isConnectionResetError]) { + transientError = YES; + } else if (retryOnTransientError && [myError isErrorWithDomain:[self errorDomain] code:HTTP_SERVICE_NOT_AVAILABLE]) { + // Sometimes SQS returns a 503 error, and we're supposed to retry in that case. + transientError = YES; + needSleep = YES; + } else if (retryOnTransientError && [myError isTransientError]) { + transientError = YES; + needSleep = YES; + } else { + HSLogError(@"%@ %@: %@", method, url, myError); + break; + } + + if (transientError) { + HSLogDetail(@"retrying %@ %@: %@", method, url, myError); + } + if (needSleep) { + [NSThread sleepForTimeInterval:sleepTime]; + sleepTime *= RETRY_SLEEP_GROWTH_FACTOR; + if (sleepTime > MAX_RETRY_SLEEP) { + sleepTime = MAX_RETRY_SLEEP; + } + } + } + [theResponse retain]; + [myError retain]; + [pool drain]; + [theResponse autorelease]; + [myError autorelease]; + if (error != NULL) { *error = myError; } + return theResponse; +} + + +#pragma mark internal +- (AWSQueryResponse *)executeOnce:(NSError **)error { + id conn = [[[HTTPConnectionFactory theFactory] newHTTPConnectionToURL:url method:method dataTransferDelegate:nil] autorelease]; + if (conn == nil) { + return nil; + } + [conn setRequestHostHeader]; + HSLogDebug(@"%@ %@", method, url); + NSData *responseData = [conn executeRequest:error]; + if (responseData == nil) { + return nil; + } + int code = [conn responseCode]; + if (code >= 200 && code <= 299) { + HSLogDebug(@"HTTP %d; returning response length=%lu", code, (unsigned long)[responseData length]); + AWSQueryResponse *response = [[[AWSQueryResponse alloc] initWithCode:code headers:[conn responseHeaders] body:responseData] autorelease]; + return response; + } + + if (code == HTTP_NOT_FOUND) { + SETNSERROR([self errorDomain], ERROR_NOT_FOUND, @"%@ not found", url); + HSLogDebug(@"returning not-found error"); + return nil; + } + if (code == HTTP_METHOD_NOT_ALLOWED) { + HSLogError(@"%@ 405 error", url); + SETNSERROR([self errorDomain], ERROR_RRS_NOT_FOUND, @"%@ 405 error", url); + } + AWSQueryError *queryError = [[[AWSQueryError alloc] initWithDomain:[self errorDomain] httpStatusCode:code responseBody:responseData] autorelease]; + NSError *myError = [queryError nsError]; + HSLogDebug(@"%@ %@ error: %@", method, conn, myError); + if (error != NULL) { + *error = myError; + } + return nil; +} +@end diff --git a/cocoastack/aws/AWSQueryResponse.h b/cocoastack/aws/AWSQueryResponse.h new file mode 100644 index 0000000..2a2ede9 --- /dev/null +++ b/cocoastack/aws/AWSQueryResponse.h @@ -0,0 +1,17 @@ +// +// Created by Stefan Reitshamer on 9/16/12. +// +// + + +@interface AWSQueryResponse : NSObject { + int code; + NSDictionary *headers; + NSData *body; +} +- (id)initWithCode:(int)theCode headers:(NSDictionary *)theHeaders body:(NSData *)theBody; + +- (int)code; +- (NSString *)headerForKey:(NSString *)theKey; +- (NSData *)body; +@end diff --git a/cocoastack/aws/AWSQueryResponse.m b/cocoastack/aws/AWSQueryResponse.m new file mode 100644 index 0000000..ae3762f --- /dev/null +++ b/cocoastack/aws/AWSQueryResponse.m @@ -0,0 +1,34 @@ +// +// Created by Stefan Reitshamer on 9/16/12. +// +// + +#import "AWSQueryResponse.h" + +@implementation AWSQueryResponse +- (id)initWithCode:(int)theCode headers:(NSDictionary *)theHeaders body:(NSData *)theBody { + if (self = [super init]) { + code = theCode; + headers = [theHeaders copy]; + body = [theBody retain]; + } + return self; +} +- (void)dealloc { + [headers release]; + [body release]; + [super dealloc]; +} + + +- (int)code { + return code; +} +- (NSString *)headerForKey:(NSString *)theKey { + return [headers objectForKey:theKey]; +} +- (NSData *)body { + return body; +} + +@end diff --git a/cocoastack/aws/AWSRegion.h b/cocoastack/aws/AWSRegion.h new file mode 100644 index 0000000..e0468f8 --- /dev/null +++ b/cocoastack/aws/AWSRegion.h @@ -0,0 +1,56 @@ +// +// Created by Stefan Reitshamer on 9/23/12. +// +// + + +@interface AWSRegion : NSObject { + NSString *regionName; + NSArray *s3LocationConstraints; + NSString *s3Hostname; + NSString *displayName; + NSString *shortDisplayName; + double s3StorageDollarsPerGBMonthStandard; + double s3StorageDollarsPerGBMonthRRS; + double s3UploadDollarsPerGB; + double s3DataTransferOutDollarsPerGB; + double glacierStorageDollarsPerGBMonth; + double glacierUploadDollarsPerGB; + double glacierDataTransferOutDollarsPerGB; + BOOL supportsGlacier; +} + ++ (NSArray *)allRegions; ++ (NSArray *)s3Regions; ++ (NSArray *)glacierRegions; ++ (AWSRegion *)regionWithName:(NSString *)theRegionName; ++ (AWSRegion *)regionWithLocation:(NSString *)theLocation; ++ (AWSRegion *)regionWithS3Endpoint:(NSURL *)theEndpoint; ++ (AWSRegion *)usEast1; ++ (AWSRegion *)usWest1; ++ (AWSRegion *)usWest2; ++ (AWSRegion *)euWest1; ++ (AWSRegion *)apSoutheast1; ++ (AWSRegion *)apSoutheast2; ++ (AWSRegion *)apNortheast1; ++ (AWSRegion *)saEast1; + +- (NSString *)regionName; +- (NSString *)displayName; +- (NSString *)shortDisplayName; +- (NSString *)defaultS3LocationConstraint; +- (NSArray *)s3LocationConstraints; +- (double)s3StorageDollarsPerGBMonthStandard; +- (double)s3StorageDollarsPerGBMonthRRS; +- (double)s3UploadDollarsPerGB; +- (double)s3DataTransferOutDollarsPerGB; +- (double)glacierStorageDollarsPerGBMonth; +- (double)glacierUploadDollarsPerGB; +- (double)glacierDataTransferOutDollarsPerGB; +- (NSURL *)s3EndpointWithSSL:(BOOL)useSSL; +- (BOOL)supportsGlacier; +- (NSURL *)glacierEndpointWithSSL:(BOOL)useSSL; +- (NSURL *)snsEndpointWithSSL:(BOOL)useSSL; +- (NSURL *)sqsEndpointWithSSL:(BOOL)useSSL; + +@end diff --git a/cocoastack/aws/AWSRegion.m b/cocoastack/aws/AWSRegion.m new file mode 100644 index 0000000..331a47a --- /dev/null +++ b/cocoastack/aws/AWSRegion.m @@ -0,0 +1,340 @@ +// +// Created by Stefan Reitshamer on 9/23/12. +// +// + +#import "AWSRegion.h" +#import "RegexKitLite.h" + + + +@implementation AWSRegion ++ (NSArray *)allRegions { + return [NSArray arrayWithObjects: + [AWSRegion usEast1], + [AWSRegion usWest1], + [AWSRegion usWest2], + [AWSRegion euWest1], + [AWSRegion apSoutheast1], + [AWSRegion apSoutheast2], + [AWSRegion apNortheast1], + [AWSRegion saEast1], + nil]; +} ++ (NSArray *)s3Regions { + return [AWSRegion allRegions]; +} ++ (NSArray *)glacierRegions { + NSMutableArray *ret = [NSMutableArray array]; + for (AWSRegion *region in [AWSRegion allRegions]) { + if ([region supportsGlacier]) { + [ret addObject:region]; + } + } + return ret; +} ++ (AWSRegion *)regionWithName:(NSString *)theRegionName { + for (AWSRegion *region in [AWSRegion allRegions]) { + if ([[region regionName] isEqualToString:theRegionName]) { + return region; + } + } + return nil; +} ++ (AWSRegion *)regionWithLocation:(NSString *)theLocation { + if ([theLocation length] == 0) { + return [AWSRegion usEast1]; + } + + for (AWSRegion *region in [AWSRegion allRegions]) { + for (NSString *constraint in [region s3LocationConstraints]) { + if ([constraint caseInsensitiveCompare:theLocation] == NSOrderedSame) { + return region; + } + } + } + HSLogDebug(@"no AWS region found for location %@", theLocation); + return nil; +} ++ (AWSRegion *)regionWithS3Endpoint:(NSURL *)theEndpoint { + if (![[theEndpoint scheme] isEqualToString:@"https"] && ![[theEndpoint scheme] isEqualToString:@"http"]) { +// HSLogDebug(@"unknown AWSRegion endpoint scheme: %@", theEndpoint); + return nil; + } + + for (AWSRegion *region in [AWSRegion allRegions]) { + NSURL *endpoint = [region s3EndpointWithSSL:YES]; + if ([[theEndpoint host] isEqualToString:[endpoint host]]) { + return region; + } + } +// HSLogDebug(@"AWSRegion not found for S3 endpoint %@", theEndpoint); + return nil; +} ++ (AWSRegion *)usEast1 { + return [[[AWSRegion alloc] initWithRegionName:@"us-east-1" + s3LocationConstraints:nil + s3Hostname:@"s3.amazonaws.com" + displayName:@"US East (Northern Virginia)" + shortDisplayName:@"N. Virginia" + s3StorageDollarsPerGBMonthStandard:.030 + s3StorageDollarsPerGBMonthRRS:.024 + s3UploadDollarsPerGB:.005 + s3DataTransferOutDollarsPerGB:.12 + glacierStorageDollarsPerGBMonth:.01 + glacierUploadDollarsPerGB:.05 + glacierDataTransferOutDollarsPerGB:.12 + supportsGlacier:YES] autorelease]; +} ++ (AWSRegion *)usWest1 { + return [[[AWSRegion alloc] initWithRegionName:@"us-west-1" + s3LocationConstraints:[NSArray arrayWithObject:@"us-west-1"] + s3Hostname:@"s3-us-west-1.amazonaws.com" + displayName:@"US West (Northern California)" + shortDisplayName:@"N. California" + s3StorageDollarsPerGBMonthStandard:.033 + s3StorageDollarsPerGBMonthRRS:.0264 + s3UploadDollarsPerGB:.0055 + s3DataTransferOutDollarsPerGB:.12 + glacierStorageDollarsPerGBMonth:.011 + glacierUploadDollarsPerGB:.055 + glacierDataTransferOutDollarsPerGB:.12 + supportsGlacier:YES] autorelease]; +} ++ (AWSRegion *)usWest2 { + return [[[AWSRegion alloc] initWithRegionName:@"us-west-2" + s3LocationConstraints:[NSArray arrayWithObject:@"us-west-2"] + s3Hostname:@"s3-us-west-2.amazonaws.com" + displayName:@"US West (Oregon)" + shortDisplayName:@"Oregon" + s3StorageDollarsPerGBMonthStandard:.030 + s3StorageDollarsPerGBMonthRRS:.024 + s3UploadDollarsPerGB:.005 + s3DataTransferOutDollarsPerGB:.12 + glacierStorageDollarsPerGBMonth:.01 + glacierUploadDollarsPerGB:.05 + glacierDataTransferOutDollarsPerGB:.12 + supportsGlacier:YES] autorelease]; +} ++ (AWSRegion *)euWest1 { + return [[[AWSRegion alloc] initWithRegionName:@"eu-west-1" + s3LocationConstraints:[NSArray arrayWithObjects:@"EU", @"eu-west-1", nil] + s3Hostname:@"s3-eu-west-1.amazonaws.com" + displayName:@"EU (Ireland)" + shortDisplayName:@"Ireland" + s3StorageDollarsPerGBMonthStandard:.030 + s3StorageDollarsPerGBMonthRRS:.024 + s3UploadDollarsPerGB:.005 + s3DataTransferOutDollarsPerGB:.12 + glacierStorageDollarsPerGBMonth:.011 + glacierUploadDollarsPerGB:.055 + glacierDataTransferOutDollarsPerGB:.12 + supportsGlacier:YES] autorelease]; +} ++ (AWSRegion *)apSoutheast1 { + return [[[AWSRegion alloc] initWithRegionName:@"ap-southeast-1" + s3LocationConstraints:[NSArray arrayWithObject:@"ap-southeast-1"] + s3Hostname:@"s3-ap-southeast-1.amazonaws.com" + displayName:@"Asia Pacific (Singapore)" + shortDisplayName:@"Singapore" + s3StorageDollarsPerGBMonthStandard:.030 + s3StorageDollarsPerGBMonthRRS:.024 + s3UploadDollarsPerGB:.005 + s3DataTransferOutDollarsPerGB:.19 + glacierStorageDollarsPerGBMonth:0 + glacierUploadDollarsPerGB:0 + glacierDataTransferOutDollarsPerGB:0 + supportsGlacier:NO] autorelease]; +} ++ (AWSRegion *)apSoutheast2 { + return [[[AWSRegion alloc] initWithRegionName:@"ap-southeast-2" + s3LocationConstraints:[NSArray arrayWithObject:@"ap-southeast-2"] + s3Hostname:@"s3-ap-southeast-2.amazonaws.com" + displayName:@"Asia Pacific (Sydney)" + shortDisplayName:@"Sydney" + s3StorageDollarsPerGBMonthStandard:.033 + s3StorageDollarsPerGBMonthRRS:.0264 + s3UploadDollarsPerGB:.0055 + s3DataTransferOutDollarsPerGB:.19 + glacierStorageDollarsPerGBMonth:.012 + glacierUploadDollarsPerGB:.06 + glacierDataTransferOutDollarsPerGB:.19 + supportsGlacier:YES] autorelease]; +} ++ (AWSRegion *)apNortheast1 { + return [[[AWSRegion alloc] initWithRegionName:@"ap-northeast-1" + s3LocationConstraints:[NSArray arrayWithObject:@"ap-northeast-1"] + s3Hostname:@"s3-ap-northeast-1.amazonaws.com" + displayName:@"Asia Pacific (Tokyo)" + shortDisplayName:@"Tokyo" + s3StorageDollarsPerGBMonthStandard:.033 + s3StorageDollarsPerGBMonthRRS:.0264 + s3UploadDollarsPerGB:.005 + s3DataTransferOutDollarsPerGB:.201 + glacierStorageDollarsPerGBMonth:.012 + glacierUploadDollarsPerGB:.06 + glacierDataTransferOutDollarsPerGB:.201 + supportsGlacier:YES] autorelease]; +} ++ (AWSRegion *)saEast1 { + return [[[AWSRegion alloc] initWithRegionName:@"sa-east-1" + s3LocationConstraints:[NSArray arrayWithObject:@"sa-east-1"] + s3Hostname:@"s3-sa-east-1.amazonaws.com" + displayName:@"South America (Sao Paulo)" + shortDisplayName:@"Sao Paulo" + s3StorageDollarsPerGBMonthStandard:.0408 + s3StorageDollarsPerGBMonthRRS:.0326 + s3UploadDollarsPerGB:.007 + s3DataTransferOutDollarsPerGB:.25 + glacierStorageDollarsPerGBMonth:0 + glacierUploadDollarsPerGB:0 + glacierDataTransferOutDollarsPerGB:0 + supportsGlacier:NO] autorelease]; +} + + +- (void)dealloc { + [regionName release]; + [s3LocationConstraints release]; + [s3Hostname release]; + [displayName release]; + [shortDisplayName release]; + [super dealloc]; +} + +- (NSString *)regionName { + return regionName; +} +- (NSString *)displayName { + return displayName; +} +- (NSString *)shortDisplayName { + return shortDisplayName; +} +- (NSString *)defaultS3LocationConstraint { + return [s3LocationConstraints lastObject]; +} +- (NSArray *)s3LocationConstraints { + return s3LocationConstraints; +} +- (double)s3StorageDollarsPerGBMonthStandard { + return s3StorageDollarsPerGBMonthStandard; +} +- (double)s3StorageDollarsPerGBMonthRRS { + return s3StorageDollarsPerGBMonthRRS; +} +- (double)s3UploadDollarsPerGB { + return s3UploadDollarsPerGB; +} +- (double)s3DataTransferOutDollarsPerGB { + return s3DataTransferOutDollarsPerGB; +} +- (double)glacierStorageDollarsPerGBMonth { + return glacierStorageDollarsPerGBMonth; +} +- (double)glacierUploadDollarsPerGB { + return glacierUploadDollarsPerGB; +} +- (double)glacierDataTransferOutDollarsPerGB { + return glacierDataTransferOutDollarsPerGB; +} +- (NSURL *)s3EndpointWithSSL:(BOOL)useSSL { + return [NSURL URLWithString:[NSString stringWithFormat:@"http%@://%@", (useSSL ? @"s" : @""), s3Hostname]]; +} +- (BOOL)supportsGlacier { + return supportsGlacier; +} +- (NSString *)glacierEndpointWithSSL:(BOOL)useSSL { + if (!supportsGlacier) { + return nil; + } + if (getenv("AWS_HOST")) { + return [NSString stringWithFormat:@"%s/aws/glacier", getenv("AWS_HOST")]; + } + return [self endpointWithService:@"glacier" useSSL:useSSL]; +} +- (NSString *)snsEndpointWithSSL:(BOOL)useSSL { + if (getenv("AWS_HOST")) { + return [NSString stringWithFormat:@"%s/aws/sns", getenv("AWS_HOST")]; + } + return [self endpointWithService:@"sns" useSSL:useSSL]; +} +- (NSString *)sqsEndpointWithSSL:(BOOL)useSSL { + if (getenv("AWS_HOST")) { + return [NSString stringWithFormat:@"%s/aws/sqs", getenv("AWS_HOST")]; + } + return [self endpointWithService:@"sqs" useSSL:useSSL]; +} + + +#pragma mark NSObject +- (NSString *)description { + return [NSString stringWithFormat:@"", regionName]; +} +- (BOOL)isEqual:(id)other { + if (other == self) { + return YES; + } + if (other == nil || ![other isKindOfClass:[self class]]) { + return NO; + } + return [regionName isEqualToString:[(AWSRegion *)other regionName]]; +} +- (NSUInteger)hash { + return [regionName hash]; +} + +#pragma mark NSCopying +- (id)copyWithZone:(NSZone *)zone { + return [[AWSRegion alloc] initWithRegionName:regionName + s3LocationConstraints:s3LocationConstraints + s3Hostname:s3Hostname + displayName:displayName + shortDisplayName:shortDisplayName + s3StorageDollarsPerGBMonthStandard:s3StorageDollarsPerGBMonthStandard + s3StorageDollarsPerGBMonthRRS:s3StorageDollarsPerGBMonthRRS + s3UploadDollarsPerGB:s3UploadDollarsPerGB + s3DataTransferOutDollarsPerGB:s3DataTransferOutDollarsPerGB + glacierStorageDollarsPerGBMonth:glacierStorageDollarsPerGBMonth + glacierUploadDollarsPerGB:glacierUploadDollarsPerGB + glacierDataTransferOutDollarsPerGB:glacierDataTransferOutDollarsPerGB + supportsGlacier:supportsGlacier]; +} + + +#pragma mark internal +- (id)initWithRegionName:(NSString *)theRegionName + s3LocationConstraints:(NSArray *)theS3LocationConstraints + s3Hostname:(NSString *)theS3Hostname + displayName:(NSString *)theDisplayName + shortDisplayName:(NSString *)theShortDisplayName +s3StorageDollarsPerGBMonthStandard:(double)theS3StorageDollarsPerGBMonthStandard + s3StorageDollarsPerGBMonthRRS:(double)theS3StorageDollarsPerGBMonthRRS + s3UploadDollarsPerGB:(double)theS3UploadDollarsPerGB +s3DataTransferOutDollarsPerGB:(double)theS3DataTransferOutDollarsPerGB +glacierStorageDollarsPerGBMonth:(double)theGlacierStorageDollarsPerGBMonth +glacierUploadDollarsPerGB:(double)theGlacierUploadDollarsPerGB +glacierDataTransferOutDollarsPerGB:(double)theGlacierDataTransferOutDollarsPerGB + supportsGlacier:(BOOL)theSupportsGlacier { + if (self = [super init]) { + regionName = [theRegionName retain]; + s3LocationConstraints = [theS3LocationConstraints retain]; + s3Hostname = [theS3Hostname retain]; + displayName = [theDisplayName retain]; + shortDisplayName = [theShortDisplayName retain]; + s3StorageDollarsPerGBMonthStandard = theS3StorageDollarsPerGBMonthStandard; + s3StorageDollarsPerGBMonthRRS = theS3StorageDollarsPerGBMonthRRS; + s3UploadDollarsPerGB = theS3UploadDollarsPerGB; + s3DataTransferOutDollarsPerGB = theS3DataTransferOutDollarsPerGB; + glacierStorageDollarsPerGBMonth = theGlacierStorageDollarsPerGBMonth; + glacierUploadDollarsPerGB = theGlacierUploadDollarsPerGB; + glacierDataTransferOutDollarsPerGB = theGlacierDataTransferOutDollarsPerGB; + supportsGlacier = theSupportsGlacier; + } + return self; +} + +- (NSString *)endpointWithService:(NSString *)theServiceName useSSL:(BOOL)useSSL { + return [NSString stringWithFormat:@"http%@://%@.%@.amazonaws.com", (useSSL ? @"s" : @""), theServiceName, regionName]; +} +@end diff --git a/cocoastack/aws/SignatureV2Provider.h b/cocoastack/aws/SignatureV2Provider.h new file mode 100644 index 0000000..119d055 --- /dev/null +++ b/cocoastack/aws/SignatureV2Provider.h @@ -0,0 +1,15 @@ +// +// SignatureV2Provider.h +// Arq +// +// Created by Stefan Reitshamer on 9/16/12. +// +// + + +@interface SignatureV2Provider : NSObject { + NSData *secretKeyData; +} +- (id)initWithSecretKey:(NSString *)secret; +- (NSString *)signatureForHTTPMethod:(NSString *)theMethod url:(NSURL *)theURL; +@end diff --git a/cocoastack/aws/SignatureV2Provider.m b/cocoastack/aws/SignatureV2Provider.m new file mode 100644 index 0000000..b8c933f --- /dev/null +++ b/cocoastack/aws/SignatureV2Provider.m @@ -0,0 +1,44 @@ +// +// SignatureV2Provider.m +// Arq +// +// Created by Stefan Reitshamer on 9/16/12. +// +// + +#include +#import "SignatureV2Provider.h" +#import "NSData-Base64Extensions.h" + + +@implementation SignatureV2Provider + +- (id)initWithSecretKey:(NSString *)theSecretKey { + if (self = [super init]) { + secretKeyData = [[theSecretKey dataUsingEncoding:NSUTF8StringEncoding] retain]; + } + return self; +} +- (void)dealloc { + [secretKeyData release]; + [super dealloc]; +} + +- (NSString *)signatureForHTTPMethod:(NSString *)theMethod url:(NSURL *)theURL { + NSMutableString *stringToSign = [NSMutableString string]; + [stringToSign appendFormat:@"%@\n", theMethod]; + [stringToSign appendFormat:@"%@\n", [[theURL host] lowercaseString]]; + NSString *thePath = [theURL path]; + if ([thePath length] == 0) { + thePath = @"/"; + } + [stringToSign appendFormat:@"%@\n", thePath]; + [stringToSign appendString:[theURL query]]; + + NSData *data = [stringToSign dataUsingEncoding:NSUTF8StringEncoding]; + unsigned char digest[CC_SHA256_DIGEST_LENGTH]; + CCHmac(kCCHmacAlgSHA256, [secretKeyData bytes], [secretKeyData length], [data bytes], [data length], digest); + NSData *sig = [NSData dataWithBytes:digest length:CC_SHA256_DIGEST_LENGTH]; + return [sig encodeBase64]; +} +@end diff --git a/cocoastack/crypto/CryptoKey.h b/cocoastack/crypto/CryptoKey.h new file mode 100644 index 0000000..991892d --- /dev/null +++ b/cocoastack/crypto/CryptoKey.h @@ -0,0 +1,28 @@ +// +// CryptoKey.h +// +// Created by Stefan Reitshamer on 6/9/11. +// Copyright 2011 Haystack Software. All rights reserved. +// + +#ifdef USE_OPENSSL +#import "OpenSSLCryptoKey.h" +#else +#import "CCCryptoKey.h" +#endif + +@interface CryptoKey : NSObject { +#ifdef USE_OPENSSL + OpenSSLCryptoKey *cryptoKey; +#else + CCCryptoKey *cryptoKey; +#endif +} ++ (NSString *)errorDomain; + +- (id)initWithPassword:(NSString *)thePassword salt:(NSData *)theSalt error:(NSError **)error; +- (id)initLegacyWithPassword:(NSString *)thePassword error:(NSError **)error; + +- (NSData *)encrypt:(NSData *)plainData error:(NSError **)error; +- (NSData *)decrypt:(NSData *)encrypted error:(NSError **)error; +@end diff --git a/cocoastack/crypto/CryptoKey.m b/cocoastack/crypto/CryptoKey.m new file mode 100644 index 0000000..26fae02 --- /dev/null +++ b/cocoastack/crypto/CryptoKey.m @@ -0,0 +1,78 @@ +// +// CryptoKey.m +// Arq +// +// Created by Stefan Reitshamer on 6/9/11. +// Copyright 2011 Haystack Software. All rights reserved. +// + +#import "CryptoKey.h" + +#ifdef USE_OPENSSL +#import "OpenSSLCryptoKey.h" +#else +#import "CCCryptoKey.h" +#endif + + +@implementation CryptoKey ++ (NSString *)errorDomain { + return @"CryptoKeyErrorDomain"; +} + +- (id)init { + @throw [NSException exceptionWithName:@"InvalidInitializerException" reason:@"can't call CryptoKey init" userInfo:nil]; +} +- (id)initWithPassword:(NSString *)thePassword salt:(NSData *)theSalt error:(NSError **)error { + if (self = [super init]) { + if ([thePassword length] == 0) { + SETNSERROR([CryptoKey errorDomain], ERROR_NOT_FOUND, @"missing encryption password"); + [self release]; + return nil; + } + +#ifdef USE_OPENSSL + cryptoKey = [[OpenSSLCryptoKey alloc] initWithPassword:thePassword salt:theSalt error:error]; + HSLogDebug(@"using OpenSSL"); +#else + cryptoKey = [[CCCryptoKey alloc] initWithPassword:thePassword salt:theSalt error:error]; + HSLogDebug(@"using CommonCrypto"); +#endif + if (cryptoKey == nil) { + [self release]; + return nil; + } + } + return self; +} +- (id)initLegacyWithPassword:(NSString *)thePassword error:(NSError **)error { + if (self = [super init]) { + if ([thePassword length] == 0) { + SETNSERROR([CryptoKey errorDomain], ERROR_NOT_FOUND, @"missing encryption password"); + [self release]; + return nil; + } + +#ifdef USE_OPENSSL + cryptoKey = [[OpenSSLCryptoKey alloc] initLegacyWithPassword:thePassword error:error]; +#else + cryptoKey = [[CCCryptoKey alloc] initLegacyWithPassword:thePassword error:error]; +#endif + if (cryptoKey == nil) { + [self release]; + return nil; + } + } + return self; +} +- (void)dealloc { + [cryptoKey release]; + [super dealloc]; +} +- (NSData *)encrypt:(NSData *)plainData error:(NSError **)error { + return [cryptoKey encrypt:plainData error:error]; +} +- (NSData *)decrypt:(NSData *)encrypted error:(NSError **)error { + return [cryptoKey decrypt:encrypted error:error]; +} +@end diff --git a/cocoastack/crypto/MD5Hash.h b/cocoastack/crypto/MD5Hash.h new file mode 100644 index 0000000..c6ef9ec --- /dev/null +++ b/cocoastack/crypto/MD5Hash.h @@ -0,0 +1,12 @@ +// +// MD5Hash.h +// Arq +// +// Created by Stefan Reitshamer on 1/1/14. +// Copyright (c) 2014 Stefan Reitshamer. All rights reserved. +// + + +@interface MD5Hash : NSObject ++ (NSString *)hashDataBase64Encode:(NSData *)theData; +@end diff --git a/cocoastack/crypto/MD5Hash.m b/cocoastack/crypto/MD5Hash.m new file mode 100644 index 0000000..a98a795 --- /dev/null +++ b/cocoastack/crypto/MD5Hash.m @@ -0,0 +1,25 @@ +// +// MD5Hash.m +// Arq +// +// Created by Stefan Reitshamer on 1/1/14. +// Copyright (c) 2014 Stefan Reitshamer. All rights reserved. +// + +#import +#import "MD5Hash.h" +#import "NSString_extra.h" +#import "NSData-Base64Extensions.h" + + +@implementation MD5Hash ++ (NSString *)hashDataBase64Encode:(NSData *)data { + unsigned char digest[CC_MD5_DIGEST_LENGTH]; + memset(digest, 0, CC_MD5_DIGEST_LENGTH); + if (CC_MD5([data bytes], (CC_LONG)[data length], digest) == NULL) { + HSLogError(@"CC_MD5 failed!"); + } + NSData *digestData = [NSData dataWithBytes:digest length:CC_MD5_DIGEST_LENGTH]; + return [digestData encodeBase64]; +} +@end diff --git a/crypto/NSData-Base64Extensions.h b/cocoastack/crypto/NSData-Base64Extensions.h similarity index 92% rename from crypto/NSData-Base64Extensions.h rename to cocoastack/crypto/NSData-Base64Extensions.h index 40f7fe3..2146cb9 100644 --- a/crypto/NSData-Base64Extensions.h +++ b/cocoastack/crypto/NSData-Base64Extensions.h @@ -18,10 +18,9 @@ // 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 + @interface NSData (Base64) - (NSString *) encodeBase64; -- (NSString *) encodeBase64WithNewlines: (BOOL) encodeWithNewlines; @end diff --git a/cocoastack/crypto/NSData-Base64Extensions.m b/cocoastack/crypto/NSData-Base64Extensions.m new file mode 100644 index 0000000..1ecc65c --- /dev/null +++ b/cocoastack/crypto/NSData-Base64Extensions.m @@ -0,0 +1,93 @@ +// Copyright (c) 2006 Dave Dribin (http://www.dribin.org/dave/) +// 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 "NSData-Base64Extensions.h" +#include +#include + +@implementation NSData (Base64) + +- (NSString *) encodeBase64 { + //Point to start of the data and set buffer sizes + int inLength = (int)[self length]; + int outLength = ((((inLength * 4)/3)/4)*4) + (((inLength * 4)/3)%4 ? 4 : 0); + const char *inputBuffer = [self bytes]; + char *outputBuffer = malloc(outLength+1); + outputBuffer[outLength] = 0; + + //64 digit code + static char Encode[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + //start the count + int cycle = 0; + int inpos = 0; + int outpos = 0; + char temp; + + //Pad the last to bytes, the outbuffer must always be a multiple of 4 + outputBuffer[outLength-1] = '='; + outputBuffer[outLength-2] = '='; + + /* http://en.wikipedia.org/wiki/Base64 + Text content M a n + ASCII 77 97 110 + 8 Bit pattern 01001101 01100001 01101110 + + 6 Bit pattern 010011 010110 000101 101110 + Index 19 22 5 46 + Base64-encoded T W F u + */ + + + while (inpos < inLength){ + switch (cycle) { + case 0: + outputBuffer[outpos++] = Encode[(inputBuffer[inpos]&0xFC)>>2]; + cycle = 1; + break; + case 1: + temp = (inputBuffer[inpos++]&0x03)<<4; + outputBuffer[outpos] = Encode[temp]; + cycle = 2; + break; + case 2: + outputBuffer[outpos++] = Encode[temp|(inputBuffer[inpos]&0xF0)>> 4]; + temp = (inputBuffer[inpos++]&0x0F)<<2; + outputBuffer[outpos] = Encode[temp]; + cycle = 3; + break; + case 3: + outputBuffer[outpos++] = Encode[temp|(inputBuffer[inpos]&0xC0)>>6]; + cycle = 4; + break; + case 4: + outputBuffer[outpos++] = Encode[inputBuffer[inpos++]&0x3f]; + cycle = 0; + break; + default: + cycle = 0; + break; + } + } + NSString *pictemp = [NSString stringWithUTF8String:outputBuffer]; + free(outputBuffer); + return pictemp; +} +@end diff --git a/cocoastack/crypto/OpenSSL.h b/cocoastack/crypto/OpenSSL.h new file mode 100644 index 0000000..53043e9 --- /dev/null +++ b/cocoastack/crypto/OpenSSL.h @@ -0,0 +1,18 @@ +// +// OpenSSL.h +// Arq +// +// Created by Stefan Reitshamer on 10/8/12. +// Copyright (c) 2012 Stefan Reitshamer. All rights reserved. +// + +#import + +@interface OpenSSL : NSObject { + +} ++ (BOOL)initializeSSL:(NSError **)error; ++ (SSL_CTX *)context; ++ (NSString *)errorMessage; + +@end diff --git a/cocoastack/crypto/OpenSSL.m b/cocoastack/crypto/OpenSSL.m new file mode 100644 index 0000000..2c3b100 --- /dev/null +++ b/cocoastack/crypto/OpenSSL.m @@ -0,0 +1,66 @@ +// +// OpenSSL.m +// Arq +// +// Created by Stefan Reitshamer on 10/8/12. +// Copyright (c) 2012 Stefan Reitshamer. All rights reserved. +// + +#ifdef USE_OPENSSL + + +#import "OpenSSL.h" +#import +#import + + +static BOOL initialized = NO; +static SSL_CTX *ctx; + + +@implementation OpenSSL ++ (BOOL)initializeSSL:(NSError **)error { + if (!initialized) { +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + SSL_library_init(); + OpenSSL_add_all_algorithms(); + SSL_load_error_strings(); + ERR_load_crypto_strings(); + ctx = SSL_CTX_new(SSLv23_method()); + if (ctx == NULL) { + SETNSERROR(@"SSLErrorDomain", -1, @"SSL_CTX_new: %@", [OpenSSL errorMessage]); + return NO; + } + initialized = YES; +#pragma GCC diagnostic warning "-Wdeprecated-declarations" + } + return YES; +} ++ (SSL_CTX *)context { + return ctx; +} ++ (NSString *)errorMessage { + NSMutableString *msg = [NSMutableString string]; +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + for (;;) { + unsigned long err = ERR_get_error(); + if (err == 0) { + break; + } + if ([msg length] > 0) { + [msg appendString:@"; "]; + } + HSLogTrace(@"%s", ERR_error_string(err, NULL)); + [msg appendFormat:@"%s", ERR_reason_error_string(err)]; + } + if ([msg length] == 0) { + [msg appendString:@"(no error)"]; + } +#pragma GCC diagnostic warning "-Wdeprecated-declarations" + return msg; +} + +@end + + +#endif /* USE_OPENSSL */ \ No newline at end of file diff --git a/io/CryptoKey.h b/cocoastack/crypto/OpenSSLCryptoKey.h similarity index 51% rename from io/CryptoKey.h rename to cocoastack/crypto/OpenSSLCryptoKey.h index 10cc818..94f1c18 100644 --- a/io/CryptoKey.h +++ b/cocoastack/crypto/OpenSSLCryptoKey.h @@ -1,25 +1,27 @@ // -// CryptoKey.h +// OpenSSLCryptoKey.h // Arq // -// Created by Stefan Reitshamer on 6/9/11. -// Copyright 2011 __MyCompanyName__. All rights reserved. +// Created by Stefan Reitshamer on 10/8/12. +// Copyright (c) 2012 Stefan Reitshamer. All rights reserved. // +#ifdef USE_OPENSSL #include - -@interface CryptoKey : NSObject { +@interface OpenSSLCryptoKey : NSObject { const EVP_CIPHER *cipher; unsigned char evpKey[EVP_MAX_KEY_LENGTH]; unsigned char iv[EVP_MAX_IV_LENGTH]; } -+ (NSString *)errorDomain; - (id)initWithPassword:(NSString *)thePassword salt:(NSData *)theSalt error:(NSError **)error; - (id)initLegacyWithPassword:(NSString *)thePassword error:(NSError **)error; -- (const EVP_CIPHER *)cipher; -- (unsigned char *)evpKey; -- (unsigned char *)iv; + +- (NSData *)encrypt:(NSData *)plainData error:(NSError **)error; +- (NSData *)decrypt:(NSData *)encrypted error:(NSError **)error; + @end + +#endif diff --git a/cocoastack/crypto/OpenSSLCryptoKey.m b/cocoastack/crypto/OpenSSLCryptoKey.m new file mode 100644 index 0000000..a1e0d90 --- /dev/null +++ b/cocoastack/crypto/OpenSSLCryptoKey.m @@ -0,0 +1,184 @@ +// +// OpenSSLCryptoKey.m +// Arq +// +// Created by Stefan Reitshamer on 10/8/12. +// Copyright (c) 2012 Stefan Reitshamer. All rights reserved. +// + +#ifdef USE_OPENSSL + + +#import "OpenSSLCryptoKey.h" +#import "OpenSSL.h" +#import "CryptoKey.h" + + +#define ITERATIONS (1000) +#define KEYLEN (48) + +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + +@implementation OpenSSLCryptoKey + +- (id)initWithPassword:(NSString *)thePassword salt:(NSData *)theSalt error:(NSError **)error { + if (self = [super init]) { + if (![OpenSSL initializeSSL:error]) { + [self release]; + return nil; + } + if (theSalt != nil && [theSalt length] != 8) { + SETNSERROR([CryptoKey errorDomain], -1, @"salt must be 8 bytes or nil"); + [self release]; + return nil; + } + cipher = EVP_aes_256_cbc(); + if (cipher == NULL) { + SETNSERROR([CryptoKey errorDomain], -1, @"cipher not found!"); + [self release]; + return nil; + } + const char *cPassword = [thePassword UTF8String]; + unsigned char *cSaltCopy = NULL; + if (theSalt != nil) { + cSaltCopy = (unsigned char *)malloc([theSalt length]); + memcpy(cSaltCopy, [theSalt bytes], [theSalt length]); + } else { + HSLogWarn(@"NULL salt value for CryptoKey"); + } + unsigned char buf[KEYLEN]; + memset(buf, 0, KEYLEN); + if (PKCS5_PBKDF2_HMAC_SHA1(cPassword, (int)strlen(cPassword), cSaltCopy, (int)[theSalt length], ITERATIONS, KEYLEN, buf) == 0) { + SETNSERROR([CryptoKey errorDomain], -1, @"PKCS5_PBKDF2_HMAC_SHA1 failed"); + if (cSaltCopy != NULL) { + free(cSaltCopy); + } + [self release]; + return nil; + } + evpKey[0] = 0; + int keySize = EVP_BytesToKey(cipher, EVP_sha1(), cSaltCopy, buf, KEYLEN, ITERATIONS, evpKey, iv); + if (cSaltCopy != NULL) { + free(cSaltCopy); + } + if (keySize == 0) { + SETNSERROR([CryptoKey errorDomain], -1, @"EVP_BytesToKey: %@", [OpenSSL errorMessage]); + [self release]; + return nil; + } + if (keySize != 32) { + SETNSERROR([CryptoKey errorDomain], -1, @"invalid key length -- should be 32 bytes"); + [self release]; + return nil; + } + } + return self; +} +- (id)initLegacyWithPassword:(NSString *)thePassword error:(NSError **)error { + if (self = [super init]) { + if (![OpenSSL initializeSSL:error]) { + [self release]; + return nil; + } + + cipher = EVP_aes_256_cbc(); + evpKey[0] = 0; + NSData *passwordData = [thePassword dataUsingEncoding:NSUTF8StringEncoding]; + int keySize = EVP_BytesToKey(cipher, EVP_md5(), NULL, [passwordData bytes], (int)[passwordData length], 1, evpKey, iv); + if (keySize == 0) { + SETNSERROR([CryptoKey errorDomain], -1, @"EVP_BytesToKey: %@", [OpenSSL errorMessage]); + [self release]; + return nil; + } + if (keySize != 32) { + SETNSERROR([CryptoKey errorDomain], -1, @"invalid key length -- should be 32 bytes"); + [self release]; + return nil; + } + } + return self; +} +- (void)dealloc { + [super dealloc]; +} + +- (NSData *)encrypt:(NSData *)plainData error:(NSError **)error { + if ([plainData length] == 0) { + return [NSData data]; + } + EVP_CIPHER_CTX cipherContext; + EVP_CIPHER_CTX_init(&cipherContext); + if (!EVP_EncryptInit(&cipherContext, cipher, evpKey, iv)) { + SETNSERROR([CryptoKey errorDomain], -1, @"EVP_EncryptInit: %@", [OpenSSL errorMessage]); + EVP_CIPHER_CTX_cleanup(&cipherContext); + return nil; + } + + // Need room for data + cipher block size - 1. + unsigned char *outbuf = (unsigned char *)malloc([plainData length] + EVP_CIPHER_CTX_block_size(&cipherContext) - 1); + + int outlen = 0; + if (!EVP_EncryptUpdate(&cipherContext, outbuf, &outlen, [plainData bytes], (int)[plainData length])) { + SETNSERROR([CryptoKey errorDomain], -1, @"EVP_EncryptUpdate: %@", [OpenSSL errorMessage]); + free(outbuf); + EVP_CIPHER_CTX_cleanup(&cipherContext); + return nil; + } + + int extralen = 0; + if (!EVP_EncryptFinal(&cipherContext, outbuf + outlen, &extralen)) { + SETNSERROR([CryptoKey errorDomain], -1, @"EVP_EncryptFinal: %@", [OpenSSL errorMessage]); + free(outbuf); + EVP_CIPHER_CTX_cleanup(&cipherContext); + return nil; + } + EVP_CIPHER_CTX_cleanup(&cipherContext); + + NSData *ret = [[[NSData alloc] initWithBytesNoCopy:outbuf length:(outlen + extralen)] autorelease]; + return ret; +} +- (NSData *)decrypt:(NSData *)encrypted error:(NSError **)error { + if (encrypted == nil) { + SETNSERROR([CryptoKey errorDomain], -1, @"decrypt: nil input NSData"); + return nil; + } + if ([encrypted length] == 0) { + return [NSData data]; + } + + int inlen = (int)[encrypted length]; + unsigned char *input = (unsigned char *)[encrypted bytes]; + + EVP_CIPHER_CTX cipherContext; + EVP_CIPHER_CTX_init(&cipherContext); + if (!EVP_DecryptInit(&cipherContext, cipher, evpKey, iv)) { + SETNSERROR([CryptoKey errorDomain], -1, @"EVP_DecryptInit: %@", [OpenSSL errorMessage]); + EVP_CIPHER_CTX_cleanup(&cipherContext); + return nil; + } + + unsigned char *outbuf = (unsigned char *)malloc(inlen + EVP_CIPHER_CTX_block_size(&cipherContext)); + + int outlen = 0; + if (!EVP_DecryptUpdate(&cipherContext, outbuf, &outlen, input, inlen)) { + SETNSERROR([CryptoKey errorDomain], -1, @"EVP_DecryptUpdate: %@", [OpenSSL errorMessage]); + free(outbuf); + EVP_CIPHER_CTX_cleanup(&cipherContext); + return nil; + } + + int extralen = 0; + if (!EVP_DecryptFinal(&cipherContext, outbuf + outlen, &extralen)) { + SETNSERROR([CryptoKey errorDomain], -1, @"EVP_DecryptFinal: %@", [OpenSSL errorMessage]); + free(outbuf); + EVP_CIPHER_CTX_cleanup(&cipherContext); + return nil; + } + + EVP_CIPHER_CTX_cleanup(&cipherContext); + NSData *ret = [[[NSData alloc] initWithBytesNoCopy:outbuf length:(outlen + extralen)] autorelease]; + return ret; +} +@end + +#endif diff --git a/crypto/SHA1Hash.h b/cocoastack/crypto/SHA1Hash.h similarity index 94% rename from crypto/SHA1Hash.h rename to cocoastack/crypto/SHA1Hash.h index a2568e4..5b2c518 100644 --- a/crypto/SHA1Hash.h +++ b/cocoastack/crypto/SHA1Hash.h @@ -32,14 +32,13 @@ #import "InputStream.h" -@class Blob; @interface SHA1Hash : NSObject { } + (NSString *)errorDomain; + (NSString *)hashData:(NSData *)data; -+ (NSString *)hashBlob:(Blob *)blob blobLength:(unsigned long long *)blobLength error:(NSError **)error; + (NSString *)hashStream:(id )is withLength:(uint64_t)length error:(NSError **)error; + (NSString *)hashFile:(NSString *)path error:(NSError **)error; ++ (NSString *)hashPhoto:(NSString *)path error:(NSError **)error; @end diff --git a/cocoastack/crypto/SHA1Hash.m b/cocoastack/crypto/SHA1Hash.m new file mode 100644 index 0000000..3eb1cdc --- /dev/null +++ b/cocoastack/crypto/SHA1Hash.m @@ -0,0 +1,246 @@ +/* + Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import +#include +#import "SHA1Hash.h" +#import "NSString_extra.h" +#import "FileInputStream.h" +#import "BufferedInputStream.h" + + +#define MY_BUF_SIZE (4096) + +@interface SHA1Hash (internal) ++ (NSString *)hashStream:(id )is error:(NSError **)error; ++ (NSString *)hashStream:(id )is streamLength:(unsigned long long *)streamLength error:(NSError **)error; ++ (NSString *)hashPhotoStream:(BufferedInputStream *)bis totalLength:(unsigned long long)totalLength error:(NSError **)error; ++ (BOOL)updateSHA1:(CC_SHA1_CTX *)ctx fromStream:(id )is length:(unsigned long long)theLength error:(NSError **)error; +@end + + +@implementation SHA1Hash ++ (NSString *)errorDomain { + return @"SHA1HashErrorDomain"; +} ++ (NSString *)hashData:(NSData *)data { + unsigned char digest[CC_SHA1_DIGEST_LENGTH]; + memset(digest, 0, CC_SHA1_DIGEST_LENGTH); + if (CC_SHA1([data bytes], (CC_LONG)[data length], digest) == NULL) { + HSLogError(@"CC_SHA1 failed!"); + } + return [NSString hexStringWithBytes:digest length:CC_SHA1_DIGEST_LENGTH]; +} ++ (NSString *)hashFile:(NSString *)path error:(NSError **)error { + struct stat st; + if (lstat([path fileSystemRepresentation], &st) == -1) { + int errnum = errno; + HSLogError(@"lstat(%@) error %d: %s", path, errnum, strerror(errnum)); + SETNSERROR(@"UnixErrorDomain", errnum, @"%@: %s", path, strerror(errnum)); + return NO; + } + unsigned long long length = (unsigned long long)st.st_size; + FileInputStream *fis = [[FileInputStream alloc] initWithPath:path offset:0 length:length]; + NSString *sha1 = [SHA1Hash hashStream:fis error:error]; + [fis release]; + return sha1; +} ++ (NSString *)hashStream:(id )is withLength:(uint64_t)length error:(NSError **)error { + CC_SHA1_CTX ctx; + CC_SHA1_Init(&ctx); + uint64_t received = 0; + unsigned char *buf = (unsigned char *)malloc(MY_BUF_SIZE); + NSInteger ret = 0; + while (received < length) { + uint64_t toRead = length - received; + uint64_t toReadThisTime = toRead > MY_BUF_SIZE ? MY_BUF_SIZE : toRead; + ret = [is read:buf bufferLength:(NSUInteger)toReadThisTime error:error]; + if (ret < 0) { + break; + } + if (ret == 0) { + SETNSERROR([SHA1Hash errorDomain], ERROR_EOF, @"unexpected EOF in %@ after %qu of %qu bytes", is, received, length); + break; + } + CC_SHA1_Update(&ctx, buf, (CC_LONG)ret); + received += (uint64_t)ret; + } + free(buf); + if (ret < 0) { + return nil; + } + unsigned char digest[CC_SHA1_DIGEST_LENGTH]; + CC_SHA1_Final(digest, &ctx); + return [NSString hexStringWithBytes:digest length:CC_SHA1_DIGEST_LENGTH]; +} ++ (NSString *)hashPhoto:(NSString *)path error:(NSError **)error { + struct stat st; + if (lstat([path fileSystemRepresentation], &st) == -1) { + int errnum = errno; + HSLogError(@"lstat(%@) error %d: %s", path, errnum, strerror(errnum)); + SETNSERROR(@"UnixErrorDomain", errnum, @"%@: %s", path, strerror(errnum)); + return nil; + } + unsigned long long totalLength = (unsigned long long)st.st_size; + FileInputStream *fis = [[FileInputStream alloc] initWithPath:path offset:0 length:totalLength]; + BufferedInputStream *bis = [[BufferedInputStream alloc] initWithUnderlyingStream:fis]; + [fis release]; + NSString *sha1 = [SHA1Hash hashPhotoStream:bis totalLength:totalLength error:error]; + [bis release]; + if (sha1 == nil) { + sha1 = [SHA1Hash hashFile:path error:error]; + } + return sha1; +} +@end + +@implementation SHA1Hash (internal) ++ (NSString *)hashStream:(id )is error:(NSError **)error { + unsigned long long length; + return [SHA1Hash hashStream:is streamLength:&length error:error]; +} ++ (NSString *)hashStream:(id )is streamLength:(unsigned long long *)streamLength error:(NSError **)error { + CC_SHA1_CTX ctx; + CC_SHA1_Init(&ctx); + *streamLength = 0; + unsigned char *buf = (unsigned char *)malloc(MY_BUF_SIZE); + NSInteger ret = 0; + for (;;) { + ret = [is read:buf bufferLength:MY_BUF_SIZE error:error]; + if (ret <= 0) { + break; + } + CC_SHA1_Update(&ctx, buf, (CC_LONG)ret); + *streamLength += (unsigned long long)ret; + } + free(buf); + if (ret < 0) { + return nil; + } + unsigned char md[CC_SHA1_DIGEST_LENGTH]; + CC_SHA1_Final(md, &ctx); + return [NSString hexStringWithBytes:md length:CC_SHA1_DIGEST_LENGTH]; +} ++ (NSString *)hashPhotoStream:(BufferedInputStream *)bis totalLength:(unsigned long long)totalLength error:(NSError **)error { + CC_SHA1_CTX ctx; + CC_SHA1_Init(&ctx); + uint64_t received = 0; + if (totalLength > 4) { + unsigned char buf[2]; + if (![bis readExactly:2 into:buf error:error]) { + return nil; + } + received += 2; + if (buf[0] == 0xff && buf[1] == 0xd8) { + // It's a JPEG. Skip the metadata. + for (;;) { + if (![bis readExactly:2 into:buf error:error]) { + return nil; + } + received += 2; + unsigned int markerID = ((unsigned int)buf[0] << 8) + (unsigned int)buf[1]; + NSAssert(received <= totalLength, @"received can't be greater than totalLength"); + if (received == totalLength) { + if (markerID != 0xffd9) { + HSLogWarn(@"unexpected end marker in JPEG: 0x%04x", markerID); + } + break; + } + if (![bis readExactly:2 into:buf error:error]) { + return nil; + } + received += 2; + uint32_t segmentLength = ((uint32_t)buf[0] << 8) + (uint32_t)buf[1]; + if (markerID == 0xffda) { + // Read in the rest of the file minus the last 2 bytes. + if (![self updateSHA1:&ctx fromStream:bis length:(totalLength - 2 - received) error:error]) { + return nil; + } + received = totalLength - 2; + } else { + if (segmentLength < 3) { + SETNSERROR([SHA1Hash errorDomain], -1, @"%@: JPEG segment %04x length can't be fewer than 3 bytes long", bis, (unsigned int)markerID); + return nil; + } + NSData *data = [bis readExactly:(segmentLength - 2) error:error]; + if (data == nil) { + return nil; + } + received += segmentLength - 2; + } + } + } else { + CC_SHA1_Update(&ctx, buf, 2); + } + } + unsigned char *buf = (unsigned char *)malloc(MY_BUF_SIZE); + NSInteger ret = 0; + while (received < totalLength) { + uint64_t needed = totalLength - received; + int64_t toRead = needed > MY_BUF_SIZE ? MY_BUF_SIZE : needed; + ret = [bis read:buf bufferLength:(NSUInteger)toRead error:error]; + if (ret <= 0) { + break; + } + CC_SHA1_Update(&ctx, buf, (CC_LONG)ret); + received += (uint64_t)ret; + } + free(buf); + if (ret < 0) { + return nil; + } + unsigned char md[CC_SHA1_DIGEST_LENGTH]; + CC_SHA1_Final(md, &ctx); + return [NSString hexStringWithBytes:md length:CC_SHA1_DIGEST_LENGTH]; +} ++ (BOOL)updateSHA1:(CC_SHA1_CTX *)ctx fromStream:(id )is length:(unsigned long long)theLength error:(NSError **)error { + unsigned char *imageBuf = (unsigned char *)malloc(MY_BUF_SIZE); + uint64_t recvd = 0; + NSInteger ret = 0; + while (recvd < theLength) { + uint64_t needed = theLength - recvd; + uint64_t toRead = needed > MY_BUF_SIZE ? MY_BUF_SIZE : needed; + ret = [is read:imageBuf bufferLength:(NSUInteger)toRead error:error]; + if (ret < 0) { + break; + } + if (ret == 0) { + SETNSERROR([SHA1Hash errorDomain], -1, @"unexpected EOF reading image data from %@", is); + break; + } + CC_SHA1_Update(ctx, imageBuf, (CC_LONG)ret); + recvd += (uint64_t)ret; + } + free(imageBuf); + return ret > 0; +} +@end diff --git a/cocoastack/crypto/SHA256Hash.h b/cocoastack/crypto/SHA256Hash.h new file mode 100644 index 0000000..a188f00 --- /dev/null +++ b/cocoastack/crypto/SHA256Hash.h @@ -0,0 +1,15 @@ +// +// SHA256Hash.h +// Arq +// +// Created by Stefan Reitshamer on 9/8/12. +// +// + + +@interface SHA256Hash : NSObject { + +} ++ (NSData *)hashData:(NSData *)data; ++ (NSData *)hashBytes:(const unsigned char *)bytes length:(NSUInteger)length; +@end diff --git a/cocoastack/crypto/SHA256Hash.m b/cocoastack/crypto/SHA256Hash.m new file mode 100644 index 0000000..6456fe2 --- /dev/null +++ b/cocoastack/crypto/SHA256Hash.m @@ -0,0 +1,26 @@ +// +// SHA256Hash.m +// Arq +// +// Created by Stefan Reitshamer on 9/8/12. +// +// + +#import "SHA256Hash.h" +#import + + +@implementation SHA256Hash ++ (NSData *)hashData:(NSData *)data { + NSAssert(data != nil, @"data may not be nil!"); + return [SHA256Hash hashBytes:(const unsigned char *)[data bytes] length:[data length]]; +} ++ (NSData *)hashBytes:(const unsigned char *)bytes length:(NSUInteger)length { + unsigned char *digest = (unsigned char *)malloc(CC_SHA256_DIGEST_LENGTH); + memset(digest, 0, CC_SHA256_DIGEST_LENGTH); + if (CC_SHA256(bytes, (CC_LONG)length, digest) == NULL) { + HSLogError(@"CC_SHA256 failed!"); + } + return [NSData dataWithBytesNoCopy:digest length:CC_SHA256_DIGEST_LENGTH]; +} +@end diff --git a/cocoastack/glacier/GlacierAuthorization.h b/cocoastack/glacier/GlacierAuthorization.h new file mode 100644 index 0000000..6720687 --- /dev/null +++ b/cocoastack/glacier/GlacierAuthorization.h @@ -0,0 +1,21 @@ +// +// GlacierAuthorization.h +// +// Created by Stefan Reitshamer on 9/8/12. +// +// + +@class AWSRegion; +@protocol HTTPConnection; +@protocol GlacierSigner; + + +@interface GlacierAuthorization : NSObject { + AWSRegion *awsRegion; + id conn; + NSData *requestBody; + NSString *accessKey; + id signer; +} +- (id)initWithAWSRegion:(AWSRegion *)theAWSRegion connection:(id )theConn requestBody:(NSData *)theRequestBody accessKey:(NSString *)theAccessKey signer:(id )theSigner; +@end diff --git a/cocoastack/glacier/GlacierAuthorization.m b/cocoastack/glacier/GlacierAuthorization.m new file mode 100644 index 0000000..23cb3df --- /dev/null +++ b/cocoastack/glacier/GlacierAuthorization.m @@ -0,0 +1,120 @@ +// +// GlacierAuthorization.m +// +// Created by Stefan Reitshamer on 9/8/12. +// +// + +#import "GlacierAuthorization.h" +#import "HTTPConnection.h" +#import "GlacierSigner.h" +#import "AWSRegion.h" +#import "ISO8601Date.h" +#import "SHA256Hash.h" +#import "NSString_extra.h" + + +@implementation GlacierAuthorization +- (id)initWithAWSRegion:(AWSRegion *)theAWSRegion connection:(id )theConn requestBody:(NSData *)theRequestBody accessKey:(NSString *)theAccessKey signer:(id )theSigner { + if (self = [super init]) { + awsRegion = [theAWSRegion retain]; + conn = [theConn retain]; + requestBody = [theRequestBody retain]; + accessKey = [theAccessKey retain]; + signer = [theSigner retain]; + } + return self; +} +- (void)dealloc { + [awsRegion release]; + [conn release]; + [requestBody release]; + [accessKey release]; + [signer release]; + [super dealloc]; +} + + +#pragma mark NSObject +- (NSString *)description { + NSMutableString *canonicalRequest = [[[NSMutableString alloc] init] autorelease]; + [canonicalRequest appendString:[conn requestMethod]]; + [canonicalRequest appendString:@"\n"]; + + [canonicalRequest appendString:[conn requestPathInfo]]; + [canonicalRequest appendString:@"\n"]; + + if ([conn requestQueryString] != nil) { + NSString *query = [(NSString *)CFURLCreateStringByAddingPercentEscapes(NULL, + (CFStringRef)[conn requestQueryString], + (CFStringRef)@"=", + (CFStringRef)@"!*'();:@&+$,/?%#[]", + kCFStringEncodingUTF8) autorelease]; + + [canonicalRequest appendString:query]; + } + [canonicalRequest appendString:@"\n"]; + + // Add sorted canonical headers: + NSMutableArray *theHeaders = [NSMutableArray array]; + for (NSString *headerName in [conn requestHeaderKeys]) { + NSString *lower = [headerName lowercaseString]; + [theHeaders addObject:[NSString stringWithFormat:@"%@:%@\n", lower, [conn requestHeaderForKey:headerName]]]; + } + [theHeaders sortUsingSelector:@selector(compare:)]; + for (NSString *hdr in theHeaders) { + [canonicalRequest appendString:hdr]; + } + + // Add a newline: + [canonicalRequest appendString:@"\n"]; + + NSMutableArray *theSortedLowercaseHeaderNames = [NSMutableArray array]; + for (NSString *headerName in [conn requestHeaderKeys]) { + [theSortedLowercaseHeaderNames addObject:[headerName lowercaseString]]; + } + [theSortedLowercaseHeaderNames sortUsingSelector:@selector(compare:)]; + + // Create list of names of the signed headers: + NSMutableString *namesOfSignedHeaders = [NSMutableString string]; + NSString *separator = @""; + for (NSString *name in theSortedLowercaseHeaderNames) { + [namesOfSignedHeaders appendString:separator]; + separator = @";"; + [namesOfSignedHeaders appendString:name]; + } + + // Add list of signed headers (header names): + [canonicalRequest appendString:namesOfSignedHeaders]; + [canonicalRequest appendString:@"\n"]; + + // Add hash of payload: + NSData *payload = requestBody; + if (payload == nil) { + payload = [NSData data]; + } + [canonicalRequest appendString:[NSString hexStringWithData:[SHA256Hash hashData:payload]]]; + + + NSString *dateStamp = [ISO8601Date basicDateStringFromDate:[conn date]]; + NSString *dateTime = [ISO8601Date basicDateTimeStringFromDate:[conn date]]; + + NSString *scope = [NSString stringWithFormat:@"%@/%@/glacier/aws4_request", dateStamp, [awsRegion regionName]]; + NSString *signingCredentials = [NSString stringWithFormat:@"%@/%@", accessKey, scope]; + + HSLogTrace(@"canonical string = %@", canonicalRequest); + + NSData *canonicalRequestData = [canonicalRequest dataUsingEncoding:NSUTF8StringEncoding]; + NSString *canonicalRequestHashHex = [NSString hexStringWithData:[SHA256Hash hashData:canonicalRequestData]]; + + NSString *stringToSign = [NSString stringWithFormat:@"AWS4-HMAC-SHA256\n%@\n%@\n%@", dateTime, scope, canonicalRequestHashHex]; + + HSLogTrace(@"stringToSign = %@", stringToSign); + + //FIXME: Extract the service name from the hostname (see AwsHostNameUtils.parseServiceName() method in Java AWS SDK). + NSString *signature = [signer signString:stringToSign withDateStamp:dateStamp regionName:[awsRegion regionName] serviceName:@"glacier"]; + + NSString *ret = [[[NSString alloc] initWithFormat:@"AWS4-HMAC-SHA256 Credential=%@, SignedHeaders=%@, Signature=%@", signingCredentials, namesOfSignedHeaders, signature] autorelease]; + return ret; +} +@end diff --git a/cocoastack/glacier/GlacierAuthorizationProvider.h b/cocoastack/glacier/GlacierAuthorizationProvider.h new file mode 100644 index 0000000..a62185b --- /dev/null +++ b/cocoastack/glacier/GlacierAuthorizationProvider.h @@ -0,0 +1,19 @@ +// +// GlacierAuthorizationProvider.h +// +// Created by Stefan Reitshamer on 9/7/12. +// +// + +@protocol GlacierSigner; +@protocol HTTPConnection; +@class AWSRegion; + + +@interface GlacierAuthorizationProvider : NSObject { + NSString *accessKey; + id signer; +} +- (id)initWithAccessKey:(NSString *)access secretKey:(NSString *)secret; +- (NSString *)authorizationForAWSRegion:(AWSRegion *)theAWSRegion connection:(id )theConn requestBody:(NSData *)theRequestBody; +@end diff --git a/cocoastack/glacier/GlacierAuthorizationProvider.m b/cocoastack/glacier/GlacierAuthorizationProvider.m new file mode 100644 index 0000000..809834b --- /dev/null +++ b/cocoastack/glacier/GlacierAuthorizationProvider.m @@ -0,0 +1,34 @@ +// +// GlacierAuthorizationProvider.m +// +// Created by Stefan Reitshamer on 9/7/12. +// +// + +#import "GlacierAuthorizationProvider.h" +#import "LocalGlacierSigner.h" +#import "HTTPConnection.h" +#import "SHA256Hash.h" +#import "ISO8601Date.h" +#import "GlacierAuthorization.h" + + +@implementation GlacierAuthorizationProvider +- (id)initWithAccessKey:(NSString *)access secretKey:(NSString *)secret { + if (self = [super init]) { + accessKey = [access retain]; + signer = [[LocalGlacierSigner alloc] initWithSecretKey:secret]; + } + return self; +} +- (void)dealloc { + [accessKey release]; + [signer release]; + [super dealloc]; +} + +- (NSString *)authorizationForAWSRegion:(AWSRegion *)theAWSRegion connection:(id )theConn requestBody:(NSData *)theRequestBody { + GlacierAuthorization *authorization = [[[GlacierAuthorization alloc] initWithAWSRegion:theAWSRegion connection:theConn requestBody:theRequestBody accessKey:accessKey signer:signer] autorelease]; + return [authorization description]; +} +@end diff --git a/cocoastack/glacier/GlacierJob.h b/cocoastack/glacier/GlacierJob.h new file mode 100644 index 0000000..740df0a --- /dev/null +++ b/cocoastack/glacier/GlacierJob.h @@ -0,0 +1,24 @@ +// +// GlacierJob.h +// +// Created by Stefan Reitshamer on 9/17/12. +// +// + +@class AWSRegion; + + +@interface GlacierJob : NSObject { + AWSRegion *awsRegion; + NSString *vaultName; + NSDictionary *json; +} +- (id)initWithAWSRegion:(AWSRegion *)theAWSRegion vaultName:(NSString *)theVaultName json:(NSDictionary *)theJSON; +- (AWSRegion *)awsRegion; +- (NSString *)vaultName; +- (NSString *)jobId; +- (NSString *)action; +- (NSString *)archiveId; +- (NSString *)snsTopicArn; +- (NSDictionary *)json; +@end diff --git a/cocoastack/glacier/GlacierJob.m b/cocoastack/glacier/GlacierJob.m new file mode 100644 index 0000000..1edbab9 --- /dev/null +++ b/cocoastack/glacier/GlacierJob.m @@ -0,0 +1,47 @@ +// +// GlacierJob.m +// +// Created by Stefan Reitshamer on 9/17/12. +// +// + +#import "GlacierJob.h" + +@implementation GlacierJob +- (id)initWithAWSRegion:(AWSRegion *)theAWSRegion vaultName:(NSString *)theVaultName json:(NSDictionary *)theJSON { + if (self = [super init]) { + awsRegion = [theAWSRegion retain]; + vaultName = [theVaultName retain]; + json = [theJSON retain]; + } + return self; +} +- (void)dealloc { + [awsRegion release]; + [vaultName release]; + [json release]; + [super dealloc]; +} + +- (AWSRegion *)awsRegion { + return awsRegion; +} +- (NSString *)vaultName { + return vaultName; +} +- (NSString *)jobId { + return [json objectForKey:@"JobId"]; +} +- (NSString *)action { + return [json objectForKey:@"Action"]; +} +- (NSString *)archiveId { + return [json objectForKey:@"ArchiveId"]; +} +- (NSString *)snsTopicArn { + return [json objectForKey:@"SNSTopic"]; +} +- (NSDictionary *)json { + return json; +} +@end diff --git a/cocoastack/glacier/GlacierJobLister.h b/cocoastack/glacier/GlacierJobLister.h new file mode 100644 index 0000000..0420f84 --- /dev/null +++ b/cocoastack/glacier/GlacierJobLister.h @@ -0,0 +1,23 @@ +// +// GlacierJobLister.h +// +// Created by Stefan Reitshamer on 9/17/12. +// +// + +@class GlacierAuthorizationProvider; +@class AWSRegion; + + +@interface GlacierJobLister : NSObject { + GlacierAuthorizationProvider *gap; + NSString *vaultName; + AWSRegion *awsRegion; + BOOL useSSL; + BOOL retryOnTransientError; + NSString *marker; + NSMutableArray *jobs; +} +- (id)initWithGlacierAuthorizationProvider:(GlacierAuthorizationProvider *)theGAP vaultName:(NSString *)theVaultName awsRegion:(AWSRegion *)theAWSRegion useSSL:(BOOL)theUseSSL retryOnTransientError:(BOOL)retry; +- (NSArray *)jobs:(NSError **)error; +@end diff --git a/cocoastack/glacier/GlacierJobLister.m b/cocoastack/glacier/GlacierJobLister.m new file mode 100644 index 0000000..06bf63c --- /dev/null +++ b/cocoastack/glacier/GlacierJobLister.m @@ -0,0 +1,82 @@ +// +// GlacierJobLister.m +// +// Created by Stefan Reitshamer on 9/17/12. +// +// + +#import "GlacierJobLister.h" +#import "GlacierRequest.h" +#import "GlacierResponse.h" +#import "AWSRegion.h" +#import "NSString+SBJSON.h" +#import "GlacierJob.h" + + +@interface GlacierJobLister () +- (BOOL)get:(NSError **)error; +@end + +@implementation GlacierJobLister +- (id)initWithGlacierAuthorizationProvider:(GlacierAuthorizationProvider *)theGAP vaultName:(NSString *)theVaultName awsRegion:(AWSRegion *)theAWSRegion useSSL:(BOOL)theUseSSL retryOnTransientError:(BOOL)retry { + if (self = [super init]) { + gap = [theGAP retain]; + vaultName = [theVaultName retain]; + awsRegion = [theAWSRegion retain]; + useSSL = theUseSSL; + retryOnTransientError = retry; + jobs = [[NSMutableArray alloc] init]; + } + return self; +} +- (void)dealloc { + [gap release]; + [vaultName release]; + [awsRegion release]; + [marker release]; + [jobs release]; + [super dealloc]; +} +- (NSArray *)jobs:(NSError **)error { + for (;;) { + if (![self get:error]) { + return nil; + } + if (marker == nil) { + break; + } + } + return jobs; +} + + +#pragma mark internal +- (BOOL)get:(NSError **)error { + NSString *urlString = [NSString stringWithFormat:@"%@/-/vaults/%@/jobs", [awsRegion glacierEndpointWithSSL:useSSL], vaultName]; + if (marker != nil) { + urlString = [urlString stringByAppendingFormat:@"marker=%@", marker]; + } + NSURL *theURL = [NSURL URLWithString:urlString]; + + GlacierRequest *req = [[[GlacierRequest alloc] initWithMethod:@"GET" url:theURL awsRegion:awsRegion authorizationProvider:gap retryOnTransientError:retryOnTransientError dataTransferDelegate:nil] autorelease]; + GlacierResponse *response = [req execute:error]; + if (response == nil) { + return NO; + } + NSData *data = [response body]; + NSString *responseString = [[[NSString alloc] initWithBytes:[data bytes] length:[data length] encoding:NSUTF8StringEncoding] autorelease]; + NSDictionary *dict = [responseString JSONValue:error]; + [marker release]; + marker = [[dict objectForKey:@"Marker"] retain]; + if ([marker isKindOfClass:[NSNull class]]) { + [marker release]; + marker = nil; + } + NSArray *jobList = [dict objectForKey:@"JobList"]; + for (NSDictionary *jobDict in jobList) { + GlacierJob *job = [[[GlacierJob alloc] initWithAWSRegion:awsRegion vaultName:vaultName json:jobDict] autorelease]; + [jobs addObject:job]; + } + return YES; +} +@end diff --git a/cocoastack/glacier/GlacierRequest.h b/cocoastack/glacier/GlacierRequest.h new file mode 100644 index 0000000..7bf92d9 --- /dev/null +++ b/cocoastack/glacier/GlacierRequest.h @@ -0,0 +1,30 @@ +// +// GlacierRequest.h +// +// Created by Stefan Reitshamer on 9/7/12. +// +// + +@class GlacierAuthorizationProvider; +@class AWSRegion; +@class GlacierResponse; +@protocol DataTransferDelegate; + + +@interface GlacierRequest : NSObject { + NSString *method; + NSURL *url; + AWSRegion *awsRegion; + GlacierAuthorizationProvider *gap; + BOOL retryOnTransientError; + id dataTransferDelegate; + NSData *requestData; + NSMutableDictionary *extraHeaders; +} +- (id)initWithMethod:(NSString *)theMethod url:(NSURL *)theURL awsRegion:(AWSRegion *)theAWSRegion authorizationProvider:(GlacierAuthorizationProvider *)theGAP retryOnTransientError:(BOOL)theRetryOnTransientError dataTransferDelegate:(id )theDataTransferDelegate; + +- (NSString *)errorDomain; +- (void)setRequestData:(NSData *)thereRuestData; +- (void)setHeader:(NSString *)value forKey:(NSString *)key; +- (GlacierResponse *)execute:(NSError **)error; +@end diff --git a/cocoastack/glacier/GlacierRequest.m b/cocoastack/glacier/GlacierRequest.m new file mode 100644 index 0000000..0ce3bd8 --- /dev/null +++ b/cocoastack/glacier/GlacierRequest.m @@ -0,0 +1,184 @@ +// +// GlacierRequest.m +// +// Created by Stefan Reitshamer on 9/7/12. +// +// + +#import "GlacierRequest.h" +#import "GlacierAuthorizationProvider.h" +#import "HTTPConnectionFactory.h" +#import "HTTPConnection.h" +#import "HTTPConnectionFactory.h" +#import "InputStream.h" +#import "HTTP.h" +#import "NSError_Glacier.h" +#import "ISO8601Date.h" +#import "GlacierResponse.h" +#import "GlacierService.h" + + +#define INITIAL_RETRY_SLEEP (0.5) +#define RETRY_SLEEP_GROWTH_FACTOR (1.5) +#define MAX_RETRY_SLEEP (5.0) + + +@interface GlacierRequest () +- (GlacierResponse *)executeOnce:(NSError **)error; +@end + + +@implementation GlacierRequest + +- (id)initWithMethod:(NSString *)theMethod url:(NSURL *)theURL awsRegion:(AWSRegion *)theAWSRegion authorizationProvider:(GlacierAuthorizationProvider *)theGAP retryOnTransientError:(BOOL)theRetryOnTransientError dataTransferDelegate:(id )theDataTransferDelegate { + if (self = [super init]) { + method = [theMethod retain]; + url = [theURL retain]; + NSAssert(url != nil, @"url may not be nil!"); + awsRegion = [theAWSRegion retain]; + gap = [theGAP retain]; + retryOnTransientError = theRetryOnTransientError; + dataTransferDelegate = theDataTransferDelegate; // Do not retain. + extraHeaders = [[NSMutableDictionary alloc] init]; + } + return self; +} +- (void)dealloc { + [method release]; + [url release]; + [awsRegion release]; + [gap release]; + [requestData release]; + [extraHeaders release]; + [super dealloc]; +} + +- (NSString *)errorDomain { + return @"GlacierRequestErrorDomain"; +} +- (void)setRequestData:(NSData *)theRequestData { + [theRequestData retain]; + [requestData release]; + requestData = theRequestData; +} +- (void)setHeader:(NSString *)value forKey:(NSString *)key { + [extraHeaders setObject:value forKey:key]; +} +- (GlacierResponse *)execute:(NSError **)error { + NSAutoreleasePool *pool = nil; + NSTimeInterval sleepTime = INITIAL_RETRY_SLEEP; + GlacierResponse *theResponse = nil; + NSError *myError = nil; + for (;;) { + [pool drain]; + pool = [[NSAutoreleasePool alloc] init]; + BOOL transientError = NO; + BOOL needSleep = NO; + myError = nil; + theResponse = [self executeOnce:&myError]; + if (theResponse != nil) { + break; + } + if ([myError isErrorWithDomain:[self errorDomain] code:ERROR_NOT_FOUND]) { + break; + } else if ([myError isErrorWithDomain:[self errorDomain] code:GLACIER_ERROR_AMAZON_ERROR]) { + int httpStatusCode = [[[myError userInfo] objectForKey:@"HTTPStatusCode"] intValue]; + NSString *amazonCode = [[myError userInfo] objectForKey:@"AmazonCode"]; + + if (retryOnTransientError && [amazonCode isEqualToString:@"RequestTimeoutException"]) { + transientError = YES; + } else if (retryOnTransientError && httpStatusCode == HTTP_REQUEST_TIMEOUT) { // Maybe the Glacier response body wasn't there, but we still got a 408, so we retry. + transientError = YES; + } else if (retryOnTransientError && [[[myError userInfo] objectForKey:@"AmazonMessage"] hasPrefix:@"The request signature we calculated does not match the signature you provided."]) { + // AWS seems to randomly return this error for some people. + transientError = YES; + needSleep = YES; + } else if (retryOnTransientError && [amazonCode isEqualToString:@"ThrottlingException"]) { + transientError = YES; + needSleep = YES; + } else if (httpStatusCode == HTTP_INTERNAL_SERVER_ERROR) { + transientError = YES; + needSleep = YES; + + } else if (retryOnTransientError && httpStatusCode == HTTP_SERVICE_NOT_AVAILABLE) { + transientError = YES; + needSleep = YES; + } else if (retryOnTransientError && httpStatusCode == HTTP_VERSION_NOT_SUPPORTED) { + transientError = YES; + needSleep = YES; + } else { + HSLogError(@"%@ %@: %@", method, url, myError); + break; + } + } else if ([myError isConnectionResetError]) { + transientError = YES; + } else if (retryOnTransientError && [myError isTransientError]) { + transientError = YES; + needSleep = YES; + } else { + HSLogError(@"%@ %@: %@", method, url, myError); + break; + } + + if (transientError) { + HSLogDetail(@"retrying %@ %@: %@", method, url, myError); + } + if (needSleep) { + [NSThread sleepForTimeInterval:sleepTime]; + sleepTime *= RETRY_SLEEP_GROWTH_FACTOR; + if (sleepTime > MAX_RETRY_SLEEP) { + sleepTime = MAX_RETRY_SLEEP; + } + } + } + [theResponse retain]; + [myError retain]; + [pool drain]; + [theResponse autorelease]; + [myError autorelease]; + if (error != NULL) { *error = myError; } + return theResponse; +} + + +#pragma mark internal +- (GlacierResponse *)executeOnce:(NSError **)error { + id conn = [[[HTTPConnectionFactory theFactory] newHTTPConnectionToURL:url method:method dataTransferDelegate:dataTransferDelegate] autorelease]; + [conn setDate:[NSDate date]]; + [conn setRequestHostHeader]; + [conn setRequestHeader:[ISO8601Date basicDateTimeStringFromDate:[NSDate date]] forKey:@"x-amz-date"]; + [conn setRequestHeader:@"2012-06-01" forKey:@"x-amz-glacier-version"]; + + for (NSString *headerKey in [extraHeaders allKeys]) { + [conn setRequestHeader:[extraHeaders objectForKey:headerKey] forKey:headerKey]; + } + [conn setRequestHeader:[gap authorizationForAWSRegion:awsRegion connection:conn requestBody:requestData] forKey:@"Authorization"]; + HSLogDebug(@"%@ %@", method, url); + NSData *responseData = [conn executeRequestWithBody:requestData error:error]; + if (responseData == nil) { + return nil; + } + int code = [conn responseCode]; + if (code >= 200 && code <= 299) { + HSLogDebug(@"HTTP %d; returning response length=%lu", code, (unsigned long)[responseData length]); + GlacierResponse *response = [[[GlacierResponse alloc] initWithCode:code headers:[conn responseHeaders] body:responseData] autorelease]; + return response; + } + + if (code == HTTP_NOT_FOUND) { + SETNSERROR([self errorDomain], ERROR_NOT_FOUND, @"%@ not found", url); + HSLogDebug(@"returning not-found error"); + return nil; + } + if (code == HTTP_METHOD_NOT_ALLOWED) { + HSLogError(@"%@ 405 error", url); + SETNSERROR([self errorDomain], ERROR_RRS_NOT_FOUND, @"%@ 405 error", url); + } + NSError *myError = [NSError glacierErrorWithDomain:[self errorDomain] httpStatusCode:code responseBody:responseData]; + HSLogDebug(@"%@ %@ error: %@", method, conn, myError); + if (error != NULL) { + *error = myError; + } + return nil; +} +@end diff --git a/cocoastack/glacier/GlacierResponse.h b/cocoastack/glacier/GlacierResponse.h new file mode 100644 index 0000000..83b7511 --- /dev/null +++ b/cocoastack/glacier/GlacierResponse.h @@ -0,0 +1,21 @@ +// +// GlacierResponse.h +// Arq +// +// Created by Stefan Reitshamer on 9/11/12. +// +// + + +@interface GlacierResponse : NSObject { + int code; + NSMutableDictionary *headers; + NSData *body; +} +- (id)initWithCode:(int)theCode headers:(NSDictionary *)theHeaders body:(NSData *)theBody; + +- (int)code; +- (NSDictionary *)headers; +- (NSString *)headerForKey:(NSString *)theKey; +- (NSData *)body; +@end diff --git a/cocoastack/glacier/GlacierResponse.m b/cocoastack/glacier/GlacierResponse.m new file mode 100644 index 0000000..f0bda1a --- /dev/null +++ b/cocoastack/glacier/GlacierResponse.m @@ -0,0 +1,42 @@ +// +// GlacierResponse.m +// Arq +// +// Created by Stefan Reitshamer on 9/11/12. +// +// + +#import "GlacierResponse.h" + +@implementation GlacierResponse +- (id)initWithCode:(int)theCode headers:(NSDictionary *)theHeaders body:(NSData *)theBody { + if (self = [super init]) { + code = theCode; + headers = [[NSMutableDictionary alloc] init]; + for (NSString *key in [theHeaders allKeys]) { + [headers setObject:[theHeaders objectForKey:key] forKey:[key lowercaseString]]; + } + body = [theBody retain]; + } + return self; +} +- (void)dealloc { + [headers release]; + [body release]; + [super dealloc]; +} + + +- (int)code { + return code; +} +- (NSDictionary *)headers { + return headers; +} +- (NSString *)headerForKey:(NSString *)theKey { + return [headers objectForKey:[theKey lowercaseString]]; +} +- (NSData *)body { + return body; +} +@end diff --git a/cocoastack/glacier/GlacierService.h b/cocoastack/glacier/GlacierService.h new file mode 100644 index 0000000..a755a45 --- /dev/null +++ b/cocoastack/glacier/GlacierService.h @@ -0,0 +1,43 @@ +// +// GlacierService.h +// Arq +// +// Created by Stefan Reitshamer on 9/7/12. +// +// + +@class Vault; +@class GlacierAuthorizationProvider; +@class AWSRegion; +@protocol DataTransferDelegate; + + +enum { + GLACIER_ERROR_UNEXPECTED_RESPONSE = -51001, + GLACIER_ERROR_AMAZON_ERROR = -51002, + GLACIER_INVALID_PARAMETERS = -51003 +}; + + +@interface GlacierService : NSObject { + GlacierAuthorizationProvider *gap; + AWSRegion *awsRegion; + BOOL useSSL; + BOOL retryOnTransientError; +} ++ (NSString *)errorDomain; + +- (id)initWithGlacierAuthorizationProvider:(GlacierAuthorizationProvider *)theGAP awsRegion:(AWSRegion *)theAWSRegion useSSL:(BOOL)theUseSSL retryOnTransientError:(BOOL)retry; + +- (NSArray *)vaults:(NSError **)error; +- (Vault *)vaultWithName:(NSString *)theVaultName error:(NSError **)error; +- (BOOL)createVaultWithName:(NSString *)theName error:(NSError **)error; +- (BOOL)deleteVaultWithName:(NSString *)theName error:(NSError **)error; +- (NSString *)uploadArchive:(NSData *)data toVaultName:(NSString *)theVaultName error:(NSError **)error; +- (NSString *)uploadArchive:(NSData *)data toVaultName:(NSString *)theVaultName dataTransferDelegate:(id )theDelegate error:(NSError **)error; +- (BOOL)deleteArchive:(NSString *)theArchiveId inVault:(NSString *)theVaultName error:(NSError **)error; +- (NSString *)initiateRetrievalJobForVaultName:(NSString *)theVaultName archiveId:(NSString *)theArchiveId snsTopicArn:(NSString *)theSNSTopicArn error:(NSError **)error; +- (NSString *)initiateInventoryJobForVaultName:(NSString *)theVaultName snsTopicArn:(NSString *)theSNSTopicArn error:(NSError **)error; +- (NSArray *)jobsForVaultName:(NSString *)theVaultName error:(NSError **)error; +- (NSData *)dataForVaultName:(NSString *)theVaultName jobId:(NSString *)theJobId retries:(NSUInteger)theRetries error:(NSError **)error; +@end diff --git a/cocoastack/glacier/GlacierService.m b/cocoastack/glacier/GlacierService.m new file mode 100644 index 0000000..20ff121 --- /dev/null +++ b/cocoastack/glacier/GlacierService.m @@ -0,0 +1,223 @@ +// +// GlacierService.m +// Arq +// +// Created by Stefan Reitshamer on 9/7/12. +// +// + +#import "GlacierService.h" +#import "AWSRegion.h" +#import "GlacierRequest.h" +#import "NSString+SBJSON.h" +#import "NSObject+SBJSON.h" +#import "Vault.h" +#import "SHA256Hash.h" +#import "GlacierResponse.h" +#import "VaultLister.h" +#import "NSString_extra.h" +#import "SHA256TreeHash.h" +#import "GlacierJobLister.h" + +#define MAX_JOB_DOWNLOAD_RETRIES (10) + + +@implementation GlacierService ++ (NSString *)errorDomain { + return @"GlacierServiceErrorDomain"; +} + +- (id)initWithGlacierAuthorizationProvider:(GlacierAuthorizationProvider *)theGAP awsRegion:(AWSRegion *)theAWSRegion useSSL:(BOOL)theUseSSL retryOnTransientError:(BOOL)retry { + if (self = [super init]) { + gap = [theGAP retain]; + awsRegion = [theAWSRegion retain]; + useSSL = theUseSSL; + retryOnTransientError = retry; + } + return self; +} +- (void)dealloc { + [gap release]; + [super dealloc]; +} + +- (NSArray *)vaults:(NSError **)error { + VaultLister *lister = [[[VaultLister alloc] initWithGlacierAuthorizationProvider:gap awsRegion:awsRegion useSSL:useSSL retryOnTransientError:retryOnTransientError] autorelease]; + return [lister vaults:error]; +} +- (Vault *)vaultWithName:(NSString *)theVaultName error:(NSError **)error { + NSURL *theURL =[NSURL URLWithString:[NSString stringWithFormat:@"%@/-/vaults/%@", [awsRegion glacierEndpointWithSSL:useSSL], theVaultName]]; + GlacierRequest *req = [[[GlacierRequest alloc] initWithMethod:@"GET" url:theURL awsRegion:awsRegion authorizationProvider:gap retryOnTransientError:retryOnTransientError dataTransferDelegate:nil] autorelease]; + [req setHeader:@"application/json" forKey:@"Accept"]; + [req setHeader:@"application/x-amz-json-1.0" forKey:@"Content-Type"]; + [req setHeader:@"Glacier.DescribeVault" forKey:@"x-amz-target"]; + GlacierResponse *response = [req execute:error]; + if (response == nil) { + return nil; + } + NSData *data = [response body]; + NSString *responseString = [[[NSString alloc] initWithBytes:[data bytes] length:[data length] encoding:NSUTF8StringEncoding] autorelease]; + NSDictionary *dict = [responseString JSONValue:error]; + if (dict == nil) { + return nil; + } + return [[[Vault alloc] initWithAWSRegion:awsRegion json:dict] autorelease]; +} +- (BOOL)createVaultWithName:(NSString *)theName error:(NSError **)error { + NSURL *theURL =[NSURL URLWithString:[NSString stringWithFormat:@"%@/-/vaults/%@", [awsRegion glacierEndpointWithSSL:useSSL], theName]]; + GlacierRequest *req = [[[GlacierRequest alloc] initWithMethod:@"PUT" url:theURL awsRegion:awsRegion authorizationProvider:gap retryOnTransientError:retryOnTransientError dataTransferDelegate:nil] autorelease]; + + NSData *requestData = [NSData data]; + [req setRequestData:requestData]; + [req setHeader:[NSString stringWithFormat:@"%ld", (unsigned long)[requestData length]] forKey:@"Content-Length"]; + + [req setHeader:@"application/json" forKey:@"Accept"]; + [req setHeader:@"application/x-amz-json-1.0" forKey:@"Content-Type"]; + [req setHeader:@"Glacier.CreateVault" forKey:@"x-amz-target"]; + [req setHeader:[NSString hexStringWithData:[SHA256Hash hashData:requestData]] forKey:@"x-amz-content-sha256"]; + + + GlacierResponse *response = [req execute:error]; + if (response == nil) { + return NO; + } + return YES; +} +- (BOOL)deleteVaultWithName:(NSString *)theName error:(NSError **)error { + NSURL *theURL =[NSURL URLWithString:[NSString stringWithFormat:@"%@/-/vaults/%@", [awsRegion glacierEndpointWithSSL:useSSL], theName]]; + GlacierRequest *req = [[[GlacierRequest alloc] initWithMethod:@"DELETE" url:theURL awsRegion:awsRegion authorizationProvider:gap retryOnTransientError:retryOnTransientError dataTransferDelegate:nil] autorelease]; + + [req setHeader:@"application/json" forKey:@"Accept"]; + [req setHeader:@"application/x-amz-json-1.0" forKey:@"Content-Type"]; + [req setHeader:@"Glacier.DeleteVault" forKey:@"x-amz-target"]; + + NSError *myError = nil; + GlacierResponse *response = [req execute:&myError]; + if (response == nil) { + if ([myError isErrorWithDomain:[req errorDomain] code:GLACIER_ERROR_AMAZON_ERROR] + && [[[myError userInfo] objectForKey:@"AmazonCode"] isEqualToString:@"InvalidParameterValueException"] + && [[[[myError userInfo] objectForKey:@"AmazonMessage"] lowercaseString] hasPrefix:@"vault not empty"]) { + HSLogError(@"%@", myError); + SETNSERROR([GlacierService errorDomain], ERROR_GLACIER_VAULT_INVENTORY_NOT_UP_TO_DATE, @"Failed to delete the Glacier vault. Please wait 24 hours, select 'Legacy Glacier Vaults', and try again."); + } else { + SETERRORFROMMYERROR; + } + return NO; + } + return YES; +} +- (NSString *)uploadArchive:(NSData *)data toVaultName:(NSString *)theVaultName error:(NSError **)error { + return [self uploadArchive:data toVaultName:theVaultName dataTransferDelegate:nil error:error]; +} +- (NSString *)uploadArchive:(NSData *)data toVaultName:(NSString *)theVaultName dataTransferDelegate:(id )theDelegate error:(NSError **)error { + NSURL *theURL =[NSURL URLWithString:[NSString stringWithFormat:@"%@/-/vaults/%@/archives", [awsRegion glacierEndpointWithSSL:useSSL], theVaultName]]; + GlacierRequest *req = [[[GlacierRequest alloc] initWithMethod:@"POST" url:theURL awsRegion:awsRegion authorizationProvider:gap retryOnTransientError:retryOnTransientError dataTransferDelegate:theDelegate] autorelease]; + [req setHeader:[NSString hexStringWithData:[SHA256Hash hashData:data]] forKey:@"x-amz-content-sha256"]; + [req setHeader:[NSString stringWithFormat:@"%ld", (long)[data length]] forKey:@"Content-Length"]; + [req setHeader:[NSString hexStringWithData:[SHA256TreeHash treeHashOfData:data]] forKey:@"x-amz-sha256-tree-hash"]; + [req setRequestData:data]; + + GlacierResponse *response = [req execute:error]; + if (response == nil) { + return nil; + } + NSString *ret = [response headerForKey:@"x-amz-archive-id"]; + if (ret == nil) { + HSLogError(@"missing x-amz-archive-id header in glacier response headers %@", [response headers]); + SETNSERROR([req errorDomain], -1, @"missing x-amz-archive-id header in glacier response"); + } + return ret; +} +- (BOOL)deleteArchive:(NSString *)theArchiveId inVault:(NSString *)theVaultName error:(NSError **)error { + NSURL *theURL =[NSURL URLWithString:[NSString stringWithFormat:@"%@/-/vaults/%@/archives/%@", [awsRegion glacierEndpointWithSSL:useSSL], theVaultName, theArchiveId]]; + GlacierRequest *req = [[[GlacierRequest alloc] initWithMethod:@"DELETE" url:theURL awsRegion:awsRegion authorizationProvider:gap retryOnTransientError:retryOnTransientError dataTransferDelegate:nil] autorelease]; + + GlacierResponse *response = [req execute:error]; + if (response == nil) { + return NO; + } + return YES; +} +- (NSString *)initiateRetrievalJobForVaultName:(NSString *)theVaultName archiveId:(NSString *)theArchiveId snsTopicArn:(NSString *)theSNSTopicArn error:(NSError **)error { + NSMutableDictionary *args = [NSMutableDictionary dictionary]; + [args setObject:@"archive-retrieval" forKey:@"Type"]; + [args setObject:theArchiveId forKey:@"ArchiveId"]; + [args setObject:[NSString stringWithFormat:@"Retrieve archive %@", theArchiveId] forKey:@"Description"]; + [args setObject:theSNSTopicArn forKey:@"SNSTopic"]; + + NSData *requestData = [[args JSONRepresentation:error] dataUsingEncoding:NSUTF8StringEncoding]; + if (requestData == nil) { + return nil; + } + + NSURL *theURL =[NSURL URLWithString:[NSString stringWithFormat:@"%@/-/vaults/%@/jobs", [awsRegion glacierEndpointWithSSL:useSSL], theVaultName]]; + GlacierRequest *req = [[[GlacierRequest alloc] initWithMethod:@"POST" url:theURL awsRegion:awsRegion authorizationProvider:gap retryOnTransientError:retryOnTransientError dataTransferDelegate:nil] autorelease]; + [req setHeader:[NSString stringWithFormat:@"%ld", (long)[requestData length]] forKey:@"Content-Length"]; + [req setRequestData:requestData]; + + GlacierResponse *response = [req execute:error]; + if (response == nil) { + return nil; + } + NSString *ret = [response headerForKey:@"x-amz-job-id"]; + if (ret == nil) { + SETNSERROR([GlacierService errorDomain], -1, @"missing x-amz-job-id header in response"); + } + return ret; +} +- (NSString *)initiateInventoryJobForVaultName:(NSString *)theVaultName snsTopicArn:(NSString *)theSNSTopicArn error:(NSError **)error { + NSMutableDictionary *args = [NSMutableDictionary dictionary]; + [args setObject:@"inventory-retrieval" forKey:@"Type"]; + [args setObject:[NSString stringWithFormat:@"Inventory vault %@", theVaultName] forKey:@"Description"]; + [args setObject:@"JSON" forKey:@"Format"]; + [args setObject:theSNSTopicArn forKey:@"SNSTopic"]; + + NSData *requestData = [[args JSONRepresentation:error] dataUsingEncoding:NSUTF8StringEncoding]; + if (requestData == nil) { + return nil; + } + + NSURL *theURL =[NSURL URLWithString:[NSString stringWithFormat:@"%@/-/vaults/%@/jobs", [awsRegion glacierEndpointWithSSL:useSSL], theVaultName]]; + GlacierRequest *req = [[[GlacierRequest alloc] initWithMethod:@"POST" url:theURL awsRegion:awsRegion authorizationProvider:gap retryOnTransientError:retryOnTransientError dataTransferDelegate:nil] autorelease]; + [req setHeader:[NSString stringWithFormat:@"%ld", (long)[requestData length]] forKey:@"Content-Length"]; + [req setRequestData:requestData]; + + GlacierResponse *response = [req execute:error]; + if (response == nil) { + return nil; + } + NSString *ret = [response headerForKey:@"x-amz-job-id"]; + if (ret == nil) { + SETNSERROR([GlacierService errorDomain], -1, @"missing x-amz-job-id header in response"); + } + return ret; +} +- (NSArray *)jobsForVaultName:(NSString *)theVaultName error:(NSError **)error { + GlacierJobLister *lister = [[[GlacierJobLister alloc] initWithGlacierAuthorizationProvider:gap vaultName:theVaultName awsRegion:awsRegion useSSL:useSSL retryOnTransientError:retryOnTransientError] autorelease]; + return [lister jobs:error]; +} +- (NSData *)dataForVaultName:(NSString *)theVaultName jobId:(NSString *)theJobId retries:(NSUInteger)theRetries error:(NSError **)error { + NSURL *theURL =[NSURL URLWithString:[NSString stringWithFormat:@"%@/-/vaults/%@/jobs/%@/output", [awsRegion glacierEndpointWithSSL:useSSL], theVaultName, theJobId]]; + NSData *ret = nil; + + NSError *myError = nil; + for (NSUInteger i = 0; i < theRetries; i++) { + GlacierRequest *req = [[[GlacierRequest alloc] initWithMethod:@"GET" url:theURL awsRegion:awsRegion authorizationProvider:gap retryOnTransientError:retryOnTransientError dataTransferDelegate:nil] autorelease]; + [req setHeader:@"2012-06-01" forKey:@"x-amz-glacier-version"]; + + GlacierResponse *response = [req execute:&myError]; + if (response != nil) { + ret = [response body]; + break; + } + + HSLogError(@"failed to get data for %@ job %@ (retrying): %@", theVaultName, theJobId, [myError localizedDescription]); + [NSThread sleepForTimeInterval:5.0]; + } + if (ret == nil) { + SETERRORFROMMYERROR; + } + return ret; +} + +@end diff --git a/cocoastack/glacier/GlacierSigner.h b/cocoastack/glacier/GlacierSigner.h new file mode 100644 index 0000000..25e5fe7 --- /dev/null +++ b/cocoastack/glacier/GlacierSigner.h @@ -0,0 +1,12 @@ +// +// GlacierSigner.h +// Arq +// +// Created by Stefan Reitshamer on 9/7/12. +// +// + + +@protocol GlacierSigner +- (NSString *)signString:(NSString *)theString withDateStamp:(NSString *)theDateStamp regionName:(NSString *)theRegionName serviceName:(NSString *)theServiceName; +@end diff --git a/cocoastack/glacier/ISO8601Date.h b/cocoastack/glacier/ISO8601Date.h new file mode 100644 index 0000000..5bd0d41 --- /dev/null +++ b/cocoastack/glacier/ISO8601Date.h @@ -0,0 +1,17 @@ +// +// ISO8601Date.h +// Arq +// +// Created by Stefan Reitshamer on 9/7/12. +// +// + + +@interface ISO8601Date : NSObject { + +} ++ (NSString *)errorDomain; ++ (NSDate *)dateFromString:(NSString *)str error:(NSError **)error; ++ (NSString *)basicDateTimeStringFromDate:(NSDate *)theDate; ++ (NSString *)basicDateStringFromDate:(NSDate *)theDate; +@end diff --git a/cocoastack/glacier/ISO8601Date.m b/cocoastack/glacier/ISO8601Date.m new file mode 100644 index 0000000..e70f896 --- /dev/null +++ b/cocoastack/glacier/ISO8601Date.m @@ -0,0 +1,51 @@ +// +// ISO8601Date.m +// Arq +// +// Created by Stefan Reitshamer on 9/7/12. +// +// + +#import "ISO8601Date.h" +#import "RegexKitLite.h" + + +#define FMT822 (@"^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})\\.(\\d+)Z$") + + +@implementation ISO8601Date ++ (NSString *)errorDomain { + return @"ISO8601DateErrorDomain"; +} ++ (NSDate *)dateFromString:(NSString *)str error:(NSError **)error { + if ([str rangeOfRegex:FMT822].location == NSNotFound) { + SETNSERROR([ISO8601Date errorDomain], -1, @"invalid ISO8601 date '%@'", str); + return nil; + } + return [NSCalendarDate dateWithYear:[[str stringByMatching:FMT822 capture:1] intValue] + month:[[str stringByMatching:FMT822 capture:2] intValue] + day:[[str stringByMatching:FMT822 capture:3] intValue] + hour:[[str stringByMatching:FMT822 capture:4] intValue] + minute:[[str stringByMatching:FMT822 capture:5] intValue] + second:[[str stringByMatching:FMT822 capture:6] intValue] + timeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; + + +} ++ (NSString *)basicDateTimeStringFromDate:(NSDate *)theDate { + NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease]; + [formatter setDateFormat:@"yyyyMMdd'T'HHmmss'Z'"]; + [formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; + NSCalendar *gregorianCalendar = [[[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar] autorelease]; + [formatter setCalendar:gregorianCalendar]; + return [formatter stringFromDate:theDate]; +} ++ (NSString *)basicDateStringFromDate:(NSDate *)theDate { + NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease]; + [formatter setDateFormat:@"yyyyMMdd"]; + [formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; + NSCalendar *gregorianCalendar = [[[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar] autorelease]; + [formatter setCalendar:gregorianCalendar]; + return [formatter stringFromDate:theDate]; +} +@end diff --git a/cocoastack/glacier/LocalGlacierSigner.h b/cocoastack/glacier/LocalGlacierSigner.h new file mode 100644 index 0000000..c6bd086 --- /dev/null +++ b/cocoastack/glacier/LocalGlacierSigner.h @@ -0,0 +1,16 @@ +// +// LocalGlacierSigner.h +// Arq +// +// Created by Stefan Reitshamer on 9/7/12. +// +// + +#import "GlacierSigner.h" + + +@interface LocalGlacierSigner : NSObject { + NSString *secretKey; +} +- (id)initWithSecretKey:(NSString *)theSecretKey; +@end diff --git a/cocoastack/glacier/LocalGlacierSigner.m b/cocoastack/glacier/LocalGlacierSigner.m new file mode 100644 index 0000000..e7bbd81 --- /dev/null +++ b/cocoastack/glacier/LocalGlacierSigner.m @@ -0,0 +1,56 @@ +// +// LocalGlacierSigner.m +// Arq +// +// Created by Stefan Reitshamer on 9/7/12. +// +// + +#import "LocalGlacierSigner.h" +#include +#import "NSData-Base64Extensions.h" +#import "NSString_extra.h" + +#define TERMINATOR (@"aws4_request") + +@interface LocalGlacierSigner () +- (NSData *)signString:(NSString *)theString withKey:(NSData *)theKey; +@end + +@implementation LocalGlacierSigner +- (id)initWithSecretKey:(NSString *)theSecretKey { + if (self = [super init]) { + secretKey = [theSecretKey retain]; + } + return self; +} +- (void)dealloc { + [secretKey release]; + [super dealloc]; +} + + +#pragma mark GlacierSigner +- (NSString *)signString:(NSString *)theStringToSign withDateStamp:(NSString *)theDateStamp regionName:(NSString *)theRegionName serviceName:(NSString *)theServiceName { + // FIXME: Extract region name from endpoint. + // FIXME: Extract service name from endpoint. + + NSData *secret = [[NSString stringWithFormat:@"AWS4%@", secretKey] dataUsingEncoding:NSUTF8StringEncoding]; + NSData *date = [self signString:theDateStamp withKey:secret]; + NSData *region = [self signString:theRegionName withKey:date]; + NSData *service = [self signString:@"glacier" withKey:region]; + NSData *signing = [self signString:TERMINATOR withKey:service]; + NSData *signature = [self signString:theStringToSign withKey:signing]; + NSString *ret = [NSString hexStringWithBytes:[signature bytes] length:(unsigned int)[signature length]]; + return ret; +} + + +#pragma mark internal +- (NSData *)signString:(NSString *)theString withKey:(NSData *)theKey { + NSData *data = [theString dataUsingEncoding:NSUTF8StringEncoding]; + unsigned char digest[CC_SHA256_DIGEST_LENGTH]; + CCHmac(kCCHmacAlgSHA256, [theKey bytes], [theKey length], [data bytes], [data length], digest); + return [NSData dataWithBytes:digest length:CC_SHA256_DIGEST_LENGTH]; +} +@end diff --git a/cocoastack/glacier/NSError_Glacier.h b/cocoastack/glacier/NSError_Glacier.h new file mode 100644 index 0000000..dc8276a --- /dev/null +++ b/cocoastack/glacier/NSError_Glacier.h @@ -0,0 +1,12 @@ +// +// NSError_Glacier.h +// Arq +// +// Created by Stefan Reitshamer on 9/7/12. +// +// + + +@interface NSError (Glacier) ++ (NSError *)glacierErrorWithDomain:(NSString *)theDomain httpStatusCode:(int)theHTTPStatusCode responseBody:(NSData *)theResponseBody; +@end diff --git a/cocoastack/glacier/NSError_Glacier.m b/cocoastack/glacier/NSError_Glacier.m new file mode 100644 index 0000000..7c5ffe7 --- /dev/null +++ b/cocoastack/glacier/NSError_Glacier.m @@ -0,0 +1,38 @@ +// +// NSError_Glacier.m +// Arq +// +// Created by Stefan Reitshamer on 9/7/12. +// +// + +#import "NSError_Glacier.h" +#import "NSXMLNode_extra.h" +#import "NSError_extra.h" +#import "GlacierService.h" +#import "NSString+SBJSON.h" + + +@implementation NSError (Glacier) ++ (NSError *)glacierErrorWithDomain:(NSString *)theDomain httpStatusCode:(int)theHTTPStatusCode responseBody:(NSData *)theResponseBody { + NSString *msg = [[[NSString alloc] initWithData:theResponseBody encoding:NSUTF8StringEncoding] autorelease]; + NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObject:[NSString stringWithFormat:@"Glacier error %d", theHTTPStatusCode] forKey:NSLocalizedDescriptionKey]; + if (msg != nil) { + NSDictionary *json = [msg JSONValue:NULL]; + if (json != nil && [json isKindOfClass:[NSDictionary class]]) { + if ([json objectForKey:@"message"] != nil) { + [userInfo setObject:[json objectForKey:@"message"] forKey:NSLocalizedDescriptionKey]; + [userInfo setObject:[json objectForKey:@"message"] forKey:@"AmazonMessage"]; + } + if ([json objectForKey:@"code"] != nil) { + [userInfo setObject:[json objectForKey:@"code"] forKey:@"AmazonCode"]; + } + } + } + if ([[userInfo objectForKey:@"AmazonCode"] isEqualToString:@"SubscriptionRequiredException"]) { + [userInfo setObject:@"Your AWS account is not signed up for all services. Please visit http://aws.amazon.com and sign up for S3, Glacier, SNS and SQS." forKey:NSLocalizedDescriptionKey]; + } + [userInfo setObject:[NSNumber numberWithInt:theHTTPStatusCode] forKey:@"HTTPStatusCode"]; + return [NSError errorWithDomain:theDomain code:GLACIER_ERROR_AMAZON_ERROR userInfo:userInfo]; +} +@end diff --git a/cocoastack/glacier/SHA256TreeHash.h b/cocoastack/glacier/SHA256TreeHash.h new file mode 100644 index 0000000..c9044b8 --- /dev/null +++ b/cocoastack/glacier/SHA256TreeHash.h @@ -0,0 +1,12 @@ +// +// SHA256TreeHash.h +// Arq +// +// Created by Stefan Reitshamer on 9/12/12. +// +// + + +@interface SHA256TreeHash : NSObject ++ (NSData *)treeHashOfData:(NSData *)data; +@end diff --git a/cocoastack/glacier/SHA256TreeHash.m b/cocoastack/glacier/SHA256TreeHash.m new file mode 100644 index 0000000..f25ad28 --- /dev/null +++ b/cocoastack/glacier/SHA256TreeHash.m @@ -0,0 +1,48 @@ +// +// SHA256TreeHash.m +// Arq +// +// Created by Stefan Reitshamer on 9/12/12. +// +// + +#import "SHA256TreeHash.h" +#import "SHA256Hash.h" +#import "NSString_extra.h" + + +#define ONE_MB (1024 * 1024) + +@implementation SHA256TreeHash ++ (NSData *)treeHashOfData:(NSData *)data { + if ([data length] == 0) { + return [SHA256Hash hashData:data]; + } + + NSMutableArray *hashes = [NSMutableArray array]; + + const unsigned char *bytes = (const unsigned char *)[data bytes]; + NSUInteger length = [data length]; + NSUInteger index = 0; + while (index < length) { + NSUInteger toRead = (index + ONE_MB) > length ? (length - index) : ONE_MB; + NSData *hash = [SHA256Hash hashBytes:(bytes + index) length:toRead]; + [hashes addObject:hash]; + index += toRead; + } + + while ([hashes count] > 1) { + NSMutableArray *condensed = [NSMutableArray array]; + for (NSUInteger index = 0; index < [hashes count] / 2; index++) { + NSMutableData *combined = [NSMutableData dataWithData:[hashes objectAtIndex:(index * 2)]]; + [combined appendData:[hashes objectAtIndex:(index * 2 + 1)]]; + [condensed addObject:[SHA256Hash hashData:combined]]; + } + if ([hashes count] % 2 == 1) { + [condensed addObject:[hashes objectAtIndex:([hashes count] - 1)]]; + } + [hashes setArray:condensed]; + } + return [hashes objectAtIndex:0]; +} +@end diff --git a/cocoastack/glacier/Vault.h b/cocoastack/glacier/Vault.h new file mode 100644 index 0000000..ba359b2 --- /dev/null +++ b/cocoastack/glacier/Vault.h @@ -0,0 +1,31 @@ +// +// Vault.h +// Arq +// +// Created by Stefan Reitshamer on 9/7/12. +// +// + +@class AWSRegion; +@protocol VaultDelegate; + + +@interface Vault : NSObject { + AWSRegion *awsRegion; + NSDate *creationDate; + NSDate *lastInventoryDate; + uint64_t numberOfArchives; + uint64_t size; + NSString *vaultARN; + NSString *vaultName; +} +- (id)initWithAWSRegion:(AWSRegion *)theAWSRegion json:(NSDictionary *)theDict; + +- (AWSRegion *)awsRegion; +- (NSDate *)creationDate; +- (NSDate *)lastInventoryDate; +- (uint64_t)numberOfArchives; +- (uint64_t)size; +- (NSString *)vaultARN; +- (NSString *)vaultName; +@end diff --git a/cocoastack/glacier/Vault.m b/cocoastack/glacier/Vault.m new file mode 100644 index 0000000..9df887b --- /dev/null +++ b/cocoastack/glacier/Vault.m @@ -0,0 +1,72 @@ +// +// Vault.m +// Arq +// +// Created by Stefan Reitshamer on 9/7/12. +// +// + +#import "Vault.h" +#import "ISO8601Date.h" + + +@implementation Vault +- (id)initWithAWSRegion:(AWSRegion *)theAWSRegion json:(NSDictionary *)theDict { + if (self = [super init]) { + awsRegion = [theAWSRegion retain]; + NSString *theCreationDate = [theDict objectForKey:@"CreationDate"]; + if (theCreationDate != nil && ![theCreationDate isKindOfClass:[NSNull class]]) { + NSError *myError = nil; + creationDate = [[ISO8601Date dateFromString:theCreationDate error:&myError] retain]; + if (creationDate == nil) { + HSLogError(@"%@", myError); + } + } + + NSString *theLastInventoryDate = [theDict objectForKey:@"LastInventoryDate"]; + if (theLastInventoryDate != nil && ![theLastInventoryDate isKindOfClass:[NSNull class]]) { + NSError *myError = nil; + lastInventoryDate = [[ISO8601Date dateFromString:theLastInventoryDate error:&myError] retain]; + if (lastInventoryDate == nil) { + HSLogError(@"%@", myError); + } + } + + vaultARN = [[theDict objectForKey:@"VaultARN"] retain]; + vaultName = [[theDict objectForKey:@"VaultName"] retain]; + numberOfArchives = (uint64_t)[[theDict objectForKey:@"NumberOfArchives"] unsignedLongLongValue]; + size = (uint64_t)[[theDict objectForKey:@"SizeInBytes"] unsignedLongLongValue]; + } + return self; +} +- (void)dealloc { + [awsRegion release]; + [creationDate release]; + [lastInventoryDate release]; + [vaultARN release]; + [vaultName release]; + [super dealloc]; +} + +- (AWSRegion *)awsRegion { + return awsRegion; +} +- (NSDate *)creationDate { + return creationDate; +} +- (NSDate *)lastInventoryDate { + return lastInventoryDate; +} +- (uint64_t)numberOfArchives { + return numberOfArchives; +} +- (uint64_t)size { + return size; +} +- (NSString *)vaultARN { + return vaultARN; +} +- (NSString *)vaultName { + return vaultName; +} +@end diff --git a/cocoastack/glacier/VaultDeleter.h b/cocoastack/glacier/VaultDeleter.h new file mode 100644 index 0000000..9e2d7a9 --- /dev/null +++ b/cocoastack/glacier/VaultDeleter.h @@ -0,0 +1,31 @@ +// +// VaultDeleter.h +// Arq +// +// Created by Stefan Reitshamer on 12/1/12. +// Copyright (c) 2012 Stefan Reitshamer. All rights reserved. +// + +@class AWSRegion; +@class GlacierService; +@class SNS; +@class SQS; +@protocol VaultDeleterDelegate; +@class Vault; + + +@interface VaultDeleter : NSObject { + Vault *vault; + GlacierService *glacier; + SNS *sns; + SQS *sqs; + id delegate; + NSString *topicArn; + NSURL *queueURL; +} +- (NSString *)errorDomain; + +- (id)initWithVault:(Vault *)theVault glacier:(GlacierService *)theGlacier sns:(SNS *)theSNS sqs:(SQS *)theSQS delegate:(id )theDelegate; +- (BOOL)deleteVault:(NSError **)error; + +@end diff --git a/cocoastack/glacier/VaultDeleter.m b/cocoastack/glacier/VaultDeleter.m new file mode 100644 index 0000000..26d450f --- /dev/null +++ b/cocoastack/glacier/VaultDeleter.m @@ -0,0 +1,223 @@ +// +// VaultDeleter.m +// Arq +// +// Created by Stefan Reitshamer on 12/1/12. +// Copyright (c) 2012 Stefan Reitshamer. All rights reserved. +// + +#import "VaultDeleter.h" +#import "NSString_extra.h" +#import "GlacierService.h" +#import "SNS.h" +#import "SQS.h" +#import "Vault.h" +#import "ReceiveMessageResponse.h" +#import "SQSMessage.h" +#import "NSString+SBJSON.h" +#import "VaultDeleterDelegate.h" +#import "Vault.h" + + +#define INITIAL_SLEEP (0.5) +#define SLEEP_GROWTH_FACTOR (2.0) +#define MAX_SLEEP (60.0) +#define MAX_GLACIER_RETRIES (10) + + +@interface VaultDeleter () +- (BOOL)deleteArchives:(NSError **)error; +- (void)cleanUp; +@end + + +@implementation VaultDeleter +- (NSString *)errorDomain { + return @"VaultDeleterErrorDomain"; +} + +- (id)initWithVault:(Vault *)theVault glacier:(GlacierService *)theGlacier sns:(SNS *)theSNS sqs:(SQS *)theSQS delegate:(id )theDelegate { + if (self = [super init]) { + vault = [theVault retain]; + glacier = [theGlacier retain]; + sns = [theSNS retain]; + sqs = [theSQS retain]; + delegate = theDelegate; + } + return self; +} +- (void)dealloc { + [vault release]; + [glacier release]; + [sns release]; + [sqs release]; + [topicArn release]; + [queueURL release]; + [super dealloc]; +} + +- (BOOL)deleteVault:(NSError **)error { + if ([vault numberOfArchives] > 0) { + if ([delegate respondsToSelector:@selector(vaultDeleterStatusDidChange:)]) { + [delegate vaultDeleterStatusDidChange:@"Requesting Glacier inventory"]; + } + HSLogDebug(@"date: %@\n", [[NSDate date] description]); + HSLogDebug(@"vault %@ has %ld archives; getting inventory\n", [vault vaultName], (unsigned long)[vault numberOfArchives]); + BOOL ret = [self deleteArchives:error]; + [self cleanUp]; + if (!ret) { + return NO; + } + } + + if (![glacier deleteVaultWithName:[vault vaultName] error:error]) { + return NO; + } + + return YES; +} + + +#pragma mark internal +- (BOOL)deleteArchives:(NSError **)error { + NSString *jobUUID = [NSString stringWithRandomUUID]; + NSString *topicName = [NSString stringWithFormat:@"%@_topic", jobUUID]; + NSString *queueName = [NSString stringWithFormat:@"%@_queue", jobUUID]; + topicArn = [[sns createTopic:topicName error:error] retain]; + if (topicArn == nil) { + return NO; + } + queueURL = [[sqs createQueueWithName:queueName error:error] retain]; + if (queueURL == nil) { + return NO; + } + NSString *queueArn = [sqs queueArnForQueueURL:queueURL error:error]; + if (queueArn == nil) { + return NO; + } + if (![sqs setSendMessagePermissionToQueueURL:queueURL queueArn:queueArn forSourceArn:topicArn error:error]) { + return NO; + } + NSString *subscriptionArn = [sns subscribeQueueArn:queueArn toTopicArn:topicArn error:error]; + if (subscriptionArn == nil) { + return NO; + } + NSString *jobId = [glacier initiateInventoryJobForVaultName:[vault vaultName] snsTopicArn:topicArn error:error]; + if (jobId == nil) { + return NO; + } + + if ([delegate respondsToSelector:@selector(vaultDeleterStatusDidChange:)]) { + [delegate vaultDeleterStatusDidChange:@"Waiting (up to 5 hours) for Glacier inventory"]; + } + NSTimeInterval sleep = INITIAL_SLEEP; + BOOL ret = YES; + NSAutoreleasePool *pool = nil; + for (;;) { + [pool drain]; + pool = [[NSAutoreleasePool alloc] init]; + if ([delegate respondsToSelector:@selector(vaultDeleterAbortRequestedForVaultName:)] && [delegate vaultDeleterAbortRequestedForVaultName:[vault vaultName]]) { + ret = NO; + SETNSERROR([self errorDomain], ERROR_ABORT_REQUESTED, @"abort requested"); + break; + } + NSError *myError = nil; + ReceiveMessageResponse *response = [sqs receiveMessagesForQueueURL:queueURL maxMessages:1 error:&myError]; + if (response == nil) { + HSLogError(@"error receiving message from queue: %@", myError); + } else { + HSLogDebug(@"got %lu messages from queue", (unsigned long)[[response messages] count]); + for (SQSMessage *msg in [response messages]) { + HSLogDebug(@"message from queue: %@\n", [msg body]); + } + if ([[response messages] count] > 0) { + break; + } + } + + if ([[response messages] count] > 0) { + sleep = INITIAL_SLEEP; + } else { + sleep *= SLEEP_GROWTH_FACTOR; + } + if (sleep > MAX_SLEEP) { + sleep = MAX_SLEEP; + } + [NSThread sleepForTimeInterval:sleep]; + } + if (!ret && error != NULL) { + [*error retain]; + } + [pool drain]; + if (!ret && error != NULL) { + [*error autorelease]; + } + + if (!ret) { + return NO; + } + + NSData *inventoryData = [glacier dataForVaultName:[vault vaultName] jobId:jobId retries:MAX_GLACIER_RETRIES error:error]; + if (inventoryData == nil) { + return NO; + } + NSString *inventoryString = [[[NSString alloc] initWithData:inventoryData encoding:NSUTF8StringEncoding] autorelease]; + HSLogDebug(@"inventoryString = %@", inventoryString); + NSDictionary *json = [inventoryString JSONValue:error]; + if (json == nil) { + return NO; + } + + id archiveListObj = [json objectForKey:@"ArchiveList"]; + NSArray *theArchiveList = nil; + if ([archiveListObj isKindOfClass:[NSString class]]) { + theArchiveList = [archiveListObj JSONValue:error]; + if (theArchiveList == nil) { + return NO; + } + } else { + theArchiveList = (NSArray *)archiveListObj; + } + + if ([delegate respondsToSelector:@selector(vaultDeleterDidRetrieveArchiveCount:)]) { + [delegate vaultDeleterDidRetrieveArchiveCount:[theArchiveList count]]; + } + + NSUInteger deletedCount = 0; + for (id archiveObj in theArchiveList) { + NSDictionary *archiveDict = nil; + if ([archiveObj isKindOfClass:[NSString class]]) { + archiveDict = [archiveObj JSONValue:error]; + if (archiveDict == nil) { + return NO; + } + } else if ([archiveObj isKindOfClass:[NSDictionary class]]) { + archiveDict = (NSDictionary *)archiveObj; + } else { + HSLogError(@"unexpected object in ArchiveList"); + SETNSERROR([self errorDomain], -1, @"unexpected object in ArchiveList: %@", archiveObj); + return NO; + } + HSLogDebug(@"archiveDict: %@", archiveDict); + NSString *archiveId = [archiveDict objectForKey:@"ArchiveId"]; + NSError *myError = nil; + if (![glacier deleteArchive:archiveId inVault:[vault vaultName] error:&myError]) { + HSLogError(@"error deleting archive %@: %@", archiveId, myError); + } + deletedCount++; + if ([delegate respondsToSelector:@selector(vaultDeleterDidDeleteArchive)]) { + [delegate vaultDeleterDidDeleteArchive]; + } + } + return YES; +} +- (void)cleanUp { + NSError *myError = nil; + if (topicArn != nil && ![sns deleteTopicWithArn:topicArn error:&myError]) { + HSLogError(@"failed to delete topic %@: %@", topicArn, myError); + } + if (queueURL != nil && ![sqs deleteQueue:queueURL error:&myError]) { + HSLogError(@"failed to delete queue %@: %@", queueURL, myError); + } +} +@end diff --git a/cocoastack/glacier/VaultDeleterDelegate.h b/cocoastack/glacier/VaultDeleterDelegate.h new file mode 100644 index 0000000..c5cac2e --- /dev/null +++ b/cocoastack/glacier/VaultDeleterDelegate.h @@ -0,0 +1,16 @@ +// +// VaultDeleterDelegate.h +// Arq +// +// Created by Stefan Reitshamer on 12/1/12. +// Copyright (c) 2012 Stefan Reitshamer. All rights reserved. +// + + + +@protocol VaultDeleterDelegate +- (void)vaultDeleterStatusDidChange:(NSString *)theMessage; +- (BOOL)vaultDeleterAbortRequestedForVaultName:(NSString *)theVaultName; +- (void)vaultDeleterDidRetrieveArchiveCount:(NSUInteger)theCount; +- (void)vaultDeleterDidDeleteArchive; +@end diff --git a/cocoastack/glacier/VaultLister.h b/cocoastack/glacier/VaultLister.h new file mode 100644 index 0000000..67ae4b2 --- /dev/null +++ b/cocoastack/glacier/VaultLister.h @@ -0,0 +1,23 @@ +// +// VaultLister.h +// Arq +// +// Created by Stefan Reitshamer on 9/11/12. +// +// + +@class GlacierAuthorizationProvider; +@class AWSRegion; + + +@interface VaultLister : NSObject { + GlacierAuthorizationProvider *gap; + AWSRegion *awsRegion; + BOOL useSSL; + BOOL retryOnTransientError; + NSString *marker; + NSMutableArray *vaults; +} +- (id)initWithGlacierAuthorizationProvider:(GlacierAuthorizationProvider *)theGAP awsRegion:(AWSRegion *)theAWSRegion useSSL:(BOOL)theUseSSL retryOnTransientError:(BOOL)retry; +- (NSArray *)vaults:(NSError **)error; +@end diff --git a/cocoastack/glacier/VaultLister.m b/cocoastack/glacier/VaultLister.m new file mode 100644 index 0000000..36dcc50 --- /dev/null +++ b/cocoastack/glacier/VaultLister.m @@ -0,0 +1,83 @@ +// +// VaultLister.m +// Arq +// +// Created by Stefan Reitshamer on 9/11/12. +// +// + +#import "VaultLister.h" +#import "GlacierRequest.h" +#import "GlacierResponse.h" +#import "NSString+SBJSON.h" +#import "Vault.h" +#import "AWSRegion.h" + + +@interface VaultLister () +- (BOOL)get:(NSError **)error; +@end + +@implementation VaultLister +- (id)initWithGlacierAuthorizationProvider:(GlacierAuthorizationProvider *)theGAP awsRegion:(AWSRegion *)theAWSRegion useSSL:(BOOL)theUseSSL retryOnTransientError:(BOOL)retry { + if (self = [super init]) { + gap = [theGAP retain]; + awsRegion = [theAWSRegion retain]; + useSSL = theUseSSL; + retryOnTransientError = retry; + vaults = [[NSMutableArray alloc] init]; + } + return self; +} +- (void)dealloc { + [gap release]; + [awsRegion release]; + [marker release]; + [vaults release]; + [super dealloc]; +} +- (NSArray *)vaults:(NSError **)error { + for (;;) { + if (![self get:error]) { + return nil; + } + if (marker == nil) { + break; + } + } + return vaults; +} + + +#pragma mark internal +- (BOOL)get:(NSError **)error { + NSString *urlString = [NSString stringWithFormat:@"%@/-/vaults", [awsRegion glacierEndpointWithSSL:useSSL]]; + if (marker != nil) { + urlString = [urlString stringByAppendingFormat:@"?marker=%@", marker]; + } + NSURL *theURL = [NSURL URLWithString:urlString]; + + GlacierRequest *req = [[[GlacierRequest alloc] initWithMethod:@"GET" url:theURL awsRegion:awsRegion authorizationProvider:gap retryOnTransientError:retryOnTransientError dataTransferDelegate:nil] autorelease]; + [req setHeader:@"application/json" forKey:@"Accept"]; + [req setHeader:@"Glacier.ListVaults" forKey:@"x-amz-target"]; + GlacierResponse *response = [req execute:error]; + if (response == nil) { + return NO; + } + NSData *data = [response body]; + NSString *responseString = [[[NSString alloc] initWithBytes:[data bytes] length:[data length] encoding:NSUTF8StringEncoding] autorelease]; + NSDictionary *dict = [responseString JSONValue:error]; + [marker release]; + marker = [[dict objectForKey:@"Marker"] retain]; + if ([marker isKindOfClass:[NSNull class]]) { + [marker release]; + marker = nil; + } + NSArray *vaultList = [dict objectForKey:@"VaultList"]; + for (NSDictionary *vaultDict in vaultList) { + Vault *vault = [[[Vault alloc] initWithAWSRegion:awsRegion json:vaultDict] autorelease]; + [vaults addObject:vault]; + } + return YES; +} +@end diff --git a/cocoastack/googledrive/GoogleDrive.h b/cocoastack/googledrive/GoogleDrive.h new file mode 100644 index 0000000..86c50d2 --- /dev/null +++ b/cocoastack/googledrive/GoogleDrive.h @@ -0,0 +1,50 @@ +// +// GoogleDrive.h +// Arq +// +// Created by Stefan Reitshamer on 7/16/14. +// Copyright (c) 2014 Stefan Reitshamer. All rights reserved. +// + +@protocol TargetConnectionDelegate; +@class Target; +@class GoogleDrive; +@protocol DataTransferDelegate; + + +@protocol GoogleDriveDelegate +- (NSString *)googleDriveAccessTokenForRefreshToken:(NSString *)theRefreshToken; +- (void)googleDriveDidChangeAccessToken:(NSString *)theUpdatedAccessToken forRefreshToken:(NSString *)theRefreshToken; +- (void)googleDriveDidFindFolderId:(NSString *)theFolderId forPath:(NSString *)thePath refreshToken:(NSString *)theRefreshToken;; +- (NSString *)googleDriveFolderIdForPath:(NSString *)thePath refreshToken:(NSString *)theRefreshToken;;; +@end + + +@interface GoogleDrive : NSObject { + NSString *emailAddress; + NSString *refreshToken; + id delegate; +} + ++ (NSString *)errorDomain; ++ (NSURL *)endpoint; + +- (id)initWithEmailAddress:(NSString *)theEmailAddress refreshToken:(NSString *)theRefreshToken delegate:(id )theDelegate; + +- (NSDictionary *)aboutWithTargetConnectionDelegate:(id )theTCD error:(NSError **)error; +- (NSNumber *)fileExistsAtPath:(NSString *)thePath isDirectory:(BOOL *)isDirectory targetConnectionDelegate:(id)theDelegate error:(NSError **)error; +- (NSNumber *)fileExistsAtPath:(NSString *)thePath dataSize:(unsigned long long *)theDataSize targetConnectionDelegate:(id)theDelegate error:(NSError **)error; + +- (NSArray *)contentsOfDirectoryAtPath:(NSString *)thePath targetConnectionDelegate:(id)theDelegate error:(NSError **)error; +- (BOOL)createDirectoryAtPath:(NSString *)path withIntermediateDirectories:(BOOL)createIntermediates targetConnectionDelegate:(id)theDelegate error:(NSError **)error; +- (BOOL)removeItemAtPath:(NSString *)thePath targetConnectionDelegate:(id)theDelegate error:(NSError **)error; + +- (NSData *)contentsOfFileAtPath:(NSString *)thePath dataTransferDelegate:(id)theDTDelegate targetConnectionDelegate:(id)theTCDelegate error:(NSError **)error; +- (BOOL)writeData:(NSData *)theData mimeType:(NSString *)theMimeType toFileAtPath:(NSString *)thePath dataTransferDelegate:(id )theDelegate targetConnectionDelegate:(id )theTCD error:(NSError **)error; + +- (NSNumber *)sizeOfItemAtPath:(NSString *)thePath targetConnectionDelegate:(id)theDelegate error:(NSError **)error; + +- (NSArray *)objectsAtPath:(NSString *)thePath targetConnectionDelegate:(id)theDelegate error:(NSError **)error; +- (NSArray *)pathsOfObjectsAtPath:(NSString *)thePath targetConnectionDelegate:(id)theDelegate error:(NSError **)error; + +@end diff --git a/cocoastack/googledrive/GoogleDrive.m b/cocoastack/googledrive/GoogleDrive.m new file mode 100644 index 0000000..aaf9bd3 --- /dev/null +++ b/cocoastack/googledrive/GoogleDrive.m @@ -0,0 +1,563 @@ +// +// GoogleDrive.m +// Arq +// +// Created by Stefan Reitshamer on 7/16/14. +// Copyright (c) 2014 Stefan Reitshamer. All rights reserved. +// + +#import "GoogleDrive.h" +#import "GoogleDriveRequest.h" +#import "NSString+SBJSON.h" +#import "GoogleDriveFolderLister.h" +#import "NSObject+SBJSON.h" +#import "NSString_extra.h" +#import "S3ObjectMetadata.h" +#import "ISO8601Date.h" + + +static NSString *kFolderMimeType = @"application/vnd.google-apps.folder"; + + +@implementation GoogleDrive ++ (NSString *)errorDomain { + return @"GoogleDriveErrorDomain"; +} ++ (NSURL *)endpoint { + return [NSURL URLWithString:@"https://www.googleapis.com/drive"]; +} + +- (id)init { + NSAssert(0==1, @"don't call this init method!"); + return nil; +} + +- (id)initWithEmailAddress:(NSString *)theEmailAddress refreshToken:(NSString *)theRefreshToken delegate:(id)theDelegate { + if (self = [super init]) { + emailAddress = [theEmailAddress retain]; + refreshToken = [theRefreshToken retain]; + delegate = theDelegate; + NSAssert(delegate != nil, @"delegate may not be nil"); + } + return self; +} +- (void)dealloc { + [emailAddress release]; + [refreshToken release]; + [super dealloc]; +} + +- (NSDictionary *)aboutWithTargetConnectionDelegate:(id)theTCD error:(NSError **)error { + GoogleDriveRequest *req = [[[GoogleDriveRequest alloc] initWithEmailAddress:emailAddress method:@"GET" path:@"/drive/v2/about" queryString:nil refreshToken:refreshToken googleDriveDelegate:delegate dataTransferDelegate:nil error:error] autorelease]; + if (req == nil) { + return nil; + } + + NSData *response = [req dataWithTargetConnectionDelegate:theTCD error:error]; + if (response == nil) { + return nil; + } + + NSString *responseString = [[[NSString alloc] initWithData:response encoding:NSUTF8StringEncoding] autorelease]; + return (NSDictionary *)[responseString JSONValue:error]; +} +- (NSNumber *)fileExistsAtPath:(NSString *)thePath isDirectory:(BOOL *)isDirectory targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + return [self fileExistsAtPath:thePath isDirectory:isDirectory dataSize:NULL lastModifiedDate:NULL targetConnectionDelegate:theDelegate error:error]; +} +- (NSNumber *)fileExistsAtPath:(NSString *)thePath dataSize:(unsigned long long *)theDataSize targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + return [self fileExistsAtPath:thePath isDirectory:NULL dataSize:theDataSize lastModifiedDate:NULL targetConnectionDelegate:theDelegate error:error]; +} +- (NSNumber *)fileExistsAtPath:(NSString *)thePath isDirectory:(BOOL *)isDirectory dataSize:(unsigned long long *)theDataSize lastModifiedDate:(NSDate **)theLastModifiedDate targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + HSLogDetail(@"checking existence of %@", thePath); + + if ([thePath isEqualToString:@"/"]) { + if (isDirectory != NULL) { + *isDirectory = YES; + } + HSLogDebug(@"%@ exists", thePath); + return YES; + } + NSError *myError = nil; + NSDictionary *googleDriveItem = [self firstGoogleDriveItemAtPath:thePath targetConnectionDelegate:theDelegate error:&myError]; + if (googleDriveItem == nil) { + if (![myError isErrorWithDomain:[GoogleDrive errorDomain] code:ERROR_NOT_FOUND]) { + SETERRORFROMMYERROR; + return nil; + } + HSLogDebug(@"%@ does not exist", thePath); + return [NSNumber numberWithBool:NO]; + } + + if (isDirectory != NULL) { + *isDirectory = [[googleDriveItem objectForKey:@"mimeType"] isEqualToString:kFolderMimeType]; + } + if (theDataSize != NULL) { + NSString *fileSize = [googleDriveItem objectForKey:@"fileSize"]; + *theDataSize = (unsigned long long)[fileSize integerValue]; + } + if (theLastModifiedDate != NULL) { + NSDate *date = [ISO8601Date dateFromString:[googleDriveItem objectForKey:@"modifiedDate"] error:error]; + if (date == nil) { + return nil; + } + *theLastModifiedDate = date; + } + + HSLogDebug(@"%@ exists", thePath); + return [NSNumber numberWithBool:YES]; +} +- (NSArray *)contentsOfDirectoryAtPath:(NSString *)thePath targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + HSLogDetail(@"getting contents of directory %@", thePath); + + if ([thePath hasSuffix:@"/"]) { + thePath = [thePath substringToIndex:[thePath length] - 1]; + } + NSError *myError = nil; + NSString *folderId = [self folderIdForPath:thePath targetConnectionDelegate:theDelegate error:&myError]; + if (folderId == nil) { + if (![myError isErrorWithDomain:[GoogleDrive errorDomain] code:ERROR_NOT_FOUND]) { + SETERRORFROMMYERROR; + return nil; + } + return [NSArray array]; + } + + GoogleDriveFolderLister *lister = [[[GoogleDriveFolderLister alloc] initWithEmailAddress:emailAddress refreshToken:refreshToken folderId:folderId googleDriveDelegate:delegate targetConnectionDelegate:theDelegate] autorelease]; + NSArray *googleDriveItems = [lister googleDriveItems:error]; + if (googleDriveItems == nil) { + return nil; + } + + NSMutableArray *ret = [NSMutableArray array]; + for (NSDictionary *item in googleDriveItems) { + [ret addObject:[item objectForKey:@"title"]]; + } + + HSLogDebug(@"returning %ld items for directory %@", [ret count], thePath); + return ret; +} +- (BOOL)createDirectoryAtPath:(NSString *)thePath withIntermediateDirectories:(BOOL)createIntermediates targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + HSLogDetail(@"creating directory %@", thePath); + + NSString *parentPath = [thePath stringByDeletingLastPathComponent]; + if (createIntermediates && ![thePath isEqualToString:@"/"]) { + BOOL isDirectory = NO; + NSNumber *exists = [self fileExistsAtPath:parentPath isDirectory:&isDirectory targetConnectionDelegate:nil error:error]; + if (exists == nil) { + return NO; + } + if ([exists boolValue]) { + if (!isDirectory) { + SETNSERROR([GoogleDrive errorDomain], -1, @"%@ exists and is not a directory", parentPath); + return NO; + } + } else { + if (![self createDirectoryAtPath:parentPath withIntermediateDirectories:YES targetConnectionDelegate:theDelegate error:error]) { + return NO; + } + } + } + + NSString *parentFolderId = [self folderIdForPath:parentPath targetConnectionDelegate:theDelegate error:error]; + if (parentFolderId == nil) { + return NO; + } + + NSString *lastPathComponent = [thePath lastPathComponent]; + NSDictionary *parent = [NSDictionary dictionaryWithObject:parentFolderId forKey:@"id"]; + NSArray *parents = [NSArray arrayWithObject:parent]; + NSDictionary *requestJSON = [NSDictionary dictionaryWithObjectsAndKeys: + lastPathComponent, @"title", + parents, @"parents", + kFolderMimeType, @"mimeType", + nil]; + NSData *requestBody = [[requestJSON JSONRepresentation:error] dataUsingEncoding:NSUTF8StringEncoding]; + if (requestBody == nil) { + return NO; + } + + GoogleDriveRequest *req = [[[GoogleDriveRequest alloc] initWithEmailAddress:emailAddress + method:@"POST" + path:@"/drive/v2/files" + queryString:nil + refreshToken:refreshToken + googleDriveDelegate:delegate + dataTransferDelegate:nil + error:error] autorelease]; + if (req == nil) { + return NO; + } + [req setRequestHeader:@"application/json" forKey:@"Content-Type"]; + [req setRequestBody:requestBody]; + NSData *response = [req dataWithTargetConnectionDelegate:theDelegate error:error]; + if (response == nil) { + return NO; + } + NSString *responseString = [[[NSString alloc] initWithData:response encoding:NSUTF8StringEncoding] autorelease]; + NSDictionary *responseJSON = [responseString JSONValue:error]; + if (responseJSON == nil) { + return NO; + } + NSString *folderId = [responseJSON objectForKey:@"id"]; + [delegate googleDriveDidFindFolderId:folderId forPath:thePath refreshToken:refreshToken]; + + return YES; +} +- (BOOL)removeItemAtPath:(NSString *)thePath targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + HSLogDetail(@"deleting %@", thePath); + + if ([thePath isEqualToString:@"/"]) { + SETNSERROR([GoogleDrive errorDomain], -1, @"cannot delete root folder"); + return NO; + } + + NSError *myError = nil; + NSDictionary *googleDriveItem = [self firstGoogleDriveItemAtPath:thePath targetConnectionDelegate:theDelegate error:&myError]; + if (googleDriveItem == nil) { + if (![myError isErrorWithDomain:[GoogleDrive errorDomain] code:ERROR_NOT_FOUND]) { + SETERRORFROMMYERROR; + return NO; + } + } else { + NSString *fileId = [googleDriveItem objectForKey:@"id"]; + GoogleDriveRequest *req = [[[GoogleDriveRequest alloc] initWithEmailAddress:emailAddress + method:@"DELETE" + path:[NSString stringWithFormat:@"/drive/v2/files/%@", fileId] + queryString:nil + refreshToken:refreshToken + googleDriveDelegate:delegate + dataTransferDelegate:nil + error:error] autorelease]; + if (req == nil) { + return NO; + } + if ([req dataWithTargetConnectionDelegate:theDelegate error:&myError] == nil) { + // If we do a DELETE and the network goes away, the delete may have happened, so when the network comes back and we retry + // we might get file-not-found. Don't return an error in that case. + if (![myError isErrorWithDomain:[GoogleDrive errorDomain] code:ERROR_NOT_FOUND]) { + SETERRORFROMMYERROR; + return NO; + } + } + } + return YES; +} +- (NSData *)contentsOfFileAtPath:(NSString *)thePath dataTransferDelegate:(id)theDTDelegate targetConnectionDelegate:(id)theTCDelegate error:(NSError **)error { + HSLogDetail(@"getting contents of file %@", thePath); + + NSDictionary *googleDriveItem = [self firstGoogleDriveItemAtPath:thePath targetConnectionDelegate:theTCDelegate error:error]; + if (googleDriveItem == nil) { + return nil; + } + + NSURL *downloadURL = [NSURL URLWithString:[googleDriveItem objectForKey:@"downloadUrl"]]; + + GoogleDriveRequest *req = [[[GoogleDriveRequest alloc] initWithGetURL:downloadURL refreshToken:refreshToken googleDriveDelegate:delegate dataTransferDelegate:theDTDelegate error:error] autorelease]; + NSData *response = [req dataWithTargetConnectionDelegate:theTCDelegate error:error]; + return response; +} +- (BOOL)writeData:(NSData *)theData mimeType:(NSString *)theMimeType toFileAtPath:(NSString *)thePath dataTransferDelegate:(id )theDelegate targetConnectionDelegate:(id )theTCD error:(NSError **)error { + HSLogDebug(@"putting file %@", thePath); + + // FIXME! Hack: If it's in /objects, we checked the object list so don't check for existence of the file: + NSArray *pathComponents = [thePath pathComponents]; + if ([pathComponents count] > 2 && [[pathComponents objectAtIndex:[pathComponents count] - 1] length] == 40 && [[pathComponents objectAtIndex:[pathComponents count] - 2] isEqualToString:@"objects"]) { + HSLogDebug(@"skipping existence check for %@", thePath); + } else { + // Delete existing file if any. + if (![self removeItemAtPath:thePath targetConnectionDelegate:theTCD error:error]) { + return NO; + } + } + + + NSString *parentPath = [thePath stringByDeletingLastPathComponent]; + + NSError *myError = nil; + NSString *folderId = [self folderIdForPath:parentPath targetConnectionDelegate:theTCD error:&myError]; + if (folderId == nil) { + if (![myError isErrorWithDomain:[GoogleDrive errorDomain] code:ERROR_NOT_FOUND]) { + SETERRORFROMMYERROR; + return NO; + } + if (![self createDirectoryAtPath:parentPath withIntermediateDirectories:YES targetConnectionDelegate:theTCD error:error]) { + return NO; + } + folderId = [self folderIdForPath:parentPath targetConnectionDelegate:theTCD error:error]; + if (folderId == nil) { + return NO; + } + } + + GoogleDriveRequest *req = [[[GoogleDriveRequest alloc] initWithEmailAddress:emailAddress + method:@"POST" + path:@"/upload/drive/v2/files" + queryString:@"uploadType=multipart" + refreshToken:refreshToken + googleDriveDelegate:delegate + dataTransferDelegate:theDelegate + error:error] autorelease]; + if (req == nil) { + return NO; + } + + NSString *lastPathComponent = [thePath lastPathComponent]; + NSDictionary *parent = [NSDictionary dictionaryWithObject:folderId forKey:@"id"]; + NSArray *parents = [NSArray arrayWithObject:parent]; + NSDictionary *requestParams = [NSDictionary dictionaryWithObjectsAndKeys: + lastPathComponent, @"title", + theMimeType, @"mimeType", + parents, @"parents", nil]; + NSString *requestJSON = [requestParams JSONRepresentation:error]; + if (requestJSON == nil) { + return NO; + } + + NSString *uuid = [NSString stringWithRandomUUID]; + [req setRequestHeader:[NSString stringWithFormat:@"multipart/related; boundary=\"%@\"", uuid] forKey:@"Content-Type"]; + NSMutableData *requestBody = [NSMutableData data]; + [requestBody appendData:[[NSString stringWithFormat:@"--%@\n", uuid] dataUsingEncoding:NSUTF8StringEncoding]]; + [requestBody appendData:[@"Content-Type: application/json; charset=UTF-8\n" dataUsingEncoding:NSUTF8StringEncoding]]; + [requestBody appendData:[@"\n" dataUsingEncoding:NSUTF8StringEncoding]]; + [requestBody appendData:[requestJSON dataUsingEncoding:NSUTF8StringEncoding]]; + [requestBody appendData:[@"\n" dataUsingEncoding:NSUTF8StringEncoding]]; + + [requestBody appendData:[[NSString stringWithFormat:@"--%@\n", uuid] dataUsingEncoding:NSUTF8StringEncoding]]; + [requestBody appendData:[[NSString stringWithFormat:@"Content-Type: %@\n", theMimeType] dataUsingEncoding:NSUTF8StringEncoding]]; + [requestBody appendData:[@"\n" dataUsingEncoding:NSUTF8StringEncoding]]; + [requestBody appendData:theData]; + [requestBody appendData:[@"\n" dataUsingEncoding:NSUTF8StringEncoding]]; + [requestBody appendData:[[NSString stringWithFormat:@"--%@--\n", uuid] dataUsingEncoding:NSUTF8StringEncoding]]; + + [req setRequestBody:requestBody]; + + NSData *response = [req dataWithTargetConnectionDelegate:theTCD error:error]; + return response != nil; +} +- (NSNumber *)sizeOfItemAtPath:(NSString *)thePath targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + HSLogDetail(@"getting size of %@", thePath); + + NSError *myError = nil; + NSDictionary *googleDriveItem = [self firstGoogleDriveItemAtPath:thePath targetConnectionDelegate:theDelegate error:&myError]; + if (googleDriveItem == nil) { + if (![myError isErrorWithDomain:[GoogleDrive errorDomain] code:ERROR_NOT_FOUND]) { + SETERRORFROMMYERROR; + return nil; + } + HSLogDebug(@"path %@ does not exist; returning size = 0", thePath); + return [NSNumber numberWithUnsignedInteger:0]; + } + + return [self sizeOfGoogleDriveItem:googleDriveItem targetConnectionDelegate:theDelegate error:error]; +} +- (NSArray *)objectsAtPath:(NSString *)thePath targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + HSLogDetail(@"getting objects at %@", thePath); + + thePath = [thePath stringByDeletingTrailingSlash]; + BOOL isDir = NO; + unsigned long long dataSize = NULL; + NSDate *lastModifiedDate = nil; + NSError *myError = nil; + NSNumber *exists = [self fileExistsAtPath:thePath isDirectory:&isDir dataSize:&dataSize lastModifiedDate:&lastModifiedDate targetConnectionDelegate:theDelegate error:&myError]; + if (exists == nil) { + SETERRORFROMMYERROR; + return nil; + } + + NSArray *ret = nil; + if (![exists boolValue]) { + ret = [NSArray array]; + } else if (isDir) { + ret = [self objectsInDirectory:thePath targetConnectionDelegate:theDelegate error:error]; + } else { + S3ObjectMetadata *md = [[[S3ObjectMetadata alloc] initWithPath:thePath lastModified:lastModifiedDate size:dataSize storageClass:@"STANDARD"] autorelease]; + ret = [NSArray arrayWithObject:md]; + } + return ret; +} +- (NSArray *)pathsOfObjectsAtPath:(NSString *)thePath targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + HSLogDetail(@"getting paths of objects at %@", thePath); + + BOOL isDirectory = NO; + NSNumber *exists = [self fileExistsAtPath:thePath isDirectory:&isDirectory targetConnectionDelegate:theDelegate error:error]; + if (exists == nil) { + return nil; + } + NSArray *ret = nil; + if (![exists boolValue]) { + ret = [NSArray array]; + } else if (isDirectory) { + ret = [self pathsOfObjectsInDirectory:thePath targetConnectionDelegate:theDelegate error:error]; + } else { + ret = [NSArray arrayWithObject:thePath]; + } + return ret; +} + + +#pragma mark internal +- (NSArray *)objectsInDirectory:(NSString *)thePath targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + NSString *folderId = [self folderIdForPath:thePath targetConnectionDelegate:theDelegate error:error]; + if (folderId == nil) { + return nil; + } + + GoogleDriveFolderLister *lister = [[[GoogleDriveFolderLister alloc] initWithEmailAddress:emailAddress refreshToken:refreshToken folderId:folderId googleDriveDelegate:delegate targetConnectionDelegate:theDelegate] autorelease]; + NSArray *googleDriveItems = [lister googleDriveItems:error]; + if (googleDriveItems == nil) { + return nil; + } + + NSMutableArray *ret = [NSMutableArray array]; + for (NSDictionary *googleDriveItem in googleDriveItems) { + NSString *name = [googleDriveItem objectForKey:@"title"]; + NSString *childPath = [thePath stringByAppendingPathComponent:name]; + if ([[googleDriveItem objectForKey:@"mimeType"] isEqualToString:kFolderMimeType]) { + NSArray *childObjects = [self objectsInDirectory:childPath targetConnectionDelegate:theDelegate error:error]; + if (childObjects == nil) { + return nil; + } + [ret addObjectsFromArray:childObjects]; + } else { + NSDate *lastModifiedDate = [ISO8601Date dateFromString:[googleDriveItem objectForKey:@"modifiedDate"] error:error]; + if (lastModifiedDate == nil) { + return nil; + } + NSNumber *dataSize = [googleDriveItem objectForKey:@"fileSize"]; + S3ObjectMetadata *md = [[[S3ObjectMetadata alloc] initWithPath:childPath lastModified:lastModifiedDate size:dataSize storageClass:@"STANDARD"] autorelease]; + [ret addObject:md]; + } + } + return ret; +} +- (NSArray *)pathsOfObjectsInDirectory:(NSString *)thePath targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + NSString *folderId = [self folderIdForPath:thePath targetConnectionDelegate:theDelegate error:error]; + if (folderId == nil) { + return nil; + } + + GoogleDriveFolderLister *lister = [[[GoogleDriveFolderLister alloc] initWithEmailAddress:emailAddress + refreshToken:refreshToken + folderId:folderId + googleDriveDelegate:delegate + targetConnectionDelegate:theDelegate] autorelease]; + NSArray *googleDriveItems = [lister googleDriveItems:error]; + if (googleDriveItems == nil) { + return nil; + } + + NSMutableArray *ret = [NSMutableArray array]; + for (NSDictionary *googleDriveItem in googleDriveItems) { + NSString *name = [googleDriveItem objectForKey:@"title"]; + NSString *childPath = [thePath stringByAppendingPathComponent:name]; + if ([[googleDriveItem objectForKey:@"mimeType"] isEqualToString:kFolderMimeType]) { + NSArray *childPaths = [self pathsOfObjectsInDirectory:childPath targetConnectionDelegate:theDelegate error:error]; + if (childPaths == nil) { + return nil; + } + [ret addObjectsFromArray:childPaths]; + } else { + [ret addObject:childPath]; + } + } + return ret; +} +- (NSString *)folderIdForPath:(NSString *)thePath targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + NSString *folderId = [delegate googleDriveFolderIdForPath:thePath refreshToken:refreshToken]; + if (folderId != nil) { + return folderId; + } + + NSString *lastPathComponent = [thePath lastPathComponent]; + NSString *parentPath = [thePath stringByDeletingLastPathComponent]; + NSString *parentFolderId = [self folderIdForPath:parentPath targetConnectionDelegate:theDelegate error:error]; + if (parentFolderId == nil) { + return nil; + } + + GoogleDriveFolderLister *lister = [[[GoogleDriveFolderLister alloc] initWithEmailAddress:emailAddress + refreshToken:refreshToken + folderId:parentFolderId + name:lastPathComponent + googleDriveDelegate:delegate + targetConnectionDelegate:theDelegate] autorelease]; + NSArray *googleDriveItems = [lister googleDriveItems:error]; + if (googleDriveItems == nil) { + return nil; + } + + for (NSDictionary *item in googleDriveItems) { + if ([[item objectForKey:@"title"] isEqualToString:lastPathComponent]) { + if (![[item objectForKey:@"mimeType"] isEqualToString:kFolderMimeType]) { + SETNSERROR([GoogleDrive errorDomain], -1, @"%@ is not a folder", thePath); + } + [delegate googleDriveDidFindFolderId:[item objectForKey:@"id"] forPath:thePath refreshToken:refreshToken]; + return [item objectForKey:@"id"]; + } + } + SETNSERROR([GoogleDrive errorDomain], ERROR_NOT_FOUND, @"folderId not found for path %@", thePath); + return nil; +} +/* + * NOTE: This method ONLY RETURNS THE FIRST ITEM found with the given name. + * Google Drive API allows more than one item with the same name in the same folder, unfortunately. + */ +- (NSDictionary *)firstGoogleDriveItemAtPath:(NSString *)thePath targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + NSString *parentPath = [thePath stringByDeletingLastPathComponent]; + NSString *folderId = [self folderIdForPath:parentPath targetConnectionDelegate:theDelegate error:error]; + if (folderId == nil) { + return nil; + } + NSString *lastPathComponent = [thePath lastPathComponent]; + GoogleDriveFolderLister *lister = [[[GoogleDriveFolderLister alloc] initWithEmailAddress:emailAddress + refreshToken:refreshToken + folderId:folderId name:lastPathComponent + googleDriveDelegate:delegate + targetConnectionDelegate:theDelegate] autorelease]; + NSArray *googleDriveItems = [lister googleDriveItems:error]; + if (googleDriveItems == nil) { + return nil; + } + + NSDictionary *ret = nil; + for (NSDictionary *item in googleDriveItems) { + if ([[item objectForKey:@"title"] isEqualToString:lastPathComponent]) { + if ([[item objectForKey:@"mimeType"] isEqualToString:kFolderMimeType]) { + [delegate googleDriveDidFindFolderId:[item objectForKey:@"id"] forPath:thePath refreshToken:refreshToken]; + } + ret = item; + break; + } + } + if (ret == nil) { + SETNSERROR([GoogleDrive errorDomain], ERROR_NOT_FOUND, @"%@ not found in %@", lastPathComponent, parentPath); + } + return ret; +} +- (NSNumber *)sizeOfGoogleDriveItem:(NSDictionary *)theGoogleDriveItem targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + if (![[theGoogleDriveItem objectForKey:@"mimeType"] isEqualToString:kFolderMimeType]) { + NSString *theFileSize = [theGoogleDriveItem objectForKey:@"fileSize"]; + return [NSNumber numberWithInteger:[theFileSize integerValue]]; + } + + GoogleDriveFolderLister *lister = [[[GoogleDriveFolderLister alloc] initWithEmailAddress:emailAddress + refreshToken:refreshToken + folderId:[theGoogleDriveItem objectForKey:@"id"] + googleDriveDelegate:delegate + targetConnectionDelegate:theDelegate] autorelease]; + NSArray *googleDriveItems = [lister googleDriveItems:error]; + if (googleDriveItems == nil) { + return nil; + } + + unsigned long long total = 0; + for (NSDictionary *childGoogleDriveItem in googleDriveItems) { + NSNumber *childSize = [self sizeOfGoogleDriveItem:childGoogleDriveItem targetConnectionDelegate:theDelegate error:error]; + if (childSize == nil) { + return nil; + } + total += [childSize unsignedLongLongValue]; + } + return [NSNumber numberWithUnsignedLongLong:total]; +} +@end diff --git a/cocoastack/googledrive/GoogleDriveErrorResult.h b/cocoastack/googledrive/GoogleDriveErrorResult.h new file mode 100644 index 0000000..a87e922 --- /dev/null +++ b/cocoastack/googledrive/GoogleDriveErrorResult.h @@ -0,0 +1,16 @@ +// +// GoogleDriveErrorResult.h +// Arq +// +// Created by Stefan Reitshamer on 7/17/14. +// Copyright (c) 2014 Stefan Reitshamer. All rights reserved. +// + + +@interface GoogleDriveErrorResult : NSObject { + NSError *myError; +} +- (id)initWithAction:(NSString *)theAction data:(NSData *)theData contentType:(NSString *)theContentType httpErrorCode:(int)theHTTPStatusCode; + +- (NSError *)error; +@end diff --git a/cocoastack/googledrive/GoogleDriveErrorResult.m b/cocoastack/googledrive/GoogleDriveErrorResult.m new file mode 100644 index 0000000..5a179b1 --- /dev/null +++ b/cocoastack/googledrive/GoogleDriveErrorResult.m @@ -0,0 +1,64 @@ +// +// GoogleDriveErrorResult.m +// Arq +// +// Created by Stefan Reitshamer on 7/17/14. +// Copyright (c) 2014 Stefan Reitshamer. All rights reserved. +// + +#import "GoogleDriveErrorResult.h" +#import "GoogleDrive.h" +#import "NSString+SBJSON.h" + + +@implementation GoogleDriveErrorResult +- (id)initWithAction:(NSString *)theAction data:(NSData *)theData contentType:(NSString *)theContentType httpErrorCode:(int)theHTTPStatusCode { + if (self = [super init]) { + myError = [[self googleDriveErrorForAction:theAction data:theData contentType:theContentType httpErrorCode:theHTTPStatusCode] retain]; + if (myError == nil) { + myError = [[NSError errorWithDomain:[GoogleDrive errorDomain] code:-1 description:[NSString stringWithFormat:@"%@: HTTP error %d", theAction, theHTTPStatusCode]] retain]; + } + } + return self; +} +- (void)dealloc { + [myError release]; + [super dealloc]; +} + +- (NSError *)error { + return myError; +} + + +#pragma mark internal +- (NSError *)googleDriveErrorForAction:(NSString *)theAction data:(NSData *)theData contentType:(NSString *)theContentType httpErrorCode:(int)theHTTPStatusCode { + if ([theContentType hasPrefix:@"application/json"]) { + NSString *jsonString = [[[NSString alloc] initWithData:theData encoding:NSUTF8StringEncoding] autorelease]; + HSLogDebug(@"google drive response json: %@", jsonString); + NSError *theError = nil; + NSDictionary *json = [jsonString JSONValue:&theError]; + if (json == nil) { + HSLogError(@"failed to parse google drive response json: %@", theError); + return nil; + } + NSDictionary *error = [json objectForKey:@"error"]; + NSNumber *errorCode = [error objectForKey:@"code"]; + NSString *errorMessage = [error objectForKey:@"message"]; + + if (theHTTPStatusCode == 403) { + errorCode = [NSNumber numberWithInt:ERROR_ACCESS_DENIED]; + } else if (theHTTPStatusCode == 404) { + errorCode = [NSNumber numberWithInt:ERROR_NOT_FOUND]; + } + + NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys: + errorCode, @"errorCode", + errorMessage, @"errorMessage", + errorMessage, NSLocalizedDescriptionKey, + nil]; + return [NSError errorWithDomain:[GoogleDrive errorDomain] code:[errorCode integerValue] userInfo:userInfo]; + } + return nil; +} +@end diff --git a/cocoastack/googledrive/GoogleDriveFactory.h b/cocoastack/googledrive/GoogleDriveFactory.h new file mode 100644 index 0000000..cfd229e --- /dev/null +++ b/cocoastack/googledrive/GoogleDriveFactory.h @@ -0,0 +1,23 @@ +// +// GoogleDriveFactory.h +// Arq +// +// Created by Stefan Reitshamer on 7/17/14. +// Copyright (c) 2014 Stefan Reitshamer. All rights reserved. +// + +#import "CWLSynthesizeSingleton.h" +#import "GoogleDrive.h" + + +@interface GoogleDriveFactory : NSObject { + NSMutableDictionary *accessTokensByRefreshToken; + NSMutableDictionary *folderIdDictionariesByRefreshToken; + NSLock *lock; +} + +CWL_DECLARE_SINGLETON_FOR_CLASS(GoogleDriveFactory); + + +- (GoogleDrive *)googleDriveWithEmailAddress:(NSString *)theEmailAddress refreshToken:(NSString *)theRefreshToken; +@end diff --git a/cocoastack/googledrive/GoogleDriveFactory.m b/cocoastack/googledrive/GoogleDriveFactory.m new file mode 100644 index 0000000..0b1d1a4 --- /dev/null +++ b/cocoastack/googledrive/GoogleDriveFactory.m @@ -0,0 +1,75 @@ +// +// GoogleDriveFactory.m +// Arq +// +// Created by Stefan Reitshamer on 7/17/14. +// Copyright (c) 2014 Stefan Reitshamer. All rights reserved. +// + +#import "GoogleDriveFactory.h" + + +@implementation GoogleDriveFactory +CWL_SYNTHESIZE_SINGLETON_FOR_CLASS(GoogleDriveFactory) + +- (id)init { + if (self = [super init]) { + accessTokensByRefreshToken = [[NSMutableDictionary alloc] init]; + folderIdDictionariesByRefreshToken = [[NSMutableDictionary alloc] init]; +// [folderIdsByPath setObject:@"root" forKey:@"/"]; + lock = [[NSLock alloc] init]; + [lock setName:@"GoogleDriveFactory lock"]; + } + return self; +} +- (void)dealloc { + [accessTokensByRefreshToken release]; + [folderIdDictionariesByRefreshToken release]; + [lock release]; + [super dealloc]; +} + + +- (GoogleDrive *)googleDriveWithEmailAddress:(NSString *)theEmailAddress refreshToken:(NSString *)theRefreshToken { + return [[[GoogleDrive alloc] initWithEmailAddress:theEmailAddress refreshToken:theRefreshToken delegate:self] autorelease]; +} + + +#pragma mark GoogleDriveDelegate +- (NSString *)googleDriveAccessTokenForRefreshToken:(NSString *)theRefreshToken { + [lock lock]; + NSString *ret = [[[accessTokensByRefreshToken objectForKey:theRefreshToken] copy] autorelease]; + [lock unlock]; + return ret; +} +- (void)googleDriveDidChangeAccessToken:(NSString *)theUpdatedAccessToken forRefreshToken:(NSString *)theRefreshToken { + [lock lock]; + if (theUpdatedAccessToken == nil) { + [accessTokensByRefreshToken removeObjectForKey:theRefreshToken]; + } else { + [accessTokensByRefreshToken setObject:theUpdatedAccessToken forKey:theRefreshToken]; + } + [lock unlock]; +} +- (void)googleDriveDidFindFolderId:(NSString *)theFolderId forPath:(NSString *)thePath refreshToken:(NSString *)theRefreshToken { + [lock lock]; + NSMutableDictionary *folderIdsByPath = [folderIdDictionariesByRefreshToken objectForKey:theRefreshToken]; + if (folderIdsByPath == nil) { + folderIdsByPath = [NSMutableDictionary dictionaryWithObject:@"root" forKey:@"/"]; + [folderIdDictionariesByRefreshToken setObject:folderIdsByPath forKey:theRefreshToken]; + } + [folderIdsByPath setObject:theFolderId forKey:thePath]; + [lock unlock]; +} +- (NSString *)googleDriveFolderIdForPath:(NSString *)thePath refreshToken:(NSString *)theRefreshToken { + [lock lock]; + NSMutableDictionary *folderIdsByPath = [folderIdDictionariesByRefreshToken objectForKey:theRefreshToken]; + if (folderIdsByPath == nil) { + folderIdsByPath = [NSMutableDictionary dictionaryWithObject:@"root" forKey:@"/"]; + [folderIdDictionariesByRefreshToken setObject:folderIdsByPath forKey:theRefreshToken]; + } + NSString *ret = [[[folderIdsByPath objectForKey:thePath] copy] autorelease]; + [lock unlock]; + return ret; +} +@end diff --git a/cocoastack/googledrive/GoogleDriveFolderLister.h b/cocoastack/googledrive/GoogleDriveFolderLister.h new file mode 100644 index 0000000..f71219e --- /dev/null +++ b/cocoastack/googledrive/GoogleDriveFolderLister.h @@ -0,0 +1,28 @@ +// +// GoogleDriveFolderLister.h +// Arq +// +// Created by Stefan Reitshamer on 7/17/14. +// Copyright (c) 2014 Stefan Reitshamer. All rights reserved. +// + +@protocol GoogleDriveDelegate; +@protocol TargetConnectionDelegate; + + +@interface GoogleDriveFolderLister : NSObject { + NSString *emailAddress; + NSString *refreshToken; + NSString *folderId; + NSString *fileName; + id googleDriveDelegate; + id targetConnectionDelegate; + NSString *pageToken; +} + +- (id)initWithEmailAddress:(NSString *)theEmailAddress refreshToken:(NSString *)theRefreshToken folderId:(NSString *)theFolderId googleDriveDelegate:(id )theDelegate targetConnectionDelegate:(id)theDelegate; +- (id)initWithEmailAddress:(NSString *)theEmailAddress refreshToken:(NSString *)theRefreshToken folderId:(NSString *)theFolderId name:(NSString *)theName googleDriveDelegate:(id )theDelegate targetConnectionDelegate:(id)theDelegate; + +// Return NSDictionaries of metadata as given by Google response JSON. +- (NSArray *)googleDriveItems:(NSError **)error; +@end diff --git a/cocoastack/googledrive/GoogleDriveFolderLister.m b/cocoastack/googledrive/GoogleDriveFolderLister.m new file mode 100644 index 0000000..cd97b8a --- /dev/null +++ b/cocoastack/googledrive/GoogleDriveFolderLister.m @@ -0,0 +1,127 @@ +// +// GoogleDriveFolderLister.m +// Arq +// +// Created by Stefan Reitshamer on 7/17/14. +// Copyright (c) 2014 Stefan Reitshamer. All rights reserved. +// + +#import "GoogleDriveFolderLister.h" +#import "GoogleDriveRequest.h" +#import "NSString+SBJSON.h" +#import "GoogleDrive.h" + + +static NSString *kURLQueryAllowedCharacterSet = @"\"#%<>[\\]^`{|}"; + +@implementation GoogleDriveFolderLister +- (id)initWithEmailAddress:(NSString *)theEmailAddress refreshToken:(NSString *)theRefreshToken folderId:(NSString *)theFolderId googleDriveDelegate:(id )theGDD targetConnectionDelegate:(id)theTCD { + return [self initWithEmailAddress:theEmailAddress refreshToken:theRefreshToken folderId:theFolderId name:nil googleDriveDelegate:theGDD targetConnectionDelegate:theTCD]; +} +- (id)initWithEmailAddress:(NSString *)theEmailAddress refreshToken:(NSString *)theRefreshToken folderId:(NSString *)theFolderId name:(NSString *)theName googleDriveDelegate:(id )theGDD targetConnectionDelegate:(id)theTCD { + if (self = [super init]) { + emailAddress = [theEmailAddress retain]; + refreshToken = [theRefreshToken retain]; + folderId = [theFolderId retain]; + fileName = [theName retain]; + googleDriveDelegate = theGDD; + targetConnectionDelegate = theTCD; + } + return self; +} +- (void)dealloc { + [emailAddress release]; + [refreshToken release]; + [folderId release]; + [fileName release]; + [pageToken release]; + [super dealloc]; +} +- (NSArray *)googleDriveItems:(NSError **)error { + NSMutableArray *ret = [NSMutableArray array]; + + NSAutoreleasePool *pool = nil; + for (;;) { + [pool drain]; + pool = [[NSAutoreleasePool alloc] init]; + + NSArray *items = [self nextPage:error]; + if (items == nil) { + ret = nil; + break; + } + + [ret addObjectsFromArray:items]; + if (pageToken == nil) { + break; + } + } + + [ret retain]; + if (ret == nil && error != NULL) { + [*error retain]; + } + [pool drain]; + [ret autorelease]; + if (ret == nil && error != NULL) { + [*error autorelease]; + } + return ret; +} + +- (NSArray *)nextPage:(NSError **)error { + if (folderId == nil) { + SETNSERROR([GoogleDrive errorDomain], -1, @"folderId is nil!"); + return nil; + } + + NSString *queryString = @"maxResults=1000&q="; + NSString *search = [NSString stringWithFormat:@"explicitlyTrashed = false and '%@' in parents", folderId]; + if (fileName != nil) { + NSString *escapedFileName = [fileName stringByReplacingOccurrencesOfString:@"'" withString:@"\\'"]; + search = [search stringByAppendingFormat:@" and title = '%@'", escapedFileName]; + } + NSString *escapedSearch = [(NSString *)CFURLCreateStringByAddingPercentEscapes(NULL, + (CFStringRef)search, + (CFStringRef)@"", + (CFStringRef)kURLQueryAllowedCharacterSet, + kCFStringEncodingUTF8) autorelease]; + queryString = [queryString stringByAppendingString:escapedSearch]; + + if (pageToken != nil) { + NSString *escapedPageToken = [(NSString *)CFURLCreateStringByAddingPercentEscapes(NULL, + (CFStringRef)pageToken, + (CFStringRef)@"", + (CFStringRef)kURLQueryAllowedCharacterSet, + kCFStringEncodingUTF8) autorelease]; + queryString = [queryString stringByAppendingFormat:@"&pageToken=%@", escapedPageToken]; + } + + GoogleDriveRequest *req = [[[GoogleDriveRequest alloc] initWithEmailAddress:emailAddress + method:@"GET" + path:@"/drive/v2/files" + queryString:queryString + refreshToken:refreshToken + googleDriveDelegate:googleDriveDelegate + dataTransferDelegate:nil + error:error] autorelease]; + if (req == nil) { + return nil; + } + NSData *response = [req dataWithTargetConnectionDelegate:targetConnectionDelegate error:error]; + if (response == nil) { + return nil; + } + NSString *responseString = [[[NSString alloc] initWithData:response encoding:NSUTF8StringEncoding] autorelease]; + NSDictionary *json = (NSDictionary *)[responseString JSONValue:error]; + if (json == nil) { + return nil; + } + + [pageToken release]; + pageToken = [[json objectForKey:@"nextPageToken"] retain]; + + NSArray *items = [json objectForKey:@"items"]; + return items; +} +@end diff --git a/cocoastack/googledrive/GoogleDriveRequest.h b/cocoastack/googledrive/GoogleDriveRequest.h new file mode 100644 index 0000000..5cc2d5e --- /dev/null +++ b/cocoastack/googledrive/GoogleDriveRequest.h @@ -0,0 +1,52 @@ +// +// GoogleDriveRequest.h +// Arq +// +// Created by Stefan Reitshamer on 7/17/14. +// Copyright (c) 2014 Stefan Reitshamer. All rights reserved. +// + +@class GoogleDriveAuthorizationProvider; +@class Target; +@protocol TargetConnectionDelegate; +@protocol DataTransferDelegate; +@protocol GoogleDriveDelegate; + + +@interface GoogleDriveRequest : NSObject { + NSString *emailAddress; + NSString *method; + NSURL *url; + NSString *refreshToken; + id googleDriveDelegate; + + id dataTransferDelegate; + NSData *requestBody; + NSMutableDictionary *extraRequestHeaders; + unsigned long long bytesUploaded; + int httpResponseCode; + NSMutableDictionary *responseHeaders; +} + +- (id)initWithEmailAddress:(NSString *)theEmailAddress + method:(NSString *)theMethod + path:(NSString *)thePath + queryString:(NSString *)theQueryString + refreshToken:(NSString *)theRefreshToken + googleDriveDelegate:(id )theGoogleDriveDelegate + dataTransferDelegate:(id )theDelegate + error:(NSError **)error; + +- (id)initWithGetURL:(NSURL *)theURL + refreshToken:(NSString *)theRefreshToken + googleDriveDelegate:(id )theGoogleDriveDelegate +dataTransferDelegate:(id )theDelegate + error:(NSError **)error; + + +- (void)setRequestBody:(NSData *)theRequestBody; +- (void)setRequestHeader:(NSString *)value forKey:(NSString *)key; +- (int)httpResponseCode; +- (NSString *)responseHeaderForKey:(NSString *)theKey; +- (NSData *)dataWithTargetConnectionDelegate:(id )theDelegate error:(NSError **)error; +@end diff --git a/cocoastack/googledrive/GoogleDriveRequest.m b/cocoastack/googledrive/GoogleDriveRequest.m new file mode 100644 index 0000000..870123a --- /dev/null +++ b/cocoastack/googledrive/GoogleDriveRequest.m @@ -0,0 +1,290 @@ +// +// GoogleDriveRequest.m +// Arq +// +// Created by Stefan Reitshamer on 7/17/14. +// Copyright (c) 2014 Stefan Reitshamer. All rights reserved. +// + +#import "GoogleDriveRequest.h" +#import "GoogleDrive.h" +#import "HTTPConnection.h" +#import "HTTPConnectionFactory.h" +#import "GoogleDriveErrorResult.h" +#import "TargetConnection.h" +#import "NSDictionary_HTTP.h" +#import "NSString+SBJSON.h" + + +#define INITIAL_RETRY_SLEEP (0.5) +#define RETRY_SLEEP_GROWTH_FACTOR (1.5) +#define MAX_RETRY_SLEEP (5.0) + +static NSString *const kGoogleClientIDKey = @"1081461930698-ci22n9pcfkmebda0iofbt5qke01r1000.apps.googleusercontent.com"; +static NSString *const kGoogleClientSecretKey = @"UGO8R1b64tw1FS1MT-3K8qJD"; + + +@implementation GoogleDriveRequest +- (id)initWithEmailAddress:(NSString *)theEmailAddress method:(NSString *)theMethod path:(NSString *)thePath queryString:(NSString *)theQueryString refreshToken:(NSString *)theRefreshToken googleDriveDelegate:(id)theGoogleDriveDelegate dataTransferDelegate:(id)theDelegate error:(NSError **)error { + if (theQueryString != nil) { + if ([theQueryString hasPrefix:@"?"]) { + SETNSERROR([GoogleDrive errorDomain], -1, @"query string may not begin with a ?"); + [self release]; + return nil; + } + thePath = [[thePath stringByAppendingString:@"?"] stringByAppendingString:theQueryString]; + } + if (![thePath hasPrefix:@"/upload/drive/v2"] && ![thePath hasPrefix:@"/drive/v2"]) { + SETNSERROR([GoogleDrive errorDomain], -1, @"path must begin with /upload/drive/v2 or /drive/v2"); + [self release]; + return nil; + } + NSString *urlString = [NSString stringWithFormat:@"https://www.googleapis.com%@", thePath]; + NSURL *theURL = [[[NSURL alloc] initWithString:urlString] autorelease]; + if (theURL == nil) { + SETNSERROR([GoogleDrive errorDomain], -1, @"invalid URL: %@", urlString); + [self release]; + return nil; + } + return [self initWithEmailAddress:theEmailAddress method:theMethod url:theURL refreshToken:theRefreshToken googleDriveDelegate:theGoogleDriveDelegate dataTransferDelegate:theDelegate error:error]; +} +- (id)initWithGetURL:(NSURL *)theURL refreshToken:(NSString *)theRefreshToken googleDriveDelegate:(id)theGoogleDriveDelegate dataTransferDelegate:(id)theDelegate error:(NSError **)error { + return [self initWithEmailAddress:nil method:@"GET" url:theURL refreshToken:theRefreshToken googleDriveDelegate:theGoogleDriveDelegate dataTransferDelegate:theDelegate error:error]; +} + +- (id)initWithEmailAddress:(NSString *)theEmailAddress method:(NSString *)theMethod url:(NSURL *)theURL refreshToken:(NSString *)theRefreshToken googleDriveDelegate:(id)theGoogleDriveDelegate dataTransferDelegate:(id)theDelegate error:(NSError **)error { + if (self = [super init]) { + emailAddress = [theEmailAddress retain]; + method = [theMethod retain]; + url = [theURL retain]; + refreshToken = [theRefreshToken retain]; + googleDriveDelegate = theGoogleDriveDelegate; + dataTransferDelegate = theDelegate; + extraRequestHeaders = [[NSMutableDictionary alloc] init]; + responseHeaders = [[NSMutableDictionary alloc] init]; + } + return self; +} +- (void)dealloc { + [emailAddress release]; + [method release]; + [url release]; + [requestBody release]; + [extraRequestHeaders release]; + [responseHeaders release]; + [super dealloc]; +} + +- (void)setRequestBody:(NSData *)theRequestBody { + [theRequestBody retain]; + [requestBody release]; + requestBody = theRequestBody; +} +- (void)setRequestHeader:(NSString *)value forKey:(NSString *)key { + [extraRequestHeaders setObject:value forKey:key]; +} +- (int)httpResponseCode { + return httpResponseCode; +} +- (NSString *)responseHeaderForKey:(NSString *)theKey { + return [responseHeaders objectForKey:theKey]; +} +- (NSData *)dataWithTargetConnectionDelegate:(id )theDelegate error:(NSError **)error { + NSAutoreleasePool *pool = nil; + NSTimeInterval sleepTime = INITIAL_RETRY_SLEEP; + NSData *responseData = nil; + NSError *myError = nil; + for (;;) { + [pool drain]; + pool = [[NSAutoreleasePool alloc] init]; + BOOL needRetry = NO; + BOOL needSleep = NO; + myError = nil; + responseData = [self dataOnce:&myError]; + if (responseData != nil) { + break; + } + + HSLogDebug(@"GoogleDriveRequest dataOnce failed; %@", myError); + + if ([myError isErrorWithDomain:[GoogleDrive errorDomain] code:ERROR_NOT_FOUND]) { + break; + } + + if ([myError isTransientError]) { + needRetry = YES; + needSleep = YES; + } + + BOOL refreshedToken = NO; + if ([myError isErrorWithDomain:[GoogleDrive errorDomain] code:401]) { + NSString *accessToken = [self requestAccessToken:&myError]; + if (accessToken == nil) { + HSLogError(@"failed to get new access token: %@", myError); + break; + } + [googleDriveDelegate googleDriveDidChangeAccessToken:accessToken forRefreshToken:refreshToken]; + needRetry = YES; + refreshedToken = YES; + } + + if (!refreshedToken && (!needRetry || ![theDelegate targetConnectionShouldRetryOnTransientError:&myError])) { + HSLogError(@"%@ %@: %@", method, url, myError); + break; + } + + HSLogDetail(@"retrying %@: %@", method, myError); + if (needSleep) { + [NSThread sleepForTimeInterval:sleepTime]; + sleepTime *= RETRY_SLEEP_GROWTH_FACTOR; + if (sleepTime > MAX_RETRY_SLEEP) { + sleepTime = MAX_RETRY_SLEEP; + } + } + } + [responseData retain]; + if (responseData == nil) { + [myError retain]; + } + [pool drain]; + [responseData autorelease]; + if (responseData == nil) { + [myError autorelease]; + SETERRORFROMMYERROR; + } + + return responseData; +} + + +#pragma mark internal +- (NSData *)dataOnce:(NSError **)error { + id conn = [[[HTTPConnectionFactory theFactory] newHTTPConnectionToURL:url method:method dataTransferDelegate:dataTransferDelegate] autorelease]; + if (conn == nil) { + return nil; + } + [conn setRequestHostHeader]; + [conn setRFC822DateRequestHeader]; + if (requestBody != nil) { + [conn setRequestHeader:[NSString stringWithFormat:@"%lu", (unsigned long)[requestBody length]] forKey:@"Content-Length"]; + } + for (NSString *headerKey in [extraRequestHeaders allKeys]) { + [conn setRequestHeader:[extraRequestHeaders objectForKey:headerKey] forKey:headerKey]; + } + NSString *accessToken = [googleDriveDelegate googleDriveAccessTokenForRefreshToken:refreshToken]; + if (accessToken == nil) { + accessToken = [self requestAccessToken:error]; + if (accessToken == nil) { + return nil; + } + [googleDriveDelegate googleDriveDidChangeAccessToken:accessToken forRefreshToken:refreshToken]; + } + [conn setRequestHeader:[NSString stringWithFormat:@"Bearer %@", accessToken] forKey:@"Authorization"]; + + bytesUploaded = 0; + + HSLogDebug(@"%@ %@", method, url); + + [conn setRequestHeader:[NSString stringWithFormat:@"%lu", (unsigned long)[requestBody length]] forKey:@"Content-Length"]; + NSData *response = [conn executeRequestWithBody:requestBody error:error]; + if (response == nil) { + return nil; + } + + [responseHeaders setDictionary:[conn responseHeaders]]; + + httpResponseCode = [conn responseCode]; + if (httpResponseCode >= 200 && httpResponseCode <= 299) { + HSLogDebug(@"HTTP %d; returning response length=%ld", httpResponseCode, (long)[response length]); + return response; + } + + HSLogDebug(@"http response body: %@", [[[NSString alloc] initWithBytes:[response bytes] length:[response length] encoding:NSUTF8StringEncoding] autorelease]); +// if (httpResponseCode == HTTP_NOT_FOUND) { +// HSLogDebug(@"http response body: %@", [[[NSString alloc] initWithBytes:[response bytes] length:[response length] encoding:NSUTF8StringEncoding] autorelease]); +// S3ErrorResult *errorResult = [[[S3ErrorResult alloc] initWithAction:[NSString stringWithFormat:@"%@ %@", method, [url description]] data:response httpErrorCode:httpResponseCode] autorelease]; +// NSError *myError = [errorResult error]; +// NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:[myError userInfo]]; +// [userInfo setObject:[NSString stringWithFormat:@"%@ not found", url] forKey:NSLocalizedDescriptionKey]; +// myError = [NSError errorWithDomain:[S3Service errorDomain] code:ERROR_NOT_FOUND userInfo:userInfo]; +// HSLogDebug(@"%@", myError); +// SETERRORFROMMYERROR; +// return nil; +// } +// if (httpResponseCode == HTTP_METHOD_NOT_ALLOWED) { +// HSLogError(@"%@ 405 error", url); +// SETNSERROR([S3Service errorDomain], ERROR_RRS_NOT_FOUND, @"%@ 405 error", url); +// } +// if (httpResponseCode == HTTP_MOVED_TEMPORARILY) { +// NSString *location = [conn responseHeaderForKey:@"Location"]; +// NSDictionary *userInfo = [NSDictionary dictionaryWithObject:location forKey:@"location"]; +// NSError *myError = [NSError errorWithDomain:[S3Service errorDomain] code:ERROR_TEMPORARY_REDIRECT userInfo:userInfo]; +// if (error != NULL) { +// *error = myError; +// } +// HSLogDebug(@"returning moved-temporarily error"); +// return nil; +// } + GoogleDriveErrorResult *errorResult = [[[GoogleDriveErrorResult alloc] initWithAction:[NSString stringWithFormat:@"%@ %@", method, [url description]] data:response contentType:[responseHeaders objectForKey:@"Content-Type"] httpErrorCode:httpResponseCode] autorelease]; + NSError *myError = [errorResult error]; + HSLogDebug(@"%@ error: %@", conn, myError); + SETERRORFROMMYERROR; + + return nil; +} + +- (NSString *)requestAccessToken:(NSError **)error { + HSLogDebug(@"requesting access token"); + + id conn = [[[HTTPConnectionFactory theFactory] newHTTPConnectionToURL:[NSURL URLWithString:@"https://accounts.google.com/o/oauth2/token"] method:@"POST" dataTransferDelegate:nil] autorelease]; + if (conn == nil) { + return nil; + } + [conn setRequestHostHeader]; + [conn setRFC822DateRequestHeader]; + + NSMutableDictionary *params = [NSMutableDictionary dictionary]; + [params setObject:kGoogleClientIDKey forKey:@"client_id"]; + [params setObject:kGoogleClientSecretKey forKey:@"client_secret"]; + [params setObject:refreshToken forKey:@"refresh_token"]; + [params setObject:@"refresh_token" forKey:@"grant_type"]; + + NSString *encodedParams = [params wwwFormURLEncodedString]; + NSData *response = [conn executeRequestWithBody:[encodedParams dataUsingEncoding:NSUTF8StringEncoding] error:error]; + if (response == nil) { + return nil; + } + + if ([conn responseCode] != 200) { + if ([[conn responseHeaderForKey:@"Content-Type"] hasPrefix:@"application/json"]) { + NSString *responseJSONString = [[[NSString alloc] initWithData:response encoding:NSUTF8StringEncoding] autorelease]; + HSLogDebug(@"response JSON: %@", responseJSONString); + + NSDictionary *responseJSON = [responseJSONString JSONValue:NULL]; + if ([[responseJSON objectForKey:@"error"] isEqualToString:@"invalid_grant"]) { + // This Google Drive user has probably revoked our authorization. + NSString *errorDescription = @"Arq Access to this Google Drive account was revoked"; + if (emailAddress != nil) { + errorDescription = [NSString stringWithFormat:@"Arq access to Google Drive account '%@' was revoked", emailAddress]; + } + SETNSERROR([GoogleDrive errorDomain], ERROR_ACCESS_REVOKED, @"%@", errorDescription); + return nil; + } + } + + SETNSERROR([GoogleDrive errorDomain], [conn responseCode], @"Google Drive refresh_token HTTP error %d", [conn responseCode]); + return nil; + } + + NSString *responseString = [[[NSString alloc] initWithData:response encoding:NSUTF8StringEncoding] autorelease]; + NSDictionary *responseJSON = [responseString JSONValue:error]; + if (responseJSON == nil) { + return nil; + } + NSString *accessToken = [responseJSON objectForKey:@"access_token"]; + + HSLogDebug(@"accessToken expires in %@", [responseJSON objectForKey:@"expires_in"]); + + return accessToken; +} +@end diff --git a/http/HTTP.h b/cocoastack/http/HTTP.h similarity index 96% rename from http/HTTP.h rename to cocoastack/http/HTTP.h index c5c9490..b874419 100644 --- a/http/HTTP.h +++ b/cocoastack/http/HTTP.h @@ -38,6 +38,7 @@ #define HTTP_FORBIDDEN (403) #define HTTP_BAD_REQUEST (400) #define HTTP_METHOD_NOT_ALLOWED (405) +#define HTTP_REQUEST_TIMEOUT (408) #define HTTP_CONFLICT (409) #define HTTP_REQUESTED_RANGE_NOT_SATISFIABLE (416) #define HTTP_LENGTH_REQUIRED (411) @@ -45,3 +46,4 @@ #define HTTP_MOVED_PERMANENTLY (301) #define HTTP_MOVED_TEMPORARILY (307) #define HTTP_SERVICE_NOT_AVAILABLE (503) +#define HTTP_VERSION_NOT_SUPPORTED (505) diff --git a/http/HTTPConnection.h b/cocoastack/http/HTTPConnection.h similarity index 86% rename from http/HTTPConnection.h rename to cocoastack/http/HTTPConnection.h index 8a7c7a5..9bc2e6f 100644 --- a/http/HTTPConnection.h +++ b/cocoastack/http/HTTPConnection.h @@ -31,24 +31,28 @@ */ -#import "InputStream.h" +@protocol HTTPConnection +- (NSString *)errorDomain; -@protocol HTTPConnection - (void)setRequestHeader:(NSString *)value forKey:(NSString *)key; - (void)setRequestHostHeader; - (void)setRequestContentDispositionHeader:(NSString *)downloadName; - (void)setRFC822DateRequestHeader; +- (void)setDate:(NSDate *)theDate; +- (NSDate *)date; - (NSString *)requestMethod; - (NSString *)requestPathInfo; - (NSString *)requestQueryString; - (NSArray *)requestHeaderKeys; - (NSString *)requestHeaderForKey:(NSString *)theKey; -- (BOOL)executeRequest:(NSError **)error; -- (BOOL)executeRequestWithBody:(NSData *)theBody error:(NSError **)error; +- (NSData *)executeRequest:(NSError **)error; +- (NSData *)executeRequestWithBody:(NSData *)theBody error:(NSError **)error; - (int)responseCode; +- (NSDictionary *)responseHeaders; - (NSString *)responseHeaderForKey:(NSString *)key; - (NSString *)responseContentType; - (NSString *)responseDownloadName; -- (id )newResponseBodyStream:(NSError **)error; +- (BOOL)errorOccurred; +- (NSTimeInterval)createTime; @end diff --git a/cocoastack/http/HTTPConnectionFactory.h b/cocoastack/http/HTTPConnectionFactory.h new file mode 100644 index 0000000..8cb352f --- /dev/null +++ b/cocoastack/http/HTTPConnectionFactory.h @@ -0,0 +1,24 @@ +// +// HTTPConnectionFactory.h +// Arq +// +// Created by Stefan Reitshamer on 3/15/12. +// Copyright 2012 Haystack Software. All rights reserved. +// + + +@protocol HTTPConnection; +@protocol DataTransferDelegate; + + +@interface HTTPConnectionFactory : NSObject { +// NSTimeInterval maxConnectionLifetime; +// NSLock *lock; +// NSMutableDictionary *connectionMapsByThreadId; +} + ++ (HTTPConnectionFactory *)theFactory; +- (id )newHTTPConnectionToURL:(NSURL *)theURL + method:(NSString *)theMethod + dataTransferDelegate:(id )theDelegate; +@end diff --git a/cocoastack/http/HTTPConnectionFactory.m b/cocoastack/http/HTTPConnectionFactory.m new file mode 100644 index 0000000..0f0e097 --- /dev/null +++ b/cocoastack/http/HTTPConnectionFactory.m @@ -0,0 +1,168 @@ +// +// HTTPConnectionFactory.m +// Arq +// +// Created by Stefan Reitshamer on 3/15/12. +// Copyright 2012 __MyCompanyName__. All rights reserved. +// + +#import "HTTPConnectionFactory.h" +//#if TARGET_OS_IPHONE > 0 +#import "URLConnection.h" +//#else +//#import "CFHTTPConnection.h" +//#endif + +//#define DEFAULT_MAX_HTTPCONNECTION_LIFETIME_SECONDS (20) +//#define CLEANUP_THREAD_SLEEP_SECONDS (5) +// +// +//@interface ConnectionMap : NSObject { +// NSMutableDictionary *connections; +//} +//- (id )newConnectionToURL:(NSURL *)theURL +// method:(NSString *)theMethod +// maxConnectionLifetime:(NSTimeInterval)theMaxConnectionLifetime +// dataTransferDelegate:(id )theDelegate; +//- (void)dropUnusableConnections:(NSTimeInterval)theMaxConnectionLifetime; +//@end +// +//@implementation ConnectionMap +//- (id)init { +// if (self = [super init]) { +// connections = [[NSMutableDictionary alloc] init]; +// } +// return self; +//} +//- (void)dealloc { +// [connections release]; +// [super dealloc]; +//} +//- (id )newConnectionToURL:(NSURL *)theURL +// method:(NSString *)theMethod +// maxConnectionLifetime:(NSTimeInterval)theMaxConnectionLifetime +// dataTransferDelegate:(id )theDataTransferDelegate { +// NSString *key = [NSString stringWithFormat:@"%@ %@://%@:%d", theMethod, [theURL scheme], [theURL host], [[theURL port] intValue]]; +// id conn = [connections objectForKey:key]; +// if (conn != nil) { +// if ([conn errorOccurred] || (([NSDate timeIntervalSinceReferenceDate] - [conn createTime]) > theMaxConnectionLifetime)) { +// [connections removeObjectForKey:key]; +// HSLogTrace(@"removing connection %p", conn); +// conn = nil; +// } else { +// HSLogTrace(@"found connection %p", conn); +// conn = [[URLConnection alloc] initWithURL:theURL method:theMethod dataTransferDelegate:theDataTransferDelegate]; +// [connections setObject:conn forKey:key]; +// } +// } +// if (conn == nil) { +// conn = [[URLConnection alloc] initWithURL:theURL method:theMethod dataTransferDelegate:theDataTransferDelegate]; +// HSLogTrace(@"saving new connection %p", conn); +// [connections setObject:conn forKey:key]; +// } +// return conn; +//} +// +//- (void)dropUnusableConnections:(NSTimeInterval)theMaxConnectionLifetime { +// NSMutableArray *keysToDrop = [NSMutableArray array]; +// for (NSString *key in [connections allKeys]) { +// id conn = [connections objectForKey:key]; +// if ([conn errorOccurred] || (([NSDate timeIntervalSinceReferenceDate] - [conn createTime]) > theMaxConnectionLifetime)) { // FIXME: Duplicate logic to newConnectionToURL: method +// [keysToDrop addObject:key]; +// } +// } +// if ([keysToDrop count] > 0) { +// HSLogTrace(@"dropping %@", keysToDrop); +// [connections removeObjectsForKeys:keysToDrop]; +// } +//} +//@end + +static HTTPConnectionFactory *theFactory = nil; + +@implementation HTTPConnectionFactory ++ (HTTPConnectionFactory *)theFactory { + if (theFactory == nil) { + theFactory = [[super allocWithZone:NULL] init]; + } + return theFactory; +} + +/* Singleton recipe: */ ++ (id)allocWithZone:(NSZone *)zone { + return [[HTTPConnectionFactory theFactory] retain]; +} +- (id)copyWithZone:(NSZone *)zone { + return self; +} +- (id)retain { + return self; +} +- (NSUInteger)retainCount { + return NSUIntegerMax; //denotes an object that cannot be released +} +- (oneway void)release { + //do nothing +} +- (id)autorelease { + return self; +} + +- (id)init { + if (self = [super init]) { +// lock = [[NSLock alloc] init]; +// [lock setName:@"HTTPConnectionFactory lock"]; +// connectionMapsByThreadId = [[NSMutableDictionary alloc] init]; +// maxConnectionLifetime = DEFAULT_MAX_HTTPCONNECTION_LIFETIME_SECONDS; +// [NSThread detachNewThreadSelector:@selector(dropUnusableConnections) toTarget:self withObject:nil]; + } + return self; +} +- (void)dealloc { +// [lock release]; +// [connectionMapsByThreadId release]; + [super dealloc]; +} + +- (id )newHTTPConnectionToURL:(NSURL *)theURL + method:(NSString *)theMethod + dataTransferDelegate:(id)theDataTransferDelegate { +// id ret = nil; +// void *pthreadPtr = pthread_self(); +//#ifdef __LP64__ +// NSNumber *threadID = [NSNumber numberWithUnsignedLongLong:(uint64_t)pthreadPtr]; +//#else +// NSNumber *threadID = [NSNumber numberWithUnsignedLong:(uint32_t)pthreadPtr]; +//#endif +// [lock lock]; +// ConnectionMap *connMap = [connectionMapsByThreadId objectForKey:threadID]; +// if (connMap == nil) { +// connMap = [[ConnectionMap alloc] init]; +// [connectionMapsByThreadId setObject:connMap forKey:threadID]; +// [connMap release]; +// } +// ret = [connMap newConnectionToURL:theURL method:theMethod maxConnectionLifetime:maxConnectionLifetime dataTransferDelegate:theDelegate]; +// [lock unlock]; +// return ret; + return [[URLConnection alloc] initWithURL:theURL method:theMethod dataTransferDelegate:theDataTransferDelegate]; +} + +//#pragma mark cleanup thread +//- (void)dropUnusableConnections { +// [self retain]; +// for (;;) { +// NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; +// [NSThread sleepForTimeInterval:CLEANUP_THREAD_SLEEP_SECONDS]; +// [lock lock]; +// for (ConnectionMap *connMap in [connectionMapsByThreadId allValues]) { +// @try { +// [connMap dropUnusableConnections:maxConnectionLifetime]; +// } @catch(NSException *e) { +// HSLogError(@"unexpected exception in HTTPConnectionFactory cleanup thread: %@", [e description]); +// } +// } +// [lock unlock]; +// [pool drain]; +// } +//} +@end diff --git a/cocoastack/http/HTTPInputStream.h b/cocoastack/http/HTTPInputStream.h new file mode 100644 index 0000000..9a071d8 --- /dev/null +++ b/cocoastack/http/HTTPInputStream.h @@ -0,0 +1,27 @@ +// +// HTTPInputStream.h +// +// Created by Stefan Reitshamer on 3/16/12. +// Copyright 2012 Haystack Software. All rights reserved. +// + + +@protocol HTTPConnection; +@class NetMonitor; +#import "HTTPThrottle.h" + + +@interface HTTPInputStream : NSObject { + id conn; + NSInputStream *inputStream; + HTTPThrottleType throttleType; + NSUInteger throttleKBPS; + NSLock *httpThrottleLock; + NSTimeInterval lastReceivedTime; + NSUInteger lastReceivedLength; + NSUInteger totalReceivedLength; + NetMonitor *netMonitor; +} +- (id)initWithHTTPConnection:(id )theConn data:(NSData *)theData; +- (void)setHTTPThrottle:(HTTPThrottle *)theHTTPThrottle; +@end diff --git a/http/CFHTTPInputStream.m b/cocoastack/http/HTTPInputStream.m similarity index 72% rename from http/CFHTTPInputStream.m rename to cocoastack/http/HTTPInputStream.m index 13710af..d9d0a20 100644 --- a/http/CFHTTPInputStream.m +++ b/cocoastack/http/HTTPInputStream.m @@ -1,41 +1,52 @@ // -// CFHTTPInputStream.m -// Arq +// HTTPInputStream.m // // Created by Stefan Reitshamer on 3/16/12. -// Copyright 2012 __MyCompanyName__. All rights reserved. +// Copyright 2012 Haystack Software. All rights reserved. // -#import "CFHTTPInputStream.h" -#import "HTTPConnectionDelegate.h" -#import "CFHTTPConnection.h" +#import "HTTPInputStream.h" +#import "HTTPConnection.h" #import "NetMonitor.h" +#import "HTTPThrottle.h" -@interface CFHTTPConnection (callback) -- (void)sentRequestBytes:(NSInteger)count; -@end -@implementation CFHTTPInputStream -- (id)initWithCFHTTPConnection:(CFHTTPConnection *)theConn data:(NSData *)theData httpConnectionDelegate:(id )theHTTPConnectionDelegate { +@implementation HTTPInputStream +- (id)initWithHTTPConnection:(id )theConn data:(NSData *)theData { if (self = [super init]) { conn = theConn; // Don't retain the connection. inputStream = [[NSInputStream inputStreamWithData:theData] retain]; - httpConnectionDelegate = theHTTPConnectionDelegate; + httpThrottleLock = [[NSLock alloc] init]; + [httpThrottleLock setName:@"HTTPThrottle lock"]; netMonitor = [[NetMonitor alloc] init]; + throttleType = HTTP_THROTTLE_TYPE_NONE; } return self; } - (void)dealloc { [inputStream release]; + [httpThrottleLock release]; [netMonitor release]; [super dealloc]; } +- (void)setHTTPThrottle:(HTTPThrottle *)theHTTPThrottle { + [httpThrottleLock lock]; + throttleType = [theHTTPThrottle throttleType]; + throttleKBPS = [theHTTPThrottle throttleKBPS]; + [httpThrottleLock unlock]; +} + - (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len { + [httpThrottleLock lock]; + HTTPThrottleType theThrottleType = throttleType; + NSUInteger theThrottleKBPS = throttleKBPS; + [httpThrottleLock unlock]; + NSTimeInterval currentTime = [NSDate timeIntervalSinceReferenceDate]; - if (throttleType == THROTTLE_FIXED && throttleKBPS != 0) { + if (theThrottleType == HTTP_THROTTLE_TYPE_FIXED && theThrottleKBPS != 0) { // Don't send more than 1/10th of the max bytes/sec: - NSUInteger maxLen = throttleKBPS * 100; + NSUInteger maxLen = theThrottleKBPS * 100; if (len > maxLen) { len = maxLen; } @@ -45,7 +56,7 @@ // For some reason Activity Monitor reports "Data sent/sec" at twice what we seem to be sending! // So we send half as much -- we divide by 500 instead of 1000 here: - NSTimeInterval throttledInterval = (double)lastReceivedLength / ((double)throttleKBPS * (double)500.0); + NSTimeInterval throttledInterval = (double)lastReceivedLength / ((double)theThrottleKBPS * (double)500.0); if (throttledInterval > interval) { [NSThread sleepForTimeInterval:(throttledInterval - interval)]; @@ -53,7 +64,7 @@ } } - if (throttleType == THROTTLE_AUTOMATIC) { + if (theThrottleType == HTTP_THROTTLE_TYPE_AUTOMATIC) { NSTimeInterval interval = currentTime - lastReceivedTime; if (lastReceivedLength > 0) { double myBPS = (double)lastReceivedLength / interval; @@ -74,19 +85,10 @@ NSInteger ret = [inputStream read:buffer maxLength:len]; if (ret >= 0) { - if ([httpConnectionDelegate respondsToSelector:@selector(httpConnection:sentBytes:throttleType:throttleKBPS:pauseRequested:abortRequested:)]) { - BOOL pauseRequested = NO; - BOOL abortRequested = NO; - [httpConnectionDelegate httpConnection:conn sentBytes:ret throttleType:&throttleType throttleKBPS:&throttleKBPS pauseRequested:&pauseRequested abortRequested:&abortRequested]; - if (pauseRequested || abortRequested) { - return -1; - } - } lastReceivedTime = currentTime; lastReceivedLength = ret; totalReceivedLength += ret; } - [conn sentRequestBytes:ret]; return ret; } diff --git a/cocoastack/http/HTTPThrottle.h b/cocoastack/http/HTTPThrottle.h new file mode 100644 index 0000000..f47aa1a --- /dev/null +++ b/cocoastack/http/HTTPThrottle.h @@ -0,0 +1,26 @@ +// +// HTTPThrottle.h +// Arq +// +// Created by Stefan Reitshamer on 6/5/13. +// Copyright (c) 2013 Stefan Reitshamer. All rights reserved. +// + +typedef enum { + HTTP_THROTTLE_TYPE_NONE = 0, + HTTP_THROTTLE_TYPE_AUTOMATIC = 1, + HTTP_THROTTLE_TYPE_FIXED = 2 +} HTTPThrottleType; + + +@interface HTTPThrottle : NSObject { + HTTPThrottleType throttleType; + NSUInteger throttleKBPS; +} +- (id)init; +- (id)initWithType:(HTTPThrottleType)theType kbps:(NSUInteger)theKBPS; + +- (HTTPThrottleType)throttleType; +- (NSUInteger)throttleKBPS; + +@end diff --git a/cocoastack/http/HTTPThrottle.m b/cocoastack/http/HTTPThrottle.m new file mode 100644 index 0000000..3aa40fd --- /dev/null +++ b/cocoastack/http/HTTPThrottle.m @@ -0,0 +1,33 @@ +// +// HTTPThrottle.m +// Arq +// +// Created by Stefan Reitshamer on 6/5/13. +// Copyright (c) 2013 Stefan Reitshamer. All rights reserved. +// + +#import "HTTPThrottle.h" + + +@implementation HTTPThrottle +- (id)init { + if (self = [super init]) { + throttleType = HTTP_THROTTLE_TYPE_NONE; + } + return self; +} +- (id)initWithType:(HTTPThrottleType)theType kbps:(NSUInteger)theKBPS { + if (self = [super init]) { + throttleType = theType; + throttleKBPS = theKBPS; + } + return self; +} + +- (HTTPThrottleType)throttleType { + return throttleType; +} +- (NSUInteger)throttleKBPS { + return throttleKBPS; +} +@end diff --git a/cocoastack/http/NSDictionary_HTTP.h b/cocoastack/http/NSDictionary_HTTP.h new file mode 100644 index 0000000..6e22067 --- /dev/null +++ b/cocoastack/http/NSDictionary_HTTP.h @@ -0,0 +1,14 @@ +// +// NSDictionary_HTTP.h +// Arq +// +// Created by Stefan Reitshamer on 7/4/11. +// Copyright 2011 Haystack Software. All rights reserved. +// + + + + +@interface NSDictionary (HTTP) +- (NSString *)wwwFormURLEncodedString; +@end diff --git a/cocoastack/http/NSDictionary_HTTP.m b/cocoastack/http/NSDictionary_HTTP.m new file mode 100644 index 0000000..1483535 --- /dev/null +++ b/cocoastack/http/NSDictionary_HTTP.m @@ -0,0 +1,36 @@ +// +// NSDictionary_HTTP.m +// Arq +// +// Created by Stefan Reitshamer on 7/4/11. +// Copyright 2011 Haystack Software. All rights reserved. +// + +#import "NSDictionary_HTTP.h" + + +@implementation NSDictionary (HTTP) +- (NSString *)wwwFormURLEncodedString { + NSMutableString *ret = [NSMutableString string]; + for (NSString *key in [self allKeys]) { + if ([ret length] > 0) { + [ret appendString:@"&"]; + } + if ([key isKindOfClass:[NSNumber class]]) { + key = [(NSNumber *)key stringValue]; + } + NSString *value = [self objectForKey:key]; + if ([value isKindOfClass:[NSNumber class]]) { + value = [(NSNumber *)value stringValue]; + } + NSString *encodedKey = (NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)key, NULL, CFSTR("?=&+"), kCFStringEncodingUTF8); + NSString *encodedValue = (NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)value, NULL, CFSTR("?=&+"), kCFStringEncodingUTF8); + [ret appendString:encodedKey]; + [ret appendString:@"="]; + [ret appendString:encodedValue]; + [encodedKey release]; + [encodedValue release]; + } + return ret; +} +@end diff --git a/http/RFC2616DateFormatter.h b/cocoastack/http/RFC2616DateFormatter.h similarity index 100% rename from http/RFC2616DateFormatter.h rename to cocoastack/http/RFC2616DateFormatter.h diff --git a/http/RFC2616DateFormatter.m b/cocoastack/http/RFC2616DateFormatter.m similarity index 86% rename from http/RFC2616DateFormatter.m rename to cocoastack/http/RFC2616DateFormatter.m index 66704db..8398c98 100644 --- a/http/RFC2616DateFormatter.m +++ b/cocoastack/http/RFC2616DateFormatter.m @@ -37,7 +37,7 @@ - (id)init { if (self = [super init]) { formatter = [[NSDateFormatter alloc] init]; - [formatter setDateFormat:@"EEE, dd MMM yyyy HH:mm:ss z"]; + [formatter setDateFormat:@"EEE, dd MMM yyyy HH:mm:ss"]; [formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; usLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]; if (usLocale != nil) { @@ -55,6 +55,8 @@ } - (NSString *)rfc2616StringFromDate:(NSDate *)date { //FIXME: If US locale isn't available, put the English words into the date yourself, according to http://www.ietf.org/rfc/rfc2616.txt - return [formatter stringFromDate:date]; + + // We append " GMT" here instead of using a "z" in the format string because on 10.9 the "z" produces "GMT", but on 10.7 it produces "GMT+00:00" which makes Google Cloud Storage return a "MalformedHeaderValue" error. + return [[formatter stringFromDate:date] stringByAppendingString:@" GMT"]; } @end diff --git a/cocoastack/http/URLConnection.h b/cocoastack/http/URLConnection.h new file mode 100644 index 0000000..2531d8e --- /dev/null +++ b/cocoastack/http/URLConnection.h @@ -0,0 +1,39 @@ +// +// URLConnection.h +// Arq +// +// Created by Stefan Reitshamer on 5/3/11. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + + +#import "HTTPConnection.h" + +@class RFC2616DateFormatter; +@protocol DataTransferDelegate; +@class NetMonitor; +@class HTTPInputStream; + + +@interface URLConnection : NSObject { + NSString *method; + id delegate; + NSMutableURLRequest *mutableURLRequest; + NSURLConnection *urlConnection; + NSHTTPURLResponse *httpURLResponse; + RFC2616DateFormatter *dateFormatter; + unsigned long long totalSent; + + NSMutableData *responseData; + NSUInteger responseOffset; + BOOL errorOccurred; + NSError *_error; + NSTimeInterval createTime; + NSDate *date; + NetMonitor *netMonitor; + HTTPInputStream *httpInputStream; +} ++ (NSString *)errorDomain; + +- (id)initWithURL:(NSURL *)theURL method:(NSString *)theMethod dataTransferDelegate:(id )theDelegate; +@end diff --git a/cocoastack/http/URLConnection.m b/cocoastack/http/URLConnection.m new file mode 100644 index 0000000..37c8b5a --- /dev/null +++ b/cocoastack/http/URLConnection.m @@ -0,0 +1,339 @@ +// +// URLConnection.m +// Arq +// +// Created by Stefan Reitshamer on 5/3/11. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import "URLConnection.h" +#import "RFC2616DateFormatter.h" +#import "InputStream.h" +#import "NSData-InputStream.h" +#import "ChunkedInputStream.h" +#import "SetNSError.h" +#import "DataInputStream.h" +#import "BufferedInputStream.h" +#import "InputStreams.h" +#import "NSErrorCodes.h" +#import "NSError_extra.h" +#import "Streams.h" +#import "DataTransferDelegate.h" +#import "NetMonitor.h" +#import "RegexKitLite.h" +#import "HTTPInputStream.h" + + +static NSString *RUN_LOOP_MODE = @"HTTPConnectionRunLoopMode"; +#define DEFAULT_TIMEOUT_SECONDS (30) + + +@implementation URLConnection ++ (NSString *)errorDomain { + return @"URLConnectionErrorDomain"; +} + +- (id)initWithURL:(NSURL *)theURL method:(NSString *)theMethod dataTransferDelegate:(id)theDelegate { + if (self = [super init]) { + // Don't retain the delegate. + delegate = theDelegate; + method = [theMethod retain]; + mutableURLRequest = [[NSMutableURLRequest alloc] initWithURL:theURL]; + [mutableURLRequest setHTTPMethod:theMethod]; + + dateFormatter = [[RFC2616DateFormatter alloc] init]; + HSLogTrace(@"%@ %@", theMethod, theURL); + responseData = [[NSMutableData alloc] init]; + createTime = [NSDate timeIntervalSinceReferenceDate]; + netMonitor = [[NetMonitor alloc] init]; + } + return self; +} +- (void)dealloc { + [method release]; + [urlConnection unscheduleFromRunLoop:[NSRunLoop currentRunLoop] forMode:RUN_LOOP_MODE]; + [mutableURLRequest release]; + [urlConnection release]; + [httpURLResponse release]; + [dateFormatter release]; + [responseData release]; + [_error release]; + [date release]; + [netMonitor release]; + [httpInputStream release]; + [super dealloc]; +} + +- (NSString *)errorDomain { + return @"HTTPConnectionErrorDomain"; +} + +- (void)setRequestHeader:(NSString *)value forKey:(NSString *)key { + HSLogTrace(@"request header %@ = %@", key, value); + [mutableURLRequest setValue:value forHTTPHeaderField:key]; +} +- (void)setRequestHostHeader { + [self setRequestHeader:[[mutableURLRequest URL] host] forKey:@"Host"]; +} +- (void)setRequestContentDispositionHeader:(NSString *)downloadName { + if (downloadName != nil) { + NSString *encodedFilename = [NSString stringWithFormat:@"\"%@\"", [downloadName stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\\\""]]; + encodedFilename = [encodedFilename stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"]; + NSString *contentDisposition = [NSString stringWithFormat:@"attachment;filename=%@", encodedFilename]; + [self setRequestHeader:contentDisposition forKey:@"Content-Disposition"]; + } +} +- (void)setRFC822DateRequestHeader { + [self setRequestHeader:[dateFormatter rfc2616StringFromDate:[NSDate date]] forKey:@"Date"]; +} +- (void)setDate:(NSDate *)theDate { + [theDate retain]; + [date release]; + date = theDate; +} +- (NSDate *)date { + return date; +} +- (NSString *)requestMethod { + return [mutableURLRequest HTTPMethod]; +} +- (NSString *)requestPathInfo { + NSString *urlDescription = [[mutableURLRequest URL] description]; + NSRange rangeBeforeQueryString = [urlDescription rangeOfRegex:@"^([^?]+)"]; + NSString *stringBeforeQueryString = [urlDescription substringWithRange:rangeBeforeQueryString]; + NSString *path = [[mutableURLRequest URL] path]; + if ([stringBeforeQueryString hasSuffix:@"/"] && ![path hasSuffix:@"/"]) { + // NSURL's path method strips trailing slashes. Add it back in. + path = [path stringByAppendingString:@"/"]; + } + return [path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; +} +- (NSString *)requestQueryString { + return [[mutableURLRequest URL] query]; +} +- (NSArray *)requestHeaderKeys { + return [[mutableURLRequest allHTTPHeaderFields] allKeys]; +} +- (NSString *)requestHeaderForKey:(NSString *)theKey { + return [[mutableURLRequest allHTTPHeaderFields] objectForKey:theKey]; +} +- (NSData *)executeRequest:(NSError **)error { + return [self executeRequestWithBody:nil error:error]; +} +- (NSData *)executeRequestWithBody:(NSData *)theBody error:(NSError **)error { + if ([theBody length] > 0) { + httpInputStream = [[HTTPInputStream alloc] initWithHTTPConnection:self data:theBody]; + [mutableURLRequest setHTTPBodyStream:(NSInputStream *)httpInputStream]; + } else if (theBody != nil) { + // For 0-byte body, HTTPInputStream seems to hang, so just give it an empty NSData: + [mutableURLRequest setHTTPBody:theBody]; + } + totalSent = 0; + [responseData setLength:0]; + responseOffset = 0; + urlConnection = [[NSURLConnection alloc] initWithRequest:mutableURLRequest delegate:self startImmediately:NO]; + [urlConnection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:RUN_LOOP_MODE]; + [urlConnection start]; + + if (theBody != nil) { + HSLogDebug(@"NSURLConnection started with request body (%ld bytes)", (unsigned long)[theBody length]); + } else { + HSLogDebug(@"NSURLConnection started with no request body"); + } + HSLogTrace(@"%@", [mutableURLRequest allHTTPHeaderFields]); + + [[NSUserDefaults standardUserDefaults] synchronize]; + NSTimeInterval timeoutInterval = (NSTimeInterval)[[NSUserDefaults standardUserDefaults] doubleForKey:@"HTTPTimeoutSeconds"]; + if (timeoutInterval == 0) { + timeoutInterval = DEFAULT_TIMEOUT_SECONDS; + } +// HSLogDebug(@"HTTPTimeoutSeconds=%0.3f", timeoutInterval); + + // Loop to read in the whole damn response body because the streaming approach doesn't work reliably with Apple's stupid URL loading system. + while (urlConnection != nil) { + NSTimeInterval runToInterval = [NSDate timeIntervalSinceReferenceDate] + timeoutInterval; + + [[NSRunLoop currentRunLoop] runMode:RUN_LOOP_MODE beforeDate:[NSDate dateWithTimeIntervalSinceReferenceDate:runToInterval]]; + + if (urlConnection != nil) { + NSTimeInterval current = [NSDate timeIntervalSinceReferenceDate]; + // HSLogDebug(@"elapsed: %0.3f seconds", (current - runToInterval + timeoutInterval)); //FIXME: remove this + if ((current - runToInterval) > 0) { + HSLogWarn(@"exceeded timeout of %0.3f seconds during %@ %@", timeoutInterval, method, [mutableURLRequest URL]); + _error = [[NSError errorWithDomain:[self errorDomain] code:ERROR_TIMEOUT description:[NSString stringWithFormat:@"timeout during %@ %@", method, [mutableURLRequest URL]]] retain]; + errorOccurred = YES; + [urlConnection cancel]; + [urlConnection release]; + urlConnection = nil; + } + } + } + if (errorOccurred) { + [delegate dataTransferDidFail]; + if (error != NULL) { + *error = [[_error retain] autorelease]; + } + return nil; + } + + NSData *ret = nil; + if ([method isEqualToString:@"HEAD"]) { + HSLogTrace(@"%@: empty response body", self); + ret = [NSData data]; + } else { + NSAssert(httpURLResponse != nil, @"httpURLResponse can't be nil"); + NSString *contentLength = [self responseHeaderForKey:@"Content-Length"]; + NSString *transferEncoding = [self responseHeaderForKey:@"Transfer-Encoding"]; + HSLogDebug(@"response: status = %d, Content-Length = %@, Transfer-Encoding = %@", [self responseCode], contentLength, transferEncoding); + if (transferEncoding != nil && ![transferEncoding isEqualToString:@"Identity"]) { + if ([[transferEncoding lowercaseString] isEqualToString:@"chunked"]) { + HSLogTrace(@"chunked response body"); + id dis = [[[DataInputStream alloc] initWithData:responseData description:@"http response"] autorelease]; + BufferedInputStream *bis = [[[BufferedInputStream alloc] initWithUnderlyingStream:dis] autorelease]; + ChunkedInputStream *cis = [[[ChunkedInputStream alloc] initWithUnderlyingStream:bis] autorelease]; + ret = [cis slurp:error]; + } else { + SETNSERROR(@"StreamErrorDomain", -1, @"unknown Transfer-Encoding '%@'", transferEncoding); + return nil; + } + } else { + /* + * FIXME: handle multipart/byteranges media type. + * See rfc2616 section 4.4 ("message length"). + */ + HSLogDebug(@"response body with no Transfer-Encoding; responseData is %ld bytes", (unsigned long)[responseData length]); + ret = responseData; + } + + // If the response had "Content-Encoding: gzip" header, then NSURLConnection gunzipped it for us already and the responseData length won't match the Content-Length! + if (contentLength != nil && [contentLength integerValue] != [responseData length] && ![[self responseHeaderForKey:@"Content-Encoding"] isEqualToString:@"gzip"]) { + NSString *errorMessage = [NSString stringWithFormat:@"Actual response length %ld does not match Content-Length %@ for %@", (unsigned long)[responseData length], contentLength, [mutableURLRequest URL]]; + HSLogError(@"%@", errorMessage); + if (global_hslog_level >= HSLOG_LEVEL_DEBUG) { + NSDictionary *headers = [self responseHeaders]; + for (NSString *key in [headers allKeys]) { + HSLogDebug(@"response header: %@ = %@", key, [headers objectForKey:key]); + } + NSString *responseString = [[[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding] autorelease]; + HSLogDebug(@"response string: %@", responseString); + } + SETNSERROR([URLConnection errorDomain], -1, @"%@", errorMessage); + return nil; + } + } + NSAssert(ret != nil, @"ret may not be nil"); + return ret; +} +- (int)responseCode { + return (int)[httpURLResponse statusCode]; +} +- (NSDictionary *)responseHeaders { + return [httpURLResponse allHeaderFields]; +} +- (NSString *)responseHeaderForKey:(NSString *)key { + return [[httpURLResponse allHeaderFields] objectForKey:key]; +} +- (NSString *)responseContentType { + return [self responseHeaderForKey:@"Content-Type"]; +} +- (NSString *)responseDownloadName { + NSString *downloadName = nil; + NSString *contentDisposition = [self responseHeaderForKey:@"Content-Disposition"]; + if (contentDisposition != nil) { + NSRange filenameRange = [contentDisposition rangeOfRegex:@"attachment;filename=(.+)" capture:1]; + if (filenameRange.location != NSNotFound) { + downloadName = [contentDisposition substringWithRange:filenameRange]; + } + } + return downloadName; +} + +- (BOOL)errorOccurred { + return errorOccurred; +} +- (NSTimeInterval)createTime { + return createTime; +} + + +#pragma mark NSURLConnection delegate +- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace { + return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]; +} + +- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { +// if ([delegate httpConnectionAcceptsAnyHTTPSCertificate]) { +// [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge]; +// } else { + [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge]; +// } +} +- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { + if ([response isKindOfClass:[NSHTTPURLResponse class]]) { + [httpURLResponse release]; + httpURLResponse = (NSHTTPURLResponse *)[response retain]; + // Docs state "Each time the delegate receives the connection:didReceiveResponse: message, it should reset any progress indication and discard all previously received data.". +// HSLogDebug(@"didReceiveResponse; resetting responseData"); + [responseData setLength:0]; + responseOffset = 0; + } +} +- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)myError { + HSLogDebug(@"connection didFailWithError: %@", myError); + errorOccurred = YES; + [_error release]; + _error = [myError retain]; + [urlConnection release]; + urlConnection = nil; +} +- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { + if ([data length] > 0) { + HSLogTrace(@"received %lu bytes", (unsigned long)[data length]); + [responseData appendData:data]; + HTTPThrottle *httpThrottle = nil; + if ([delegate respondsToSelector:@selector(httpConnectionDidDownloadBytes:httpThrottle:error:)]) { + if (![delegate dataTransferDidDownloadBytes:[data length] httpThrottle:&httpThrottle error:&_error]) { + [_error retain]; + errorOccurred = YES; + [urlConnection cancel]; + [urlConnection release]; + urlConnection = nil; + } else { + [httpInputStream setHTTPThrottle:httpThrottle]; + } + } + } +} +- (void)connection:(NSURLConnection *)connection didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite { + HTTPThrottle *httpThrottle = nil; + if ([delegate respondsToSelector:@selector(dataTransferDidUploadBytes:httpThrottle:error:)]) { + if (![delegate dataTransferDidUploadBytes:bytesWritten httpThrottle:&httpThrottle error:&_error]) { + [_error retain]; + errorOccurred = YES; + [urlConnection cancel]; + [urlConnection release]; + urlConnection = nil; + return; + } else { + [httpInputStream setHTTPThrottle:httpThrottle]; + } + } +} +- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse { + return nil; +} +- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse { + return request; +} +- (void)connectionDidFinishLoading:(NSURLConnection *)connection { +// HSLogDebug(@"connectionDidFinishLoading"); + [urlConnection release]; + urlConnection = nil; +} + + +#pragma mark NSObject +- (NSString *)description { + return [NSString stringWithFormat:@"", method, [mutableURLRequest URL]]; +} +@end diff --git a/io/BooleanIO.h b/cocoastack/io/BooleanIO.h similarity index 100% rename from io/BooleanIO.h rename to cocoastack/io/BooleanIO.h diff --git a/io/BooleanIO.m b/cocoastack/io/BooleanIO.m similarity index 100% rename from io/BooleanIO.m rename to cocoastack/io/BooleanIO.m diff --git a/io/BufferedInputStream.h b/cocoastack/io/BufferedInputStream.h similarity index 98% rename from io/BufferedInputStream.h rename to cocoastack/io/BufferedInputStream.h index 5110be0..d5f85ea 100644 --- a/io/BufferedInputStream.h +++ b/cocoastack/io/BufferedInputStream.h @@ -42,6 +42,7 @@ } + (NSString *)errorDomain; - (id)initWithUnderlyingStream:(id )theUnderlyingStream; +- (int)readByte:(NSError **)error; - (NSData *)readExactly:(NSUInteger)exactLength error:(NSError **)error; - (BOOL)readExactly:(NSUInteger)exactLength into:(unsigned char *)buf error:(NSError **)error; - (NSString *)readLineWithCRLFWithMaxLength:(NSUInteger)maxLength error:(NSError **)error; diff --git a/io/BufferedInputStream.m b/cocoastack/io/BufferedInputStream.m similarity index 87% rename from io/BufferedInputStream.m rename to cocoastack/io/BufferedInputStream.m index 74b24bc..359e1e2 100644 --- a/io/BufferedInputStream.m +++ b/cocoastack/io/BufferedInputStream.m @@ -32,9 +32,9 @@ #import "BufferedInputStream.h" #import "InputStream.h" -#import "NSErrorCodes.h" + #import "InputStreams.h" -#import "SetNSError.h" + #define MY_BUF_SIZE (4096) @@ -56,6 +56,23 @@ free(buf); [super dealloc]; } +- (int)readByte:(NSError **)error { + if ((len - pos) == 0) { + NSInteger myRet = [underlyingStream read:buf bufferLength:MY_BUF_SIZE error:error]; + if (myRet < 0) { + return -1; + } + pos = 0; + len = myRet; + } + if ((len - pos) == 0) { + SETNSERROR([BufferedInputStream errorDomain], ERROR_EOF, @"%@ EOF", self); + return -1; + } + int ret = buf[pos++]; + totalBytesReceived++; + return ret; +} - (NSData *)readExactly:(NSUInteger)exactLength error:(NSError **)error { NSMutableData *data = [NSMutableData dataWithLength:exactLength]; unsigned char *dataBuf = [data mutableBytes]; @@ -66,7 +83,8 @@ } - (BOOL)readExactly:(NSUInteger)exactLength into:(unsigned char *)outBuf error:(NSError **)error { if (exactLength > 2147483648) { - SETNSERROR(@"InputStreamErrorDomain", -1, @"absurd length %lu requested", exactLength); + NSString *err = [NSString stringWithFormat:@"absurd length %lu requested", (unsigned long)exactLength]; + SETNSERROR(@"InputStreamErrorDomain", -1, @"%@", err); return NO; } NSUInteger received = 0; @@ -76,7 +94,7 @@ return NO; } if (ret == 0) { - SETNSERROR([BufferedInputStream errorDomain], ERROR_EOF, @"%@ EOF after %lu of %lu bytes received", self, received, exactLength); + SETNSERROR([BufferedInputStream errorDomain], ERROR_EOF, @"%@ EOF after %lu of %lu bytes received", self, (unsigned long)received, (unsigned long)exactLength); return NO; } received += ret; @@ -88,7 +106,7 @@ NSUInteger received = 0; for (;;) { if (received > maxLength) { - SETNSERROR(@"InputStreamErrorDomain", -1, @"exceeded maxLength %lu before finding CRLF", maxLength); + SETNSERROR(@"InputStreamErrorDomain", -1, @"exceeded maxLength %lu before finding CRLF", (unsigned long)maxLength); free(lineBuf); return nil; } diff --git a/io/BufferedOutputStream.h b/cocoastack/io/BufferedOutputStream.h similarity index 93% rename from io/BufferedOutputStream.h rename to cocoastack/io/BufferedOutputStream.h index 4c9ad6b..4e9148a 100644 --- a/io/BufferedOutputStream.h +++ b/cocoastack/io/BufferedOutputStream.h @@ -3,7 +3,7 @@ // iPhotoSync // // Created by Stefan Reitshamer on 8/25/10. -// Copyright 2010 __MyCompanyName__. All rights reserved. +// Copyright 2010 Haystack Software. All rights reserved. // diff --git a/io/BufferedOutputStream.m b/cocoastack/io/BufferedOutputStream.m similarity index 97% rename from io/BufferedOutputStream.m rename to cocoastack/io/BufferedOutputStream.m index a1f0e92..aac5860 100644 --- a/io/BufferedOutputStream.m +++ b/cocoastack/io/BufferedOutputStream.m @@ -3,12 +3,12 @@ // iPhotoSync // // Created by Stefan Reitshamer on 8/25/10. -// Copyright 2010 __MyCompanyName__. All rights reserved. +// Copyright 2010 Haystack Software. All rights reserved. // #import "BufferedOutputStream.h" -#import "NSErrorCodes.h" -#import "SetNSError.h" + + #import "DataOutputStream.h" #import "FDOutputStream.h" #import "FileOutputStream.h" diff --git a/io/ChunkedInputStream.h b/cocoastack/io/ChunkedInputStream.h similarity index 100% rename from io/ChunkedInputStream.h rename to cocoastack/io/ChunkedInputStream.h diff --git a/io/ChunkedInputStream.m b/cocoastack/io/ChunkedInputStream.m similarity index 97% rename from io/ChunkedInputStream.m rename to cocoastack/io/ChunkedInputStream.m index 77ca478..e7b30cd 100644 --- a/io/ChunkedInputStream.m +++ b/cocoastack/io/ChunkedInputStream.m @@ -31,9 +31,9 @@ */ #import "ChunkedInputStream.h" -#import "SetNSError.h" + #import "InputStreams.h" -#import "NSErrorCodes.h" + #import "BufferedInputStream.h" #define MAX_CHUNK_LENGTH_LINE_LENGTH (1024) @@ -65,7 +65,7 @@ return -1; } chunkLength = (NSUInteger)scanned; - HSLogTrace(@"chunk length = %lu", chunkLength); + HSLogTrace(@"chunk length = %lu", (unsigned long)chunkLength); } if (chunkLength == 0) { SETNSERROR(@"StreamsErrorDomain", ERROR_EOF, @"EOF (zero chunk length)"); diff --git a/io/DataIO.h b/cocoastack/io/DataIO.h similarity index 100% rename from io/DataIO.h rename to cocoastack/io/DataIO.h diff --git a/io/DataIO.m b/cocoastack/io/DataIO.m similarity index 97% rename from io/DataIO.m rename to cocoastack/io/DataIO.m index 2dfd4a7..a6adb52 100644 --- a/io/DataIO.m +++ b/cocoastack/io/DataIO.m @@ -50,7 +50,7 @@ if (![IntegerIO readUInt64:&length from:is error:error]) { return NO; } - *value = [is readExactly:length error:error]; + *value = [is readExactly:(NSUInteger)length error:error]; return *value != nil; } diff --git a/io/DataInputStream.h b/cocoastack/io/DataInputStream.h similarity index 87% rename from io/DataInputStream.h rename to cocoastack/io/DataInputStream.h index 8aba0b3..894f144 100644 --- a/io/DataInputStream.h +++ b/cocoastack/io/DataInputStream.h @@ -37,8 +37,9 @@ @interface DataInputStream : NSObject { NSData *data; + NSString *description; NSUInteger pos; } -- (id)initWithData:(NSData *)theData; -- (id)initWithData:(NSData *)theData offset:(unsigned long long)theOffset length:(unsigned long long)theLength; +- (id)initWithData:(NSData *)theData description:(NSString *)theDescription; +- (id)initWithData:(NSData *)theData description:(NSString *)theDescription offset:(unsigned long long)theOffset length:(unsigned long long)theLength; @end diff --git a/io/DataInputStream.m b/cocoastack/io/DataInputStream.m similarity index 83% rename from io/DataInputStream.m rename to cocoastack/io/DataInputStream.m index 9e39ade..d2544a6 100644 --- a/io/DataInputStream.m +++ b/cocoastack/io/DataInputStream.m @@ -33,24 +33,27 @@ #import "DataInputStream.h" -#import "SetNSError.h" -#import "NSErrorCodes.h" + + @implementation DataInputStream -- (id)initWithData:(NSData *)theData { +- (id)initWithData:(NSData *)theData description:(NSString *)theDescription { if (self = [super init]) { data = [theData retain]; + description = [theDescription retain]; } return self; } -- (id)initWithData:(NSData *)theData offset:(unsigned long long)theOffset length:(unsigned long long)theLength { +- (id)initWithData:(NSData *)theData description:(NSString *)theDescription offset:(unsigned long long)theOffset length:(unsigned long long)theLength { if (self = [super init]) { data = [theData subdataWithRange:NSMakeRange((NSUInteger)theOffset, (NSUInteger)theLength)]; + description = [theDescription retain]; } return self; } - (void)dealloc { [data release]; + [description release]; [super dealloc]; } @@ -78,4 +81,10 @@ } return ret; } + + +#pragma mark NSObject +- (NSString *)description { + return [NSString stringWithFormat:@"", (unsigned long)[data length], description]; +} @end diff --git a/io/DataOutputStream.h b/cocoastack/io/DataOutputStream.h similarity index 100% rename from io/DataOutputStream.h rename to cocoastack/io/DataOutputStream.h diff --git a/io/DataOutputStream.m b/cocoastack/io/DataOutputStream.m similarity index 100% rename from io/DataOutputStream.m rename to cocoastack/io/DataOutputStream.m diff --git a/cocoastack/io/DataTransferDelegate.h b/cocoastack/io/DataTransferDelegate.h new file mode 100644 index 0000000..12389ab --- /dev/null +++ b/cocoastack/io/DataTransferDelegate.h @@ -0,0 +1,16 @@ +// +// DataTransferDelegate.h +// Arq +// +// Created by Stefan Reitshamer on 3/19/14. +// Copyright (c) 2014 Stefan Reitshamer. All rights reserved. +// + +@class HTTPThrottle; + + +@protocol DataTransferDelegate +- (BOOL)dataTransferDidUploadBytes:(uint64_t)count httpThrottle:(HTTPThrottle **)theHTTPThrottle error:(NSError **)error; +- (BOOL)dataTransferDidDownloadBytes:(uint64_t)count httpThrottle:(HTTPThrottle **)theHTTPThrottle error:(NSError **)error; +- (void)dataTransferDidFail; +@end diff --git a/io/DateIO.h b/cocoastack/io/DateIO.h similarity index 100% rename from io/DateIO.h rename to cocoastack/io/DateIO.h diff --git a/io/DateIO.m b/cocoastack/io/DateIO.m similarity index 100% rename from io/DateIO.m rename to cocoastack/io/DateIO.m diff --git a/io/DoubleIO.h b/cocoastack/io/DoubleIO.h similarity index 100% rename from io/DoubleIO.h rename to cocoastack/io/DoubleIO.h diff --git a/io/DoubleIO.m b/cocoastack/io/DoubleIO.m similarity index 99% rename from io/DoubleIO.m rename to cocoastack/io/DoubleIO.m index 2413c9c..048bf0c 100644 --- a/io/DoubleIO.m +++ b/cocoastack/io/DoubleIO.m @@ -32,7 +32,7 @@ #import "DoubleIO.h" #import "StringIO.h" -#import "SetNSError.h" + #import "BufferedInputStream.h" #import "BufferedOutputStream.h" diff --git a/io/FDInputStream.h b/cocoastack/io/FDInputStream.h similarity index 100% rename from io/FDInputStream.h rename to cocoastack/io/FDInputStream.h diff --git a/io/FDInputStream.m b/cocoastack/io/FDInputStream.m similarity index 95% rename from io/FDInputStream.m rename to cocoastack/io/FDInputStream.m index 2f4937a..2dc6e3e 100644 --- a/io/FDInputStream.m +++ b/cocoastack/io/FDInputStream.m @@ -33,9 +33,9 @@ #import "FDInputStream.h" -#import "SetNSError.h" + #import "InputStreams.h" -#import "NSErrorCodes.h" + #define MY_BUF_SIZE (4096) #define DEFAULT_READ_TIMEOUT_SECONDS (60) @@ -112,7 +112,7 @@ select_again: goto select_again; } else if (ret == -1) { int errnum = errno; - HSLogError(@"select(%d) error %d: %s", fd, errnum, strerror(errnum)); + HSLogError(@"select on %@ (fd=%d) error %d: %s", label, fd, errnum, strerror(errnum)); SETNSERROR(@"UnixErrorDomain", errnum, @"select: %s", strerror(errnum)); return -1; } else if (ret == 0) { @@ -126,8 +126,8 @@ read_again: goto read_again; } else if (ret == -1) { int errnum = errno; - HSLogError(@"read(%d) error %d: %s", fd, errnum, strerror(errnum)); - SETNSERROR(@"UnixErrorDomain", errnum, @"failed to read from file descriptor %d: %s", fd, strerror(errnum)); + HSLogError(@"read from %@ (fd=%d) error %d: %s", label, fd, errnum, strerror(errnum)); + SETNSERROR(@"UnixErrorDomain", errnum, @"failed to read from %@: %s", label, strerror(errnum)); return -1; } if (ret > 0) { diff --git a/io/FDOutputStream.h b/cocoastack/io/FDOutputStream.h similarity index 100% rename from io/FDOutputStream.h rename to cocoastack/io/FDOutputStream.h diff --git a/io/FDOutputStream.m b/cocoastack/io/FDOutputStream.m similarity index 99% rename from io/FDOutputStream.m rename to cocoastack/io/FDOutputStream.m index 33415fd..9f1d51d 100644 --- a/io/FDOutputStream.m +++ b/cocoastack/io/FDOutputStream.m @@ -33,7 +33,7 @@ #import "FDOutputStream.h" -#import "SetNSError.h" + #import "NSError_extra.h" @implementation FDOutputStream diff --git a/io/FileInputStream.h b/cocoastack/io/FileInputStream.h similarity index 100% rename from io/FileInputStream.h rename to cocoastack/io/FileInputStream.h diff --git a/io/FileInputStream.m b/cocoastack/io/FileInputStream.m similarity index 98% rename from io/FileInputStream.m rename to cocoastack/io/FileInputStream.m index cc69f99..a91b153 100644 --- a/io/FileInputStream.m +++ b/cocoastack/io/FileInputStream.m @@ -33,9 +33,9 @@ #import "FileInputStream.h" -#import "SetNSError.h" + #import "InputStreams.h" -#import "NSErrorCodes.h" + #define MY_BUF_SIZE (4096) diff --git a/io/FileOutputStream.h b/cocoastack/io/FileOutputStream.h similarity index 99% rename from io/FileOutputStream.h rename to cocoastack/io/FileOutputStream.h index ee63eb1..de40788 100644 --- a/io/FileOutputStream.h +++ b/cocoastack/io/FileOutputStream.h @@ -48,5 +48,4 @@ - (NSString *)path; - (void)close; - (BOOL)seekTo:(unsigned long long)offset error:(NSError **)error; -- (NSString *)path; @end diff --git a/io/FileOutputStream.m b/cocoastack/io/FileOutputStream.m similarity index 99% rename from io/FileOutputStream.m rename to cocoastack/io/FileOutputStream.m index 90d2995..6566b0b 100644 --- a/io/FileOutputStream.m +++ b/cocoastack/io/FileOutputStream.m @@ -34,7 +34,7 @@ #import "FileOutputStream.h" -#import "SetNSError.h" + @interface FileOutputStream (internal) - (BOOL)open:(NSError **)error; diff --git a/io/GunzipInputStream.h b/cocoastack/io/GunzipInputStream.h similarity index 100% rename from io/GunzipInputStream.h rename to cocoastack/io/GunzipInputStream.h diff --git a/io/GunzipInputStream.m b/cocoastack/io/GunzipInputStream.m similarity index 95% rename from io/GunzipInputStream.m rename to cocoastack/io/GunzipInputStream.m index c1f96d2..7bec880 100644 --- a/io/GunzipInputStream.m +++ b/cocoastack/io/GunzipInputStream.m @@ -34,8 +34,8 @@ #import "GunzipInputStream.h" #import "InputStreams.h" -#import "SetNSError.h" -#import "NSErrorCodes.h" + + #define MY_BUF_SIZE (4096) @@ -83,14 +83,14 @@ if (!wasInitialized || stream.avail_out > 0) { // There weren't inflated bytes remaining last time, so read in some more before trying to inflate some more. stream.next_in = inBuf; // zlib changes next_in pointer so we have to reset it every time. - stream.avail_in = [underlyingStream read:stream.next_in bufferLength:MY_BUF_SIZE error:error]; + stream.avail_in = (unsigned int)[underlyingStream read:stream.next_in bufferLength:MY_BUF_SIZE error:error]; if (stream.avail_in <= 0) { return stream.avail_in; } } stream.next_out = theBuf; - stream.avail_out = theBufferLength; + stream.avail_out = (unsigned int)theBufferLength; int ret = inflate(&stream, flush); switch (ret) { case Z_NEED_DICT: diff --git a/io/InputStream.h b/cocoastack/io/InputStream.h similarity index 100% rename from io/InputStream.h rename to cocoastack/io/InputStream.h diff --git a/io/InputStreams.h b/cocoastack/io/InputStreams.h similarity index 100% rename from io/InputStreams.h rename to cocoastack/io/InputStreams.h diff --git a/io/InputStreams.m b/cocoastack/io/InputStreams.m similarity index 97% rename from io/InputStreams.m rename to cocoastack/io/InputStreams.m index b852355..7af9d68 100644 --- a/io/InputStreams.m +++ b/cocoastack/io/InputStreams.m @@ -33,8 +33,8 @@ #import "InputStreams.h" -#import "SetNSError.h" -#import "NSErrorCodes.h" + + #import "BufferedInputStream.h" #define MY_BUF_SIZE (4096) diff --git a/io/IntegerIO.h b/cocoastack/io/IntegerIO.h similarity index 100% rename from io/IntegerIO.h rename to cocoastack/io/IntegerIO.h diff --git a/io/IntegerIO.m b/cocoastack/io/IntegerIO.m similarity index 100% rename from io/IntegerIO.m rename to cocoastack/io/IntegerIO.m diff --git a/io/NSData-InputStream.h b/cocoastack/io/NSData-InputStream.h similarity index 90% rename from io/NSData-InputStream.h rename to cocoastack/io/NSData-InputStream.h index f464d7f..e78c32c 100644 --- a/io/NSData-InputStream.h +++ b/cocoastack/io/NSData-InputStream.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com All rights reserved. @@ -34,5 +34,5 @@ @class DataInputStream; @interface NSData (InputStream) -- (DataInputStream *)newInputStream; +- (DataInputStream *)newInputStreamWithDescription:(NSString *)theDescription; @end diff --git a/io/NSData-InputStream.m b/cocoastack/io/NSData-InputStream.m similarity index 87% rename from io/NSData-InputStream.m rename to cocoastack/io/NSData-InputStream.m index 87a6aa2..da524d3 100644 --- a/io/NSData-InputStream.m +++ b/cocoastack/io/NSData-InputStream.m @@ -1,5 +1,5 @@ /* - Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com + Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com All rights reserved. @@ -34,8 +34,8 @@ #import "DataInputStream.h" @implementation NSData (InputStream) -- (DataInputStream *)newInputStream { - return [[DataInputStream alloc] initWithData:self]; +- (DataInputStream *)newInputStreamWithDescription:(NSString *)theDescription { + return [[DataInputStream alloc] initWithData:self description:theDescription]; } @end diff --git a/cocoastack/io/NSErrorIO.h b/cocoastack/io/NSErrorIO.h new file mode 100644 index 0000000..5685ea7 --- /dev/null +++ b/cocoastack/io/NSErrorIO.h @@ -0,0 +1,18 @@ +// +// NSErrorIO.h +// Arq +// +// Created by Stefan Reitshamer on 6/5/13. +// Copyright (c) 2013 Stefan Reitshamer. All rights reserved. +// + +@class BufferedInputStream; +@class BufferedOutputStream; + + +@interface NSErrorIO : NSObject { + +} ++ (BOOL)write:(NSError *)theError to:(BufferedOutputStream *)theBOS error:(NSError **)error; ++ (BOOL)read:(NSError **)theError from:(BufferedInputStream *)theBIS error:(NSError **)error; +@end diff --git a/cocoastack/io/NSErrorIO.m b/cocoastack/io/NSErrorIO.m new file mode 100644 index 0000000..b3ea86c --- /dev/null +++ b/cocoastack/io/NSErrorIO.m @@ -0,0 +1,52 @@ +// +// NSErrorIO.m +// Arq +// +// Created by Stefan Reitshamer on 6/5/13. +// Copyright (c) 2013 Stefan Reitshamer. All rights reserved. +// + +#import "NSErrorIO.h" +#import "BooleanIO.h" +#import "StringIO.h" +#import "IntegerIO.h" + + +@implementation NSErrorIO ++ (BOOL)write:(NSError *)theError to:(BufferedOutputStream *)theBOS error:(NSError **)error { + if (![BooleanIO write:(theError != nil) to:theBOS error:error]) { + return NO; + } + if (theError != nil) { + if (![StringIO write:[theError domain] to:theBOS error:error] + || ![IntegerIO writeInt64:[theError code] to:theBOS error:error] + || ![StringIO write:[theError localizedDescription] to:theBOS error:error]) { + return NO; + } + } + return YES; +} ++ (BOOL)read:(NSError **)theError from:(BufferedInputStream *)theBIS error:(NSError **)error { + if (theError != NULL) { + *theError = nil; + } + BOOL isNotNil = NO; + if (![BooleanIO read:&isNotNil from:theBIS error:error]) { + return NO; + } + if (isNotNil) { + NSString *domain = nil; + int64_t code = 0; + NSString *description = nil; + if (![StringIO read:&domain from:theBIS error:error] + || ![IntegerIO readInt64:&code from:theBIS error:error] + || ![StringIO read:&description from:theBIS error:error]) { + return NO; + } + if (theError != NULL) { + *theError = [NSError errorWithDomain:domain code:(NSInteger)code description:description]; + } + } + return YES; +} +@end diff --git a/io/NSFileManager_extra.h b/cocoastack/io/NSFileManager_extra.h similarity index 100% rename from io/NSFileManager_extra.h rename to cocoastack/io/NSFileManager_extra.h diff --git a/io/NSFileManager_extra.m b/cocoastack/io/NSFileManager_extra.m similarity index 99% rename from io/NSFileManager_extra.m rename to cocoastack/io/NSFileManager_extra.m index 6387ee1..5a0ace0 100644 --- a/io/NSFileManager_extra.m +++ b/cocoastack/io/NSFileManager_extra.m @@ -35,9 +35,8 @@ #include #include #import "NSFileManager_extra.h" -#import "SetNSError.h" + #import "FileInputStream.h" -#import "EncryptedInputStream.h" #import "Streams.h" @implementation NSFileManager (extra) diff --git a/io/NetMonitor.h b/cocoastack/io/NetMonitor.h similarity index 100% rename from io/NetMonitor.h rename to cocoastack/io/NetMonitor.h diff --git a/io/NetMonitor.m b/cocoastack/io/NetMonitor.m similarity index 100% rename from io/NetMonitor.m rename to cocoastack/io/NetMonitor.m diff --git a/io/OutputStream.h b/cocoastack/io/OutputStream.h similarity index 100% rename from io/OutputStream.h rename to cocoastack/io/OutputStream.h diff --git a/io/Streams.h b/cocoastack/io/Streams.h similarity index 91% rename from io/Streams.h rename to cocoastack/io/Streams.h index 1a92570..6f16bfb 100644 --- a/io/Streams.h +++ b/cocoastack/io/Streams.h @@ -44,4 +44,5 @@ + (BOOL)transferFrom:(id )is to:(id )os bytesWritten:(unsigned long long *)written error:(NSError **)error; + (BOOL)transferFrom:(id )is atomicallyToFile:(NSString *)path bytesWritten:(unsigned long long *)written error:(NSError **)error; + (BOOL)transferFrom:(id )is atomicallyToFile:(NSString *)path targetUID:(uid_t)theTargetUID targetGID:(gid_t)theTargetGID bytesWritten:(unsigned long long *)written error:(NSError **)error; ++ (BOOL)writeData:(NSData *)theData atomicallyToFile:(NSString *)path targetUID:(uid_t)theTargetUID targetGID:(gid_t)theTargetGID bytesWritten:(unsigned long long *)written error:(NSError **)error; @end diff --git a/io/Streams.m b/cocoastack/io/Streams.m similarity index 90% rename from io/Streams.m rename to cocoastack/io/Streams.m index 7dbb37b..59fa6fa 100644 --- a/io/Streams.m +++ b/cocoastack/io/Streams.m @@ -33,10 +33,13 @@ #import "Streams.h" -#import "SetNSError.h" + +#import "Sysctl.h" +#import "NetMonitor.h" #import "FDOutputStream.h" #import "BufferedOutputStream.h" -#import "NSErrorCodes.h" +#import "DataInputStream.h" + #define MY_BUF_SIZE (4096) @@ -94,7 +97,9 @@ } } if (ret) { - *written = [fos bytesWritten]; + if (written != NULL) { + *written = [fos bytesWritten]; + } } else { HSLogError(@"error transferring bytes to %@", path); } @@ -102,6 +107,10 @@ close(fd); return ret; } ++ (BOOL)writeData:(NSData *)theData atomicallyToFile:(NSString *)path targetUID:(uid_t)theTargetUID targetGID:(gid_t)theTargetGID bytesWritten:(unsigned long long *)written error:(NSError **)error { + DataInputStream *dis = [[[DataInputStream alloc] initWithData:theData description:@"no description"] autorelease]; + return [Streams transferFrom:dis atomicallyToFile:path targetUID:theTargetUID targetGID:theTargetGID bytesWritten:written error:error]; +} @end @implementation Streams (internal) diff --git a/io/StringIO.h b/cocoastack/io/StringIO.h similarity index 100% rename from io/StringIO.h rename to cocoastack/io/StringIO.h diff --git a/io/StringIO.m b/cocoastack/io/StringIO.m similarity index 91% rename from io/StringIO.m rename to cocoastack/io/StringIO.m index f45d2e9..2a28dc0 100644 --- a/io/StringIO.m +++ b/cocoastack/io/StringIO.m @@ -40,7 +40,7 @@ #import "BooleanIO.h" #import "BufferedInputStream.h" #import "BufferedOutputStream.h" -#import "SetNSError.h" + @implementation StringIO + (void)write:(NSString *)str to:(NSMutableData *)data { @@ -49,7 +49,7 @@ const char *utf8 = [str UTF8String]; uint64_t len = (uint64_t)strlen(utf8); [IntegerIO writeUInt64:len to:data]; - [data appendBytes:utf8 length:len]; + [data appendBytes:utf8 length:(NSUInteger)len]; } } + (BOOL)write:(NSString *)str to:(BufferedOutputStream *)os error:(NSError **)error { @@ -79,14 +79,14 @@ return NO; } if (len > 2147483648) { - SETNSERROR(@"InputStreamErrorDomain", -1, @"absurd string length %llu in [StringIO newString:]", len); + SETNSERROR(@"InputStreamErrorDomain", -1, @"absurd string length %llu in [StringIO newString:] from %@", len, is); return NO; } - unsigned char *buf = (unsigned char *)malloc(len); + unsigned char *buf = (unsigned char *)malloc((size_t)len); *value = nil; - BOOL ret = [is readExactly:len into:buf error:error]; + BOOL ret = [is readExactly:(NSUInteger)len into:buf error:error]; if (ret) { - *value = [[NSString alloc] initWithBytes:buf length:len encoding:NSUTF8StringEncoding]; + *value = [[NSString alloc] initWithBytes:buf length:(NSUInteger)len encoding:NSUTF8StringEncoding]; } free(buf); if (!ret) { diff --git a/cocoastack/json/NSObject+SBJSON.h b/cocoastack/json/NSObject+SBJSON.h new file mode 100644 index 0000000..0a8c1a5 --- /dev/null +++ b/cocoastack/json/NSObject+SBJSON.h @@ -0,0 +1,52 @@ +/* + Copyright (C) 2009 Stig Brautaset. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the author nor the names of its contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + + +/** + @brief Adds JSON generation to Foundation classes + + This is a category on NSObject that adds methods for returning JSON representations + of standard objects to the objects themselves. This means you can call the + -JSONRepresentation method on an NSArray object and it'll do what you want. + */ +@interface NSObject (NSObject_SBJSON) + +/** + @brief Returns a string containing the receiver encoded in JSON. + + This method is added as a category on NSObject but is only actually + supported for the following objects: + @li NSDictionary + @li NSArray + */ +- (NSString *)JSONRepresentation:(NSError **)error; + +@end + diff --git a/cocoastack/json/NSObject+SBJSON.m b/cocoastack/json/NSObject+SBJSON.m new file mode 100644 index 0000000..f2d82aa --- /dev/null +++ b/cocoastack/json/NSObject+SBJSON.m @@ -0,0 +1,47 @@ +/* + Copyright (C) 2009 Stig Brautaset. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the author nor the names of its contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import "NSObject+SBJSON.h" +#import "SBJsonWriter.h" + +@implementation NSObject (NSObject_SBJSON) + +- (NSString *)JSONRepresentation:(NSError **)error { + SBJsonWriter *jsonWriter = [SBJsonWriter new]; + jsonWriter.humanReadable = YES; + NSString *json = [jsonWriter stringWithObject:self]; + [jsonWriter autorelease]; + if (!json) { + SETNSERROR(@"JSONErrorDomain", -1, @"%@", [jsonWriter errorTrace]); + return nil; + } + return json; +} + +@end diff --git a/cocoastack/json/NSString+SBJSON.h b/cocoastack/json/NSString+SBJSON.h new file mode 100644 index 0000000..c799077 --- /dev/null +++ b/cocoastack/json/NSString+SBJSON.h @@ -0,0 +1,47 @@ +/* + Copyright (C) 2009 Stig Brautaset. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the author nor the names of its contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +/** + @brief Adds JSON parsing methods to NSString + +This is a category on NSString that adds methods for parsing the target string. +*/ +@interface NSString (NSString_SBJSON) + +/** + @brief Returns the NSDictionary or NSArray represented by the current string's JSON representation. + + Returns the dictionary or array represented in the receiver, or nil on error. + + Returns the NSDictionary or NSArray represented by the current string's JSON representation. + */ +- (id)JSONValue:(NSError **)error; + +@end diff --git a/cocoastack/json/NSString+SBJSON.m b/cocoastack/json/NSString+SBJSON.m new file mode 100644 index 0000000..4f87e23 --- /dev/null +++ b/cocoastack/json/NSString+SBJSON.m @@ -0,0 +1,40 @@ +/* + Copyright (C) 2007-2009 Stig Brautaset. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the author nor the names of its contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import "NSString+SBJSON.h" +#import "SBJsonParser.h" + +@implementation NSString (NSString_SBJSON) + +- (id)JSONValue:(NSError **)error { + SBJsonParser *jsonParser = [[[SBJsonParser alloc] init] autorelease]; + return [jsonParser objectWithString:self error:error]; +} + +@end diff --git a/cocoastack/json/SBJsonBase.h b/cocoastack/json/SBJsonBase.h new file mode 100644 index 0000000..9662de5 --- /dev/null +++ b/cocoastack/json/SBJsonBase.h @@ -0,0 +1,85 @@ +/* + Copyright (C) 2009 Stig Brautaset. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the author nor the names of its contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +extern NSString * SBJSONErrorDomain; + + +enum { + EUNSUPPORTED = 1, + EPARSENUM, + EPARSE, + EFRAGMENT, + ECTRL, + EUNICODE, + EDEPTH, + EESCAPE, + ETRAILCOMMA, + ETRAILGARBAGE, + EEOF, + EINPUT +}; + +/** + @brief Common base class for parsing & writing. + + This class contains the common error-handling code and option between the parser/writer. + */ +@interface SBJsonBase : NSObject { + NSMutableArray *errorTrace; + +@protected + NSUInteger depth, maxDepth; +} + +/** + @brief The maximum recursing depth. + + Defaults to 512. If the input is nested deeper than this the input will be deemed to be + malicious and the parser returns nil, signalling an error. ("Nested too deep".) You can + turn off this security feature by setting the maxDepth value to 0. + */ +@property NSUInteger maxDepth; + +/** + @brief Return an error trace, or nil if there was no errors. + + Note that this method returns the trace of the last method that failed. + You need to check the return value of the call you're making to figure out + if the call actually failed, before you know call this method. + */ + @property(copy,readonly) NSArray* errorTrace; + +/// @internal for use in subclasses to add errors to the stack trace +- (void)addErrorWithCode:(NSUInteger)code description:(NSString*)str; + +/// @internal for use in subclasess to clear the error before a new parsing attempt +- (void)clearErrorTrace; + +@end diff --git a/cocoastack/json/SBJsonBase.m b/cocoastack/json/SBJsonBase.m new file mode 100644 index 0000000..6684325 --- /dev/null +++ b/cocoastack/json/SBJsonBase.m @@ -0,0 +1,78 @@ +/* + Copyright (C) 2009 Stig Brautaset. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the author nor the names of its contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import "SBJsonBase.h" +NSString * SBJSONErrorDomain = @"org.brautaset.JSON.ErrorDomain"; + + +@implementation SBJsonBase + +@synthesize errorTrace; +@synthesize maxDepth; + +- (id)init { + self = [super init]; + if (self) + self.maxDepth = 512; + return self; +} + +- (void)dealloc { + [errorTrace release]; + [super dealloc]; +} + +- (void)addErrorWithCode:(NSUInteger)code description:(NSString*)str { + NSDictionary *userInfo; + if (!errorTrace) { + errorTrace = [NSMutableArray new]; + userInfo = [NSDictionary dictionaryWithObject:str forKey:NSLocalizedDescriptionKey]; + + } else { + userInfo = [NSDictionary dictionaryWithObjectsAndKeys: + str, NSLocalizedDescriptionKey, + [errorTrace lastObject], NSUnderlyingErrorKey, + nil]; + } + + NSError *error = [NSError errorWithDomain:SBJSONErrorDomain code:code userInfo:userInfo]; + + [self willChangeValueForKey:@"errorTrace"]; + [errorTrace addObject:error]; + [self didChangeValueForKey:@"errorTrace"]; +} + +- (void)clearErrorTrace { + [self willChangeValueForKey:@"errorTrace"]; + [errorTrace release]; + errorTrace = nil; + [self didChangeValueForKey:@"errorTrace"]; +} + +@end diff --git a/cocoastack/json/SBJsonParser.h b/cocoastack/json/SBJsonParser.h new file mode 100644 index 0000000..6325030 --- /dev/null +++ b/cocoastack/json/SBJsonParser.h @@ -0,0 +1,85 @@ +/* + Copyright (C) 2009 Stig Brautaset. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the author nor the names of its contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import "SBJsonBase.h" + +/** + @brief The JSON parser class. + + JSON is mapped to Objective-C types in the following way: + + @li Null -> NSNull + @li String -> NSMutableString + @li Array -> NSMutableArray + @li Object -> NSMutableDictionary + @li Boolean -> NSNumber (initialised with -initWithBool:) + @li Number -> (NSNumber | NSDecimalNumber) + + Since Objective-C doesn't have a dedicated class for boolean values, these turns into NSNumber + instances. These are initialised with the -initWithBool: method, and + round-trip back to JSON properly. (They won't silently suddenly become 0 or 1; they'll be + represented as 'true' and 'false' again.) + + As an optimisation short JSON integers turn into NSNumber instances, while complex ones turn into NSDecimalNumber instances. + We can thus avoid any loss of precision as JSON allows ridiculously large numbers. + + */ +@interface SBJsonParser : SBJsonBase { + +@private + const char *c; +} + +/** + @brief Return the object represented by the given string + + Returns the object represented by the passed-in string or nil on error. The returned object can be + a string, number, boolean, null, array or dictionary. + + @param repr the json string to parse + */ +- (id)objectWithString:(NSString *)repr; + +/** + @brief Return the object represented by the given string + + Returns the object represented by the passed-in string or nil on error. The returned object can be + a string, number, boolean, null, array or dictionary. + + @param jsonText the json string to parse + @param error pointer to an NSError object to populate on error + */ + +- (id)objectWithString:(NSString*)jsonText + error:(NSError**)error; + + +@end + + diff --git a/cocoastack/json/SBJsonParser.m b/cocoastack/json/SBJsonParser.m new file mode 100644 index 0000000..222ef70 --- /dev/null +++ b/cocoastack/json/SBJsonParser.m @@ -0,0 +1,516 @@ +/* + Copyright (C) 2009,2010 Stig Brautaset. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the author nor the names of its contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import "SBJsonParser.h" + +@interface SBJsonParser () + +- (BOOL)scanValue:(NSObject **)o; + +- (BOOL)scanRestOfArray:(NSMutableArray **)o; +- (BOOL)scanRestOfDictionary:(NSMutableDictionary **)o; +- (BOOL)scanRestOfNull:(NSNull **)o; +- (BOOL)scanRestOfFalse:(NSNumber **)o; +- (BOOL)scanRestOfTrue:(NSNumber **)o; +- (BOOL)scanRestOfString:(NSMutableString **)o; + +// Cannot manage without looking at the first digit +- (BOOL)scanNumber:(NSNumber **)o; + +- (BOOL)scanHexQuad:(unichar *)x; +- (BOOL)scanUnicodeChar:(unichar *)x; + +- (BOOL)scanIsAtEnd; + +@end + +#define skipWhitespace(c) while (isspace(*c)) c++ +#define skipDigits(c) while (isdigit(*c)) c++ + + +@implementation SBJsonParser + +static char ctrl[0x22]; + + ++ (void)initialize { + ctrl[0] = '\"'; + ctrl[1] = '\\'; + for (int i = 1; i < 0x20; i++) + ctrl[i+1] = i; + ctrl[0x21] = 0; +} + +- (id)objectWithString:(NSString *)repr { + [self clearErrorTrace]; + + if (!repr) { + [self addErrorWithCode:EINPUT description:@"Input was 'nil'"]; + return nil; + } + + depth = 0; + c = [repr UTF8String]; + + id o; + if (![self scanValue:&o]) { + return nil; + } + + // We found some valid JSON. But did it also contain something else? + if (![self scanIsAtEnd]) { + [self addErrorWithCode:ETRAILGARBAGE description:@"Garbage after JSON"]; + return nil; + } + + NSAssert1(o, @"Should have a valid object from %@", repr); + + // Check that the object we've found is a valid JSON container. + if (![o isKindOfClass:[NSDictionary class]] && ![o isKindOfClass:[NSArray class]]) { + [self addErrorWithCode:EFRAGMENT description:@"Valid fragment, but not JSON"]; + return nil; + } + + return o; +} + +- (id)objectWithString:(NSString*)repr error:(NSError**)error { + id tmp = [self objectWithString:repr]; + if (tmp) + return tmp; + + if (error) + *error = [self.errorTrace lastObject]; + return nil; +} + + +/* + In contrast to the public methods, it is an error to omit the error parameter here. + */ +- (BOOL)scanValue:(NSObject **)o +{ + skipWhitespace(c); + + switch (*c++) { + case '{': + return [self scanRestOfDictionary:(NSMutableDictionary **)o]; + break; + case '[': + return [self scanRestOfArray:(NSMutableArray **)o]; + break; + case '"': + return [self scanRestOfString:(NSMutableString **)o]; + break; + case 'f': + return [self scanRestOfFalse:(NSNumber **)o]; + break; + case 't': + return [self scanRestOfTrue:(NSNumber **)o]; + break; + case 'n': + return [self scanRestOfNull:(NSNull **)o]; + break; + case '-': + case '0'...'9': + c--; // cannot verify number correctly without the first character + return [self scanNumber:(NSNumber **)o]; + break; + case '+': + [self addErrorWithCode:EPARSENUM description: @"Leading + disallowed in number"]; + return NO; + break; + case 0x0: + [self addErrorWithCode:EEOF description:@"Unexpected end of string"]; + return NO; + break; + default: + [self addErrorWithCode:EPARSE description: @"Unrecognised leading character"]; + return NO; + break; + } + + NSAssert(0, @"Should never get here"); + return NO; +} + +- (BOOL)scanRestOfTrue:(NSNumber **)o +{ + if (!strncmp(c, "rue", 3)) { + c += 3; + *o = [NSNumber numberWithBool:YES]; + return YES; + } + [self addErrorWithCode:EPARSE description:@"Expected 'true'"]; + return NO; +} + +- (BOOL)scanRestOfFalse:(NSNumber **)o +{ + if (!strncmp(c, "alse", 4)) { + c += 4; + *o = [NSNumber numberWithBool:NO]; + return YES; + } + [self addErrorWithCode:EPARSE description: @"Expected 'false'"]; + return NO; +} + +- (BOOL)scanRestOfNull:(NSNull **)o { + if (!strncmp(c, "ull", 3)) { + c += 3; + *o = [NSNull null]; + return YES; + } + [self addErrorWithCode:EPARSE description: @"Expected 'null'"]; + return NO; +} + +- (BOOL)scanRestOfArray:(NSMutableArray **)o { + if (maxDepth && ++depth > maxDepth) { + [self addErrorWithCode:EDEPTH description: @"Nested too deep"]; + return NO; + } + + *o = [NSMutableArray arrayWithCapacity:8]; + + for (; *c ;) { + id v; + + skipWhitespace(c); + if (*c == ']' && c++) { + depth--; + return YES; + } + + if (![self scanValue:&v]) { + [self addErrorWithCode:EPARSE description:@"Expected value while parsing array"]; + return NO; + } + + [*o addObject:v]; + + skipWhitespace(c); + if (*c == ',' && c++) { + skipWhitespace(c); + if (*c == ']') { + [self addErrorWithCode:ETRAILCOMMA description: @"Trailing comma disallowed in array"]; + return NO; + } + } + } + + [self addErrorWithCode:EEOF description: @"End of input while parsing array"]; + return NO; +} + +- (BOOL)scanRestOfDictionary:(NSMutableDictionary **)o +{ + if (maxDepth && ++depth > maxDepth) { + [self addErrorWithCode:EDEPTH description: @"Nested too deep"]; + return NO; + } + + *o = [NSMutableDictionary dictionaryWithCapacity:7]; + + for (; *c ;) { + id k, v; + + skipWhitespace(c); + if (*c == '}' && c++) { + depth--; + return YES; + } + + if (!(*c == '\"' && c++ && [self scanRestOfString:&k])) { + [self addErrorWithCode:EPARSE description: @"Object key string expected"]; + return NO; + } + + skipWhitespace(c); + if (*c != ':') { + [self addErrorWithCode:EPARSE description: @"Expected ':' separating key and value"]; + return NO; + } + + c++; + if (![self scanValue:&v]) { + NSString *string = [NSString stringWithFormat:@"Object value expected for key: %@", k]; + [self addErrorWithCode:EPARSE description: string]; + return NO; + } + + [*o setObject:v forKey:k]; + + skipWhitespace(c); + if (*c == ',' && c++) { + skipWhitespace(c); + if (*c == '}') { + [self addErrorWithCode:ETRAILCOMMA description: @"Trailing comma disallowed in object"]; + return NO; + } + } + } + + [self addErrorWithCode:EEOF description: @"End of input while parsing object"]; + return NO; +} + +- (BOOL)scanRestOfString:(NSMutableString **)o +{ + // if the string has no control characters in it, return it in one go, without any temporary allocations. + size_t len = strcspn(c, ctrl); + if (len && *(c + len) == '\"') + { + *o = [[[NSMutableString alloc] initWithBytes:(char*)c length:len encoding:NSUTF8StringEncoding] autorelease]; + c += len + 1; + return YES; + } + + *o = [NSMutableString stringWithCapacity:16]; + do { + // First see if there's a portion we can grab in one go. + // Doing this caused a massive speedup on the long string. + len = strcspn(c, ctrl); + if (len) { + // check for + id t = [[NSString alloc] initWithBytesNoCopy:(char*)c + length:len + encoding:NSUTF8StringEncoding + freeWhenDone:NO]; + if (t) { + [*o appendString:t]; + [t release]; + c += len; + } + } + + if (*c == '"') { + c++; + return YES; + + } else if (*c == '\\') { + unichar uc = *++c; + switch (uc) { + case '\\': + case '/': + case '"': + break; + + case 'b': uc = '\b'; break; + case 'n': uc = '\n'; break; + case 'r': uc = '\r'; break; + case 't': uc = '\t'; break; + case 'f': uc = '\f'; break; + + case 'u': + c++; + if (![self scanUnicodeChar:&uc]) { + [self addErrorWithCode:EUNICODE description: @"Broken unicode character"]; + return NO; + } + c--; // hack. + break; + default: + [self addErrorWithCode:EESCAPE description: [NSString stringWithFormat:@"Illegal escape sequence '0x%x'", uc]]; + return NO; + break; + } + CFStringAppendCharacters((CFMutableStringRef)*o, &uc, 1); + c++; + + } else if (*c < 0x20) { + [self addErrorWithCode:ECTRL description: [NSString stringWithFormat:@"Unescaped control character '0x%x'", *c]]; + return NO; + + } else { + NSLog(@"should not be able to get here"); + } + } while (*c); + + [self addErrorWithCode:EEOF description:@"Unexpected EOF while parsing string"]; + return NO; +} + +- (BOOL)scanUnicodeChar:(unichar *)x +{ + unichar hi, lo; + + if (![self scanHexQuad:&hi]) { + [self addErrorWithCode:EUNICODE description: @"Missing hex quad"]; + return NO; + } + + if (hi >= 0xd800) { // high surrogate char? + if (hi < 0xdc00) { // yes - expect a low char + + if (!(*c == '\\' && ++c && *c == 'u' && ++c && [self scanHexQuad:&lo])) { + [self addErrorWithCode:EUNICODE description: @"Missing low character in surrogate pair"]; + return NO; + } + + if (lo < 0xdc00 || lo >= 0xdfff) { + [self addErrorWithCode:EUNICODE description:@"Invalid low surrogate char"]; + return NO; + } + + hi = (hi - 0xd800) * 0x400 + (lo - 0xdc00) + 0x10000; + + } else if (hi < 0xe000) { + [self addErrorWithCode:EUNICODE description:@"Invalid high character in surrogate pair"]; + return NO; + } + } + + *x = hi; + return YES; +} + +- (BOOL)scanHexQuad:(unichar *)x +{ + *x = 0; + for (int i = 0; i < 4; i++) { + unichar uc = *c; + c++; + int d = (uc >= '0' && uc <= '9') + ? uc - '0' : (uc >= 'a' && uc <= 'f') + ? (uc - 'a' + 10) : (uc >= 'A' && uc <= 'F') + ? (uc - 'A' + 10) : -1; + if (d == -1) { + [self addErrorWithCode:EUNICODE description:@"Missing hex digit in quad"]; + return NO; + } + *x *= 16; + *x += d; + } + return YES; +} + +- (BOOL)scanNumber:(NSNumber **)o +{ + BOOL simple = YES; + + const char *ns = c; + + // The logic to test for validity of the number formatting is relicensed + // from JSON::XS with permission from its author Marc Lehmann. + // (Available at the CPAN: http://search.cpan.org/dist/JSON-XS/ .) + + if ('-' == *c) + c++; + + if ('0' == *c && c++) { + if (isdigit(*c)) { + [self addErrorWithCode:EPARSENUM description: @"Leading 0 disallowed in number"]; + return NO; + } + + } else if (!isdigit(*c) && c != ns) { + [self addErrorWithCode:EPARSENUM description: @"No digits after initial minus"]; + return NO; + + } else { + skipDigits(c); + } + + // Fractional part + if ('.' == *c && c++) { + simple = NO; + if (!isdigit(*c)) { + [self addErrorWithCode:EPARSENUM description: @"No digits after decimal point"]; + return NO; + } + skipDigits(c); + } + + // Exponential part + if ('e' == *c || 'E' == *c) { + simple = NO; + c++; + + if ('-' == *c || '+' == *c) + c++; + + if (!isdigit(*c)) { + [self addErrorWithCode:EPARSENUM description: @"No digits after exponent"]; + return NO; + } + skipDigits(c); + } + + // If we are only reading integers, don't go through the expense of creating an NSDecimal. + // This ends up being a very large perf win. + if (simple) { + BOOL negate = NO; + long long val = 0; + const char *d = ns; + + if (*d == '-') { + negate = YES; + d++; + } + + while (isdigit(*d)) { + val *= 10; + if (val < 0) + goto longlong_overflow; + val += *d - '0'; + if (val < 0) + goto longlong_overflow; + d++; + } + + *o = [NSNumber numberWithLongLong:negate ? -val : val]; + return YES; + + } else { + // jumped to by simple branch, if an overflow occured + longlong_overflow:; + + id str = [[NSString alloc] initWithBytesNoCopy:(char*)ns + length:c - ns + encoding:NSUTF8StringEncoding + freeWhenDone:NO]; + [str autorelease]; + if (str && (*o = [NSDecimalNumber decimalNumberWithString:str])) + return YES; + + [self addErrorWithCode:EPARSENUM description: @"Failed creating decimal instance"]; + return NO; + } +} + +- (BOOL)scanIsAtEnd +{ + skipWhitespace(c); + return !*c; +} + + +@end diff --git a/cocoastack/json/SBJsonWriter.h b/cocoastack/json/SBJsonWriter.h new file mode 100644 index 0000000..49cf97a --- /dev/null +++ b/cocoastack/json/SBJsonWriter.h @@ -0,0 +1,126 @@ +/* + Copyright (C) 2009 Stig Brautaset. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the author nor the names of its contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import "SBJsonBase.h" + +/** + @brief The JSON writer class. + + Objective-C types are mapped to JSON types in the following way: + + @li NSNull -> Null + @li NSString -> String + @li NSArray -> Array + @li NSDictionary -> Object + @li NSNumber (-initWithBool:) -> Boolean + @li NSNumber -> Number + + In JSON the keys of an object must be strings. NSDictionary keys need + not be, but attempting to convert an NSDictionary with non-string keys + into JSON will throw an exception. + + NSNumber instances created with the +initWithBool: method are + converted into the JSON boolean "true" and "false" values, and vice + versa. Any other NSNumber instances are converted to a JSON number the + way you would expect. + + */ +@interface SBJsonWriter : SBJsonBase { + +@private + BOOL sortKeys, humanReadable; +} + +/** + @brief Whether we are generating human-readable (multiline) JSON. + + Set whether or not to generate human-readable JSON. The default is NO, which produces + JSON without any whitespace. (Except inside strings.) If set to YES, generates human-readable + JSON with linebreaks after each array value and dictionary key/value pair, indented two + spaces per nesting level. + */ +@property BOOL humanReadable; + +/** + @brief Whether or not to sort the dictionary keys in the output. + + If this is set to YES, the dictionary keys in the JSON output will be in sorted order. + (This is useful if you need to compare two structures, for example.) The default is NO. + */ +@property BOOL sortKeys; + +/** + @brief Return JSON representation (or fragment) for the given object. + + Returns a string containing JSON representation of the passed in value, or nil on error. + If nil is returned and @p error is not NULL, @p *error can be interrogated to find the cause of the error. + + @param value any instance that can be represented as a JSON fragment + + */ +- (NSString*)stringWithObject:(id)value; + +/** + @brief Return JSON representation (or fragment) for the given object. + + Returns a string containing JSON representation of the passed in value, or nil on error. + If nil is returned and @p error is not NULL, @p *error can be interrogated to find the cause of the error. + + @param value any instance that can be represented as a JSON fragment + @param error pointer to object to be populated with NSError on failure + + */- (NSString*)stringWithObject:(id)value + error:(NSError**)error; + + +@end + +/** + @brief Allows generation of JSON for otherwise unsupported classes. + + If you have a custom class that you want to create a JSON representation for you can implement + this method in your class. It should return a representation of your object defined + in terms of objects that can be translated into JSON. For example, a Person + object might implement it like this: + + @code + - (id)proxyForJson { + return [NSDictionary dictionaryWithObjectsAndKeys: + name, @"name", + phone, @"phone", + email, @"email", + nil]; + } + @endcode + + */ +@interface NSObject (SBProxyForJson) +- (id)proxyForJson; +@end + diff --git a/cocoastack/json/SBJsonWriter.m b/cocoastack/json/SBJsonWriter.m new file mode 100644 index 0000000..aaa90d2 --- /dev/null +++ b/cocoastack/json/SBJsonWriter.m @@ -0,0 +1,239 @@ +/* + Copyright (C) 2009 Stig Brautaset. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the author nor the names of its contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import "SBJsonWriter.h" + +@interface SBJsonWriter () + +- (BOOL)appendValue:(id)fragment into:(NSMutableString*)json; +- (BOOL)appendArray:(NSArray*)fragment into:(NSMutableString*)json; +- (BOOL)appendDictionary:(NSDictionary*)fragment into:(NSMutableString*)json; +- (BOOL)appendString:(NSString*)fragment into:(NSMutableString*)json; + +- (NSString*)indent; + +@end + +@implementation SBJsonWriter + +@synthesize sortKeys; +@synthesize humanReadable; + +static NSMutableCharacterSet *kEscapeChars; + ++ (void)initialize { + kEscapeChars = [[NSMutableCharacterSet characterSetWithRange: NSMakeRange(0,32)] retain]; + [kEscapeChars addCharactersInString: @"\"\\"]; +} + +- (NSString*)stringWithObject:(id)value { + [self clearErrorTrace]; + + if ([value isKindOfClass:[NSDictionary class]] || [value isKindOfClass:[NSArray class]]) { + depth = 0; + NSMutableString *json = [NSMutableString stringWithCapacity:128]; + if ([self appendValue:value into:json]) + return json; + } + + if ([value respondsToSelector:@selector(proxyForJson)]) { + NSString *tmp = [self stringWithObject:[value proxyForJson]]; + if (tmp) + return tmp; + } + + [self addErrorWithCode:EFRAGMENT description:@"Not valid type for JSON"]; + return nil; +} + +- (NSString*)stringWithObject:(id)value error:(NSError**)error { + NSString *tmp = [self stringWithObject:value]; + if (tmp) + return tmp; + + if (error) + *error = [self.errorTrace lastObject]; + return nil; +} + +- (NSString*)indent { + return [@"\n" stringByPaddingToLength:1 + 2 * depth withString:@" " startingAtIndex:0]; +} + +- (BOOL)appendValue:(id)fragment into:(NSMutableString*)json { + if ([fragment isKindOfClass:[NSDictionary class]]) { + if (![self appendDictionary:fragment into:json]) + return NO; + + } else if ([fragment isKindOfClass:[NSArray class]]) { + if (![self appendArray:fragment into:json]) + return NO; + + } else if ([fragment isKindOfClass:[NSString class]]) { + if (![self appendString:fragment into:json]) + return NO; + + } else if ([fragment isKindOfClass:[NSNumber class]]) { + if ('c' == *[fragment objCType]) { + [json appendString:[fragment boolValue] ? @"true" : @"false"]; + } else if ([fragment isEqualToNumber:[NSDecimalNumber notANumber]]) { + [self addErrorWithCode:EUNSUPPORTED description:@"NaN is not a valid number in JSON"]; + return NO; + + } else if ([fragment isEqualToNumber:[NSNumber numberWithDouble:INFINITY]] || [fragment isEqualToNumber:[NSNumber numberWithDouble:-INFINITY]]) { + [self addErrorWithCode:EUNSUPPORTED description:@"Infinity is not a valid number in JSON"]; + return NO; + + } else { + [json appendString:[fragment stringValue]]; + } + } else if ([fragment isKindOfClass:[NSNull class]]) { + [json appendString:@"null"]; + } else if ([fragment respondsToSelector:@selector(proxyForJson)]) { + [self appendValue:[fragment proxyForJson] into:json]; + + } else { + [self addErrorWithCode:EUNSUPPORTED description:[NSString stringWithFormat:@"JSON serialisation not supported for %@", [fragment class]]]; + return NO; + } + return YES; +} + +- (BOOL)appendArray:(NSArray*)fragment into:(NSMutableString*)json { + if (maxDepth && ++depth > maxDepth) { + [self addErrorWithCode:EDEPTH description: @"Nested too deep"]; + return NO; + } + [json appendString:@"["]; + + BOOL addComma = NO; + for (id value in fragment) { + if (addComma) + [json appendString:@","]; + else + addComma = YES; + + if ([self humanReadable]) + [json appendString:[self indent]]; + + if (![self appendValue:value into:json]) { + return NO; + } + } + + depth--; + if ([self humanReadable] && [fragment count]) + [json appendString:[self indent]]; + [json appendString:@"]"]; + return YES; +} + +- (BOOL)appendDictionary:(NSDictionary*)fragment into:(NSMutableString*)json { + if (maxDepth && ++depth > maxDepth) { + [self addErrorWithCode:EDEPTH description: @"Nested too deep"]; + return NO; + } + [json appendString:@"{"]; + + NSString *colon = [self humanReadable] ? @" : " : @":"; + BOOL addComma = NO; + NSArray *keys = [fragment allKeys]; + if (self.sortKeys) + keys = [keys sortedArrayUsingSelector:@selector(compare:)]; + + for (id value in keys) { + if (addComma) + [json appendString:@","]; + else + addComma = YES; + + if ([self humanReadable]) + [json appendString:[self indent]]; + + if (![value isKindOfClass:[NSString class]]) { + [self addErrorWithCode:EUNSUPPORTED description: @"JSON object key must be string"]; + return NO; + } + + if (![self appendString:value into:json]) + return NO; + + [json appendString:colon]; + if (![self appendValue:[fragment objectForKey:value] into:json]) { + [self addErrorWithCode:EUNSUPPORTED description:[NSString stringWithFormat:@"Unsupported value for key %@ in object", value]]; + return NO; + } + } + + depth--; + if ([self humanReadable] && [fragment count]) + [json appendString:[self indent]]; + [json appendString:@"}"]; + return YES; +} + +- (BOOL)appendString:(NSString*)fragment into:(NSMutableString*)json { + + [json appendString:@"\""]; + + NSRange esc = [fragment rangeOfCharacterFromSet:kEscapeChars]; + if ( !esc.length ) { + // No special chars -- can just add the raw string: + [json appendString:fragment]; + + } else { + NSUInteger length = [fragment length]; + for (NSUInteger i = 0; i < length; i++) { + unichar uc = [fragment characterAtIndex:i]; + switch (uc) { + case '"': [json appendString:@"\\\""]; break; + case '\\': [json appendString:@"\\\\"]; break; + case '\t': [json appendString:@"\\t"]; break; + case '\n': [json appendString:@"\\n"]; break; + case '\r': [json appendString:@"\\r"]; break; + case '\b': [json appendString:@"\\b"]; break; + case '\f': [json appendString:@"\\f"]; break; + default: + if (uc < 0x20) { + [json appendFormat:@"\\u%04x", uc]; + } else { + CFStringAppendCharacters((CFMutableStringRef)json, &uc, 1); + } + break; + + } + } + } + + [json appendString:@"\""]; + return YES; +} + + +@end diff --git a/plist/ArrayNode.h b/cocoastack/plist/ArrayNode.h similarity index 100% rename from plist/ArrayNode.h rename to cocoastack/plist/ArrayNode.h diff --git a/plist/ArrayNode.m b/cocoastack/plist/ArrayNode.m similarity index 100% rename from plist/ArrayNode.m rename to cocoastack/plist/ArrayNode.m diff --git a/plist/BinaryPListReader.h b/cocoastack/plist/BinaryPListReader.h similarity index 100% rename from plist/BinaryPListReader.h rename to cocoastack/plist/BinaryPListReader.h diff --git a/plist/BinaryPListReader.m b/cocoastack/plist/BinaryPListReader.m similarity index 99% rename from plist/BinaryPListReader.m rename to cocoastack/plist/BinaryPListReader.m index 2d1a1ff..0ecff20 100644 --- a/plist/BinaryPListReader.m +++ b/cocoastack/plist/BinaryPListReader.m @@ -44,7 +44,7 @@ #import "DoubleIO.h" #import "PListNodeType.h" #import "DataInputStream.h" -#import "SetNSError.h" + #import "NSData-InputStream.h" @interface BinaryPListReader (internal) diff --git a/plist/BinaryPListWriter.h b/cocoastack/plist/BinaryPListWriter.h similarity index 100% rename from plist/BinaryPListWriter.h rename to cocoastack/plist/BinaryPListWriter.h diff --git a/plist/BinaryPListWriter.m b/cocoastack/plist/BinaryPListWriter.m similarity index 100% rename from plist/BinaryPListWriter.m rename to cocoastack/plist/BinaryPListWriter.m diff --git a/plist/BooleanNode.h b/cocoastack/plist/BooleanNode.h similarity index 100% rename from plist/BooleanNode.h rename to cocoastack/plist/BooleanNode.h diff --git a/plist/BooleanNode.m b/cocoastack/plist/BooleanNode.m similarity index 100% rename from plist/BooleanNode.m rename to cocoastack/plist/BooleanNode.m diff --git a/plist/DictNode.h b/cocoastack/plist/DictNode.h similarity index 95% rename from plist/DictNode.h rename to cocoastack/plist/DictNode.h index 95b7a62..7a0fc76 100644 --- a/plist/DictNode.h +++ b/cocoastack/plist/DictNode.h @@ -70,11 +70,8 @@ - (void)removeKey:(NSString *)key; - (void)removeAllObjects; -- (BOOL)writeXMLToFile:(NSString *)path error:(NSError **)error; - (BOOL)writeXMLToFile:(NSString *)path targetUID:(uid_t)theTargetUID targetGID:(uid_t)theTargetGID error:(NSError **)error; - (NSData *)XMLData; -- (BOOL)writeAtomicallyToBinaryFile:(NSString *)path error:(NSError **)error; -- (NSData *)binaryData; - (BOOL)isEqualToDictNode:(DictNode *)dictNode; @end diff --git a/plist/DictNode.m b/cocoastack/plist/DictNode.m similarity index 91% rename from plist/DictNode.m rename to cocoastack/plist/DictNode.m index aeecf80..765138a 100644 --- a/plist/DictNode.m +++ b/cocoastack/plist/DictNode.m @@ -42,7 +42,7 @@ #import "XMLPListReader.h" #import "XMLPListWriter.h" #import "BinaryPListWriter.h" -#import "SetNSError.h" + #import "FileInputStream.h" #import "DataInputStream.h" #import "BinaryPListReader.h" @@ -59,8 +59,12 @@ if (!data) { return nil; } - DictNode *dn = [DictNode dictNodeWithXMLData:data error:error]; + NSError *myError = nil; + DictNode *dn = [DictNode dictNodeWithXMLData:data error:&myError]; [data release]; + if (dn == nil) { + SETNSERROR(@"DictNodeErrorDomain", -1, @"error parsing %@: %@", path, [myError localizedDescription]); + } return dn; } + (DictNode *)dictNodeWithXMLData:(NSData *)data error:(NSError **)error { @@ -87,7 +91,7 @@ return ret; } + (DictNode *)dictNodeWithBinaryData:(NSData *)data error:(NSError **)error { - DataInputStream *dis = [[DataInputStream alloc] initWithData:data]; + DataInputStream *dis = [[DataInputStream alloc] initWithData:data description:@"DictNode"]; BufferedInputStream *bis = [[BufferedInputStream alloc] initWithUnderlyingStream:dis]; BinaryPListReader *reader = [[BinaryPListReader alloc] initWithStream:bis]; [bis release]; @@ -110,7 +114,7 @@ [super dealloc]; } - (int)size { - return [dict count]; + return (int)[dict count]; } - (BOOL)containsKey:(NSString *)key { return [dict objectForKey:key] != nil; @@ -194,17 +198,14 @@ [orderedKeys removeAllObjects]; } -- (BOOL)writeXMLToFile:(NSString *)path error:(NSError **)error { +- (BOOL)writeXMLToFile:(NSString *)path targetUID:(uid_t)theTargetUID targetGID:(uid_t)theTargetGID error:(NSError **)error { NSMutableData *data = [[NSMutableData alloc] init]; XMLPListWriter *writer = [[XMLPListWriter alloc] initWithMutableData:data]; [writer write:self]; [writer release]; BOOL ret = [data writeToFile:path options:NSAtomicWrite error:error]; [data release]; - return ret; -} -- (BOOL)writeXMLToFile:(NSString *)path targetUID:(uid_t)theTargetUID targetGID:(uid_t)theTargetGID error:(NSError **)error { - if (![self writeXMLToFile:path error:error]) { + if (!ret) { return NO; } if (chown([path fileSystemRepresentation], theTargetUID, theTargetGID) == -1) { @@ -221,22 +222,6 @@ [writer release]; return data; } -- (BOOL)writeAtomicallyToBinaryFile:(NSString *)path error:(NSError **)error { - NSMutableData *data = [[NSMutableData alloc] init]; - BinaryPListWriter *bplw = [[BinaryPListWriter alloc] initWithMutableData:data]; - [bplw write:self]; - [bplw release]; - BOOL ret = [data writeToFile:path options:NSAtomicWrite error:error]; - [data release]; - return ret; -} -- (NSData *)binaryData { - NSMutableData *data = [[[NSMutableData alloc] init] autorelease]; - BinaryPListWriter *bplw = [[BinaryPListWriter alloc] initWithMutableData:data]; - [bplw write:self]; - [bplw release]; - return data; -} - (BOOL)isEqualToDictNode:(DictNode *)dictNode { if (self == dictNode) { return YES; diff --git a/plist/IntegerNode.h b/cocoastack/plist/IntegerNode.h similarity index 100% rename from plist/IntegerNode.h rename to cocoastack/plist/IntegerNode.h diff --git a/plist/IntegerNode.m b/cocoastack/plist/IntegerNode.m similarity index 97% rename from plist/IntegerNode.m rename to cocoastack/plist/IntegerNode.m index cdac5fb..f8a7c6a 100644 --- a/plist/IntegerNode.m +++ b/cocoastack/plist/IntegerNode.m @@ -33,7 +33,7 @@ #import "PListNode.h" #import "PListNodeType.h" #import "IntegerNode.h" -#import "SetNSError.h" + @implementation IntegerNode - (id)initWithInt:(int)theValue { @@ -69,7 +69,7 @@ if (self == other) { return YES; } - return value = [other longlongValue]; + return value == [other longlongValue]; } #pragma mark NSCopying protocol diff --git a/plist/PListNode.h b/cocoastack/plist/PListNode.h similarity index 100% rename from plist/PListNode.h rename to cocoastack/plist/PListNode.h diff --git a/plist/PListNodeType.h b/cocoastack/plist/PListNodeType.h similarity index 100% rename from plist/PListNodeType.h rename to cocoastack/plist/PListNodeType.h diff --git a/plist/RealNode.h b/cocoastack/plist/RealNode.h similarity index 100% rename from plist/RealNode.h rename to cocoastack/plist/RealNode.h diff --git a/plist/RealNode.m b/cocoastack/plist/RealNode.m similarity index 97% rename from plist/RealNode.m rename to cocoastack/plist/RealNode.m index 593608f..962d62f 100644 --- a/plist/RealNode.m +++ b/cocoastack/plist/RealNode.m @@ -32,7 +32,7 @@ #import "PListNodeType.h" #import "RealNode.h" -#import "SetNSError.h" + @implementation RealNode - (id)initWithDouble:(double)theValue { @@ -59,7 +59,7 @@ if (other == self) { return YES; } - return value = [other doubleValue]; + return value == [other doubleValue]; } #pragma mark PListNode protocol diff --git a/plist/StringNode.h b/cocoastack/plist/StringNode.h similarity index 100% rename from plist/StringNode.h rename to cocoastack/plist/StringNode.h diff --git a/plist/StringNode.m b/cocoastack/plist/StringNode.m similarity index 96% rename from plist/StringNode.m rename to cocoastack/plist/StringNode.m index dcf6b48..25557cc 100644 --- a/plist/StringNode.m +++ b/cocoastack/plist/StringNode.m @@ -32,6 +32,7 @@ #import "PListNodeType.h" #import "StringNode.h" +#import "NSObject_extra.h" @implementation StringNode @@ -52,7 +53,7 @@ if (self == other) { return YES; } - return value == [other stringValue]; + return [NSObject equalObjects:value and:[other stringValue]]; } #pragma mark PListNode protocol diff --git a/plist/XMLPListReader.h b/cocoastack/plist/XMLPListReader.h similarity index 100% rename from plist/XMLPListReader.h rename to cocoastack/plist/XMLPListReader.h diff --git a/plist/XMLPListReader.m b/cocoastack/plist/XMLPListReader.m similarity index 97% rename from plist/XMLPListReader.m rename to cocoastack/plist/XMLPListReader.m index 62b79e4..ea89b08 100644 --- a/plist/XMLPListReader.m +++ b/cocoastack/plist/XMLPListReader.m @@ -39,7 +39,7 @@ #import "PListNode.h" #import "PListNodeType.h" #import "XMLPListReader.h" -#import "SetNSError.h" + @interface XMLPListReader (internal) - (ArrayNode *)readArray:(NSXMLNode *)elem error:(NSError **)error; @@ -60,8 +60,10 @@ [super dealloc]; } - (DictNode *)read:(NSError **)error { - NSXMLDocument *doc = [[NSXMLDocument alloc] initWithData:data options:0 error:error]; + NSError *myError = nil; + NSXMLDocument *doc = [[NSXMLDocument alloc] initWithData:data options:0 error:&myError]; if (!doc) { + SETNSERROR(@"XMLPlistReaderErrorDomain", [myError code], @"error parsing XML plist: %@", myError); return nil; } DictNode *dn = nil; diff --git a/plist/XMLPListWriter.h b/cocoastack/plist/XMLPListWriter.h similarity index 100% rename from plist/XMLPListWriter.h rename to cocoastack/plist/XMLPListWriter.h diff --git a/plist/XMLPListWriter.m b/cocoastack/plist/XMLPListWriter.m similarity index 97% rename from plist/XMLPListWriter.m rename to cocoastack/plist/XMLPListWriter.m index d3e43db..77349e6 100644 --- a/plist/XMLPListWriter.m +++ b/cocoastack/plist/XMLPListWriter.m @@ -85,9 +85,9 @@ @implementation XMLPListWriter (internal) - (void)writeArray:(ArrayNode *)node toElement:(NSXMLElement *)elem { NSXMLElement *arrayElem = [[NSXMLElement alloc] initWithName:@"array"]; - int size = [node size]; - for (int i = 0; i < size; i++) { - [self writePListNode:[node objectAtIndex:i] toElement:arrayElem]; + NSUInteger size = [node size]; + for (NSUInteger i = 0; i < size; i++) { + [self writePListNode:[node objectAtIndex:(int)i] toElement:arrayElem]; } [elem addChild:arrayElem]; [arrayElem release]; diff --git a/cocoastack/plist/XMLPlistParser.h b/cocoastack/plist/XMLPlistParser.h new file mode 100644 index 0000000..7a5818a --- /dev/null +++ b/cocoastack/plist/XMLPlistParser.h @@ -0,0 +1,69 @@ +/* + Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +@protocol XMLPlistParserDelegate; + +@interface XMLPlistParser : NSObject { + NSString *path; + id delegate; + NSXMLParser *parser; + NSMutableArray *keyNames; + NSMutableArray *containerNames; + NSMutableString *currentStringValue; +} +- (id)initWithContentsOfPath:(NSString *)thePath; +- (void)setDelegate:(id )theDelegate; +- (void)parse; +@end + +@protocol XMLPlistParserDelegate +- (void)parser:(XMLPlistParser *)parser didStartArray:(NSString *)key; +- (void)parser:(XMLPlistParser *)parser didEndArray:(NSString *)key; +- (void)parser:(XMLPlistParser *)parser didStartDict:(NSString *)key; +- (void)parser:(XMLPlistParser *)parser didEndDict:(NSString *)key; +- (void)parser:(XMLPlistParser *)parser foundString:(NSString *)value key:(NSString *)key; +- (void)parser:(XMLPlistParser *)parser foundInteger:(long long)value key:(NSString *)key; +- (void)parser:(XMLPlistParser *)parser foundBoolean:(BOOL)value key:(NSString *)key; +- (void)parser:(XMLPlistParser *)parser foundReal:(double)value key:(NSString *)key; + +- (void)parser:(XMLPlistParser *)parser didStartArrayInArray:(NSString *)arrayName; +- (void)parser:(XMLPlistParser *)parser didEndArrayInArray:(NSString *)arrayName; +- (void)parser:(XMLPlistParser *)parser didStartDictInArray:(NSString *)arrayName; +- (void)parser:(XMLPlistParser *)parser didEndDictInArray:(NSString *)arrayName; +- (void)parser:(XMLPlistParser *)parser foundString:(NSString *)value inArray:(NSString *)arrayName; +- (void)parser:(XMLPlistParser *)parser foundInteger:(long long)value inArray:(NSString *)arrayName; +- (void)parser:(XMLPlistParser *)parser foundBoolean:(BOOL)value inArray:(NSString *)arrayName; +- (void)parser:(XMLPlistParser *)parser foundReal:(double)value inArray:(NSString *)arrayName; +- (void)parser:(XMLPlistParser *)parser parseErrorOccurred:(NSError *)parseError lineNumber:(NSInteger)theLineNumber columnNumber:(NSInteger)theColumnNumber; +- (void)parserDidEndPlist:(XMLPlistParser *)parser; +@end diff --git a/cocoastack/plist/XMLPlistParser.m b/cocoastack/plist/XMLPlistParser.m new file mode 100644 index 0000000..150f946 --- /dev/null +++ b/cocoastack/plist/XMLPlistParser.m @@ -0,0 +1,192 @@ +/* + Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import "XMLPlistParser.h" + + +@implementation XMLPlistParser +- (id)initWithContentsOfPath:(NSString *)thePath { + if (self = [super init]) { + path = [thePath copy]; + keyNames = [[NSMutableArray alloc] init]; + containerNames = [[NSMutableArray alloc] init]; + } + return self; +} +- (void)dealloc { + [currentStringValue release]; + [parser release]; + [path release]; + [keyNames release]; + [containerNames release]; + [super dealloc]; +} +- (void)setDelegate:(id )theDelegate { + [delegate release]; + delegate = [theDelegate retain]; +} +- (void)parse { + [parser release]; + parser = [[NSXMLParser alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path]]; + [parser setDelegate:self]; + [parser parse]; +} + +#pragma mark NSXMLParserDelegate +- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI + qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + if ([elementName isEqualToString:@"dict"]) { + if ([keyNames count] > 0) { + if ([[containerNames lastObject] isEqualToString:@"array"]) { + [delegate parser:self didStartDictInArray:[keyNames lastObject]]; + } else if ([[containerNames lastObject] isEqualToString:@"dict"]) { + [delegate parser:self didStartDict:[keyNames lastObject]]; + } + } + [containerNames addObject:elementName]; + } else if ([elementName isEqualToString:@"array"]) { + if ([[containerNames lastObject] isEqualToString:@"array"]) { + [delegate parser:self didStartArrayInArray:[keyNames lastObject]]; + } else if ([[containerNames lastObject] isEqualToString:@"dict"]) { + [delegate parser:self didStartArray:[keyNames lastObject]]; + } + [containerNames addObject:elementName]; + } else { + [currentStringValue release]; + currentStringValue = nil; + } + [pool drain]; +} +- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { + if (currentStringValue == nil) { + currentStringValue = [[NSMutableString alloc] init]; + } + [currentStringValue appendString:string]; +} +- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + if ([elementName isEqualToString:@"key"]) { + [keyNames addObject:currentStringValue]; + [currentStringValue release]; + currentStringValue = nil; + } else if ([elementName isEqualToString:@"string"]) { + if ([[containerNames lastObject] isEqualToString:@"array"]) { + [delegate parser:self foundString:currentStringValue inArray:[keyNames lastObject]]; + } else if ([[containerNames lastObject] isEqualToString:@"dict"]) { + [delegate parser:self foundString:currentStringValue key:[keyNames lastObject]]; + [keyNames removeLastObject]; + } + [currentStringValue release]; + currentStringValue = nil; + } else if ([elementName isEqualToString:@"integer"]) { + if ([[containerNames lastObject] isEqualToString:@"array"]) { + [delegate parser:self foundInteger:[currentStringValue intValue] inArray:[keyNames lastObject]]; + } else if ([[containerNames lastObject] isEqualToString:@"dict"]) { + [delegate parser:self foundInteger:[currentStringValue intValue] key:[keyNames lastObject]]; + [keyNames removeLastObject]; + } + [currentStringValue release]; + currentStringValue = nil; + } else if ([elementName isEqualToString:@"real"]) { + double value = 0; + NSScanner *scanner = [NSScanner scannerWithString:currentStringValue]; + if (![scanner scanDouble:&value]) { + HSLogError(@"error scanning double from '%@'", currentStringValue); + } + if ([[containerNames lastObject] isEqualToString:@"array"]) { + [delegate parser:self foundReal:value inArray:[keyNames lastObject]]; + } else if ([[containerNames lastObject] isEqualToString:@"dict"]) { + [delegate parser:self foundReal:value key:[keyNames lastObject]]; + [keyNames removeLastObject]; + } + [currentStringValue release]; + currentStringValue = nil; + } else if ([elementName isEqualToString:@"true"]) { + if ([[containerNames lastObject] isEqualToString:@"array"]) { + [delegate parser:self foundBoolean:YES inArray:[keyNames lastObject]]; + } else if ([[containerNames lastObject] isEqualToString:@"dict"]) { + [delegate parser:self foundBoolean:YES key:[keyNames lastObject]]; + [keyNames removeLastObject]; + } + [currentStringValue release]; + currentStringValue = nil; + } else if ([elementName isEqualToString:@"false"]) { + if ([[containerNames lastObject] isEqualToString:@"array"]) { + [delegate parser:self foundBoolean:NO inArray:[keyNames lastObject]]; + } else if ([[containerNames lastObject] isEqualToString:@"dict"]) { + [delegate parser:self foundBoolean:NO key:[keyNames lastObject]]; + [keyNames removeLastObject]; + } + [currentStringValue release]; + currentStringValue = nil; + } else if ([elementName isEqualToString:@"dict"]) { + NSAssert([[containerNames lastObject] isEqualToString:elementName], @"must be last object in containerNames"); + [containerNames removeLastObject]; + if ([keyNames count] > 0) { + if ([[containerNames lastObject] isEqualToString:@"array"]) { + [delegate parser:self didEndDictInArray:[keyNames lastObject]]; + } else if ([[containerNames lastObject] isEqualToString:@"dict"]) { + [delegate parser:self didEndDict:[keyNames lastObject]]; + [keyNames removeLastObject]; + } + } + } else if ([elementName isEqualToString:@"array"]) { + NSAssert([[containerNames lastObject] isEqualToString:elementName], @"must be last object in containerNames"); + [containerNames removeLastObject]; + if ([[containerNames lastObject] isEqualToString:@"array"]) { + [delegate parser:self didEndArrayInArray:[keyNames lastObject]]; + } else if ([[containerNames lastObject] isEqualToString:@"dict"]) { + [delegate parser:self didEndArray:[keyNames lastObject]]; + [keyNames removeLastObject]; + } + } + [pool drain]; +} +- (void)parser:(NSXMLParser *)theParser parseErrorOccurred:(NSError *)parseError { + [delegate parser:self parseErrorOccurred:parseError lineNumber:[theParser lineNumber] columnNumber:[theParser columnNumber]]; +} +- (void)parserDidEndDocument:(NSXMLParser *)parser { + [delegate parserDidEndPlist:self]; +} +@end +//- (void)parserDidStartArray:(XMLPlistParser *)parser; +//- (void)parserDidEndArray:(XMLPlistParser *)parser; +//- (void)parserDidStartDict:(XMLPlistParser *)parser; +//- (void)parserDidEndDict:(XMLPlistParser *)parser; +//- (void)parser:(XMLPlistParser *)parser foundDictKey:(NSString *)name; +//- (void)parser:(XMLPlistParser *)parser foundString:(NSString *)value; +//- (void)parser:(XMLPlistParser *)parser foundInteger:(long long)value; +//- (void)parser:(XMLPlistParser *)parser foundBoolean:(BOOL)value; +//- (void)parser:(XMLPlistParser *)parser foundReal:(double)value; +//- (void)parser:(XMLPlistParser *)parser parseErrorOccurred:(NSError *)parseError; +//- (void)parserDidEndPlist:(XMLPlistParser *)parser; diff --git a/cocoastack/remotefs/GoogleDriveRemoteFS.h b/cocoastack/remotefs/GoogleDriveRemoteFS.h new file mode 100644 index 0000000..83d5982 --- /dev/null +++ b/cocoastack/remotefs/GoogleDriveRemoteFS.h @@ -0,0 +1,20 @@ +// +// GoogleDriveRemoteFS.h +// Arq +// +// Created by Stefan Reitshamer on 7/16/14. +// Copyright (c) 2014 Stefan Reitshamer. All rights reserved. +// + +#import "RemoteFS.h" +@class Target; +@class GoogleDrive; + + +@interface GoogleDriveRemoteFS : NSObject { + Target *target; + GoogleDrive *googleDrive; +} +- (id)initWithTarget:(Target *)theTarget; + +@end diff --git a/cocoastack/remotefs/GoogleDriveRemoteFS.m b/cocoastack/remotefs/GoogleDriveRemoteFS.m new file mode 100644 index 0000000..58deec9 --- /dev/null +++ b/cocoastack/remotefs/GoogleDriveRemoteFS.m @@ -0,0 +1,125 @@ +// +// GoogleDriveRemoteFS.m +// Arq +// +// Created by Stefan Reitshamer on 7/16/14. +// Copyright (c) 2014 Stefan Reitshamer. All rights reserved. +// + +#import "GoogleDriveRemoteFS.h" +#import "NSString_extra.h" +#import "Target.h" +#import "GoogleDrive.h" +#import "GoogleDriveFactory.h" + + +@implementation GoogleDriveRemoteFS + +- (id)initWithTarget:(Target *)theTarget { + if (self = [super init]) { + target = [theTarget retain]; + } + return self; +} +- (void)dealloc { + [target release]; + [googleDrive release]; + [super dealloc]; +} + + +#pragma mark RemoteFS +- (NSString *)errorDomain { + return @"RemoteFSErrorDomain"; +} +- (NSNumber *)fileExistsAtPath:(NSString *)thePath isDirectory:(BOOL *)isDirectory targetConnectionDelegate:(id)theTCD error:(NSError **)error { + if (![self setUp:error]) { + return nil; + } + thePath = [thePath stringByDeletingTrailingSlash]; + + NSNumber *ret = [googleDrive fileExistsAtPath:thePath isDirectory:isDirectory targetConnectionDelegate:theTCD error:error]; + if (ret == nil) { + return nil; + } + return ret; +} +- (NSNumber *)fileExistsAtPath:(NSString *)thePath dataSize:(unsigned long long *)theDataSize targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + if (![self setUp:error]) { + return nil; + } + return [googleDrive fileExistsAtPath:thePath dataSize:theDataSize targetConnectionDelegate:theDelegate error:error]; +} +- (NSArray *)contentsOfDirectoryAtPath:(NSString *)thePath targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + if (![self setUp:error]) { + return nil; + } + return [googleDrive contentsOfDirectoryAtPath:thePath targetConnectionDelegate:theDelegate error:error]; +} +- (NSData *)contentsOfFileAtPath:(NSString *)thePath dataTransferDelegate:(id)theDTDelegate targetConnectionDelegate:(id)theTCDelegate error:(NSError **)error { + if (![self setUp:error]) { + return nil; + } + return [googleDrive contentsOfFileAtPath:thePath dataTransferDelegate:theDTDelegate targetConnectionDelegate:theTCDelegate error:error]; +} +- (BOOL)writeData:(NSData *)theData atomicallyToFileAtPath:(NSString *)thePath dataTransferDelegate:(id)theDTDelegate targetConnectionDelegate:(id)theTCDelegate error:(NSError **)error { + if (![self setUp:error]) { + return NO; + } + return [googleDrive writeData:theData mimeType:@"binary/octet-stream" toFileAtPath:thePath dataTransferDelegate:theDTDelegate targetConnectionDelegate:theTCDelegate error:error]; +} +- (BOOL)removeItemAtPath:(NSString *)thePath targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + if (![self setUp:error]) { + return NO; + } + return [googleDrive removeItemAtPath:thePath targetConnectionDelegate:theDelegate error:error]; +} +- (BOOL)createDirectoryAtPath:(NSString *)path withIntermediateDirectories:(BOOL)createIntermediates targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + return [googleDrive createDirectoryAtPath:path withIntermediateDirectories:createIntermediates targetConnectionDelegate:theDelegate error:error]; +} +- (NSNumber *)sizeOfItemAtPath:(NSString *)thePath targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + if (![self setUp:error]) { + return nil; + } + return [googleDrive sizeOfItemAtPath:thePath targetConnectionDelegate:theDelegate error:error]; + return nil; +} + +// Returns an NSArray of S3ObjectMetadata objects. +- (NSArray *)objectsAtPath:(NSString *)thePath targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + if (![self setUp:error]) { + return nil; + } + return [googleDrive objectsAtPath:thePath targetConnectionDelegate:theDelegate error:error]; + return nil; +} +- (NSArray *)pathsOfObjectsAtPath:(NSString *)thePath targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + if (![self setUp:error]) { + return nil; + } + return [googleDrive pathsOfObjectsAtPath:thePath targetConnectionDelegate:theDelegate error:error]; + return nil; +} +- (NSNumber *)isObjectRestoredAtPath:(NSString *)thePath targetConnectionDelegate:(id )theTargetConnectionDelegate error:(NSError **)error { + SETNSERROR([self errorDomain], -1, @"isObjectRestoredAtPath is not supported by GoogleDriveRemoteFS"); + return nil; +} +- (BOOL)restoreObjectAtPath:(NSString *)thePath forDays:(NSUInteger)theDays alreadyRestoredOrRestoring:(BOOL *)alreadyRestoredOrRestoring targetConnectionDelegate:(id )theTargetConnectionDelegate error:(NSError **)error { + SETNSERROR([self errorDomain], -1, @"restoreObjectAtPath is not supported by GoogleDriveRemoteFS"); + return NO; +} + + +#pragma mark internal +- (BOOL)setUp:(NSError **)error { + if (googleDrive == nil) { + NSString *secret = [target secret:error]; + if (secret == nil) { + return NO; + } + googleDrive = [[[GoogleDriveFactory sharedGoogleDriveFactory] googleDriveWithEmailAddress:[[target endpoint] user] refreshToken:secret] retain]; + } + return YES; +} + +@end diff --git a/cocoastack/remotefs/RemoteFS.h b/cocoastack/remotefs/RemoteFS.h new file mode 100644 index 0000000..e773314 --- /dev/null +++ b/cocoastack/remotefs/RemoteFS.h @@ -0,0 +1,28 @@ +// +// RemoteFS.h +// Arq +// +// Created by Stefan Reitshamer on 3/18/14. +// Copyright (c) 2014 Stefan Reitshamer. All rights reserved. +// + +@protocol DataTransferDelegate; +@protocol TargetConnectionDelegate; + + +@protocol RemoteFS +- (NSString *)errorDomain; + +- (NSNumber *)fileExistsAtPath:(NSString *)thePath isDirectory:(BOOL *)isDirectory targetConnectionDelegate:(id )theDelegate error:(NSError **)error; +- (NSNumber *)fileExistsAtPath:(NSString *)thePath dataSize:(unsigned long long *)theDataSize targetConnectionDelegate:(id )theDelegate error:(NSError **)error; +- (NSArray *)contentsOfDirectoryAtPath:(NSString *)thePath targetConnectionDelegate:(id )theDelegate error:(NSError **)error; +- (NSData *)contentsOfFileAtPath:(NSString *)thePath dataTransferDelegate:(id )theDTDelegate targetConnectionDelegate:(id )theTCDelegate error:(NSError **)error; +- (BOOL)writeData:(NSData *)theData atomicallyToFileAtPath:(NSString *)thePath dataTransferDelegate:(id )theDTDelegate targetConnectionDelegate:(id )theTCDelegate error:(NSError **)error; +- (BOOL)removeItemAtPath:(NSString *)thePath targetConnectionDelegate:(id )theDelegate error:(NSError **)error; +- (BOOL)createDirectoryAtPath:(NSString *)path withIntermediateDirectories:(BOOL)createIntermediates targetConnectionDelegate:(id )theDelegate error:(NSError **)error; +- (NSNumber *)sizeOfItemAtPath:(NSString *)thePath targetConnectionDelegate:(id )theDelegate error:(NSError **)error; +- (NSArray *)objectsAtPath:(NSString *)thePath targetConnectionDelegate:(id )theDelegate error:(NSError **)error; +- (NSArray *)pathsOfObjectsAtPath:(NSString *)thePath targetConnectionDelegate:(id )theDelegate error:(NSError **)error; +- (NSNumber *)isObjectRestoredAtPath:(NSString *)thePath targetConnectionDelegate:(id )theDelegate error:(NSError **)error; +- (BOOL)restoreObjectAtPath:(NSString *)thePath forDays:(NSUInteger)theDays alreadyRestoredOrRestoring:(BOOL *)alreadyRestoredOrRestoring targetConnectionDelegate:(id )theDelegate error:(NSError **)error; +@end diff --git a/cocoastack/remotefs/S3RemoteFS.h b/cocoastack/remotefs/S3RemoteFS.h new file mode 100644 index 0000000..defc8e1 --- /dev/null +++ b/cocoastack/remotefs/S3RemoteFS.h @@ -0,0 +1,20 @@ +// +// S3RemoteFS.h +// Arq +// +// Created by Stefan Reitshamer on 3/18/14. +// Copyright (c) 2014 Stefan Reitshamer. All rights reserved. +// + +#import "RemoteFS.h" +@class Target; +@class S3Service; + + +@interface S3RemoteFS : NSObject { + Target *target; + S3Service *s3; +} + +- (id)initWithTarget:(Target *)theTarget; +@end diff --git a/cocoastack/remotefs/S3RemoteFS.m b/cocoastack/remotefs/S3RemoteFS.m new file mode 100644 index 0000000..9badb46 --- /dev/null +++ b/cocoastack/remotefs/S3RemoteFS.m @@ -0,0 +1,148 @@ +// +// S3RemoteFS.m +// Arq +// +// Created by Stefan Reitshamer on 3/18/14. +// Copyright (c) 2014 Stefan Reitshamer. All rights reserved. +// + +#import "S3RemoteFS.h" +#import "Target.h" +#import "S3AuthorizationProvider.h" +#import "S3Service.h" +#import "NSString_extra.h" +#import "S3ObjectMetadata.h" + + +@implementation S3RemoteFS +- (id)initWithTarget:(Target *)theTarget { + if (self = [super init]) { + target = [theTarget retain]; + } + return self; +} +- (void)dealloc { + [target release]; + [s3 release]; + [super dealloc]; +} + + +#pragma mark RemoteFS +- (NSString *)errorDomain { + return @"RemoteFSErrorDomain"; +} +- (NSNumber *)fileExistsAtPath:(NSString *)thePath isDirectory:(BOOL *)isDirectory targetConnectionDelegate:(id)theTCD error:(NSError **)error { + if (![self setUp:error]) { + return nil; + } + thePath = [thePath stringByDeletingTrailingSlash]; + + NSNumber *ret = [s3 containsObjectAtPath:thePath dataSize:NULL targetConnectionDelegate:theTCD error:error]; + if (ret == nil) { + return nil; + } + if (isDirectory != NULL) { + NSString *dir = [thePath stringByAppendingString:@"/"]; + NSArray *prefixes = [s3 commonPrefixesForPathPrefix:dir delimiter:@"/" targetConnectionDelegate:theTCD error:error]; + if (prefixes == nil) { + return nil; + } + *isDirectory = [prefixes count] > 0; //FIXME: Does this work?! Never been tested! + } + return ret; +} +- (NSNumber *)fileExistsAtPath:(NSString *)thePath dataSize:(unsigned long long *)theDataSize targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + if (![self setUp:error]) { + return nil; + } + return [s3 containsObjectAtPath:thePath dataSize:theDataSize targetConnectionDelegate:theDelegate error:error]; +} +- (NSArray *)contentsOfDirectoryAtPath:(NSString *)thePath targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + if (![self setUp:error]) { + return nil; + } + if (![thePath hasSuffix:@"/"]) { + thePath = [thePath stringByAppendingString:@"/"]; + } + return [s3 commonPrefixesForPathPrefix:thePath delimiter:@"/" targetConnectionDelegate:theDelegate error:error]; +} +- (NSData *)contentsOfFileAtPath:(NSString *)thePath dataTransferDelegate:(id)theDTDelegate targetConnectionDelegate:(id)theTCDelegate error:(NSError **)error { + if (![self setUp:error]) { + return nil; + } + return [s3 dataAtPath:thePath dataTransferDelegate:theDTDelegate targetConnectionDelegate:theTCDelegate error:error]; +} +- (BOOL)writeData:(NSData *)theData atomicallyToFileAtPath:(NSString *)thePath dataTransferDelegate:(id)theDTDelegate targetConnectionDelegate:(id)theTCDelegate error:(NSError **)error { + if (![self setUp:error]) { + return NO; + } + return [s3 putData:theData atPath:thePath dataTransferDelegate:theDTDelegate targetConnectionDelegate:theTCDelegate error:error]; +} +- (BOOL)removeItemAtPath:(NSString *)thePath targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + if (![self setUp:error]) { + return NO; + } + return [s3 deletePath:thePath targetConnectionDelegate:theDelegate error:error]; +} +- (BOOL)createDirectoryAtPath:(NSString *)path withIntermediateDirectories:(BOOL)createIntermediates targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + return YES; +} +- (NSNumber *)sizeOfItemAtPath:(NSString *)thePath targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + if (![self setUp:error]) { + return nil; + } + NSArray *objects = [s3 objectsWithPrefix:thePath targetConnectionDelegate:theDelegate error:error]; + if (objects == nil) { + return nil; + } + unsigned long long total = 0; + for (S3ObjectMetadata *md in objects) { + total += [md size]; + } + return [NSNumber numberWithUnsignedLongLong:total]; +} +- (NSArray *)objectsAtPath:(NSString *)thePath targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + if (![self setUp:error]) { + return nil; + } + return [s3 objectsWithPrefix:thePath targetConnectionDelegate:theDelegate error:error]; +} +- (NSArray *)pathsOfObjectsAtPath:(NSString *)thePath targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + if (![self setUp:error]) { + return nil; + } + return [s3 pathsWithPrefix:thePath delimiter:nil targetConnectionDelegate:theDelegate error:error]; +} +- (NSNumber *)isObjectRestoredAtPath:(NSString *)thePath targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + if (![self setUp:error]) { + return nil; + } + return [s3 isObjectRestoredAtPath:thePath targetConnectionDelegate:theDelegate error:error]; +} +- (BOOL)restoreObjectAtPath:(NSString *)thePath forDays:(NSUInteger)theDays alreadyRestoredOrRestoring:(BOOL *)alreadyRestoredOrRestoring targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + if (![self setUp:error]) { + return nil; + } + return [s3 restoreObjectAtPath:thePath forDays:theDays alreadyRestoredOrRestoring:alreadyRestoredOrRestoring targetConnectionDelegate:theDelegate error:error]; +} + + +#pragma mark internal +- (BOOL)setUp:(NSError **)error { + if (s3 == nil) { + NSString *secret = [target secret:error]; + if (secret == nil) { + return NO; + } + S3AuthorizationProvider *sap = [[[S3AuthorizationProvider alloc] initWithAccessKey:[[target endpoint] user] secretKey:secret] autorelease]; + NSString *portString = @""; + if ([[[target endpoint] port] intValue] != 0) { + portString = [NSString stringWithFormat:@":%d", [[[target endpoint] port] intValue]]; + } + NSURL *s3Endpoint = [NSURL URLWithString:[NSString stringWithFormat:@"%@://%@%@", [[target endpoint] scheme], [[target endpoint] host], portString]]; + s3 = [[S3Service alloc] initWithS3AuthorizationProvider:sap endpoint:s3Endpoint useAmazonRRS:NO]; + } + return YES; +} +@end diff --git a/cocoastack/remotefs/SFTPRemoteFS.h b/cocoastack/remotefs/SFTPRemoteFS.h new file mode 100644 index 0000000..301a52a --- /dev/null +++ b/cocoastack/remotefs/SFTPRemoteFS.h @@ -0,0 +1,26 @@ +// +// SFTPRemoteFS.h +// Arq +// +// Created by Stefan Reitshamer on 3/18/14. +// Copyright (c) 2014 Stefan Reitshamer. All rights reserved. +// + +#import "RemoteFS.h" +@class Target; +@class SFTPServer; +@protocol TargetConnectionDelegate; + + +@interface SFTPRemoteFS : NSObject { + Target *target; + + NSString *tempDir; + SFTPServer *sftpServer; + NSTimeInterval sleepTime; +} + +- (id)initWithTarget:(Target *)theTarget tempDir:(NSString *)theTempDir; + +- (BOOL)renameObjectAtPath:(NSString *)theSource toPath:(NSString *)theDest targetConnectionDelegate:(id )theTargetConnectionDelegate error:(NSError **)error; +@end diff --git a/cocoastack/remotefs/SFTPRemoteFS.m b/cocoastack/remotefs/SFTPRemoteFS.m new file mode 100644 index 0000000..49114e6 --- /dev/null +++ b/cocoastack/remotefs/SFTPRemoteFS.m @@ -0,0 +1,555 @@ +// +// SFTPRemoteFS.m +// Arq +// +// Created by Stefan Reitshamer on 3/18/14. +// Copyright (c) 2014 Stefan Reitshamer. All rights reserved. +// + +#import "SFTPRemoteFS.h" +#import "Target.h" +#import "SFTPServer.h" +#import "NSString_extra.h" +#import "NSError_extra.h" +#import "TargetConnection.h" + +#define INITIAL_RETRY_SLEEP (0.5) +#define RETRY_SLEEP_GROWTH_FACTOR (1.5) +#define MAX_RETRY_SLEEP (5.0) + + +@implementation SFTPRemoteFS +- (id)initWithTarget:(Target *)theTarget tempDir:(NSString *)theTempDir { + if (self = [super init]) { + target = [theTarget retain]; + tempDir = [theTempDir retain]; + } + return self; +} +- (void)dealloc { + [target release]; + [tempDir release]; + [sftpServer release]; + [super dealloc]; +} + +- (BOOL)renameObjectAtPath:(NSString *)theSource toPath:(NSString *)theDest targetConnectionDelegate:(id )theTargetConnectionDelegate error:(NSError **)error { + sleepTime = INITIAL_RETRY_SLEEP; + BOOL ret = nil; + NSAutoreleasePool *pool = nil; + NSError *myError = nil; + for (;;) { + [pool drain]; + pool = [[NSAutoreleasePool alloc] init]; + myError = nil; + + if (![self connectWithTargetConnectionDelegate:theTargetConnectionDelegate error:&myError]) { + break; + } + + ret = [sftpServer ensureParentPathExistsForPath:theDest error:&myError] + && [sftpServer removeItemAtPath:theDest error:&myError] + && [sftpServer renameItemAtPath:theSource toPath:theDest error:&myError]; + if (ret) { + break; + } + if (![theTargetConnectionDelegate targetConnectionShouldRetryOnTransientError:&myError]) { + break; + } + if (![myError isTransientError]) { + break; + } + + HSLogDetail(@"SFTP error (retrying): %@", myError); + [self disconnect]; + [self sleepBeforeRetry]; + } + [myError retain]; + [pool drain]; + [myError autorelease]; + if (!ret) { + SETERRORFROMMYERROR; + } + return ret; +} + + +#pragma mark RemoteFS +- (NSString *)errorDomain { + return @"RemoteFSErrorDomain"; +} +- (NSNumber *)fileExistsAtPath:(NSString *)thePath isDirectory:(BOOL *)isDirectory targetConnectionDelegate:(id )theTargetConnectionDelegate error:(NSError **)error { + sleepTime = INITIAL_RETRY_SLEEP; + NSNumber *ret = nil; + NSAutoreleasePool *pool = nil; + NSError *myError = nil; + for (;;) { + [pool drain]; + pool = [[NSAutoreleasePool alloc] init]; + myError = nil; + + if (![self connectWithTargetConnectionDelegate:theTargetConnectionDelegate error:&myError]) { + break; + } + + ret = [sftpServer fileExistsAtPath:thePath isDirectory:isDirectory error:&myError]; + if (ret) { + break; + } + if (![theTargetConnectionDelegate targetConnectionShouldRetryOnTransientError:&myError]) { + break; + } + if (![myError isTransientError]) { + break; + } + + HSLogDetail(@"SFTP error (retrying): %@", myError); + [self disconnect]; + [self sleepBeforeRetry]; + } + [ret retain]; + [myError retain]; + [pool drain]; + [ret autorelease]; + [myError autorelease]; + if (ret == nil) { + SETERRORFROMMYERROR; + } + return ret; +} +- (NSNumber *)fileExistsAtPath:(NSString *)thePath dataSize:(unsigned long long *)theDataSize targetConnectionDelegate:(id )theTargetConnectionDelegate error:(NSError **)error { + sleepTime = INITIAL_RETRY_SLEEP; + NSNumber *ret = nil; + NSAutoreleasePool *pool = nil; + NSError *myError = nil; + for (;;) { + [pool drain]; + pool = [[NSAutoreleasePool alloc] init]; + myError = nil; + + if (![self connectWithTargetConnectionDelegate:theTargetConnectionDelegate error:&myError]) { + break; + } + + ret = [sftpServer fileExistsAtPath:thePath dataSize:theDataSize error:&myError]; + if (ret) { + break; + } + if (![theTargetConnectionDelegate targetConnectionShouldRetryOnTransientError:&myError]) { + break; + } + if (![myError isTransientError]) { + break; + } + + HSLogDetail(@"SFTP error (retrying): %@", myError); + [self disconnect]; + [self sleepBeforeRetry]; + } + [ret retain]; + [myError retain]; + [pool drain]; + [ret autorelease]; + [myError autorelease]; + if (ret == nil) { + SETERRORFROMMYERROR; + } + return ret; +} +- (NSArray *)contentsOfDirectoryAtPath:(NSString *)thePath targetConnectionDelegate:(id )theTargetConnectionDelegate error:(NSError **)error { + sleepTime = INITIAL_RETRY_SLEEP; + NSArray *ret = nil; + NSAutoreleasePool *pool = nil; + NSError *myError = nil; + for (;;) { + [pool drain]; + pool = [[NSAutoreleasePool alloc] init]; + myError = nil; + + if (![self connectWithTargetConnectionDelegate:theTargetConnectionDelegate error:&myError]) { + break; + } + + ret = [sftpServer contentsOfDirectoryAtPath:thePath error:&myError]; + if (ret) { + break; + } + if (![theTargetConnectionDelegate targetConnectionShouldRetryOnTransientError:&myError]) { + break; + } + if (![myError isTransientError]) { + break; + } + + HSLogDetail(@"SFTP error (retrying): %@", myError); + [self disconnect]; + [self sleepBeforeRetry]; + } + [ret retain]; + [myError retain]; + [pool drain]; + [ret autorelease]; + [myError autorelease]; + if (ret == nil) { + SETERRORFROMMYERROR; + } + return ret; +} +- (NSData *)contentsOfFileAtPath:(NSString *)thePath dataTransferDelegate:(id)theDTD targetConnectionDelegate:(id)theTCD error:(NSError **)error { + sleepTime = INITIAL_RETRY_SLEEP; + NSData *ret = nil; + NSAutoreleasePool *pool = nil; + NSError *myError = nil; + for (;;) { + [pool drain]; + pool = [[NSAutoreleasePool alloc] init]; + myError = nil; + + if (![self connectWithTargetConnectionDelegate:theTCD error:&myError]) { + break; + } + + ret = [sftpServer contentsOfFileAtPath:thePath dataTransferDelegate:theDTD error:&myError]; + if (ret) { + break; + } + if (![theTCD targetConnectionShouldRetryOnTransientError:&myError]) { + break; + } + if (![myError isTransientError]) { + break; + } + + HSLogDetail(@"SFTP error (retrying): %@", myError); + [self disconnect]; + [self sleepBeforeRetry]; + } + [ret retain]; + [myError retain]; + [pool drain]; + [ret autorelease]; + [myError autorelease]; + if (ret == nil) { + SETERRORFROMMYERROR; + } + return ret; +} +- (BOOL)writeData:(NSData *)theData atomicallyToFileAtPath:(NSString *)thePath dataTransferDelegate:(id)theDTD targetConnectionDelegate:(id)theTCD error:(NSError **)error { + sleepTime = INITIAL_RETRY_SLEEP; + NSString *tempPath = [tempDir stringByAppendingPathComponent:[NSString stringWithRandomUUID]]; + BOOL ret = nil; + NSAutoreleasePool *pool = nil; + NSError *myError = nil; + for (;;) { + [pool drain]; + pool = [[NSAutoreleasePool alloc] init]; + myError = nil; + + if (![self connectWithTargetConnectionDelegate:theTCD error:&myError]) { + break; + } + + ret = [sftpServer writeData:theData toFileAtPath:tempPath dataTransferDelegate:theDTD error:&myError] + && [sftpServer ensureParentPathExistsForPath:thePath error:&myError] + && [sftpServer removeItemAtPath:thePath error:&myError] + && [sftpServer renameItemAtPath:tempPath toPath:thePath error:&myError]; + if (ret) { + break; + } + if (![theTCD targetConnectionShouldRetryOnTransientError:&myError]) { + break; + } + if (![myError isTransientError]) { + break; + } + + HSLogDetail(@"SFTP error (retrying): %@", myError); + [self disconnect]; + [self sleepBeforeRetry]; + } + [myError retain]; + [pool drain]; + [myError autorelease]; + if (!ret) { + SETERRORFROMMYERROR; + } + return ret; +} +- (BOOL)removeItemAtPath:(NSString *)thePath targetConnectionDelegate:(id )theTargetConnectionDelegate error:(NSError **)error { + sleepTime = INITIAL_RETRY_SLEEP; + BOOL ret = nil; + NSAutoreleasePool *pool = nil; + NSError *myError = nil; + for (;;) { + [pool drain]; + pool = [[NSAutoreleasePool alloc] init]; + myError = nil; + + if (![self connectWithTargetConnectionDelegate:theTargetConnectionDelegate error:&myError]) { + break; + } + + ret = [sftpServer removeItemAtPath:thePath error:&myError]; + if (ret) { + break; + } + if (![theTargetConnectionDelegate targetConnectionShouldRetryOnTransientError:&myError]) { + break; + } + if (![myError isTransientError]) { + break; + } + + HSLogDetail(@"SFTP error (retrying): %@", myError); + [self disconnect]; + [self sleepBeforeRetry]; + } + [myError retain]; + [pool drain]; + [myError autorelease]; + if (!ret) { + SETERRORFROMMYERROR; + } + return ret; +} +- (BOOL)createDirectoryAtPath:(NSString *)thePath withIntermediateDirectories:(BOOL)createIntermediates targetConnectionDelegate:(id )theTargetConnectionDelegate error:(NSError **)error { + sleepTime = INITIAL_RETRY_SLEEP; + BOOL ret = nil; + NSAutoreleasePool *pool = nil; + NSError *myError = nil; + for (;;) { + [pool drain]; + pool = [[NSAutoreleasePool alloc] init]; + myError = nil; + + if (![self connectWithTargetConnectionDelegate:theTargetConnectionDelegate error:&myError]) { + break; + } + + ret = [sftpServer createDirectoryAtPath:thePath withIntermediateDirectories:createIntermediates error:&myError]; + if (ret) { + break; + } + if (![theTargetConnectionDelegate targetConnectionShouldRetryOnTransientError:&myError]) { + break; + } + if (![myError isTransientError]) { + break; + } + + HSLogDetail(@"SFTP error (retrying): %@", myError); + [self disconnect]; + [self sleepBeforeRetry]; + } + [myError retain]; + [pool drain]; + [myError autorelease]; + if (!ret) { + SETERRORFROMMYERROR; + } + return ret; +} +- (NSNumber *)sizeOfItemAtPath:(NSString *)thePath targetConnectionDelegate:(id )theTargetConnectionDelegate error:(NSError **)error { + sleepTime = INITIAL_RETRY_SLEEP; + NSNumber *ret = nil; + NSAutoreleasePool *pool = nil; + NSError *myError = nil; + for (;;) { + [pool drain]; + pool = [[NSAutoreleasePool alloc] init]; + myError = nil; + + if (![self connectWithTargetConnectionDelegate:theTargetConnectionDelegate error:&myError]) { + break; + } + + ret = [sftpServer sizeOfItemAtPath:thePath error:&myError]; + if (ret) { + break; + } + if (![theTargetConnectionDelegate targetConnectionShouldRetryOnTransientError:&myError]) { + break; + } + if (![myError isTransientError]) { + break; + } + + HSLogDetail(@"SFTP error (retrying): %@", myError); + [self disconnect]; + [self sleepBeforeRetry]; + } + [ret retain]; + [myError retain]; + [pool drain]; + [ret autorelease]; + [myError autorelease]; + if (ret == nil) { + SETERRORFROMMYERROR; + } + return ret; +} +- (NSArray *)objectsAtPath:(NSString *)thePath targetConnectionDelegate:(id )theTargetConnectionDelegate error:(NSError **)error { + sleepTime = INITIAL_RETRY_SLEEP; + NSArray *ret = nil; + NSAutoreleasePool *pool = nil; + NSError *myError = nil; + for (;;) { + [pool drain]; + pool = [[NSAutoreleasePool alloc] init]; + myError = nil; + + if (![self connectWithTargetConnectionDelegate:theTargetConnectionDelegate error:&myError]) { + break; + } + + ret = [sftpServer objectsAtPath:thePath error:&myError]; + if (ret) { + break; + } + if (![theTargetConnectionDelegate targetConnectionShouldRetryOnTransientError:&myError]) { + break; + } + if (![myError isTransientError]) { + break; + } + + HSLogDetail(@"SFTP error (retrying): %@", myError); + [self disconnect]; + [self sleepBeforeRetry]; + } + [ret retain]; + [myError retain]; + [pool drain]; + [ret autorelease]; + [myError autorelease]; + if (ret == nil) { + SETERRORFROMMYERROR; + } + return ret; +} +- (NSArray *)pathsOfObjectsAtPath:(NSString *)thePath targetConnectionDelegate:(id )theTargetConnectionDelegate error:(NSError **)error { + sleepTime = INITIAL_RETRY_SLEEP; + NSArray *ret = nil; + NSAutoreleasePool *pool = nil; + NSError *myError = nil; + for (;;) { + [pool drain]; + pool = [[NSAutoreleasePool alloc] init]; + myError = nil; + + if (![self connectWithTargetConnectionDelegate:theTargetConnectionDelegate error:&myError]) { + break; + } + + ret = [sftpServer pathsOfObjectsAtPath:thePath error:&myError]; + if (ret) { + break; + } + if (![theTargetConnectionDelegate targetConnectionShouldRetryOnTransientError:&myError]) { + break; + } + if (![myError isTransientError]) { + break; + } + + HSLogDetail(@"SFTP error (retrying): %@", myError); + [self disconnect]; + [self sleepBeforeRetry]; + } + [ret retain]; + [myError retain]; + [pool drain]; + [ret autorelease]; + [myError autorelease]; + if (ret == nil) { + SETERRORFROMMYERROR; + } + return ret; +} +- (NSNumber *)isObjectRestoredAtPath:(NSString *)thePath targetConnectionDelegate:(id )theTargetConnectionDelegate error:(NSError **)error { + SETNSERROR([self errorDomain], -1, @"isObjectRestoredAtPath is not supported by SFTPRemoteFS"); + return nil; +} +- (BOOL)restoreObjectAtPath:(NSString *)thePath forDays:(NSUInteger)theDays alreadyRestoredOrRestoring:(BOOL *)alreadyRestoredOrRestoring targetConnectionDelegate:(id )theTargetConnectionDelegate error:(NSError **)error { + SETNSERROR([self errorDomain], -1, @"restoreObjectAtPath is not supported by SFTPRemoteFS"); + return NO; +} + + +#pragma mark internal +- (BOOL)connectWithTargetConnectionDelegate:(id )theTargetConnectionDelegate error:(NSError **)error { + sleepTime = INITIAL_RETRY_SLEEP; + BOOL ret = YES; + NSAutoreleasePool *pool = nil; + NSError *myError = nil; + for (;;) { + [pool drain]; + pool = [[NSAutoreleasePool alloc] init]; + myError = nil; + + ret = [self connect:&myError]; + if (ret) { + break; + } + if (![theTargetConnectionDelegate targetConnectionShouldRetryOnTransientError:&myError]) { + break; + } + if (![myError isTransientError]) { + break; + } + + HSLogDetail(@"SFTP error (retrying): %@", myError); + [self sleepBeforeRetry]; + } + [myError retain]; + [pool drain]; + [myError autorelease]; + if (!ret) { + SETERRORFROMMYERROR; + } + return ret; +} +- (BOOL)connect:(NSError **)error { + if (sftpServer == nil) { + NSString *secret = [target secret:error]; + if (secret == nil) { + return NO; + } + + NSString *privateKeyPath = nil; + NSString *password = nil; + NSString *passphrase = nil; + if ([[NSFileManager defaultManager] fileExistsAtPath:[secret stringByExpandingTildeInPath]]) { + privateKeyPath = [secret stringByExpandingTildeInPath]; + } else { + password = secret; + } + + NSError *myError = nil; + passphrase = [target passphrase:&myError]; + if (passphrase == nil) { + if ([myError code] != ERROR_NOT_FOUND) { + SETERRORFROMMYERROR; + return NO; + } + } + sftpServer = [[SFTPServer alloc] initWithURL:[target endpoint] password:password privateKeyPath:privateKeyPath passphrase:passphrase error:error]; + if (sftpServer == nil) { + return NO; + } + } + return YES; +} +- (void)disconnect { + [sftpServer release]; + sftpServer = nil; +} +- (void)sleepBeforeRetry { + HSLogDebug(@"sleeping for %0.1f seconds", sleepTime); + [NSThread sleepForTimeInterval:sleepTime]; + sleepTime *= RETRY_SLEEP_GROWTH_FACTOR; + if (sleepTime > MAX_RETRY_SLEEP) { + sleepTime = MAX_RETRY_SLEEP; + } +} +@end diff --git a/cocoastack/s3/LifecycleConfiguration.h b/cocoastack/s3/LifecycleConfiguration.h new file mode 100644 index 0000000..8a75d50 --- /dev/null +++ b/cocoastack/s3/LifecycleConfiguration.h @@ -0,0 +1,19 @@ +// +// LifecycleConfiguration.h +// Arq +// +// Created by Stefan Reitshamer on 2/21/14. +// Copyright (c) 2014 Stefan Reitshamer. All rights reserved. +// + + +@interface LifecycleConfiguration : NSObject { + NSMutableArray *elementNames; + NSMutableString *currentStringBuffer; + NSMutableArray *ruleIds; + BOOL errorOccurred; + NSError *myError; +} +- (id)initWithData:(NSData *)theData error:(NSError **)error; +- (BOOL)containsRuleWithId:(NSString *)theId; +@end diff --git a/cocoastack/s3/LifecycleConfiguration.m b/cocoastack/s3/LifecycleConfiguration.m new file mode 100644 index 0000000..eb9311b --- /dev/null +++ b/cocoastack/s3/LifecycleConfiguration.m @@ -0,0 +1,73 @@ +// +// LifecycleConfiguration.m +// Arq +// +// Created by Stefan Reitshamer on 2/21/14. +// Copyright (c) 2014 Stefan Reitshamer. All rights reserved. +// + +#import "LifecycleConfiguration.h" + +@implementation LifecycleConfiguration +- (id)initWithData:(NSData *)theData error:(NSError **)error { + if (self = [super init]) { + elementNames = [[NSMutableArray alloc] init]; + ruleIds = [[NSMutableArray alloc] init]; + NSXMLParser *parser = [[NSXMLParser alloc] initWithData:theData]; + [parser setDelegate:self]; + [parser parse]; + [parser release]; + + if (errorOccurred) { + if (error != NULL) { + *error = [[myError retain] autorelease]; + } + [self release]; + return nil; + } + } + return self; +} +- (void)dealloc { + [elementNames release]; + [currentStringBuffer release]; + [ruleIds release]; + [myError release]; + [super dealloc]; +} +- (BOOL)containsRuleWithId:(NSString *)theId { + return [ruleIds containsObject:theId]; +} + + + +#pragma mark - NSXMLParserDelegate +- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName + namespaceURI:(NSString *)namespaceURI + qualifiedName:(NSString *)qualifiedName + attributes:(NSDictionary *)attributeDict { + [elementNames addObject:elementName]; + [currentStringBuffer release]; + currentStringBuffer = nil; +} +- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { + if (currentStringBuffer == nil) { + currentStringBuffer = [[NSMutableString alloc] init]; + } + [currentStringBuffer appendString:string]; +} +- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { + if ([elementNames isEqual:[NSArray arrayWithObjects:@"LifecycleConfiguration", @"Rule", @"ID", nil]]) { + NSString *ruleId = [[currentStringBuffer copy] autorelease]; + [ruleIds addObject:ruleId]; + } + [elementNames removeLastObject]; +} +- (void)parser:(NSXMLParser *)theParser parseErrorOccurred:(NSError *)parseError { + HSLogError(@"error parsing amazon response: %@", parseError); + errorOccurred = YES; + myError = [parseError retain]; +} +- (void)parserDidEndDocument:(NSXMLParser *)parser { +} +@end diff --git a/s3/LocalS3Signer.h b/cocoastack/s3/LocalS3Signer.h similarity index 80% rename from s3/LocalS3Signer.h rename to cocoastack/s3/LocalS3Signer.h index e89dc08..df187cd 100644 --- a/s3/LocalS3Signer.h +++ b/cocoastack/s3/LocalS3Signer.h @@ -3,7 +3,7 @@ // Arq // // Created by Stefan Reitshamer on 12/30/10. -// Copyright 2010 __MyCompanyName__. All rights reserved. +// Copyright 2010 Haystack Software. All rights reserved. // diff --git a/s3/LocalS3Signer.m b/cocoastack/s3/LocalS3Signer.m similarity index 84% rename from s3/LocalS3Signer.m rename to cocoastack/s3/LocalS3Signer.m index 5e4c216..9a1e839 100644 --- a/s3/LocalS3Signer.m +++ b/cocoastack/s3/LocalS3Signer.m @@ -3,7 +3,7 @@ // Arq // // Created by Stefan Reitshamer on 12/30/10. -// Copyright 2010 __MyCompanyName__. All rights reserved. +// Copyright 2010 Haystack Software. All rights reserved. // #import "LocalS3Signer.h" @@ -23,6 +23,7 @@ [super dealloc]; } + #pragma mark S3Signer - (NSString *)sign:(NSString *)theString error:(NSError **)error { NSData *clearTextData = [theString dataUsingEncoding:NSUTF8StringEncoding]; @@ -34,4 +35,10 @@ [hmacSHA1 release]; return base64; } + + +#pragma mark NSCopying +- (id)copyWithZone:(NSZone *)zone { + return [[LocalS3Signer alloc] initWithSecretKey:secretKey]; +} @end diff --git a/s3/PathReceiver.h b/cocoastack/s3/PathReceiver.h similarity index 100% rename from s3/PathReceiver.h rename to cocoastack/s3/PathReceiver.h diff --git a/s3/PathReceiver.m b/cocoastack/s3/PathReceiver.m similarity index 100% rename from s3/PathReceiver.m rename to cocoastack/s3/PathReceiver.m diff --git a/s3/RemoteS3Signer.h b/cocoastack/s3/RemoteS3Signer.h similarity index 87% rename from s3/RemoteS3Signer.h rename to cocoastack/s3/RemoteS3Signer.h index f6b3ae1..193ab08 100644 --- a/s3/RemoteS3Signer.h +++ b/cocoastack/s3/RemoteS3Signer.h @@ -3,7 +3,7 @@ // Arq // // Created by Stefan Reitshamer on 12/30/10. -// Copyright 2010 __MyCompanyName__. All rights reserved. +// Copyright 2010 Haystack Software. All rights reserved. // diff --git a/s3/RemoteS3Signer.m b/cocoastack/s3/RemoteS3Signer.m similarity index 76% rename from s3/RemoteS3Signer.m rename to cocoastack/s3/RemoteS3Signer.m index e30258e..293b608 100644 --- a/s3/RemoteS3Signer.m +++ b/cocoastack/s3/RemoteS3Signer.m @@ -3,14 +3,14 @@ // Arq // // Created by Stefan Reitshamer on 12/30/10. -// Copyright 2010 __MyCompanyName__. All rights reserved. +// Copyright 2010 Haystack Software. All rights reserved. // #import "RemoteS3Signer.h" #import "HTTPConnection.h" -#import "URLConnection.h" +#import "HTTPConnectionFactory.h" #import "NSData-InputStream.h" -#import "SetNSError.h" + #import "HTTP.h" #import "InputStream.h" @@ -45,18 +45,10 @@ #pragma mark S3Signer - (NSString *)sign:(NSString *)theString error:(NSError **)error { - id conn = [[[URLConnection alloc] initWithURL:url method:@"POST" delegate:nil] autorelease]; + id conn = [[[HTTPConnectionFactory theFactory] newHTTPConnectionToURL:url method:@"POST" dataTransferDelegate:nil] autorelease]; [conn setRequestHeader:accessKey forKey:@"X-Arq-AccessKey"]; NSData *requestData = [theString dataUsingEncoding:NSUTF8StringEncoding]; - if (![conn executeRequestWithBody:requestData error:error]) { - return nil; - } - id responseBodyStream = [conn newResponseBodyStream:error]; - if (responseBodyStream == nil) { - return nil; - } - NSData *data = [responseBodyStream slurp:error]; - [responseBodyStream release]; + NSData *data = [conn executeRequestWithBody:requestData error:error]; if (data == nil) { return nil; } diff --git a/s3/S3AuthorizationProvider.h b/cocoastack/s3/S3AuthorizationProvider.h similarity index 100% rename from s3/S3AuthorizationProvider.h rename to cocoastack/s3/S3AuthorizationProvider.h diff --git a/s3/S3AuthorizationProvider.m b/cocoastack/s3/S3AuthorizationProvider.m similarity index 91% rename from s3/S3AuthorizationProvider.m rename to cocoastack/s3/S3AuthorizationProvider.m index f3644b6..f629c26 100644 --- a/s3/S3AuthorizationProvider.m +++ b/cocoastack/s3/S3AuthorizationProvider.m @@ -139,19 +139,26 @@ for (NSString *xamz in xamzHeaders) { [buf appendString:xamz]; } - [buf appendString:[theConnection requestPathInfo]]; + NSString *pathInfo = [theConnection requestPathInfo]; + if (pathInfo != nil) { + [buf appendString:pathInfo]; + } NSString *queryString = [theConnection requestQueryString]; - if ([queryString isEqualToString:@"?acl"] - || [queryString isEqualToString:@"?logging"] - || [queryString isEqualToString:@"?torrent"] - || [queryString isEqualToString:@"?location"]) { + if ([queryString isEqualToString:@"acl"] + || [queryString isEqualToString:@"logging"] + || [queryString isEqualToString:@"torrent"] + || [queryString isEqualToString:@"location"] + || [queryString isEqualToString:@"lifecycle"] + || [queryString isEqualToString:@"restore"] + || [queryString isEqualToString:@"delete"]) { + [buf appendString:@"?"]; [buf appendString:queryString]; } #if 0 { HSLogDebug(@"string to sign: <%@>", buf); const char *stringToSignBytes = [buf UTF8String]; - int stringToSignLen = strlen(stringToSignBytes); + int stringToSignLen = (int)strlen(stringToSignBytes); NSMutableString *displayBytes = [[[NSMutableString alloc] init] autorelease]; for (int i = 0; i < stringToSignLen; i++) { [displayBytes appendString:[NSString stringWithFormat:@"%02x ", stringToSignBytes[i]]]; diff --git a/crypto/OpenSSL.h b/cocoastack/s3/S3DeleteReceiver.h similarity index 90% rename from crypto/OpenSSL.h rename to cocoastack/s3/S3DeleteReceiver.h index 67bf1d6..b107106 100644 --- a/crypto/OpenSSL.h +++ b/cocoastack/s3/S3DeleteReceiver.h @@ -31,13 +31,12 @@ */ -#import - - -@interface OpenSSL : NSObject { +#import "S3Receiver.h" +@class S3Service; +@interface S3DeleteReceiver : NSObject { + S3Service *s3; } -+ (BOOL)initializeSSL:(NSError **)error; -+ (SSL_CTX *)context; -+ (NSString *)errorMessage; +- (id)initWithS3Service:(S3Service *)theS3; + @end diff --git a/io/DataInputStreamFactory.m b/cocoastack/s3/S3DeleteReceiver.m similarity index 66% rename from io/DataInputStreamFactory.m rename to cocoastack/s3/S3DeleteReceiver.m index 0480fe4..426983a 100644 --- a/io/DataInputStreamFactory.m +++ b/cocoastack/s3/S3DeleteReceiver.m @@ -30,33 +30,36 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#import "S3DeleteReceiver.h" +#import "S3Service.h" - -#import "DataInputStreamFactory.h" -#import "DataInputStream.h" -#import "NSData-InputStream.h" - -@implementation DataInputStreamFactory -- (id)initWithData:(NSData *)theData dataDescription:(NSString *)theDataDescription { - if (self = [super init]) { - data = [theData retain]; - dataDescription = [theDataDescription retain]; - } - return self; +@implementation S3DeleteReceiver +- (id)init { + @throw [NSException exceptionWithName:@"InvalidInitializerException" reason:@"wrong S3DeleteReceiver initializer called" userInfo:nil]; +} +- (id)initWithS3Service:(S3Service *)theS3 { + if (self = [super init]) { + s3 = [theS3 retain]; + } + return self; } - (void)dealloc { - [data release]; - [dataDescription release]; - [super dealloc]; + [s3 release]; + [super dealloc]; } - -#pragma mark InputStreamFactory protocol -- (id ) newInputStream { - return [data newInputStream]; -} - -#pragma mark NSObject -- (NSString *)description { - return [NSString stringWithFormat:@"", [data length], dataDescription]; +- (BOOL)receiveS3ObjectMetadata:(S3ObjectMetadata *)metadata error:(NSError **)error { + if (error != NULL) { + *error = nil; + } + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + BOOL ret = [s3 deletePath:[metadata path] targetConnectionDelegate:nil error:error]; + if (error != NULL) { + [*error retain]; + } + [pool drain]; + if (error != NULL) { + [*error autorelease]; + } + return ret; } @end diff --git a/cocoastack/s3/S3ErrorResult.h b/cocoastack/s3/S3ErrorResult.h new file mode 100644 index 0000000..84994bc --- /dev/null +++ b/cocoastack/s3/S3ErrorResult.h @@ -0,0 +1,18 @@ +// +// AmazonErrorResult.h +// +// Created by Stefan Reitshamer on 3/9/12. +// Copyright (c) 2012 Haystack Software. All rights reserved. +// + + +@interface S3ErrorResult : NSObject { + NSMutableDictionary *values; + NSMutableString *currentStringBuffer; + BOOL errorOccurred; + NSError *amazonError; +} +- (id)initWithAction:(NSString *)theAction data:(NSData *)theData httpErrorCode:(int)theHTTPStatusCode; + +- (NSError *)error; +@end diff --git a/cocoastack/s3/S3ErrorResult.m b/cocoastack/s3/S3ErrorResult.m new file mode 100644 index 0000000..0614401 --- /dev/null +++ b/cocoastack/s3/S3ErrorResult.m @@ -0,0 +1,87 @@ +// +// AmazonErrorResult.m +// +// Created by Stefan Reitshamer on 3/9/12. +// Copyright (c) 2012 Haystack Software. All rights reserved. +// + +#import "S3ErrorResult.h" +#import "S3Service.h" + + +@implementation S3ErrorResult +- (id)initWithAction:(NSString *)theAction data:(NSData *)theData httpErrorCode:(int)theHTTPStatusCode { + if (self = [super init]) { + values = [[NSMutableDictionary alloc] init]; + NSXMLParser *parser = [[NSXMLParser alloc] initWithData:theData]; + [parser setDelegate:self]; + [parser parse]; + [parser release]; + if (errorOccurred) { + HSLogDebug(@"error parsing amazon result %@", [[[NSString alloc] initWithData:theData encoding:NSUTF8StringEncoding] autorelease]); + if (theHTTPStatusCode == 500) { + // DreamObjects can return a 500 with an HTML response body, so we fake it as an Amazon XML error response so that S3Request retries the request: + NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:500], @"HTTPStatusCode", @"InternalError", @"AmazonCode", nil]; + amazonError = [[NSError errorWithDomain:[S3Service errorDomain] code:S3SERVICE_ERROR_AMAZON_ERROR userInfo:userInfo] retain]; + } else { + amazonError = [[NSError errorWithDomain:[S3Service errorDomain] code:S3SERVICE_ERROR_AMAZON_ERROR description:[NSString stringWithFormat:@"%@: AWS error", theAction]] retain]; + } + } else { + NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; + // Typically we have 'Code', 'Message', and 'Resource' keys in userInfo. + // We create an error with 'AmazonCode', 'AmazonMessage', 'AmazonResource' and NSLocalizedDescriptionKey. + [userInfo setObject:[NSNumber numberWithInt:theHTTPStatusCode] forKey:@"HTTPStatusCode"]; + for (NSString *key in [values allKeys]) { + [userInfo setObject:[values objectForKey:key] forKey:[@"Amazon" stringByAppendingString:key]]; + } + NSString *msg = nil; + if ([values objectForKey:@"Message"] == nil) { + msg = [NSString stringWithFormat:@"S3 error %ld: %@", (unsigned long)theHTTPStatusCode, [userInfo objectForKey:@"AmazonCode"]]; + } else { + msg = [NSString stringWithFormat:@"%@: %@", theAction, [values objectForKey:@"Message"]]; + } + [userInfo setObject:msg forKey:NSLocalizedDescriptionKey]; + amazonError = [[NSError errorWithDomain:[S3Service errorDomain] code:S3SERVICE_ERROR_AMAZON_ERROR userInfo:userInfo] retain]; + } + } + return self; +} +- (void)dealloc { + [values release]; + [currentStringBuffer release]; + [amazonError release]; + [super dealloc]; +} + +- (NSError *)error { + return amazonError; +} + + +#pragma mark - NSXMLParserDelegate +- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName + namespaceURI:(NSString *)namespaceURI + qualifiedName:(NSString *)qualifiedName + attributes:(NSDictionary *)attributeDict { + [currentStringBuffer release]; + currentStringBuffer = nil; +} +- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { + if (currentStringBuffer == nil) { + currentStringBuffer = [[NSMutableString alloc] init]; + } + [currentStringBuffer appendString:string]; +} +- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { + if (currentStringBuffer != nil) { + [values setObject:[NSString stringWithString:currentStringBuffer] forKey:elementName]; + } +} +- (void)parser:(NSXMLParser *)theParser parseErrorOccurred:(NSError *)parseError { + errorOccurred = YES; + HSLogError(@"error parsing amazon error response: %@", parseError); +} +- (void)parserDidEndDocument:(NSXMLParser *)parser { +} + +@end diff --git a/s3/S3Lister.h b/cocoastack/s3/S3Lister.h similarity index 86% rename from s3/S3Lister.h rename to cocoastack/s3/S3Lister.h index 1077bb1..c8d1e34 100644 --- a/s3/S3Lister.h +++ b/cocoastack/s3/S3Lister.h @@ -34,12 +34,13 @@ #import "S3Receiver.h" @class RFC2616DateFormatter; @class S3AuthorizationProvider; +@protocol TargetConnectionDelegate; + @interface S3Lister : NSObject { RFC2616DateFormatter *dateFormatter; S3AuthorizationProvider *sap; - BOOL useSSL; - BOOL retryOnTransientError; + NSURL *endpoint; int received; BOOL isTruncated; NSString *prefix; @@ -48,7 +49,7 @@ NSString *marker; NSMutableArray *foundPrefixes; } -- (id)initWithS3AuthorizationProvider:(S3AuthorizationProvider *)theSAP useSSL:(BOOL)isUseSSL retryOnTransientError:(BOOL)retry prefix:(NSString *)thePrefix delimiter:(NSString *)theDelimiter receiver:(id)theReceiver; -- (BOOL)listObjects:(NSError **)error; +- (id)initWithS3AuthorizationProvider:(S3AuthorizationProvider *)theSAP endpoint:(NSURL *)theEndpoint prefix:(NSString *)thePrefix delimiter:(NSString *)theDelimiter receiver:(id)theReceiver; +- (BOOL)listObjectsWithTargetConnectionDelegate:(id )theDelegate error:(NSError **)error; - (NSArray *)foundPrefixes; @end diff --git a/s3/S3Lister.m b/cocoastack/s3/S3Lister.m similarity index 66% rename from s3/S3Lister.m rename to cocoastack/s3/S3Lister.m index 0167935..e9307a0 100644 --- a/s3/S3Lister.m +++ b/cocoastack/s3/S3Lister.m @@ -31,25 +31,19 @@ */ #import "RFC2616DateFormatter.h" -#import "NSError_S3.h" #import "S3AuthorizationProvider.h" #import "S3Lister.h" -#import "SetNSError.h" #import "HTTP.h" #import "S3Service.h" #import "S3Request.h" -@interface S3Lister (internal) -- (BOOL)get:(NSError **)error; -@end @implementation S3Lister -- (id)initWithS3AuthorizationProvider:(S3AuthorizationProvider *)theSAP useSSL:(BOOL)isUseSSL retryOnTransientError:(BOOL)retry prefix:(NSString *)thePrefix delimiter:(NSString *)theDelimiter receiver:(id)theReceiver { +- (id)initWithS3AuthorizationProvider:(S3AuthorizationProvider *)theSAP endpoint:(NSURL *)theEndpoint prefix:(NSString *)thePrefix delimiter:(NSString *)theDelimiter receiver:(id)theReceiver { if (self = [super init]) { dateFormatter = [[RFC2616DateFormatter alloc] init]; sap = [theSAP retain]; - useSSL = isUseSSL; - retryOnTransientError = retry; + endpoint = [theEndpoint retain]; received = 0; isTruncated = YES; prefix = [thePrefix copy]; @@ -62,6 +56,7 @@ } - (void)dealloc { [dateFormatter release]; + [endpoint release]; [sap release]; [prefix release]; [delimiter release]; @@ -70,7 +65,7 @@ [foundPrefixes release]; [super dealloc]; } -- (BOOL)listObjects:(NSError **)error { +- (BOOL)listObjectsWithTargetConnectionDelegate:(id)theDelegate error:(NSError **)error { if (error != NULL) { *error = nil; } @@ -80,17 +75,17 @@ while (isTruncated) { [pool drain]; pool = [[NSAutoreleasePool alloc] init]; - if (![self get:error]) { + if (![self getWithTargetConnectionDelegate:theDelegate error:error]) { ret = NO; break; } } - if (error != NULL) { + if (!ret && error != NULL) { [*error retain]; } [pool drain]; - if (error != NULL) { + if (!ret && error != NULL) { [*error autorelease]; } return ret; @@ -98,10 +93,9 @@ - (NSArray *)foundPrefixes { return foundPrefixes; } -@end - -@implementation S3Lister (internal) -- (BOOL)get:(NSError **)error { + +#pragma mark internal +- (BOOL)getWithTargetConnectionDelegate:(id )theDelegate error:(NSError **)error { if (![prefix hasPrefix:@"/"]) { SETNSERROR([S3Service errorDomain], S3SERVICE_INVALID_PARAMETERS, @"path must start with /"); return NO; @@ -114,7 +108,7 @@ } NSString *s3BucketName = [strippedPrefix substringToIndex:range.location]; NSString *pathPrefix = [strippedPrefix substringFromIndex:range.location]; - NSMutableString *queryString = [NSMutableString stringWithFormat:@"?prefix=%@", [[pathPrefix substringFromIndex:1] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; + NSMutableString *queryString = [NSMutableString stringWithFormat:@"prefix=%@", [[pathPrefix substringFromIndex:1] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; if (delimiter != nil) { [queryString appendString:@"&delimiter="]; [queryString appendString:[delimiter stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; @@ -125,55 +119,46 @@ [queryString appendString:[NSString stringWithFormat:@"&marker=%@", [suffix stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]]; } - S3Request *s3r = [[S3Request alloc] initWithMethod:@"GET" path:[NSString stringWithFormat:@"/%@/", s3BucketName] queryString:queryString authorizationProvider:sap useSSL:useSSL retryOnTransientError:retryOnTransientError error:error]; + S3Request *s3r = [[[S3Request alloc] initWithMethod:@"GET" endpoint:endpoint path:[NSString stringWithFormat:@"/%@/", s3BucketName] queryString:queryString authorizationProvider:sap error:error] autorelease]; if (s3r == nil) { return NO; } - ServerBlob *sb = [s3r newServerBlob:error]; - [s3r release]; - if (sb == nil) { - return NO; - } - NSData *data = [sb slurp:error]; - [sb release]; - if (data == nil) { + NSData *response = [s3r dataWithTargetConnectionDelegate:theDelegate error:error]; + if (response == nil) { return NO; } NSError *myError = nil; - NSXMLDocument *xmlDoc = [[[NSXMLDocument alloc] initWithData:data options:0 error:&myError] autorelease]; - if (!xmlDoc) { - SETNSERROR([S3Service errorDomain], [myError code], @"error parsing List Objects XML response: %@", myError); + NSArray *listBucketResultContents = [self parseXMLResponse:response s3BucketName:s3BucketName error:&myError]; + if (listBucketResultContents == nil && myError == nil) { + [NSThread sleepForTimeInterval:0.2]; + listBucketResultContents = [self parseXMLResponse:response s3BucketName:s3BucketName error:&myError]; + } + if (listBucketResultContents == nil) { + if (myError == nil) { + myError = [NSError errorWithDomain:[S3Service errorDomain] code:-1 description:@"Failed to parse ListBucketResult XML response"]; + } + SETERRORFROMMYERROR; + if (error != NULL) { + HSLogError(@"error getting //ListBucketResult/Contents nodes: %@", *error); + } return NO; } + if (listBucketResultContents == nil) { + return NO; + } + NSString *lastPath = nil; - NSXMLElement *rootElement = [xmlDoc rootElement]; - NSArray *isTruncatedNodes = [rootElement nodesForXPath:@"//ListBucketResult/IsTruncated" error:error]; - if (!isTruncatedNodes || [isTruncatedNodes count] == 0) { - return NO; - } - isTruncated = [[[isTruncatedNodes objectAtIndex:0] stringValue] isEqualToString:@"true"]; - if (delimiter != nil) { - NSArray *prefixNodes = [rootElement nodesForXPath:@"//ListBucketResult/CommonPrefixes/Prefix" error:error]; - if (prefixNodes == nil) { - return NO; - } - for (NSXMLNode *prefixNode in prefixNodes) { - NSString *thePrefix = [prefixNode stringValue]; - thePrefix = [thePrefix substringToIndex:([thePrefix length] - 1)]; - [foundPrefixes addObject:[NSString stringWithFormat:@"/%@/%@", s3BucketName, thePrefix]]; - } - } - NSArray *objects = [rootElement nodesForXPath:@"//ListBucketResult/Contents" error:error]; - if (!objects) { - return NO; - } - for (NSXMLNode *objectNode in objects) { + + for (NSXMLNode *objectNode in listBucketResultContents) { S3ObjectMetadata *md = [[S3ObjectMetadata alloc] initWithS3BucketName:s3BucketName node:objectNode error:error]; if (!md) { return NO; } if (![receiver receiveS3ObjectMetadata:md error:error]) { [md release]; + if (error != NULL) { + HSLogError(@"error receiving object metadata: %@", *error); + } return NO; } lastPath = [[[md path] retain] autorelease]; @@ -186,4 +171,40 @@ } return YES; } + +- (NSArray *)parseXMLResponse:(NSData *)response s3BucketName:(NSString *)s3BucketName error:(NSError **)error { + NSError *myError = nil; + NSXMLDocument *xmlDoc = [[[NSXMLDocument alloc] initWithData:response options:0 error:&myError] autorelease]; + if (!xmlDoc) { + SETNSERROR([S3Service errorDomain], [myError code], @"error parsing List Objects XML response: %@", myError); + return NO; + } + NSXMLElement *rootElement = [xmlDoc rootElement]; + NSArray *isTruncatedNodes = [rootElement nodesForXPath:@"//ListBucketResult/IsTruncated" error:&myError]; + if (isTruncatedNodes == nil) { + HSLogError(@"nodesForXPath: %@", myError); + SETERRORFROMMYERROR; + return NO; + } + if ([isTruncatedNodes count] == 0) { + isTruncated = NO; + } else { + isTruncated = [[[isTruncatedNodes objectAtIndex:0] stringValue] isEqualToString:@"true"]; + } + if (delimiter != nil) { + NSArray *prefixNodes = [rootElement nodesForXPath:@"//ListBucketResult/CommonPrefixes/Prefix" error:error]; + if (prefixNodes == nil) { + if (error != NULL) { + HSLogError(@"error getting //ListBucketResult/CommonPrefixes/Prefix nodes: %@", *error); + } + return NO; + } + for (NSXMLNode *prefixNode in prefixNodes) { + NSString *thePrefix = [prefixNode stringValue]; + thePrefix = [thePrefix substringToIndex:([thePrefix length] - 1)]; + [foundPrefixes addObject:[NSString stringWithFormat:@"/%@/%@", s3BucketName, thePrefix]]; + } + } + return [rootElement nodesForXPath:@"//ListBucketResult/Contents" error:error]; +} @end diff --git a/cocoastack/s3/S3MultiDeleteResponse.h b/cocoastack/s3/S3MultiDeleteResponse.h new file mode 100644 index 0000000..a18d516 --- /dev/null +++ b/cocoastack/s3/S3MultiDeleteResponse.h @@ -0,0 +1,21 @@ +// +// S3MultiDeleteResponse.h +// Arq +// +// Created by Stefan Reitshamer on 1/13/14. +// Copyright (c) 2014 Stefan Reitshamer. All rights reserved. +// + + +@interface S3MultiDeleteResponse : NSObject { + NSMutableArray *elementNames; + NSMutableString *currentStringBuffer; + NSString *errorKey; + NSString *errorCode; + NSString *errorMessage; + NSMutableDictionary *errorCodesByPath; +} +- (id)initWithData:(NSData *)theData; + +- (NSDictionary *)errorCodesByPath; +@end diff --git a/cocoastack/s3/S3MultiDeleteResponse.m b/cocoastack/s3/S3MultiDeleteResponse.m new file mode 100644 index 0000000..5c22ca3 --- /dev/null +++ b/cocoastack/s3/S3MultiDeleteResponse.m @@ -0,0 +1,78 @@ +// +// S3MultiDeleteResponse.m +// Arq +// +// Created by Stefan Reitshamer on 1/13/14. +// Copyright (c) 2014 Stefan Reitshamer. All rights reserved. +// + +#import "S3MultiDeleteResponse.h" + +@implementation S3MultiDeleteResponse +- (id)initWithData:(NSData *)theData { + if (self = [super init]) { + errorCodesByPath = [[NSMutableDictionary alloc] init]; + + elementNames = [[NSMutableArray alloc] init]; + NSXMLParser *parser = [[NSXMLParser alloc] initWithData:theData]; + [parser setDelegate:self]; + [parser parse]; + [parser release]; + } + return self; +} +- (void)dealloc { + [elementNames release]; + [currentStringBuffer release]; + [errorKey release]; + [errorCode release]; + [errorMessage release]; + [errorCodesByPath release]; + [super dealloc]; +} +- (NSDictionary *)errorCodesByPath { + return errorCodesByPath; +} + + +#pragma mark - NSXMLParserDelegate +- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName + namespaceURI:(NSString *)namespaceURI + qualifiedName:(NSString *)qualifiedName + attributes:(NSDictionary *)attributeDict { + [elementNames addObject:elementName]; + [currentStringBuffer release]; + currentStringBuffer = nil; +} +- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { + if (currentStringBuffer == nil) { + currentStringBuffer = [[NSMutableString alloc] init]; + } + [currentStringBuffer appendString:string]; +} +- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { + if ([elementNames isEqual:[NSArray arrayWithObjects:@"DeleteResult", @"Error", @"Key", nil]]) { + [errorKey release]; + errorKey = [currentStringBuffer copy]; + } + if ([elementNames isEqual:[NSArray arrayWithObjects:@"DeleteResult", @"Error", @"Code", nil]]) { + [errorCode release]; + errorCode = [currentStringBuffer copy]; + } + if ([elementNames isEqual:[NSArray arrayWithObjects:@"DeleteResult", @"Error", @"Message", nil]]) { + [errorMessage release]; + errorMessage = [currentStringBuffer copy]; + } + if ([elementNames isEqual:[NSArray arrayWithObjects:@"DeleteResult", @"Error", nil]]) { + HSLogError(@"%@: %@ (%@)", errorKey, errorCode, errorMessage); + [errorCodesByPath setObject:errorCode forKey:errorKey]; + } + [elementNames removeLastObject]; +} +- (void)parser:(NSXMLParser *)theParser parseErrorOccurred:(NSError *)parseError { + HSLogError(@"error parsing amazon response: %@", parseError); +} +- (void)parserDidEndDocument:(NSXMLParser *)parser { +} + +@end diff --git a/s3/S3ObjectMetadata.h b/cocoastack/s3/S3ObjectMetadata.h similarity index 100% rename from s3/S3ObjectMetadata.h rename to cocoastack/s3/S3ObjectMetadata.h diff --git a/s3/S3ObjectMetadata.m b/cocoastack/s3/S3ObjectMetadata.m similarity index 95% rename from s3/S3ObjectMetadata.m rename to cocoastack/s3/S3ObjectMetadata.m index afd8e1d..b54155b 100644 --- a/s3/S3ObjectMetadata.m +++ b/cocoastack/s3/S3ObjectMetadata.m @@ -69,8 +69,12 @@ if (!nodes) { goto init_error; } - NSXMLNode *storageClassNode = [nodes objectAtIndex:0]; - storageClass = [[storageClassNode stringValue] retain]; + if ([nodes count] == 0) { + storageClass = @"STANDARD"; + } else { + NSXMLNode *storageClassNode = [nodes objectAtIndex:0]; + storageClass = [[storageClassNode stringValue] retain]; + } goto init_done; init_error: [self release]; diff --git a/s3/S3ObjectReceiver.h b/cocoastack/s3/S3ObjectReceiver.h similarity index 100% rename from s3/S3ObjectReceiver.h rename to cocoastack/s3/S3ObjectReceiver.h diff --git a/s3/S3ObjectReceiver.m b/cocoastack/s3/S3ObjectReceiver.m similarity index 100% rename from s3/S3ObjectReceiver.m rename to cocoastack/s3/S3ObjectReceiver.m diff --git a/s3/S3Owner.h b/cocoastack/s3/S3Owner.h similarity index 100% rename from s3/S3Owner.h rename to cocoastack/s3/S3Owner.h diff --git a/s3/S3Owner.m b/cocoastack/s3/S3Owner.m similarity index 100% rename from s3/S3Owner.m rename to cocoastack/s3/S3Owner.m diff --git a/s3/S3Receiver.h b/cocoastack/s3/S3Receiver.h similarity index 100% rename from s3/S3Receiver.h rename to cocoastack/s3/S3Receiver.h diff --git a/shared/FileACL.h b/cocoastack/s3/S3Request.h similarity index 57% rename from shared/FileACL.h rename to cocoastack/s3/S3Request.h index 894c0d9..4a2896e 100644 --- a/shared/FileACL.h +++ b/cocoastack/s3/S3Request.h @@ -31,10 +31,27 @@ */ +@class S3AuthorizationProvider; +@protocol DataTransferDelegate; +@protocol TargetConnectionDelegate; -@interface FileACL : NSObject { +@interface S3Request : NSObject { + NSString *method; + NSURL *url; + S3AuthorizationProvider *sap; + id dataTransferDelegate; + NSData *requestBody; + NSMutableDictionary *extraRequestHeaders; + unsigned long long bytesUploaded; + int httpResponseCode; + NSMutableDictionary *responseHeaders; } -+ (BOOL)aclText:(NSString **)aclText forFile:(NSString *)path error:(NSError **)error; -+ (BOOL)writeACLText:(NSString *)aclText toFile:(NSString *)path error:(NSError **)error; +- (id)initWithMethod:(NSString *)theMethod endpoint:(NSURL *)theEndpoint path:(NSString *)thePath queryString:(NSString *)theQueryString authorizationProvider:(S3AuthorizationProvider *)theSAP error:(NSError **)error; +- (id)initWithMethod:(NSString *)theMethod endpoint:(NSURL *)theEndpoint path:(NSString *)thePath queryString:(NSString *)theQueryString authorizationProvider:(S3AuthorizationProvider *)theSAP dataTransferDelegate:(id )theDelegate error:(NSError **)error; +- (void)setRequestBody:(NSData *)theRequestBody; +- (void)setRequestHeader:(NSString *)value forKey:(NSString *)key; +- (int)httpResponseCode; +- (NSString *)responseHeaderForKey:(NSString *)theKey; +- (NSData *)dataWithTargetConnectionDelegate:(id )theDelegate error:(NSError **)error; @end diff --git a/cocoastack/s3/S3Request.m b/cocoastack/s3/S3Request.m new file mode 100644 index 0000000..03b94e7 --- /dev/null +++ b/cocoastack/s3/S3Request.m @@ -0,0 +1,264 @@ +/* + Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import "S3Request.h" +#import "HTTP.h" +#import "URLConnection.h" +#import "S3Service.h" +#import "RegexKitLite.h" +#import "NSError_extra.h" +#import "S3AuthorizationProvider.h" +#import "S3ErrorResult.h" +#import "AWSRegion.h" +#import "HTTPConnectionFactory.h" +#import "AWSRegion.h" +#import "TargetConnection.h" + + +#define INITIAL_RETRY_SLEEP (0.5) +#define RETRY_SLEEP_GROWTH_FACTOR (1.5) +#define MAX_RETRY_SLEEP (5.0) + + +@implementation S3Request +- (id)initWithMethod:(NSString *)theMethod endpoint:(NSURL *)theEndpoint path:(NSString *)thePath queryString:(NSString *)theQueryString authorizationProvider:(S3AuthorizationProvider *)theSAP error:(NSError **)error { + return [self initWithMethod:theMethod endpoint:theEndpoint path:thePath queryString:theQueryString authorizationProvider:theSAP dataTransferDelegate:nil error:error]; +} +- (id)initWithMethod:(NSString *)theMethod endpoint:(NSURL *)theEndpoint path:(NSString *)thePath queryString:(NSString *)theQueryString authorizationProvider:(S3AuthorizationProvider *)theSAP dataTransferDelegate:(id)theDelegate error:(NSError **)error { + if (self = [super init]) { + method = [theMethod copy]; + sap = [theSAP retain]; + dataTransferDelegate = theDelegate; // Don't retain it. + extraRequestHeaders = [[NSMutableDictionary alloc] init]; + responseHeaders = [[NSMutableDictionary alloc] init]; + + if (theQueryString != nil) { + if ([theQueryString hasPrefix:@"?"]) { + SETNSERROR([S3Service errorDomain], -1, @"query string may not begin with a ?"); + [self release]; + return nil; + } + thePath = [[thePath stringByAppendingString:@"?"] stringByAppendingString:theQueryString]; + } + NSString *urlString = [NSString stringWithFormat:@"%@%@", [theEndpoint description], thePath]; + url = [[NSURL alloc] initWithString:urlString]; + if (url == nil) { + SETNSERROR([S3Service errorDomain], -1, @"invalid URL: %@", urlString); + [self release]; + return nil; + } + } + return self; +} +- (void)dealloc { + [method release]; + [url release]; + [sap release]; + [requestBody release]; + [extraRequestHeaders release]; + [responseHeaders release]; + [super dealloc]; +} +- (void)setRequestBody:(NSData *)theRequestBody { + [theRequestBody retain]; + [requestBody release]; + requestBody = theRequestBody; +} +- (void)setRequestHeader:(NSString *)value forKey:(NSString *)key { + [extraRequestHeaders setObject:value forKey:key]; +} +- (int)httpResponseCode { + return httpResponseCode; +} +- (NSString *)responseHeaderForKey:(NSString *)theKey { + return [responseHeaders objectForKey:theKey]; +} +- (NSData *)dataWithTargetConnectionDelegate:(id)theDelegate error:(NSError **)error { + NSAutoreleasePool *pool = nil; + NSTimeInterval sleepTime = INITIAL_RETRY_SLEEP; + NSData *responseData = nil; + NSError *myError = nil; + for (;;) { + [pool drain]; + pool = [[NSAutoreleasePool alloc] init]; + BOOL needRetry = NO; + BOOL needSleep = NO; + myError = nil; + responseData = [self dataOnce:&myError]; + if (responseData != nil) { + break; + } + + HSLogDebug(@"S3Request dataOnce failed; %@", myError); + + if ([myError isErrorWithDomain:[S3Service errorDomain] code:ERROR_NOT_FOUND]) { + break; + } + if ([myError isErrorWithDomain:[S3Service errorDomain] code:ERROR_TEMPORARY_REDIRECT]) { + NSString *location = [[myError userInfo] objectForKey:@"location"]; + HSLogDebug(@"redirecting %@ to %@", url, location); + [url release]; + url = [[NSURL alloc] initWithString:location]; + if (url == nil) { + HSLogError(@"invalid redirect URL %@", location); + myError = [NSError errorWithDomain:[S3Service errorDomain] code:-1 description:[NSString stringWithFormat:@"invalid redirect URL %@", location]]; + break; + } + needRetry = YES; + } else if ([myError isErrorWithDomain:[S3Service errorDomain] code:S3SERVICE_ERROR_AMAZON_ERROR]) { + int httpStatusCode = [[[myError userInfo] objectForKey:@"HTTPStatusCode"] intValue]; + NSString *amazonCode = [[myError userInfo] objectForKey:@"AmazonCode"]; + + if ([amazonCode isEqualToString:@"RequestTimeout"] || [amazonCode isEqualToString:@"RequestTimeoutException"]) { + needRetry = YES; + } else if (httpStatusCode == HTTP_INTERNAL_SERVER_ERROR) { + needRetry = YES; + needSleep = YES; + } else if (httpStatusCode == HTTP_SERVICE_NOT_AVAILABLE) { + needRetry = YES; + needSleep = YES; + } else if (httpStatusCode == HTTP_CONFLICT && [amazonCode isEqualToString:@"OperationAborted"]) { + // "A conflicting conditional operation is currently in progress against this resource. Please try again." + // Happens sometimes when putting bucket lifecycle policy. + needRetry = YES; + needSleep = YES; + } + } else if ([myError isConnectionResetError]) { + needRetry = YES; + } else if ([myError isTransientError]) { + needRetry = YES; + needSleep = YES; + } + + if (!needRetry || ![theDelegate targetConnectionShouldRetryOnTransientError:&myError]) { + HSLogError(@"%@ %@: %@", method, url, myError); + break; + } + + HSLogDetail(@"retrying %@ %@: %@", method, url, myError); + if (needSleep) { + [NSThread sleepForTimeInterval:sleepTime]; + sleepTime *= RETRY_SLEEP_GROWTH_FACTOR; + if (sleepTime > MAX_RETRY_SLEEP) { + sleepTime = MAX_RETRY_SLEEP; + } + } + } + [responseData retain]; + if (responseData == nil) { + [myError retain]; + } + [pool drain]; + [responseData autorelease]; + if (responseData == nil) { + [myError autorelease]; + SETERRORFROMMYERROR; + } + + return responseData; +} + + +#pragma mark internal +- (NSData *)dataOnce:(NSError **)error { + id conn = [[[HTTPConnectionFactory theFactory] newHTTPConnectionToURL:url method:method dataTransferDelegate:dataTransferDelegate] autorelease]; + if (conn == nil) { + return nil; + } + [conn setRequestHostHeader]; + [conn setRFC822DateRequestHeader]; + if (requestBody != nil) { + [conn setRequestHeader:[NSString stringWithFormat:@"%lu", (unsigned long)[requestBody length]] forKey:@"Content-Length"]; + } + for (NSString *headerKey in [extraRequestHeaders allKeys]) { + [conn setRequestHeader:[extraRequestHeaders objectForKey:headerKey] forKey:headerKey]; + } + if (![sap setAuthorizationRequestHeaderOnHTTPConnection:conn error:error]) { + return nil; + } + bytesUploaded = 0; + + HSLogDebug(@"%@ %@", method, url); + + NSData *response = [conn executeRequestWithBody:requestBody error:error]; + if (response == nil) { + return nil; + } + + [responseHeaders setDictionary:[conn responseHeaders]]; + + httpResponseCode = [conn responseCode]; + if (httpResponseCode >= 200 && httpResponseCode <= 299) { + HSLogDebug(@"HTTP %d; returning response length=%ld", httpResponseCode, (long)[response length]); + return response; + } + + HSLogTrace(@"http response body: %@", [[[NSString alloc] initWithBytes:[response bytes] length:[response length] encoding:NSUTF8StringEncoding] autorelease]); + if (httpResponseCode == HTTP_NOT_FOUND) { + HSLogDebug(@"http response body: %@", [[[NSString alloc] initWithBytes:[response bytes] length:[response length] encoding:NSUTF8StringEncoding] autorelease]); + S3ErrorResult *errorResult = [[[S3ErrorResult alloc] initWithAction:[NSString stringWithFormat:@"%@ %@", method, [url description]] data:response httpErrorCode:httpResponseCode] autorelease]; + NSError *myError = [errorResult error]; + NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:[myError userInfo]]; + [userInfo setObject:[NSString stringWithFormat:@"%@ not found", url] forKey:NSLocalizedDescriptionKey]; + myError = [NSError errorWithDomain:[S3Service errorDomain] code:ERROR_NOT_FOUND userInfo:userInfo]; + HSLogDebug(@"%@", myError); + SETERRORFROMMYERROR; + return nil; + } + if (httpResponseCode == HTTP_METHOD_NOT_ALLOWED) { + HSLogError(@"%@ 405 error", url); + SETNSERROR([S3Service errorDomain], ERROR_RRS_NOT_FOUND, @"%@ 405 error", url); + } + if (httpResponseCode == HTTP_MOVED_TEMPORARILY) { + NSString *location = [conn responseHeaderForKey:@"Location"]; + NSDictionary *userInfo = [NSDictionary dictionaryWithObject:location forKey:@"location"]; + NSError *myError = [NSError errorWithDomain:[S3Service errorDomain] code:ERROR_TEMPORARY_REDIRECT userInfo:userInfo]; + if (error != NULL) { + *error = myError; + } + HSLogDebug(@"returning moved-temporarily error"); + return nil; + } + S3ErrorResult *errorResult = [[[S3ErrorResult alloc] initWithAction:[NSString stringWithFormat:@"%@ %@", method, [url description]] data:response httpErrorCode:httpResponseCode] autorelease]; + NSError *myError = [errorResult error]; + HSLogDebug(@"%@ error: %@", conn, myError); + SETERRORFROMMYERROR; + + if ([[[myError userInfo] objectForKey:@"AmazonCode"] isEqualToString:@"MalformedHeaderValue"]) { + HSLogDebug(@"request headers:"); + for (NSString *headerKey in [conn requestHeaderKeys]) { + HSLogDebug(@"header: %@ = %@", headerKey, [conn requestHeaderForKey:headerKey]); + } + } + return nil; +} +@end diff --git a/cocoastack/s3/S3Service.h b/cocoastack/s3/S3Service.h new file mode 100644 index 0000000..c65d83e --- /dev/null +++ b/cocoastack/s3/S3Service.h @@ -0,0 +1,96 @@ +/* + Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +#import "S3Receiver.h" +#import "InputStream.h" +@class S3AuthorizationProvider; +@class S3Owner; +@protocol DataTransferDelegate; +@protocol TargetConnectionDelegate; + + +#define S3_INITIAL_RETRY_SLEEP (0.5) +#define S3_RETRY_SLEEP_GROWTH_FACTOR (1.5) +#define S3_MAX_RETRY (5) + +extern NSString *kS3StorageClassStandard; +extern NSString *kS3StorageClassReducedRedundancy; + + +enum { + S3SERVICE_ERROR_UNEXPECTED_RESPONSE = -51001, + S3SERVICE_ERROR_AMAZON_ERROR = -51002, + S3SERVICE_INVALID_PARAMETERS = -51003 +}; + +@interface S3Service : NSObject { + S3AuthorizationProvider *sap; + NSURL *endpoint; + BOOL useAmazonRRS; +} ++ (NSString *)errorDomain; + +- (id)initWithS3AuthorizationProvider:(S3AuthorizationProvider *)theSAP endpoint:(NSURL *)theEndpoint useAmazonRRS:(BOOL)isUseAmazonRRS; + +- (S3Owner *)s3OwnerWithTargetConnectionDelegate:(id )theTCD error:(NSError **)error; +- (NSArray *)s3BucketNamesWithTargetConnectionDelegate:(id )theTCD error:(NSError **)error; +- (NSNumber *)s3BucketExists:(NSString *)s3BucketName targetConnectionDelegate:(id )theTCD error:(NSError **)error; +- (NSString *)locationOfS3Bucket:(NSString *)theS3BucketName targetConnectionDelegate:(id )theTCD error:(NSError **)error; + +- (NSArray *)pathsWithPrefix:(NSString *)prefix targetConnectionDelegate:(id )theTCD error:(NSError **)error; +- (NSArray *)pathsWithPrefix:(NSString *)prefix delimiter:(NSString *)delimiter targetConnectionDelegate:(id )theTCD error:(NSError **)error; +- (NSArray *)commonPrefixesForPathPrefix:(NSString *)prefix delimiter:(NSString *)delimiter targetConnectionDelegate:(id )theTCD error:(NSError **)error; +- (NSArray *)objectsWithPrefix:(NSString *)prefix targetConnectionDelegate:(id )theTCD error:(NSError **)error; +- (BOOL)listObjectsWithPrefix:(NSString *)prefix receiver:(id )receiver targetConnectionDelegate:(id )theTCD error:(NSError **)error; +- (NSNumber *)containsObjectAtPath:(NSString *)path dataSize:(unsigned long long *)dataSize targetConnectionDelegate:(id )theTCD error:(NSError **)error; +- (NSNumber *)isObjectRestoredAtPath:(NSString *)thePath targetConnectionDelegate:(id )theTCD error:(NSError **)error; +- (BOOL)restoreObjectAtPath:(NSString *)thePath forDays:(NSUInteger)theDays alreadyRestoredOrRestoring:(BOOL *)alreadyRestoredOrRestoring targetConnectionDelegate:(id )theTCD error:(NSError **)error; + +- (NSData *)dataAtPath:(NSString *)path targetConnectionDelegate:(id )theDelegate error:(NSError **)error; +- (NSData *)dataAtPath:(NSString *)path dataTransferDelegate:(id )theDelegate targetConnectionDelegate:(id )theDelegate error:(NSError **)error; + +- (S3AuthorizationProvider *)s3AuthorizationProvider; + +- (BOOL)createS3Bucket:(NSString *)s3BucketName withLocationConstraint:(NSString *)theLocationConstraint targetConnectionDelegate:(id )theDelegate error:(NSError **)error; +- (BOOL)deleteS3Bucket:(NSString *)s3BucketName targetConnectionDelegate:(id )theDelegate error:(NSError **)error; +- (BOOL)putData:(NSData *)theData atPath:(NSString *)path targetConnectionDelegate:(id )theTCD error:(NSError **)error; +- (BOOL)putData:(NSData *)theData atPath:(NSString *)path dataTransferDelegate:(id )theDTD targetConnectionDelegate:(id )theTCD error:(NSError **)error; +- (BOOL)deletePaths:(NSArray *)thePaths targetConnectionDelegate:(id )theTCD error:(NSError **)error; +- (BOOL)deletePath:(NSString *)path targetConnectionDelegate:(id )theTCD error:(NSError **)error; +- (BOOL)setStorageClass:(NSString *)storageClass forPath:(NSString *)path targetConnectionDelegate:(id )theTCD error:(NSError **)error; +- (NSString *)storageClass; +- (BOOL)copy:(NSString *)sourcePath to:(NSString *)destPath targetConnectionDelegate:(id )theTCD error:(NSError **)error; + +- (NSNumber *)containsLifecyclePolicyWithId:(NSString *)theId forS3BucketName:(NSString *)theS3BucketName targetConnectionDelegate:(id )theTCD error:(NSError **)error; +- (BOOL)putGlacierLifecyclePolicyWithId:(NSString *)theId forPrefixes:(NSArray *)thePrefixes s3BucketName:(NSString *)theS3BucketName transitionDays:(NSUInteger)theTransitionDays targetConnectionDelegate:(id )theTCD error:(NSError **)error; +@end diff --git a/cocoastack/s3/S3Service.m b/cocoastack/s3/S3Service.m new file mode 100644 index 0000000..ae49b7a --- /dev/null +++ b/cocoastack/s3/S3Service.m @@ -0,0 +1,582 @@ +/* + Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import "InputStream.h" +#import "RegexKitLite.h" +#import "S3Owner.h" +#import "S3Lister.h" +#import "S3AuthorizationProvider.h" +#import "S3Service.h" +#import "PathReceiver.h" +#import "DataInputStream.h" +#import "HTTP.h" +#import "Streams.h" +#import "S3ObjectReceiver.h" +#import "NSData-InputStream.h" +#import "S3Request.h" +#import "NSError_extra.h" +#import "AWSRegion.h" +#import "HTTPConnectionFactory.h" +#import "HTTPConnection.h" +#import "MD5Hash.h" +#import "S3MultiDeleteResponse.h" +#import "S3ErrorResult.h" +#import "LifecycleConfiguration.h" + + +NSString *kS3StorageClassStandard = @"STANDARD"; +NSString *kS3StorageClassReducedRedundancy = @"REDUCED_REDUNDANCY"; + +/* + * WARNING: + * This class *must* be reentrant! + */ + + +@implementation S3Service ++ (NSString *)errorDomain { + return @"S3ServiceErrorDomain"; +} + +- (id)initWithS3AuthorizationProvider:(S3AuthorizationProvider *)theSAP endpoint:(NSURL *)theEndpoint useAmazonRRS:(BOOL)isUseAmazonRRS { + if (self = [super init]) { + sap = [theSAP retain]; + endpoint = [theEndpoint retain]; + useAmazonRRS = isUseAmazonRRS; + } + return self; +} +- (void)dealloc { + [sap release]; + [endpoint release]; + [super dealloc]; +} + +- (S3Owner *)s3OwnerWithTargetConnectionDelegate:(id)theDelegate error:(NSError **)error { + if (error) { + *error = 0; + } + NSXMLDocument *doc = [self listBucketsWithTargetConnectionDelegate:theDelegate error:error]; + if (!doc) { + return nil; + } + NSXMLElement *rootElem = [doc rootElement]; + NSArray *idNodes = [rootElem nodesForXPath:@"//ListAllMyBucketsResult/Owner/ID" error:error]; + if (!idNodes) { + return nil; + } + if ([idNodes count] == 0) { + HSLogError(@"ListAllMyBucketsResult/Owner/ID node not found"); + return nil; + } + NSXMLNode *ownerIDNode = [idNodes objectAtIndex:0]; + NSArray *displayNameNodes = [rootElem nodesForXPath:@"//ListAllMyBucketsResult/Owner/DisplayName" error:error]; + if (!displayNameNodes) { + return nil; + } + if ([displayNameNodes count] == 0) { + HSLogError(@"ListAllMyBucketsResult/Owner/DisplayName not found"); + return nil; + } + NSXMLNode *displayNameNode = [displayNameNodes objectAtIndex:0]; + HSLogDebug(@"s3 owner ID: %@", [displayNameNode stringValue]); + return [[[S3Owner alloc] initWithDisplayName:[displayNameNode stringValue] idString:[ownerIDNode stringValue]] autorelease]; +} +- (NSArray *)s3BucketNamesWithTargetConnectionDelegate:(id)theDelegate error:(NSError **)error { + NSXMLDocument *doc = [self listBucketsWithTargetConnectionDelegate:theDelegate error:error]; + if (!doc) { + return nil; + } + NSXMLElement *rootElem = [doc rootElement]; + NSArray *nameNodes = [rootElem nodesForXPath:@"//ListAllMyBucketsResult/Buckets/Bucket/Name" error:error]; + if (!nameNodes) { + return nil; + } + NSMutableArray *bucketNames = [[[NSMutableArray alloc] init] autorelease]; + for (NSXMLNode *nameNode in nameNodes) { + [bucketNames addObject:[nameNode stringValue]]; + } + return bucketNames; +} +- (NSNumber *)s3BucketExists:(NSString *)s3BucketName targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + NSArray *s3BucketNames = [self s3BucketNamesWithTargetConnectionDelegate:theDelegate error:error]; + if (s3BucketNames == nil) { + HSLogDebug(@"error getting S3 bucket names"); + return nil; + } + return [NSNumber numberWithBool:[s3BucketNames containsObject:s3BucketName]]; +} +- (NSString *)locationOfS3Bucket:(NSString *)theS3BucketName targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"https://s3.amazonaws.com/%@/?location", theS3BucketName]]; + id conn = [[[HTTPConnectionFactory theFactory] newHTTPConnectionToURL:url method:@"GET" dataTransferDelegate:nil] autorelease]; + [conn setRequestHostHeader]; + [conn setRFC822DateRequestHeader]; + if (![sap setAuthorizationRequestHeaderOnHTTPConnection:conn error:error]) { + return nil; + } + HSLogDebug(@"GET %@", url); + NSData *response = [conn executeRequest:error]; + if (response == nil) { + return nil; + } + int code = [conn responseCode]; + if (code == 404) { + SETNSERROR([S3Service errorDomain], ERROR_NOT_FOUND, @"bucket %@ not found", theS3BucketName); + return nil; + } else if (code != 200) { + S3ErrorResult *errorResult = [[[S3ErrorResult alloc] initWithAction:[NSString stringWithFormat:@"GET %@", url] data:response httpErrorCode:(int)code] autorelease]; + NSError *myError = [errorResult error]; + HSLogDebug(@"GET %@ error: %@", conn, myError); + if (error != NULL) { + *error = myError; + } + return nil; + } + + NSError *myError = nil; + NSXMLDocument *xmlDoc = [[[NSXMLDocument alloc] initWithData:response options:0 error:&myError] autorelease]; + if (!xmlDoc) { + SETNSERROR([S3Service errorDomain], [myError code], @"error parsing List Objects XML response: %@", myError); + return nil; + } + NSXMLElement *rootElement = [xmlDoc rootElement]; + NSArray *nodes = [rootElement nodesForXPath:@"//LocationConstraint" error:error]; + if (!nodes) { + return nil; + } + if ([nodes count] == 0) { + SETNSERROR([S3Service errorDomain], -1, @"missing LocationConstraint in response data"); + return nil; + } + NSXMLNode *node = [nodes objectAtIndex:0]; + NSString *constraint = [node stringValue]; + return constraint; +} +- (NSArray *)pathsWithPrefix:(NSString *)prefix targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + return [self pathsWithPrefix:prefix delimiter:nil targetConnectionDelegate:theDelegate error:error]; +} +- (NSArray *)pathsWithPrefix:(NSString *)prefix delimiter:(NSString *)delimiter targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + PathReceiver *rec = [[[PathReceiver alloc] init] autorelease]; + S3Lister *lister = [[[S3Lister alloc] initWithS3AuthorizationProvider:sap endpoint:endpoint prefix:prefix delimiter:delimiter receiver:rec] autorelease]; + if (![lister listObjectsWithTargetConnectionDelegate:theDelegate error:error]) { + return nil; + } + NSMutableArray *ret = [NSMutableArray arrayWithArray:[rec paths]]; + [ret addObjectsFromArray:[lister foundPrefixes]]; + [ret sortUsingSelector:@selector(compare:)]; + return ret; +} +- (NSArray *)commonPrefixesForPathPrefix:(NSString *)prefix delimiter:(NSString *)delimiter targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + NSArray *paths = [self pathsWithPrefix:prefix delimiter:delimiter targetConnectionDelegate:theDelegate error:error]; + if (paths == nil) { + return nil; + } + NSMutableArray *ret = [NSMutableArray array]; + for (NSString *path in paths) { + [ret addObject:[path substringFromIndex:[prefix length]]]; + } + return ret; +} +- (NSArray *)objectsWithPrefix:(NSString *)prefix targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + S3ObjectReceiver *receiver = [[[S3ObjectReceiver alloc] init] autorelease]; + if (![self listObjectsWithPrefix:prefix receiver:receiver targetConnectionDelegate:theDelegate error:error]) { + return NO; + } + return [receiver objects]; +} +- (BOOL)listObjectsWithPrefix:(NSString *)prefix receiver:(id )receiver targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + S3Lister *lister = [[[S3Lister alloc] initWithS3AuthorizationProvider:sap endpoint:endpoint prefix:prefix delimiter:nil receiver:receiver] autorelease]; + return lister && [lister listObjectsWithTargetConnectionDelegate:theDelegate error:error]; +} +- (NSNumber *)containsObjectAtPath:(NSString *)path dataSize:(unsigned long long *)dataSize targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + BOOL ret = YES; + S3Request *s3r = [[S3Request alloc] initWithMethod:@"HEAD" endpoint:endpoint path:path queryString:nil authorizationProvider:sap error:error]; + if (s3r == nil) { + return nil; + } + NSError *myError = nil; + BOOL contains = NO; + NSData *response = [s3r dataWithTargetConnectionDelegate:theDelegate error:&myError]; + if (response != nil) { + contains = YES; + HSLogTrace(@"S3 path %@ exists", path); + if (dataSize != NULL) { + NSString *contentLength = [s3r responseHeaderForKey:@"Content-Length"]; + *dataSize = (unsigned long long)[contentLength longLongValue]; // This would be bad for negative content-length (I guess that won't happen though) + } + } else if ([myError isErrorWithDomain:[S3Service errorDomain] code:ERROR_NOT_FOUND]) { + contains = NO; + HSLogDebug(@"S3 path %@ does NOT exist", path); + } else if ([myError isErrorWithDomain:[S3Service errorDomain] code:ERROR_RRS_NOT_FOUND]) { + contains = NO; + HSLogDebug(@"S3 path %@ returns 405 error", path); + } else { + contains = NO; + ret = NO; + HSLogDebug(@"error getting HEAD for %@: %@", path, myError); + SETERRORFROMMYERROR; + } + [s3r release]; + if (!ret) { + return nil; + } + return [NSNumber numberWithBool:contains]; +} +- (NSNumber *)isObjectRestoredAtPath:(NSString *)thePath targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + S3Request *s3r = [[[S3Request alloc] initWithMethod:@"HEAD" endpoint:endpoint path:thePath queryString:nil authorizationProvider:sap error:error] autorelease]; + if (s3r == nil) { + return nil; + } + NSData *response = [s3r dataWithTargetConnectionDelegate:theDelegate error:error]; + if (response == nil) { + return nil; + } + NSString *restoreHeader = [s3r responseHeaderForKey:@"x-amz-restore"]; + if (restoreHeader == nil) { + // We're assuming here that the caller of this method has first requested a restore of this object. + // If the object is new and hasn't been shifted to Glacier yet, the storage class of this object + // would be Standard, the restore request would have failed, and there will be no x-amz-restore header. + // There's no way to determine the object's current storage class from the results of a + // HEAD request (unbelievably) so we have to make this assumption. + + return [NSNumber numberWithBool:YES]; + } + HSLogDebug(@"S3 path %@: x-amz-restore=%@", thePath, restoreHeader); + BOOL restored = [restoreHeader rangeOfString:@"ongoing-request=\"false\""].location != NSNotFound; + return [NSNumber numberWithBool:restored]; +} +- (BOOL)restoreObjectAtPath:(NSString *)thePath forDays:(NSUInteger)theDays alreadyRestoredOrRestoring:(BOOL *)alreadyRestoredOrRestoring targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + if (alreadyRestoredOrRestoring != NULL) { + *alreadyRestoredOrRestoring = NO; + } + S3Request *s3r = [[[S3Request alloc] initWithMethod:@"POST" endpoint:endpoint path:thePath queryString:@"restore" authorizationProvider:sap error:error] autorelease]; + if (s3r == nil) { + return NO; + } + NSString *requestBodyString = [NSString stringWithFormat:@"%ld", (unsigned long)theDays]; + NSData *requestBody = [requestBodyString dataUsingEncoding:NSUTF8StringEncoding]; + NSString *md5Hash = [MD5Hash hashDataBase64Encode:requestBody]; + [s3r setRequestHeader:md5Hash forKey:@"Content-MD5"]; + [s3r setRequestHeader:@"application/xml" forKey:@"Content-Type"]; + [s3r setRequestBody:requestBody]; + + NSError *myError = nil; + NSData *response = [s3r dataWithTargetConnectionDelegate:theDelegate error:&myError]; + if (response == nil) { + if ([myError code] == S3SERVICE_ERROR_AMAZON_ERROR + && [[[myError userInfo] objectForKey:@"HTTPStatusCode"] intValue] == HTTP_CONFLICT) { + // AWS returns 409 conflict if it's already in the process of being restored. + if (alreadyRestoredOrRestoring != NULL) { + *alreadyRestoredOrRestoring = YES; + } + return YES; + } + if ([myError code] == S3SERVICE_ERROR_AMAZON_ERROR + && [[[myError userInfo] objectForKey:@"HTTPStatusCode"] intValue] == HTTP_FORBIDDEN + && [[[myError userInfo] objectForKey:@"AmazonCode"] isEqualToString:@"InvalidObjectState"]) { + // AWS returns 403 and "InvalidObjectState" if the object's storage class isn't Glacier. + // If the object is new and hasn't been shifted to Glacier yet, AWS will return this error, + // which in our scenario isn't actually an error. + if (alreadyRestoredOrRestoring != NULL) { + *alreadyRestoredOrRestoring = YES; + } + return YES; + } + SETERRORFROMMYERROR; + return NO; + } + + // If the bucket does not have a restored copy of the object, Amazon S3 returns the following 202 Accepted response. + // If a copy of the object is already restored, Amazon S3 returns a 200 OK response, only updating the restored copy's expiry time. + if ([s3r httpResponseCode] == HTTP_OK) { + if (alreadyRestoredOrRestoring != NULL) { + *alreadyRestoredOrRestoring = YES; + } + } + return YES; +} +- (NSData *)dataAtPath:(NSString *)path targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + return [self dataAtPath:path dataTransferDelegate:nil targetConnectionDelegate:theDelegate error:error]; +} +- (NSData *)dataAtPath:(NSString *)path dataTransferDelegate:(id)theDTD targetConnectionDelegate:(id)theTCD error:(NSError **)error { + S3Request *s3r = [[S3Request alloc] initWithMethod:@"GET" endpoint:endpoint path:path queryString:nil authorizationProvider:sap dataTransferDelegate:theDTD error:error]; + if (s3r == nil) { + return nil; + } + NSData *ret = [s3r dataWithTargetConnectionDelegate:theTCD error:error]; + [s3r release]; + return ret; +} +- (S3AuthorizationProvider *)s3AuthorizationProvider { + return sap; +} +- (BOOL)createS3Bucket:(NSString *)s3BucketName withLocationConstraint:(NSString *)theLocationConstraint targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + S3Request *s3r = [[[S3Request alloc] initWithMethod:@"PUT" endpoint:endpoint path:[NSString stringWithFormat:@"/%@/", s3BucketName] queryString:nil authorizationProvider:sap error:error] autorelease]; + if (s3r == nil) { + return NO; + } + // [s3r setHeader:@"bucket-owner-full-control" forKey:@"x-amz-acl"]; + + if (theLocationConstraint != nil) { + NSString *xml = [NSString stringWithFormat:@"%@", theLocationConstraint]; + NSData *data = [xml dataUsingEncoding:NSUTF8StringEncoding]; + [s3r setRequestBody:data]; + } else { + [s3r setRequestBody:[NSData data]]; + } + return [s3r dataWithTargetConnectionDelegate:theDelegate error:error] != nil; +} +- (BOOL)deleteS3Bucket:(NSString *)s3BucketName targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + S3Request *s3r = [[[S3Request alloc] initWithMethod:@"DELETE" endpoint:endpoint path:[NSString stringWithFormat:@"/%@/", s3BucketName] queryString:nil authorizationProvider:sap error:error] autorelease]; + if (s3r == nil) { + return NO; + } + [s3r setRequestBody:[NSData data]]; // Do this so it sets Content-Length: 0 header. + return [s3r dataWithTargetConnectionDelegate:theDelegate error:error] != nil; +} +- (BOOL)putData:(NSData *)data atPath:(NSString *)path targetConnectionDelegate:(id)theDelegate error:(NSError **)error { + return [self putData:data atPath:path dataTransferDelegate:nil targetConnectionDelegate:theDelegate error:error]; +} +- (BOOL)putData:(NSData *)data atPath:(NSString *)path dataTransferDelegate:(id)theDTD targetConnectionDelegate:(id)theTCD error:(NSError **)error { + if (![path hasPrefix:@"/"]) { + SETNSERROR([S3Service errorDomain], S3SERVICE_INVALID_PARAMETERS, @"path must begin with '/'"); + return NO; + } + HSLogDebug(@"putting %lu bytes in S3 at %@", (unsigned long)[data length], path); + S3Request *s3r = [[S3Request alloc] initWithMethod:@"PUT" endpoint:endpoint path:path queryString:nil authorizationProvider:sap dataTransferDelegate:theDTD error:error]; + if (s3r == nil) { + return NO; + } + [s3r setRequestBody:data]; + if (useAmazonRRS) { + [s3r setRequestHeader:kS3StorageClassReducedRedundancy forKey:@"x-amz-storage-class"]; + } + NSData *ret = [s3r dataWithTargetConnectionDelegate:theTCD error:error]; + [s3r release]; + if (ret == nil) { + return NO; + } + return YES; +} +- (BOOL)deletePaths:(NSArray *)thePaths targetConnectionDelegate:(id)theTCD error:(NSError **)error { + if ([thePaths count] == 0) { + HSLogWarn(@"0 paths to delete"); + return YES; + } + + // if ([AWSRegion regionWithS3Endpoint:endpoint] == nil) { + // HSLogDebug(@"not an AWS endpoint; not using multi-delete"); + + + // S3 Multi-delete doesn't seem to work! Using single delete: + + BOOL ret = YES; + NSAutoreleasePool *pool = nil; + for (NSString *path in thePaths) { + [pool drain]; + pool = [[NSAutoreleasePool alloc] init]; + if (![self deletePath:path targetConnectionDelegate:theTCD error:error]) { + ret = NO; + break; + } + } + if (!ret && error != NULL) { + [*error retain]; + } + [pool drain]; + if (!ret && error != NULL) { + [*error autorelease]; + } + + return ret; +} + +- (BOOL)deletePath:(NSString *)path targetConnectionDelegate:(id)theTCD error:(NSError **)error { + if (![path hasPrefix:@"/"]) { + HSLogError(@"invalid path %@", path); + SETNSERROR([S3Service errorDomain], S3SERVICE_INVALID_PARAMETERS, @"path must begin with /"); + return NO; + } + NSRange searchRange = NSMakeRange(1, [path length] - 1); + NSRange nextSlashRange = [path rangeOfString:@"/" options:0 range:searchRange]; + if (nextSlashRange.location == NSNotFound) { + SETNSERROR([S3Service errorDomain], S3SERVICE_INVALID_PARAMETERS, @"path must be of the format //path"); + return NO; + } + + HSLogDebug(@"deleting %@", path); + S3Request *s3r = [[[S3Request alloc] initWithMethod:@"DELETE" endpoint:endpoint path:path queryString:nil authorizationProvider:sap error:error] autorelease]; + if (s3r == nil) { + return NO; + } + NSData *response = [s3r dataWithTargetConnectionDelegate:theTCD error:error]; + if (response == nil) { + return NO; + } + return YES; +} +- (BOOL)setStorageClass:(NSString *)storageClass forPath:(NSString *)path targetConnectionDelegate:(id)theTCD error:(NSError **)error { + if (![path hasPrefix:@"/"]) { + SETNSERROR([S3Service errorDomain], S3SERVICE_INVALID_PARAMETERS, @"path must begin with '/'"); + return NO; + } + HSLogTrace(@"setting storage class to %@ for %@", storageClass, path); + S3Request *s3r = [[S3Request alloc] initWithMethod:@"PUT" endpoint:endpoint path:path queryString:nil authorizationProvider:sap error:error]; + if (s3r == nil) { + return NO; + } + [s3r setRequestHeader:storageClass forKey:@"x-amz-storage-class"]; + [s3r setRequestHeader:path forKey:@"x-amz-copy-source"]; + NSData *ret = [s3r dataWithTargetConnectionDelegate:theTCD error:error]; + [s3r release]; + if (ret == nil) { + return NO; + } + return YES; +} +- (NSString *)storageClass { + return useAmazonRRS ? kS3StorageClassReducedRedundancy : kS3StorageClassStandard; +} +- (BOOL)copy:(NSString *)sourcePath to:(NSString *)destPath targetConnectionDelegate:(id)theTCD error:(NSError **)error { + S3Request *s3r = [[[S3Request alloc] initWithMethod:@"PUT" endpoint:endpoint path:destPath queryString:nil authorizationProvider:sap error:error] autorelease]; + if (s3r == nil) { + return NO; + } + [s3r setRequestHeader:sourcePath forKey:@"x-amz-copy-source"]; + NSData *response = [s3r dataWithTargetConnectionDelegate:theTCD error:error]; + if (response == nil) { + return NO; + } + if ([response length] > 0) { + HSLogTrace(@"s3 copy response: %@", [[[NSString alloc] initWithBytes:[response bytes] length:[response length] encoding:NSUTF8StringEncoding] autorelease]); + } + return YES; +} +- (NSNumber *)containsLifecyclePolicyWithId:(NSString *)theId forS3BucketName:(NSString *)theS3BucketName targetConnectionDelegate:(id)theTCD error:(NSError **)error { + NSString *path = [NSString stringWithFormat:@"/%@/", theS3BucketName]; + S3Request *s3r = [[S3Request alloc] initWithMethod:@"GET" endpoint:endpoint path:path queryString:@"lifecycle" authorizationProvider:sap error:error]; + if (s3r == nil) { + return NO; + } + NSError *myError = nil; + NSData *response = [s3r dataWithTargetConnectionDelegate:theTCD error:&myError]; + [s3r release]; + if (response == nil) { + if ([myError isErrorWithDomain:[S3Service errorDomain] code:ERROR_NOT_FOUND]) { + return [NSNumber numberWithBool:NO]; + } + SETERRORFROMMYERROR; + return nil; + } + LifecycleConfiguration *config = [[[LifecycleConfiguration alloc] initWithData:response error:error] autorelease]; + if (config == nil) { + return nil; + } + BOOL contains = [config containsRuleWithId:theId]; + return [NSNumber numberWithBool:contains]; +} +- (BOOL)putGlacierLifecyclePolicyWithId:(NSString *)theId forPrefixes:(NSArray *)thePrefixes s3BucketName:(NSString *)theS3BucketName transitionDays:(NSUInteger)theTransitionDays targetConnectionDelegate:(id)theTCD error:(NSError **)error { + NSString *path = [NSString stringWithFormat:@"/%@/", theS3BucketName]; + + NSMutableString *configurationXML = [NSMutableString string]; + [configurationXML appendString:@""]; + for (NSUInteger index = 0; index < [thePrefixes count]; index++) { + NSString *ruleId = [NSString stringWithFormat:@"%@-%ld", theId, (unsigned long)index]; + [configurationXML appendString:@""]; + [configurationXML appendFormat:@"%@", ruleId]; + [configurationXML appendFormat:@"%@", [thePrefixes objectAtIndex:index]]; + [configurationXML appendString:@"Enabled"]; + [configurationXML appendFormat:@"%luGLACIER", (unsigned long)theTransitionDays]; + [configurationXML appendString:@""]; + } + [configurationXML appendString:@""]; + NSData *requestBody = [configurationXML dataUsingEncoding:NSUTF8StringEncoding]; + S3Request *s3r = [[S3Request alloc] initWithMethod:@"PUT" endpoint:endpoint path:path queryString:@"lifecycle" authorizationProvider:sap error:error]; + if (s3r == nil) { + return NO; + } + NSString *md5Hash = [MD5Hash hashDataBase64Encode:requestBody]; + [s3r setRequestHeader:md5Hash forKey:@"Content-MD5"]; + [s3r setRequestBody:requestBody]; + NSData *ret = [s3r dataWithTargetConnectionDelegate:theTCD error:error]; + [s3r release]; + return ret != nil; +} + + +#pragma mark NSCopying +- (id)copyWithZone:(NSZone *)zone { + return [[S3Service alloc] initWithS3AuthorizationProvider:sap endpoint:endpoint useAmazonRRS:useAmazonRRS]; +} + + +#pragma mark internal +- (NSXMLDocument *)listBucketsWithTargetConnectionDelegate:(id )theDelegate error:(NSError **)error { + S3Request *s3r = [[S3Request alloc] initWithMethod:@"GET" endpoint:endpoint path:@"/" queryString:nil authorizationProvider:sap error:error]; + if (s3r == nil) { + return nil; + } + NSError *myError = nil; + NSData *response = [s3r dataWithTargetConnectionDelegate:theDelegate error:&myError]; + [s3r release]; + if (response == nil) { + SETERRORFROMMYERROR; + if ([myError isErrorWithDomain:[S3Service errorDomain] code:S3SERVICE_ERROR_AMAZON_ERROR]) { + NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:[myError userInfo]]; + [userInfo setObject:[myError localizedDescription] forKey:NSLocalizedDescriptionKey]; + NSError *rewritten = [NSError errorWithDomain:[S3Service errorDomain] code:[[[myError userInfo] objectForKey:@"HTTPStatusCode"] intValue] userInfo:userInfo]; + if (error != NULL) { + *error = rewritten; + } + } + return nil; + } + NSXMLDocument *ret = [[[NSXMLDocument alloc] initWithData:response options:0 error:&myError] autorelease]; + if (ret == nil) { + HSLogDebug(@"error parsing List Buckets result XML %@", [[[NSString alloc] initWithBytes:[response bytes] length:[response length] encoding:NSUTF8StringEncoding] autorelease]); + SETNSERROR([S3Service errorDomain], [myError code], @"error parsing S3 List Buckets result XML: %@", [myError description]); + } + return ret; +} +- (BOOL)createOrDeleteS3Bucket:(NSString *)s3BucketName methodName:(NSString *)methodName requestBody:(NSData *)data targetConnectionDelegate:(id )theDelegate error:(NSError **)error { + S3Request *s3r = [[[S3Request alloc] initWithMethod:methodName endpoint:endpoint path:[NSString stringWithFormat:@"/%@/", s3BucketName] queryString:nil authorizationProvider:sap error:error] autorelease]; + if (s3r == nil) { + return NO; + } + if (data == nil) { + [s3r setRequestBody:[NSData data]]; + } else { + [s3r setRequestBody:data]; + } + return [s3r dataWithTargetConnectionDelegate:theDelegate error:error] != nil; +} +@end diff --git a/s3/S3Signer.h b/cocoastack/s3/S3Signer.h similarity index 60% rename from s3/S3Signer.h rename to cocoastack/s3/S3Signer.h index 8eb47b2..f931835 100644 --- a/s3/S3Signer.h +++ b/cocoastack/s3/S3Signer.h @@ -3,12 +3,12 @@ // Arq // // Created by Stefan Reitshamer on 12/30/10. -// Copyright 2010 __MyCompanyName__. All rights reserved. +// Copyright 2010 Haystack Software. All rights reserved. // -@protocol S3Signer +@protocol S3Signer - (NSString *)sign:(NSString *)theString error:(NSError **)error; @end diff --git a/cocoastack/sftp/SFTPServer.h b/cocoastack/sftp/SFTPServer.h new file mode 100644 index 0000000..59aa5ea --- /dev/null +++ b/cocoastack/sftp/SFTPServer.h @@ -0,0 +1,52 @@ +// +// SFTPServer.h +// Arq +// +// Created by Stefan Reitshamer on 1/31/14. +// Copyright (c) 2014 Stefan Reitshamer. All rights reserved. +// + +#include "libssh2.h" +#include "libssh2_sftp.h" +@protocol DataTransferDelegate; + + +@interface SFTPServer : NSObject { + NSDate *dateCreated; + BOOL errorOccurred; + int port; + NSString *username; + NSString *password; + NSString *privateKeyPath; + NSString *passphrase; + NSString *hostname; + int sock; + LIBSSH2_SESSION *session; + LIBSSH2_SFTP *sftp; +} + +- (id)initWithURL:(NSURL *)theURL + password:(NSString *)thePassword + privateKeyPath:(NSString *)thePrivateKeyPath + passphrase:(NSString *)thePassphrase + error:(NSError **)error; + +- (NSString *)errorDomain; +- (NSDate *)dateCreated; +- (BOOL)errorOccurred; + +- (NSString *)realPathForPath:(NSString *)thePath error:(NSError **)error; +- (NSNumber *)fileExistsAtPath:(NSString *)thePath isDirectory:(BOOL *)isDirectory error:(NSError **)error; +- (NSNumber *)fileExistsAtPath:(NSString *)thePath dataSize:(unsigned long long *)theDataSize error:(NSError **)error; +- (NSArray *)contentsOfDirectoryAtPath:(NSString *)thePath error:(NSError **)error; +- (NSData *)contentsOfFileAtPath:(NSString *)thePath dataTransferDelegate:(id )theDelegate error:(NSError **)error; +- (BOOL)writeData:(NSData *)theData toFileAtPath:(NSString *)thePath dataTransferDelegate:(id )theDelegate error:(NSError **)error; +- (BOOL)renameItemAtPath:(NSString *)theFromPath toPath:(NSString *)theToPath error:(NSError **)error; +- (BOOL)removeItemAtPath:(NSString *)thePath error:(NSError **)error; +- (BOOL)createDirectoryAtPath:(NSString *)path withIntermediateDirectories:(BOOL)createIntermediates error:(NSError **)error; +- (BOOL)ensureParentPathExistsForPath:(NSString *)thePath error:(NSError **)error; +- (NSNumber *)sizeOfItemAtPath:(NSString *)thePath error:(NSError **)error; +- (NSArray *)objectsAtPath:(NSString *)thePath error:(NSError **)error; +- (NSArray *)pathsOfObjectsAtPath:(NSString *)thePath error:(NSError **)error; + +@end diff --git a/cocoastack/sftp/SFTPServer.m b/cocoastack/sftp/SFTPServer.m new file mode 100644 index 0000000..d946d61 --- /dev/null +++ b/cocoastack/sftp/SFTPServer.m @@ -0,0 +1,1239 @@ +// +// SFTPServer.m +// Arq +// +// Created by Stefan Reitshamer on 1/31/14. +// Copyright (c) 2014 Stefan Reitshamer. All rights reserved. +// + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpointer-sign" +#pragma clang diagnostic ignored "-Wtautological-compare" +#pragma clang diagnostic ignored "-Wshorten-64-to-32" + + +#include +#include +#include + +#import "SFTPServer.h" +#import "S3ObjectMetadata.h" +#import "NSString_extra.h" +#import "DataTransferDelegate.h" +#import "NetMonitor.h" +#import "HTTPThrottle.h" + + +#define BUFLEN (32768) +#define SFTP_TIMEOUT_MILLISECONDS (30000) + + +@implementation SFTPServer +- (id)initWithURL:(NSURL *)theURL + password:(NSString *)thePassword + privateKeyPath:(NSString *)thePrivateKeyPath + passphrase:(NSString *)thePassphrase + error:(NSError **)error { + if (self = [super init]) { + dateCreated = [[NSDate alloc] init]; + port = [[theURL port] intValue]; + username = [[theURL user] retain]; + password = [thePassword retain]; + privateKeyPath = [thePrivateKeyPath retain]; + passphrase = [thePassphrase retain]; + hostname = [[theURL host] retain]; + sock = -1; + + HSLogDetail(@"%p: connecting SFTPServer %@:%d", self, hostname, port); + + NSSocketPort *socketPort = [[[NSSocketPort alloc] initRemoteWithTCPPort:port host:hostname] autorelease]; + if ([socketPort address] == nil) { + // Return this error so that [NSError isTransientError] returns YES: + SETNSERROR(@"UnixErrorDomain", EADDRNOTAVAIL, @"unable to resolve host %@", hostname); + [self release]; + return nil; + } + const struct sockaddr *addr = [[socketPort address] bytes]; + sock = socket(addr->sa_family, SOCK_STREAM, 0); + if (sock == -1) { + int errnum = errno; + SETNSERROR([self errorDomain], errnum, @"Failed to create socket: %s", strerror(errnum)); + [self release]; + return nil; + } + if (connect(sock, [[socketPort address] bytes], (socklen_t)[[socketPort address] length])) { + int errnum = errno; + SETNSERROR(@"UnixErrorDomain", errnum, @"Failed to connect to %@ at port %d: %s", hostname, port, strerror(errnum)); + [self release]; + return nil; + } + + session = libssh2_session_init(); + if (session == NULL) { + SETNSERROR([self errorDomain], -1, @"libssh2_session_init failed"); + [self release]; + return nil; + } + if (libssh2_session_startup(session, sock) < 0) { + char *msg = NULL; + int sessionError = libssh2_session_last_error(session, &msg, NULL, 0); + NSError *myError = [NSError errorWithDomain:[self errorDomain] code:sessionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:sessionError], @"libssh2SessionError", + [NSString stringWithFormat:@"libssh2_session_startup error: %s", msg], NSLocalizedDescriptionKey, + nil]]; + SETERRORFROMMYERROR; + [self release]; + return nil; + } + + libssh2_session_set_blocking(session, 1); + libssh2_session_set_timeout(session, SFTP_TIMEOUT_MILLISECONDS); + + if ([password length] > 0) { + if (libssh2_userauth_password_ex(session, [username UTF8String], (int)strlen([username UTF8String]), [password UTF8String], (int)strlen([password UTF8String]), NULL)) { + char *msg = NULL; + int sessionError = libssh2_session_last_error(session, &msg, NULL, 0); + HSLogError(@"libssh2_userauth_password_ex error %d: %s", sessionError, msg); + NSError *myError = [NSError errorWithDomain:[self errorDomain] code:sessionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:sessionError], @"libssh2SessionError", + [NSString stringWithFormat:@"SFTP: %s", msg], NSLocalizedDescriptionKey, + nil]]; + SETERRORFROMMYERROR; + [self release]; + return nil; + } + } else if ([privateKeyPath length] > 0) { + if (libssh2_userauth_publickey_fromfile_ex(session, [username UTF8String], (int)strlen([username UTF8String]), NULL, [privateKeyPath fileSystemRepresentation], [passphrase UTF8String])) { + char *msg = NULL; + int sessionError = libssh2_session_last_error(session, &msg, NULL, 0); + HSLogError(@"libssh2_userauth_publickey_fromfile_ex error %d: %s", sessionError, msg); + NSError *myError = [NSError errorWithDomain:[self errorDomain] code:sessionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:sessionError], @"libssh2SessionError", + [NSString stringWithFormat:@"SFTP: %s", msg], NSLocalizedDescriptionKey, + nil]]; + SETERRORFROMMYERROR; + [self release]; + return nil; + } + } else { + SETNSERROR([self errorDomain], -1, @"SFTP: no password or private key path given"); + [self release]; + return nil; + } + + sftp = libssh2_sftp_init(session); + if (sftp == NULL) { + SETNSERROR([self errorDomain], -1, @"sftp init failed"); + [self release]; + return nil; + } + } + return self; +} +- (void)dealloc { + if (sftp != NULL) { + HSLogDetail(@"%p: disconnected from SFTP server %@:%d", self, hostname, port); + if (libssh2_sftp_shutdown(sftp) < 0) { + HSLogError(@"libssh2_sftp_shutdown failed"); + } + sftp = NULL; + } + if (session != NULL) { + libssh2_session_free(session); + session = NULL; + } + if (sock != -1) { + close(sock); + sock = -1; + } + + [dateCreated release]; + [username release]; + [password release]; + [privateKeyPath release]; + [passphrase release]; + [hostname release]; + [super dealloc]; +} + +- (NSString *)errorDomain { + return @"SFTPServerErrorDomain"; +} +- (NSDate *)dateCreated { + return dateCreated; +} +- (BOOL)errorOccurred { + return errorOccurred; +} + +- (NSString *)realPathForPath:(NSString *)thePath error:(NSError **)error { + thePath = [thePath stringByDeletingTrailingSlash]; + char target[65536]; + int len = libssh2_sftp_realpath(sftp, [thePath UTF8String], target, 65536); + if (len < 0) { + char *msg = NULL; + int sessionError = libssh2_session_last_error(session, &msg, NULL, 0); + if (sessionError == LIBSSH2_ERROR_SFTP_PROTOCOL) { + unsigned long sftpError = libssh2_sftp_last_error(sftp); + NSError *myError = [NSError errorWithDomain:[self errorDomain] code:sftpError userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:sessionError], @"libssh2SessionError", + [NSNumber numberWithInt:sftpError], @"libssh2SFTPError", + [NSString stringWithFormat:@"sftp opendir(%@) error: %@", thePath, [self descriptionForSFTPStatusCode:sftpError]], NSLocalizedDescriptionKey, + nil]]; + SETERRORFROMMYERROR; + } else { + NSError *myError = [NSError errorWithDomain:[self errorDomain] code:sessionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:sessionError], @"libssh2SessionError", + [NSString stringWithFormat:@"sftp opendir(%@) error: %s", thePath, msg], NSLocalizedDescriptionKey, + nil]]; + SETERRORFROMMYERROR; + } + return nil; + } + return [[[NSString alloc] initWithBytes:target length:len encoding:NSUTF8StringEncoding] autorelease]; +} +- (NSNumber *)fileExistsAtPath:(NSString *)thePath isDirectory:(BOOL *)isDirectory error:(NSError **)error { + thePath = [thePath stringByDeletingTrailingSlash]; + return [self fileExistsAtPath:thePath isDirectory:isDirectory dataSize:NULL lastModifiedDate:NULL error:error]; +} +- (NSNumber *)fileExistsAtPath:(NSString *)thePath dataSize:(unsigned long long *)theDataSize error:(NSError **)error { + thePath = [thePath stringByDeletingTrailingSlash]; + return [self fileExistsAtPath:thePath isDirectory:NULL dataSize:theDataSize lastModifiedDate:NULL error:error]; +} + +- (NSArray *)contentsOfDirectoryAtPath:(NSString *)thePath error:(NSError **)error { + thePath = [thePath stringByDeletingTrailingSlash]; + + HSLogDebug(@"libssh2_sftp_opendir(%@)", thePath); + LIBSSH2_SFTP_HANDLE *handle = libssh2_sftp_opendir(sftp, (char *)[thePath UTF8String]); + if (handle == NULL) { + char *msg = NULL; + int sessionError = libssh2_session_last_error(session, &msg, NULL, 0); + if (sessionError == LIBSSH2_ERROR_SFTP_PROTOCOL) { + unsigned long sftpError = libssh2_sftp_last_error(sftp); + // SFTP servers can return access-denied if the parent directory doesn't exist: + if (sftpError == LIBSSH2_FX_NO_SUCH_FILE || sftpError == LIBSSH2_FX_PERMISSION_DENIED) { + return [NSArray array]; + } + + NSError *myError = [NSError errorWithDomain:[self errorDomain] code:sftpError userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:sessionError], @"libssh2SessionError", + [NSNumber numberWithInt:sftpError], @"libssh2SFTPError", + [NSString stringWithFormat:@"sftp opendir(%@) error: %@", thePath, [self descriptionForSFTPStatusCode:sftpError]], NSLocalizedDescriptionKey, + nil]]; + SETERRORFROMMYERROR; + } else { + NSError *myError = [NSError errorWithDomain:[self errorDomain] code:sessionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:sessionError], @"libssh2SessionError", + [NSString stringWithFormat:@"sftp opendir(%@) error: %s", thePath, msg], NSLocalizedDescriptionKey, + nil]]; + SETERRORFROMMYERROR; + } + errorOccurred = YES; + return nil; + } + + NSMutableArray *contents = [NSMutableArray array]; + char buf[BUFLEN]; + for (;;) { + LIBSSH2_SFTP_ATTRIBUTES attrs; +// HSLogDebug(@"libssh2_sftp_readdir(%@)", thePath); + int rc = libssh2_sftp_readdir(handle, buf, BUFLEN, &attrs); + if (rc < 0) { + char *msg = NULL; + int sessionError = libssh2_session_last_error(session, &msg, NULL, 0); + if (sessionError == LIBSSH2_ERROR_SFTP_PROTOCOL) { + unsigned long sftpError = libssh2_sftp_last_error(sftp); + NSError *myError = [NSError errorWithDomain:[self errorDomain] code:sftpError userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:sessionError], @"libssh2SessionError", + [NSNumber numberWithInt:sftpError], @"libssh2SFTPError", + [NSString stringWithFormat:@"sftp readdir(%@) error: %@", thePath, [self descriptionForSFTPStatusCode:sftpError]], NSLocalizedDescriptionKey, + nil]]; + SETERRORFROMMYERROR; + } else { + NSError *myError = [NSError errorWithDomain:[self errorDomain] code:sessionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:sessionError], @"libssh2SessionError", + [NSString stringWithFormat:@"sftp readdir(%@) error: %s", thePath, msg], NSLocalizedDescriptionKey, + nil]]; + SETERRORFROMMYERROR; + } + errorOccurred = YES; + contents = nil; + break; + } else if (rc > 0) { + NSString *name = [[[NSString alloc] initWithBytes:buf length:rc encoding:NSUTF8StringEncoding] autorelease]; + if (![name isEqualToString:@"."] && ![name isEqualToString:@".."]) { + [contents addObject:name]; + } + } else { + break; + } + } + if (libssh2_sftp_closedir(handle) < 0) { + errorOccurred = YES; + } + + return contents; +} + + +- (NSData *)contentsOfFileAtPath:(NSString *)thePath dataTransferDelegate:(id )theDelegate error:(NSError **)error { + thePath = [thePath stringByDeletingTrailingSlash]; + HSLogDebug(@"libssh2_sftp_open_ex(%@) for reading", thePath); + // Open the file with all permissions because some broken SFTP servers (e.g. SSH-2.0-mod_sftp/0.9.8) set the file's permissions + // when we open it for *reading* (the permissions are only supposed to be used when creating a file!) + LIBSSH2_SFTP_HANDLE *handle = libssh2_sftp_open_ex(sftp, + [thePath UTF8String], + strlen([thePath UTF8String]), + LIBSSH2_FXF_READ, LIBSSH2_SFTP_S_IRWXU|LIBSSH2_SFTP_S_IRWXG|LIBSSH2_SFTP_S_IRWXO, LIBSSH2_SFTP_OPENFILE); + if (handle == NULL) { + char *msg = NULL; + int sessionError = libssh2_session_last_error(session, &msg, NULL, 0); + if (sessionError == LIBSSH2_ERROR_SFTP_PROTOCOL) { + unsigned long sftpError = libssh2_sftp_last_error(sftp); + if (sftpError == LIBSSH2_FX_NO_SUCH_FILE || sftpError == LIBSSH2_FX_PERMISSION_DENIED) { + SETNSERROR([self errorDomain], ERROR_NOT_FOUND, @"%@ not found", thePath); + return nil; + } + + NSError *myError = [NSError errorWithDomain:[self errorDomain] code:sftpError userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:sessionError], @"libssh2SessionError", + [NSNumber numberWithInt:sftpError], @"libssh2SFTPError", + [NSString stringWithFormat:@"sftp open(%@) for reading error: %@", thePath, [self descriptionForSFTPStatusCode:sftpError]], NSLocalizedDescriptionKey, + nil]]; + SETERRORFROMMYERROR; + } else { + NSError *myError = [NSError errorWithDomain:[self errorDomain] code:sessionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:sessionError], @"libssh2SessionError", + [NSString stringWithFormat:@"sftp open(%@) for reading error: %s", thePath, msg], NSLocalizedDescriptionKey, + nil]]; + SETERRORFROMMYERROR; + } + errorOccurred = YES; + return nil; + } + + NSMutableData *data = [NSMutableData data]; + ssize_t recvd; + char buf[BUFLEN]; + for (;;) { + recvd = libssh2_sftp_read(handle, buf, BUFLEN); + if (recvd == 0) { + break; + } else if (recvd > 0) { + [data appendBytes:buf length:recvd]; + } else { + char *msg = NULL; + int sessionError = libssh2_session_last_error(session, &msg, NULL, 0); + if (sessionError == LIBSSH2_ERROR_SFTP_PROTOCOL) { + unsigned long sftpError = libssh2_sftp_last_error(sftp); + NSError *myError = [NSError errorWithDomain:[self errorDomain] code:sftpError userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:sessionError], @"libssh2SessionError", + [NSNumber numberWithInt:sftpError], @"libssh2SFTPError", + [NSString stringWithFormat:@"sftp read(%@) error: %@", thePath, [self descriptionForSFTPStatusCode:sftpError]], NSLocalizedDescriptionKey, + nil]]; + SETERRORFROMMYERROR; + } else { + NSError *myError = [NSError errorWithDomain:[self errorDomain] code:sessionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:sessionError], @"libssh2SessionError", + [NSString stringWithFormat:@"sftp read(%@) error: %s", thePath, msg], NSLocalizedDescriptionKey, + nil]]; + SETERRORFROMMYERROR; + } + + errorOccurred = YES; + data = nil; + break; + } + + HTTPThrottle *httpThrottle = nil; + if (theDelegate != nil && ![theDelegate dataTransferDidDownloadBytes:recvd httpThrottle:&httpThrottle error:error]) { + errorOccurred = YES; + data = nil; + break; + } + //FIXME: Use the throttle! + } + if (libssh2_sftp_close(handle) < 0) { + errorOccurred = YES; + } + return data; +} + +- (BOOL)writeData:(NSData *)theData toFileAtPath:(NSString *)thePath dataTransferDelegate:(id )theDelegate error:(NSError **)error { + thePath = [thePath stringByDeletingTrailingSlash]; + if (![self doWriteData:theData toFileAtPath:thePath dataTransferDelegate:(id )theDelegate error:NULL]) { + if (![self ensureParentPathExistsForPath:thePath error:error]) { + return NO; + } + if (![self doWriteData:theData toFileAtPath:thePath dataTransferDelegate:(id )theDelegate error:error]) { + return NO; + } + } + return YES; +} + + +- (BOOL)renameItemAtPath:(NSString *)theFromPath toPath:(NSString *)theToPath error:(NSError **)error { + HSLogDebug(@"libssh2_sftp_rename_ex(%@, %@)", theFromPath, theToPath); + if (libssh2_sftp_rename(sftp, (const char *)[theFromPath UTF8String], (const char *)[theToPath UTF8String]) < 0) { + char *msg = NULL; + int sessionError = libssh2_session_last_error(session, &msg, NULL, 0); + if (sessionError == LIBSSH2_ERROR_SFTP_PROTOCOL) { + unsigned long sftpError = libssh2_sftp_last_error(sftp); + NSError *myError = [NSError errorWithDomain:[self errorDomain] code:sftpError userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:sessionError], @"libssh2SessionError", + [NSNumber numberWithInt:sftpError], @"libssh2SFTPError", + [NSString stringWithFormat:@"sftp rename(%@, %@) error: %@", theFromPath, theToPath, [self descriptionForSFTPStatusCode:sftpError]], NSLocalizedDescriptionKey, + nil]]; + SETERRORFROMMYERROR; + } else { + NSError *myError = [NSError errorWithDomain:[self errorDomain] code:sessionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:sessionError], @"libssh2SessionError", + [NSString stringWithFormat:@"sftp rename(%@, %@) error: %s", theFromPath, theToPath, msg], NSLocalizedDescriptionKey, + nil]]; + SETERRORFROMMYERROR; + } + errorOccurred = YES; + return NO; + } + return YES; +} + + +- (BOOL)removeItemAtPath:(NSString *)thePath error:(NSError **)error { + thePath = [thePath stringByDeletingTrailingSlash]; + BOOL isDir = NO; + NSNumber *exists = [self fileExistsAtPath:thePath isDirectory:&isDir error:error]; + if (exists == nil) { + return NO; + } + if (![exists boolValue]) { + return YES; + } + if (isDir) { + if (![self removeDirectoryAtPath:thePath error:error]) { + errorOccurred = YES; + return NO; + } + } else { + if (![self removeFileAtPath:thePath error:error]) { + return NO; + } + } + return YES; +} + + +- (BOOL)createDirectoryAtPath:(NSString *)thePath withIntermediateDirectories:(BOOL)createIntermediates error:(NSError **)error { + thePath = [thePath stringByDeletingTrailingSlash]; + if (![thePath hasPrefix:@"/"]) { + SETNSERROR([self errorDomain], -1, @"can't create directory (doesn't begin with a slash): %@", thePath); + return NO; + } + + if (createIntermediates && ![thePath isEqualToString:@"/"]) { + NSString *parentPath = [thePath stringByDeletingLastPathComponent]; + BOOL isDirectory = NO; + NSNumber *exists = [self fileExistsAtPath:parentPath isDirectory:&isDirectory error:error]; + if (exists == nil) { + errorOccurred = YES; + return NO; + } + if ([exists boolValue]) { + if (!isDirectory) { + SETNSERROR([self errorDomain], -1, @"%@ exists and is not a directory", parentPath); + return NO; + } + } else { + if (![self createDirectoryAtPath:parentPath withIntermediateDirectories:YES error:error]) { + errorOccurred = YES; + return NO; + } + } + } + HSLogDebug(@"libssh2_sftp_mkdir_ex(%@)", thePath); + int ret = libssh2_sftp_mkdir_ex(sftp, (char *)[thePath UTF8String], strlen([thePath UTF8String]), LIBSSH2_SFTP_S_IRWXU|LIBSSH2_SFTP_S_IRWXG|LIBSSH2_SFTP_S_IRWXO); + if (ret) { + char *msg = NULL; + int sessionError = libssh2_session_last_error(session, &msg, NULL, 0); + if (sessionError == LIBSSH2_ERROR_SFTP_PROTOCOL) { + unsigned long sftpError = libssh2_sftp_last_error(sftp); + NSError *myError = [NSError errorWithDomain:[self errorDomain] code:sftpError userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:sessionError], @"libssh2SessionError", + [NSNumber numberWithInt:sftpError], @"libssh2SFTPError", + [NSString stringWithFormat:@"sftp mkdir(%@) error: %@", thePath, [self descriptionForSFTPStatusCode:sftpError]], NSLocalizedDescriptionKey, + nil]]; + SETERRORFROMMYERROR; + } else { + NSError *myError = [NSError errorWithDomain:[self errorDomain] code:sessionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:sessionError], @"libssh2SessionError", + [NSString stringWithFormat:@"sftp mkdir(%@) error: %s", thePath, msg], NSLocalizedDescriptionKey, + nil]]; + SETERRORFROMMYERROR; + } + errorOccurred = YES; + return NO; + } + return YES; +} + +- (BOOL)ensureParentPathExistsForPath:(NSString *)thePath error:(NSError **)error { + thePath = [thePath stringByDeletingTrailingSlash]; + NSString *parentPath = [thePath stringByDeletingLastPathComponent]; + BOOL isDirectory = NO; + NSNumber *exists = [self fileExistsAtPath:parentPath isDirectory:&isDirectory error:error]; + if (exists == nil) { + return NO; + } + if ([exists boolValue]) { + if (!isDirectory) { + SETNSERROR([self errorDomain], -1, @"Parent path %@ exists and is not a directory", parentPath); + return NO; + } + } else { + if (![self createDirectoryAtPath:parentPath withIntermediateDirectories:YES error:error]) { + return NO; + } + } + return YES; +} + +- (NSNumber *)sizeOfItemAtPath:(NSString *)thePath error:(NSError **)error { + thePath = [thePath stringByDeletingTrailingSlash]; + BOOL isDir = NO; + unsigned long long dataSize = 0; + NSNumber *exists = [self fileExistsAtPath:thePath isDirectory:&isDir dataSize:&dataSize lastModifiedDate:NULL error:error]; + if (exists == nil) { + return nil; + } + if (![exists boolValue]) { + HSLogDebug(@"path %@ does not exist; returning size = 0", thePath); + return [NSNumber numberWithUnsignedInteger:0]; + } + + NSNumber *ret = nil; + if (isDir) { + ret = [self sizeOfDirectoryAtPath:thePath error:error]; + } else { + ret = [NSNumber numberWithUnsignedLongLong:dataSize]; + } + return ret; +} + + +- (NSArray *)objectsAtPath:(NSString *)thePath error:(NSError **)error { + thePath = [thePath stringByDeletingTrailingSlash]; + BOOL isDir = NO; + unsigned long long dataSize = NULL; + NSDate *lastModifiedDate = nil; + NSError *myError = nil; + NSNumber *exists = [self fileExistsAtPath:thePath isDirectory:&isDir dataSize:&dataSize lastModifiedDate:&lastModifiedDate error:&myError]; + if (exists == nil) { + SETERRORFROMMYERROR; + return nil; + } + + NSArray *ret = nil; + if (![exists boolValue]) { + ret = [NSArray array]; + } else if (isDir) { + ret = [self objectsInDirectory:thePath error:error]; + } else { + S3ObjectMetadata *md = [[[S3ObjectMetadata alloc] initWithPath:thePath lastModified:lastModifiedDate size:dataSize storageClass:@"STANDARD"] autorelease]; + ret = [NSArray arrayWithObject:md]; + } + return ret; +} + + +- (NSArray *)pathsOfObjectsAtPath:(NSString *)thePath error:(NSError **)error { + thePath = [thePath stringByDeletingTrailingSlash]; + BOOL isDir = NO; + unsigned long long dataSize = NULL; + NSDate *lastModifiedDate = nil; + NSError *myError = nil; + NSNumber *exists = [self fileExistsAtPath:thePath isDirectory:&isDir dataSize:&dataSize lastModifiedDate:&lastModifiedDate error:&myError]; + if (exists == nil) { + SETERRORFROMMYERROR; + return nil; + } + + NSArray *ret = nil; + if (![exists boolValue]) { + ret = [NSArray array]; + } else if (isDir) { + ret = [self pathsOfObjectsInDirectory:thePath error:error]; + } else { + ret = [NSArray arrayWithObject:thePath]; + } + return ret; +} + + + +#pragma mark internal +- (NSNumber *)fileExistsAtPath:(NSString *)thePath isDirectory:(BOOL *)isDirectory dataSize:(unsigned long long *)theDataSize lastModifiedDate:(NSDate **)theLastModifiedDate error:(NSError **)error { + LIBSSH2_SFTP_ATTRIBUTES attrs; + + HSLogDebug(@"libssh2_sftp_lstat(%@)", thePath); + if (libssh2_sftp_lstat(sftp, (char *)[thePath UTF8String], &attrs)) { + char *msg = NULL; + int sessionError = libssh2_session_last_error(session, &msg, NULL, 0); + if (sessionError == LIBSSH2_ERROR_SFTP_PROTOCOL) { + unsigned long sftpError = libssh2_sftp_last_error(sftp); + // SFTP servers can return access-denied if the parent directory doesn't exist: + if (sftpError == LIBSSH2_FX_NO_SUCH_FILE || sftpError == LIBSSH2_FX_PERMISSION_DENIED) { + return [NSNumber numberWithBool:NO]; + } + + NSError *myError = [NSError errorWithDomain:[self errorDomain] code:sftpError userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:sessionError], @"libssh2SessionError", + [NSNumber numberWithInt:sftpError], @"libssh2SFTPError", + [NSString stringWithFormat:@"sftp lstat(%@) error: %@", thePath, [self descriptionForSFTPStatusCode:sftpError]], NSLocalizedDescriptionKey, + nil]]; + SETERRORFROMMYERROR; + } else { + NSError *myError = [NSError errorWithDomain:[self errorDomain] code:sessionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:sessionError], @"libssh2SessionError", + [NSString stringWithFormat:@"sftp lstat(%@) error: %s", thePath, msg], NSLocalizedDescriptionKey, + nil]]; + SETERRORFROMMYERROR; + } + errorOccurred = YES; + return nil; + } + if (isDirectory != NULL) { + if ((attrs.flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) == 0) { + SETNSERROR([self errorDomain], -1, @"Permissions attribute not available"); + return nil; + } + *isDirectory = S_ISDIR(attrs.permissions); + } + if (theDataSize != NULL) { + if ((attrs.flags & LIBSSH2_SFTP_ATTR_SIZE) == 0) { + SETNSERROR([self errorDomain], -1, @"Size attribute not available"); + return nil; + } + *theDataSize = attrs.filesize; + } + if (theLastModifiedDate != NULL) { + if ((attrs.flags & LIBSSH2_SFTP_ATTR_ACMODTIME) == 0) { + SETNSERROR([self errorDomain], -1, @"Last modified time not available"); + return nil; + } + *theLastModifiedDate = [NSDate dateWithTimeIntervalSince1970:(double)attrs.mtime]; + } + return [NSNumber numberWithBool:YES]; +} +- (BOOL)doWriteData:(NSData *)theData toFileAtPath:(NSString *)thePath dataTransferDelegate:(id )theDelegate error:(NSError **)error { + // Delete trailing slash if any, to avoid permission-denied or other errors from SFTP servers: + thePath = [thePath stringByDeletingTrailingSlash]; + + HSLogDebug(@"writing %lu bytes to %@:%@", (unsigned long)[theData length], hostname, thePath); + + HSLogDebug(@"libssh2_sftp_open_ex(%@)", thePath); + LIBSSH2_SFTP_HANDLE *handle = libssh2_sftp_open_ex(sftp, + (char *)[thePath UTF8String], + strlen([thePath UTF8String]), + LIBSSH2_FXF_WRITE|LIBSSH2_FXF_CREAT|LIBSSH2_FXF_TRUNC, + LIBSSH2_SFTP_S_IRWXU|LIBSSH2_SFTP_S_IRWXG|LIBSSH2_SFTP_S_IRWXO, + LIBSSH2_SFTP_OPENFILE); + if (handle == NULL) { + char *msg = NULL; + int sessionError = libssh2_session_last_error(session, &msg, NULL, 0); + if (sessionError == LIBSSH2_ERROR_SFTP_PROTOCOL) { + unsigned long sftpError = libssh2_sftp_last_error(sftp); + NSError *myError = [NSError errorWithDomain:[self errorDomain] code:sftpError userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:sessionError], @"libssh2SessionError", + [NSNumber numberWithInt:sftpError], @"libssh2SFTPError", + [NSString stringWithFormat:@"sftp open(%@) for writing error: %@", thePath, [self descriptionForSFTPStatusCode:sftpError]], NSLocalizedDescriptionKey, + nil]]; + SETERRORFROMMYERROR; + } else { + NSError *myError = [NSError errorWithDomain:[self errorDomain] code:sessionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:sessionError], @"libssh2SessionError", + [NSString stringWithFormat:@"sftp open(%@) for writing error: %s", thePath, msg], NSLocalizedDescriptionKey, + nil]]; + SETERRORFROMMYERROR; + } + errorOccurred = YES; + return NO; + } + BOOL ret = YES; + unsigned char *bytes = (unsigned char *)[theData bytes]; + ssize_t total = [theData length]; + ssize_t sent = 0; + NetMonitor *netMonitor = [[[NetMonitor alloc] init] autorelease]; + ssize_t lastSentLength = 0; + NSTimeInterval lastSentTime = 0; + HTTPThrottleType throttleType = HTTP_THROTTLE_TYPE_NONE; + NSUInteger throttleKBPS = 0; + + while (sent < total) { + size_t len = total - sent; + NSTimeInterval currentTime = [NSDate timeIntervalSinceReferenceDate]; + if (throttleType == HTTP_THROTTLE_TYPE_FIXED && throttleKBPS != 0) { + // Don't send more than 1/10th of the max bytes/sec: + NSUInteger maxLen = throttleKBPS * 100; + if (len > maxLen) { + len = maxLen; + } + + if (lastSentTime != 0) { + NSTimeInterval interval = currentTime - lastSentTime; + + // For some reason Activity Monitor reports "Data sent/sec" at twice what we seem to be sending! + // So we send half as much -- we divide by 500 instead of 1000 here: + NSTimeInterval throttledInterval = (double)lastSentLength / ((double)throttleKBPS * (double)500.0); + + if (throttledInterval > interval) { + [NSThread sleepForTimeInterval:(throttledInterval - interval)]; + } + } + } + + if (throttleType == HTTP_THROTTLE_TYPE_AUTOMATIC) { + NSTimeInterval interval = currentTime - lastSentTime; + if (lastSentLength > 0) { + double myBPS = (double)lastSentLength / interval; + double throttle = [netMonitor sample:myBPS]; + if (throttle < 1.0) { + HSLogDebug(@"throttle = %f", throttle); + } + NSTimeInterval throttledInterval = (throttle == 0) ? 0.5 : ((interval / throttle) - interval); + if (throttledInterval > 0) { + if (throttledInterval > 0.5) { + throttledInterval = 0.5; + } + HSLogDebug(@"auto-throttle: sleeping %f seconds", throttledInterval); + [NSThread sleepForTimeInterval:throttledInterval]; + } + } + } + + HSLogDebug(@"attempting to SFTP write bytes %ld to %ld to %@", sent, (sent+len), thePath); + ssize_t sentThisTime = libssh2_sftp_write(handle, bytes + sent, len); + if (sentThisTime < 0) { + char *msg = NULL; + int sessionError = libssh2_session_last_error(session, &msg, NULL, 0); + if (sessionError == LIBSSH2_ERROR_SFTP_PROTOCOL) { + unsigned long sftpError = libssh2_sftp_last_error(sftp); + NSError *myError = [NSError errorWithDomain:[self errorDomain] code:sftpError userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:sessionError], @"libssh2SessionError", + [NSNumber numberWithInt:sftpError], @"libssh2SFTPError", + [NSString stringWithFormat:@"sftp write(%@) error: %@", thePath, [self descriptionForSFTPStatusCode:sftpError]], NSLocalizedDescriptionKey, + nil]]; + SETERRORFROMMYERROR; + } else { + NSError *myError = [NSError errorWithDomain:[self errorDomain] code:sessionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:sessionError], @"libssh2SessionError", + [NSString stringWithFormat:@"sftp write(%@) error: %s", thePath, msg], NSLocalizedDescriptionKey, + nil]]; + SETERRORFROMMYERROR; + } + + errorOccurred = YES; + ret = NO; + break; + } + + sent += sentThisTime; + lastSentTime = currentTime; + lastSentLength = sentThisTime; + + HTTPThrottle *httpThrottle = nil; + if (theDelegate != nil && ![theDelegate dataTransferDidUploadBytes:sentThisTime httpThrottle:&httpThrottle error:error]) { + errorOccurred = YES; + ret = NO; + break; + } + throttleType = [httpThrottle throttleType]; + throttleKBPS = [httpThrottle throttleKBPS]; + } + if (libssh2_sftp_close(handle) < 0) { + errorOccurred = YES; + } + if (ret) { + HSLogDebug(@"wrote %lu bytes to %@:%@", (unsigned long)[theData length], hostname, thePath); + } + return ret; +} +- (BOOL)removeFileAtPath:(NSString *)thePath error:(NSError **)error { + // Delete trailing slash if any, to avoid permission-denied or other errors from SFTP servers: + thePath = [thePath stringByDeletingTrailingSlash]; + + HSLogDebug(@"libssh2_sftp_unlink_ex(%@)", thePath); + if (libssh2_sftp_unlink_ex(sftp, (char *)[thePath UTF8String], strlen([thePath UTF8String])) == -1) { + char *msg = NULL; + int sessionError = libssh2_session_last_error(session, &msg, NULL, 0); + if (sessionError == LIBSSH2_ERROR_SFTP_PROTOCOL) { + unsigned long sftpError = libssh2_sftp_last_error(sftp); + NSError *myError = [NSError errorWithDomain:[self errorDomain] code:sftpError userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:sessionError], @"libssh2SessionError", + [NSNumber numberWithInt:sftpError], @"libssh2SFTPError", + [NSString stringWithFormat:@"sftp unlink(%@) error: %@", thePath, [self descriptionForSFTPStatusCode:sftpError]], NSLocalizedDescriptionKey, + nil]]; + SETERRORFROMMYERROR; + } else { + NSError *myError = [NSError errorWithDomain:[self errorDomain] code:sessionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:sessionError], @"libssh2SessionError", + [NSString stringWithFormat:@"sftp unlink(%@) error: %s", thePath, msg], NSLocalizedDescriptionKey, + nil]]; + SETERRORFROMMYERROR; + } + errorOccurred = YES; + return NO; + } + return YES; +} +- (BOOL)removeDirectoryAtPath:(NSString *)thePath error:(NSError **)error { + // Delete trailing slash if any, to avoid permission-denied or other errors from SFTP servers: + thePath = [thePath stringByDeletingTrailingSlash]; + + HSLogDebug(@"libssh2_sftp_opendir(%@)", thePath); + LIBSSH2_SFTP_HANDLE *handle = libssh2_sftp_opendir(sftp, (char *)[thePath UTF8String]); + if (handle == NULL) { + char *msg = NULL; + int sessionError = libssh2_session_last_error(session, &msg, NULL, 0); + if (sessionError == LIBSSH2_ERROR_SFTP_PROTOCOL) { + unsigned long sftpError = libssh2_sftp_last_error(sftp); + // SFTP servers can return access-denied if the parent directory doesn't exist: + if (sftpError == LIBSSH2_FX_NO_SUCH_FILE || sftpError == LIBSSH2_FX_PERMISSION_DENIED) { + return [NSArray array]; + } + + NSError *myError = [NSError errorWithDomain:[self errorDomain] code:sftpError userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:sessionError], @"libssh2SessionError", + [NSNumber numberWithInt:sftpError], @"libssh2SFTPError", + [NSString stringWithFormat:@"sftp opendir(%@) error: %@", thePath, [self descriptionForSFTPStatusCode:sftpError]], NSLocalizedDescriptionKey, + nil]]; + SETERRORFROMMYERROR; + } else { + NSError *myError = [NSError errorWithDomain:[self errorDomain] code:sessionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:sessionError], @"libssh2SessionError", + [NSString stringWithFormat:@"sftp opendir(%@) error: %s", thePath, msg], NSLocalizedDescriptionKey, + nil]]; + SETERRORFROMMYERROR; + } + errorOccurred = YES; + return nil; + } + + BOOL ret = YES; + char buf[BUFLEN]; + for (;;) { + LIBSSH2_SFTP_ATTRIBUTES attrs; + int rc = libssh2_sftp_readdir(handle, buf, BUFLEN, &attrs); + if (rc < 0) { + char *msg = NULL; + int sessionError = libssh2_session_last_error(session, &msg, NULL, 0); + if (sessionError == LIBSSH2_ERROR_SFTP_PROTOCOL) { + unsigned long sftpError = libssh2_sftp_last_error(sftp); + NSError *myError = [NSError errorWithDomain:[self errorDomain] code:sftpError userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:sessionError], @"libssh2SessionError", + [NSNumber numberWithInt:sftpError], @"libssh2SFTPError", + [NSString stringWithFormat:@"sftp readdir(%@) error: %@", thePath, [self descriptionForSFTPStatusCode:sftpError]], NSLocalizedDescriptionKey, + nil]]; + SETERRORFROMMYERROR; + } else { + NSError *myError = [NSError errorWithDomain:[self errorDomain] code:sessionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:sessionError], @"libssh2SessionError", + [NSString stringWithFormat:@"sftp readdir(%@) error: %s", thePath, msg], NSLocalizedDescriptionKey, + nil]]; + SETERRORFROMMYERROR; + } + errorOccurred = YES; + ret = NO; + break; + } else if (rc > 0) { + NSString *name = [[[NSString alloc] initWithBytes:buf length:rc encoding:NSUTF8StringEncoding] autorelease]; + if (![name isEqualToString:@"."] && ![name isEqualToString:@".."]) { + NSString *childPath = [thePath stringByAppendingPathComponent:name]; + if ((attrs.flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) == 0) { + SETNSERROR([self errorDomain], -1, @"Permissions attribute not available"); + ret = NO; + break; + } + if (S_ISDIR(attrs.permissions)) { + if (![self removeDirectoryAtPath:childPath error:error]) { + ret = NO; + break; + } + } else { + if (![self removeFileAtPath:childPath error:error]) { + ret = NO; + break; + } + } + } + } else { + break; + } + } + if (libssh2_sftp_closedir(handle) < 0) { + errorOccurred = YES; + } + if (!ret) { + return NO; + } + + HSLogDebug(@"libssh2_sftp_rmdir_ex(%@)", thePath); + if (libssh2_sftp_rmdir_ex(sftp, [thePath UTF8String], strlen([thePath UTF8String]))) { + char *msg = NULL; + int sessionError = libssh2_session_last_error(session, &msg, NULL, 0); + if (sessionError == LIBSSH2_ERROR_SFTP_PROTOCOL) { + unsigned long sftpError = libssh2_sftp_last_error(sftp); + NSError *myError = [NSError errorWithDomain:[self errorDomain] code:sftpError userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:sessionError], @"libssh2SessionError", + [NSNumber numberWithInt:sftpError], @"libssh2SFTPError", + [NSString stringWithFormat:@"sftp rmdir(%@) error: %@", thePath, [self descriptionForSFTPStatusCode:sftpError]], NSLocalizedDescriptionKey, + nil]]; + SETERRORFROMMYERROR; + } else { + NSError *myError = [NSError errorWithDomain:[self errorDomain] code:sessionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:sessionError], @"libssh2SessionError", + [NSString stringWithFormat:@"sftp rmdir(%@) error: %s", thePath, msg], NSLocalizedDescriptionKey, + nil]]; + SETERRORFROMMYERROR; + } + errorOccurred = YES; + return NO; + } + return YES; +} +- (NSNumber *)sizeOfDirectoryAtPath:(NSString *)thePath error:(NSError **)error { + // Delete trailing slash if any, to avoid permission-denied or other errors from SFTP servers: + thePath = [thePath stringByDeletingTrailingSlash]; + + HSLogDebug(@"libssh2_sftp_opendir(%@)", thePath); + LIBSSH2_SFTP_HANDLE *handle = libssh2_sftp_opendir(sftp, (char *)[thePath UTF8String]); + if (handle == NULL) { + char *msg = NULL; + int sessionError = libssh2_session_last_error(session, &msg, NULL, 0); + if (sessionError == LIBSSH2_ERROR_SFTP_PROTOCOL) { + unsigned long sftpError = libssh2_sftp_last_error(sftp); + NSError *myError = [NSError errorWithDomain:[self errorDomain] code:sftpError userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:sessionError], @"libssh2SessionError", + [NSNumber numberWithInt:sftpError], @"libssh2SFTPError", + [NSString stringWithFormat:@"sftp opendir(%@) error: %@", thePath, [self descriptionForSFTPStatusCode:sftpError]], NSLocalizedDescriptionKey, + nil]]; + SETERRORFROMMYERROR; + } else { + NSError *myError = [NSError errorWithDomain:[self errorDomain] code:sessionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:sessionError], @"libssh2SessionError", + [NSString stringWithFormat:@"sftp opendir(%@) error: %s", thePath, msg], NSLocalizedDescriptionKey, + nil]]; + SETERRORFROMMYERROR; + } + errorOccurred = YES; + return nil; + } + + BOOL ret = YES; + unsigned long long total = 0; + char buf[BUFLEN]; + for (;;) { + LIBSSH2_SFTP_ATTRIBUTES attrs; + int rc = libssh2_sftp_readdir(handle, buf, BUFLEN, &attrs); + if (rc < 0) { + char *msg = NULL; + int sessionError = libssh2_session_last_error(session, &msg, NULL, 0); + if (sessionError == LIBSSH2_ERROR_SFTP_PROTOCOL) { + unsigned long sftpError = libssh2_sftp_last_error(sftp); + NSError *myError = [NSError errorWithDomain:[self errorDomain] code:sftpError userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:sessionError], @"libssh2SessionError", + [NSNumber numberWithInt:sftpError], @"libssh2SFTPError", + [NSString stringWithFormat:@"sftp readdir(%@) error: %@", thePath, [self descriptionForSFTPStatusCode:sftpError]], NSLocalizedDescriptionKey, + nil]]; + SETERRORFROMMYERROR; + } else { + NSError *myError = [NSError errorWithDomain:[self errorDomain] code:sessionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:sessionError], @"libssh2SessionError", + [NSString stringWithFormat:@"sftp readdir(%@) error: %s", thePath, msg], NSLocalizedDescriptionKey, + nil]]; + SETERRORFROMMYERROR; + } + errorOccurred = YES; + ret = NO; + break; + } else if (rc > 0) { + NSString *name = [[[NSString alloc] initWithBytes:buf length:rc encoding:NSUTF8StringEncoding] autorelease]; + if (![name isEqualToString:@"."] && ![name isEqualToString:@".."]) { + if ((attrs.flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) == 0) { + SETNSERROR([self errorDomain], -1, @"Permissions attribute not available"); + ret = NO; + break; + } + if (S_ISDIR(attrs.permissions)) { + NSString *childPath = [thePath stringByAppendingPathComponent:name]; + NSNumber *childSize = [self sizeOfDirectoryAtPath:childPath error:error]; + if (childSize == nil) { + ret = NO; + break; + } + total += [childSize unsignedLongLongValue]; + } else { + if ((attrs.flags & LIBSSH2_SFTP_ATTR_SIZE) == 0) { + SETNSERROR([self errorDomain], -1, @"Size attribute not available"); + ret = NO; + break; + } + total += attrs.filesize; + } + } + } else { + break; + } + } + if (libssh2_sftp_closedir(handle) < 0) { + errorOccurred = YES; + } + + if (!ret) { + return nil; + } + + return [NSNumber numberWithUnsignedLongLong:total]; +} +- (NSArray *)objectsInDirectory:(NSString *)thePath error:(NSError **)error { + // Delete trailing slash if any, to avoid permission-denied or other errors from SFTP servers: + thePath = [thePath stringByDeletingTrailingSlash]; + + HSLogDebug(@"libssh2_sftp_opendir(%@)", thePath); + LIBSSH2_SFTP_HANDLE *handle = libssh2_sftp_opendir(sftp, (char *)[thePath UTF8String]); + if (handle == NULL) { + char *msg = NULL; + int sessionError = libssh2_session_last_error(session, &msg, NULL, 0); + if (sessionError == LIBSSH2_ERROR_SFTP_PROTOCOL) { + unsigned long sftpError = libssh2_sftp_last_error(sftp); + NSError *myError = [NSError errorWithDomain:[self errorDomain] code:sftpError userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:sessionError], @"libssh2SessionError", + [NSNumber numberWithInt:sftpError], @"libssh2SFTPError", + [NSString stringWithFormat:@"sftp opendir(%@) error: %@", thePath, [self descriptionForSFTPStatusCode:sftpError]], NSLocalizedDescriptionKey, + nil]]; + SETERRORFROMMYERROR; + } else { + NSError *myError = [NSError errorWithDomain:[self errorDomain] code:sessionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:sessionError], @"libssh2SessionError", + [NSString stringWithFormat:@"sftp opendir(%@) error: %s", thePath, msg], NSLocalizedDescriptionKey, + nil]]; + SETERRORFROMMYERROR; + } + errorOccurred = YES; + return nil; + } + + NSMutableArray *ret = [NSMutableArray array]; + char buf[BUFLEN]; + for (;;) { + LIBSSH2_SFTP_ATTRIBUTES attrs; + int rc = libssh2_sftp_readdir(handle, buf, BUFLEN, &attrs); + if (rc < 0) { + char *msg = NULL; + int sessionError = libssh2_session_last_error(session, &msg, NULL, 0); + if (sessionError == LIBSSH2_ERROR_SFTP_PROTOCOL) { + unsigned long sftpError = libssh2_sftp_last_error(sftp); + NSError *myError = [NSError errorWithDomain:[self errorDomain] code:sftpError userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:sessionError], @"libssh2SessionError", + [NSNumber numberWithInt:sftpError], @"libssh2SFTPError", + [NSString stringWithFormat:@"sftp readdir(%@) error: %@", thePath, [self descriptionForSFTPStatusCode:sftpError]], NSLocalizedDescriptionKey, + nil]]; + SETERRORFROMMYERROR; + } else { + NSError *myError = [NSError errorWithDomain:[self errorDomain] code:sessionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:sessionError], @"libssh2SessionError", + [NSString stringWithFormat:@"sftp readdir(%@) error: %s", thePath, msg], NSLocalizedDescriptionKey, + nil]]; + SETERRORFROMMYERROR; + } + errorOccurred = YES; + ret = nil; + break; + } else if (rc > 0) { + NSString *name = [[[NSString alloc] initWithBytes:buf length:rc encoding:NSUTF8StringEncoding] autorelease]; + if (![name isEqualToString:@"."] && ![name isEqualToString:@".."]) { + if ((attrs.flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) == 0) { + SETNSERROR([self errorDomain], -1, @"Permissions attribute not available"); + ret = nil; + break; + } + NSString *childPath = [thePath stringByAppendingPathComponent:name]; + if (S_ISDIR(attrs.permissions)) { + NSArray *childObjects = [self objectsInDirectory:childPath error:error]; + if (childObjects == nil) { + ret = nil; + break; + } + [ret addObjectsFromArray:childObjects]; + } else { + if ((attrs.flags & LIBSSH2_SFTP_ATTR_SIZE) == 0) { + SETNSERROR([self errorDomain], -1, @"Size attribute not available"); + ret = nil; + break; + } + if ((attrs.flags & LIBSSH2_SFTP_ATTR_ACMODTIME) == 0) { + SETNSERROR([self errorDomain], -1, @"Mod time attribute not available"); + ret = nil; + break; + } + NSDate *date = [NSDate dateWithTimeIntervalSince1970:(double)attrs.mtime]; + S3ObjectMetadata *md = [[[S3ObjectMetadata alloc] initWithPath:childPath lastModified:date size:(long)attrs.filesize storageClass:@"STANDARD"] autorelease]; + [ret addObject:md]; + } + } + } else { + break; + } + } + if (libssh2_sftp_closedir(handle) < 0) { + errorOccurred = YES; + } + + return ret; +} +- (NSArray *)pathsOfObjectsInDirectory:(NSString *)thePath error:(NSError **)error { + // Delete trailing slash if any, to avoid permission-denied or other errors from SFTP servers: + thePath = [thePath stringByDeletingTrailingSlash]; + + HSLogDebug(@"libssh2_sftp_opendir(%@)", thePath); + LIBSSH2_SFTP_HANDLE *handle = libssh2_sftp_opendir(sftp, (char *)[thePath UTF8String]); + if (handle == NULL) { + char *msg = NULL; + int sessionError = libssh2_session_last_error(session, &msg, NULL, 0); + if (sessionError == LIBSSH2_ERROR_SFTP_PROTOCOL) { + unsigned long sftpError = libssh2_sftp_last_error(sftp); + NSError *myError = [NSError errorWithDomain:[self errorDomain] code:sftpError userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:sessionError], @"libssh2SessionError", + [NSNumber numberWithInt:sftpError], @"libssh2SFTPError", + [NSString stringWithFormat:@"sftp opendir(%@) error: %@", thePath, [self descriptionForSFTPStatusCode:sftpError]], NSLocalizedDescriptionKey, + nil]]; + SETERRORFROMMYERROR; + } else { + NSError *myError = [NSError errorWithDomain:[self errorDomain] code:sessionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:sessionError], @"libssh2SessionError", + [NSString stringWithFormat:@"sftp opendir(%@) error: %s", thePath, msg], NSLocalizedDescriptionKey, + nil]]; + SETERRORFROMMYERROR; + } + errorOccurred = YES; + return nil; + } + + NSMutableArray *ret = [NSMutableArray array]; + char buf[BUFLEN]; + for (;;) { + LIBSSH2_SFTP_ATTRIBUTES attrs; + int rc = libssh2_sftp_readdir(handle, buf, BUFLEN, &attrs); + if (rc < 0) { + char *msg = NULL; + int sessionError = libssh2_session_last_error(session, &msg, NULL, 0); + if (sessionError == LIBSSH2_ERROR_SFTP_PROTOCOL) { + unsigned long sftpError = libssh2_sftp_last_error(sftp); + NSError *myError = [NSError errorWithDomain:[self errorDomain] code:sftpError userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:sessionError], @"libssh2SessionError", + [NSNumber numberWithInt:sftpError], @"libssh2SFTPError", + [NSString stringWithFormat:@"sftp readdir(%@) error: %@", thePath, [self descriptionForSFTPStatusCode:sftpError]], NSLocalizedDescriptionKey, + nil]]; + SETERRORFROMMYERROR; + } else { + NSError *myError = [NSError errorWithDomain:[self errorDomain] code:sessionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:sessionError], @"libssh2SessionError", + [NSString stringWithFormat:@"sftp readdir(%@) error: %s", thePath, msg], NSLocalizedDescriptionKey, + nil]]; + SETERRORFROMMYERROR; + } + errorOccurred = YES; + return nil; + } else if (rc > 0) { + NSString *name = [[[NSString alloc] initWithBytes:buf length:rc encoding:NSUTF8StringEncoding] autorelease]; + if (![name isEqualToString:@"."] && ![name isEqualToString:@".."]) { + if ((attrs.flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) == 0) { + SETNSERROR([self errorDomain], -1, @"Permissions attribute not available"); + ret = nil; + break; + } + NSString *childPath = [thePath stringByAppendingPathComponent:name]; + if (S_ISDIR(attrs.permissions)) { + NSArray *childPaths = [self pathsOfObjectsInDirectory:childPath error:error]; + if (childPaths == nil) { + ret = nil; + break; + } + [ret addObjectsFromArray:childPaths]; + } else { + if ((attrs.flags & LIBSSH2_SFTP_ATTR_SIZE) == 0) { + SETNSERROR([self errorDomain], -1, @"Size attribute not available"); + ret = nil; + break; + } + if ((attrs.flags & LIBSSH2_SFTP_ATTR_ACMODTIME) == 0) { + SETNSERROR([self errorDomain], -1, @"Mod time attribute not available"); + ret = nil; + break; + } + [ret addObject:childPath]; + } + } + } else { + break; + } + } + if (libssh2_sftp_closedir(handle) < 0) { + errorOccurred = YES; + } + + return ret; +} +- (NSString *)descriptionForSFTPStatusCode:(unsigned long)errnum { + switch (errnum) { + case LIBSSH2_FX_OK: + return @"OK"; + case LIBSSH2_FX_EOF: + return @"EOF"; + case LIBSSH2_FX_NO_SUCH_FILE: + return @"No such file"; + case LIBSSH2_FX_PERMISSION_DENIED: + return @"Permission denied"; + case LIBSSH2_FX_FAILURE: + return @"Failure"; + case LIBSSH2_FX_BAD_MESSAGE: + return @"Bad message"; + case LIBSSH2_FX_NO_CONNECTION: + return @"No connection"; + case LIBSSH2_FX_CONNECTION_LOST: + return @"Connection lost"; + case LIBSSH2_FX_OP_UNSUPPORTED: + return @"Op unsupported"; + case LIBSSH2_FX_INVALID_HANDLE: + return @"Invalid handle"; + case LIBSSH2_FX_NO_SUCH_PATH: + return @"No such path"; + case LIBSSH2_FX_FILE_ALREADY_EXISTS: + return @"File already exists"; + case LIBSSH2_FX_WRITE_PROTECT: + return @"Write protect"; + case LIBSSH2_FX_NO_MEDIA: + return @"No media"; + case LIBSSH2_FX_NO_SPACE_ON_FILESYSTEM: + return @"No space on filesystem"; + case LIBSSH2_FX_QUOTA_EXCEEDED: + return @"Quota exceeded"; + case LIBSSH2_FX_UNKNOWN_PRINCIPLE: + return @"Unknown principle"; + case LIBSSH2_FX_LOCK_CONFlICT: + return @"Lock conflict"; + case LIBSSH2_FX_DIR_NOT_EMPTY: + return @"Dir not empty"; + case LIBSSH2_FX_NOT_A_DIRECTORY: + return @"Not a directory"; + case LIBSSH2_FX_INVALID_FILENAME: + return @"Invalid filename"; + case LIBSSH2_FX_LINK_LOOP: + return @"Link loop"; + } + return @"Unknown SFTP error code"; +} +@end + +#pragma clang diagnostic pop diff --git a/cocoastack/shared/CWLSynthesizeSingleton.h b/cocoastack/shared/CWLSynthesizeSingleton.h new file mode 100644 index 0000000..71dbbe4 --- /dev/null +++ b/cocoastack/shared/CWLSynthesizeSingleton.h @@ -0,0 +1,90 @@ +// +// CWLSynthesizeSingleton.h +// CocoaWithLove +// +// Created by Matt Gallagher on 2011/08/23. +// Copyright (c) 2011 Matt Gallagher. All rights reserved. +// +// Permission is given to use this source code file, free of charge, in any +// project, commercial or otherwise, entirely at your risk, with the condition +// that any redistribution (in part or whole) of source code must retain +// this copyright and permission notice. Attribution in compiled projects is +// appreciated but not required. +// + +#import + +#define CWL_DECLARE_SINGLETON_FOR_CLASS_WITH_ACCESSOR(classname, accessorMethodName) \ ++ (classname *)accessorMethodName; + +#if __has_feature(objc_arc) + #define CWL_SYNTHESIZE_SINGLETON_RETAIN_METHODS +#else + #define CWL_SYNTHESIZE_SINGLETON_RETAIN_METHODS \ + - (id)retain \ + { \ + return self; \ + } \ + \ + - (NSUInteger)retainCount \ + { \ + return NSUIntegerMax; \ + } \ + \ + - (oneway void)release \ + { \ + } \ + \ + - (id)autorelease \ + { \ + return self; \ + } +#endif + +#define CWL_SYNTHESIZE_SINGLETON_FOR_CLASS_WITH_ACCESSOR(classname, accessorMethodName) \ + \ +static classname *accessorMethodName##Instance = nil; \ + \ ++ (classname *)accessorMethodName \ +{ \ + @synchronized(self) \ + { \ + if (accessorMethodName##Instance == nil) \ + { \ + accessorMethodName##Instance = [super allocWithZone:NULL]; \ + accessorMethodName##Instance = [accessorMethodName##Instance init]; \ + method_exchangeImplementations(\ + class_getClassMethod([accessorMethodName##Instance class], @selector(accessorMethodName)),\ + class_getClassMethod([accessorMethodName##Instance class], @selector(cwl_lockless_##accessorMethodName)));\ + method_exchangeImplementations(\ + class_getInstanceMethod([accessorMethodName##Instance class], @selector(init)),\ + class_getInstanceMethod([accessorMethodName##Instance class], @selector(cwl_onlyInitOnce)));\ + } \ + } \ + \ + return accessorMethodName##Instance; \ +} \ + \ ++ (classname *)cwl_lockless_##accessorMethodName \ +{ \ + return accessorMethodName##Instance; \ +} \ +\ ++ (id)allocWithZone:(NSZone *)zone \ +{ \ + return [self accessorMethodName]; \ +} \ + \ +- (id)copyWithZone:(NSZone *)zone \ +{ \ + return self; \ +} \ +- (id)cwl_onlyInitOnce \ +{ \ + return self;\ +} \ + \ +CWL_SYNTHESIZE_SINGLETON_RETAIN_METHODS + +#define CWL_DECLARE_SINGLETON_FOR_CLASS(classname) CWL_DECLARE_SINGLETON_FOR_CLASS_WITH_ACCESSOR(classname, shared##classname) +#define CWL_SYNTHESIZE_SINGLETON_FOR_CLASS(classname) CWL_SYNTHESIZE_SINGLETON_FOR_CLASS_WITH_ACCESSOR(classname, shared##classname) diff --git a/shared/Computer.h b/cocoastack/shared/Computer.h similarity index 96% rename from shared/Computer.h rename to cocoastack/shared/Computer.h index 1a0b48c..d53d9b1 100644 --- a/shared/Computer.h +++ b/cocoastack/shared/Computer.h @@ -37,4 +37,6 @@ } + (NSString *)name; ++ (NSString *)serialNumber; ++ (NSString *)machineType; @end diff --git a/shared/Computer.m b/cocoastack/shared/Computer.m similarity index 65% rename from shared/Computer.m rename to cocoastack/shared/Computer.m index c7ce727..c684be7 100644 --- a/shared/Computer.m +++ b/cocoastack/shared/Computer.m @@ -33,6 +33,18 @@ #import "Computer.h" #include + +#define SERIAL_BUF_SIZE (1024) + + +static void get_serial_number(char *buf, int bufSize) { + io_registry_entry_t ioRegistryRoot = IORegistryEntryFromPath(kIOMasterPortDefault, "IOService:/"); + CFStringRef uuidCf = (CFStringRef) IORegistryEntryCreateCFProperty(ioRegistryRoot, CFSTR(kIOPlatformSerialNumberKey), kCFAllocatorDefault, 0); + IOObjectRelease(ioRegistryRoot); + CFStringGetCString(uuidCf, buf, bufSize, kCFStringEncodingMacRoman); + CFRelease(uuidCf); +} + @implementation Computer + (NSString *)name { NSString *theName = (NSString *)SCDynamicStoreCopyComputerName(NULL,NULL); @@ -41,4 +53,18 @@ } return [theName autorelease]; } ++ (NSString *)serialNumber { + char buf[SERIAL_BUF_SIZE]; + get_serial_number(buf, SERIAL_BUF_SIZE); + return [NSString stringWithUTF8String:buf]; +} ++ (NSString *)machineType { + OSErr err; + char *machineName=NULL; // This is really a Pascal-string with a length byte. + err = Gestalt(gestaltUserVisibleMachineName, (SInt32*) &machineName); //gestaltUserVisibleMachineName = 'mnam' + if (err != noErr) { + return nil; + } + return [[[NSString alloc] initWithBytes:machineName+1 length:machineName[0] encoding:NSUTF8StringEncoding] autorelease]; +} @end diff --git a/shared/HSLog.h b/cocoastack/shared/HSLog.h similarity index 100% rename from shared/HSLog.h rename to cocoastack/shared/HSLog.h diff --git a/shared/HSLog.m b/cocoastack/shared/HSLog.m similarity index 93% rename from shared/HSLog.m rename to cocoastack/shared/HSLog.m index 8b90b25..3e70580 100644 --- a/shared/HSLog.m +++ b/cocoastack/shared/HSLog.m @@ -32,10 +32,13 @@ #import "HSLog.h" -unsigned int global_hslog_level = HSLOG_LEVEL_WARN; +unsigned int global_hslog_level = HSLOG_LEVEL_ERROR; void setHSLogLevel(int level) { - global_hslog_level = level; + if (global_hslog_level != level) { + global_hslog_level = level; + NSLog(@"set log level to %@", nameForHSLogLevel(level)); + } } extern int hsLogLevelForName(NSString *levelName) { if ([[levelName lowercaseString] isEqualToString:@"error"]) { diff --git a/shared/NSData-GZip.h b/cocoastack/shared/NSData-GZip.h similarity index 96% rename from shared/NSData-GZip.h rename to cocoastack/shared/NSData-GZip.h index e89cc1d..383b8f7 100644 --- a/shared/NSData-GZip.h +++ b/cocoastack/shared/NSData-GZip.h @@ -30,12 +30,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - +#import @interface NSData (GZip) -- (NSData *)gzipInflate; +- (NSData *)gzipInflate:(NSError **)error; - (NSData *)gzipDeflate; @end diff --git a/shared/NSData-GZip.m b/cocoastack/shared/NSData-GZip.m similarity index 74% rename from shared/NSData-GZip.m rename to cocoastack/shared/NSData-GZip.m index c3dbbd6..af7776b 100644 --- a/shared/NSData-GZip.m +++ b/cocoastack/shared/NSData-GZip.m @@ -34,13 +34,13 @@ #include @implementation NSData (GZip) -- (NSData *)gzipInflate { +- (NSData *)gzipInflate:(NSError **)error { if ([self length] == 0) { return self; } - unsigned full_length = [self length]; - unsigned half_length = [self length] / 2; + unsigned full_length = (unsigned)[self length]; + unsigned half_length = (unsigned)[self length] / 2; NSMutableData *decompressed = [NSMutableData dataWithLength: full_length + half_length]; BOOL done = NO; @@ -48,12 +48,13 @@ z_stream stream; stream.next_in = (Bytef *)[self bytes]; - stream.avail_in = [self length]; + stream.avail_in = (unsigned)[self length]; stream.total_out = 0; stream.zalloc = Z_NULL; stream.zfree = Z_NULL; if (inflateInit2(&stream, (15+32)) != Z_OK) { + SETNSERROR(@"GunzipErrorDomain", -1, @"inflateinit2 failed"); return nil; } while (!done) { @@ -62,17 +63,38 @@ [decompressed increaseLengthBy: half_length]; } stream.next_out = [decompressed mutableBytes] + stream.total_out; - stream.avail_out = [decompressed length] - stream.total_out; + stream.avail_out = (unsigned int)([decompressed length] - stream.total_out); // Inflate another chunk. status = inflate (&stream, Z_SYNC_FLUSH); if (status == Z_STREAM_END) { done = YES; } else if (status != Z_OK) { + switch (status) { + case Z_NEED_DICT: + HSLogError(@"Z_NEED_DICT"); + break; + case Z_STREAM_ERROR: + HSLogError(@"Z_STREAM_ERROR"); + break; + case Z_DATA_ERROR: + HSLogError(@"Z_DATA_ERROR"); + break; + case Z_MEM_ERROR: + HSLogError(@"Z_MEM_ERROR"); + break; + case Z_BUF_ERROR: + HSLogError(@"Z_BUF_ERROR"); + break; + default: + HSLogError(@"inflate error"); + break; + } break; } } if (inflateEnd (&stream) != Z_OK) { + SETNSERROR(@"GunzipErrorDomain", -1, @"inflateEnd failed"); return nil; } @@ -81,6 +103,7 @@ [decompressed setLength: stream.total_out]; return [NSData dataWithData: decompressed]; } else { + SETNSERROR(@"GunzipErrorDomain", -1, @"inflate failed"); return nil; } } @@ -96,7 +119,7 @@ strm.opaque = Z_NULL; strm.total_out = 0; strm.next_in=(Bytef *)[self bytes]; - strm.avail_in = [self length]; + strm.avail_in = (unsigned int)[self length]; // Compresssion Levels: // Z_NO_COMPRESSION @@ -116,7 +139,7 @@ } strm.next_out = [compressed mutableBytes] + strm.total_out; - strm.avail_out = [compressed length] - strm.total_out; + strm.avail_out = (unsigned int)([compressed length] - strm.total_out); deflate(&strm, Z_FINISH); @@ -125,7 +148,7 @@ deflateEnd(&strm); [compressed setLength: strm.total_out]; - return [NSData dataWithData:compressed]; + return compressed; } @end diff --git a/shared/NSErrorCodes.h b/cocoastack/shared/NSErrorCodes.h similarity index 87% rename from shared/NSErrorCodes.h rename to cocoastack/shared/NSErrorCodes.h index ac8aaad..19ca145 100644 --- a/shared/NSErrorCodes.h +++ b/cocoastack/shared/NSErrorCodes.h @@ -36,7 +36,6 @@ #define ERROR_NOT_LICENSED (-5) #define ERROR_BUCKET_CONFIGURATION_CHANGED (-6) #define ERROR_ABORT_REQUESTED (-7) -#define ERROR_PACK_INDEX_ENTRY_NOT_RESOLVABLE (-8) #define ERROR_COULD_NOT_CONNECT_TO_AGENT (-9) #define ERROR_AGENT_COMMUNICATION (-10) #define ERROR_TIMEOUT (-11) @@ -49,3 +48,11 @@ #define ERROR_INVALID_FILE (-18) #define ERROR_DELAYS_IN_S3_EVENTUAL_CONSISTENCY (-19) #define ERROR_INVALID_COMMIT_HEADER (-20) +#define ERROR_GLACIER_VAULT_INVENTORY_NOT_UP_TO_DATE (-21) +#define ERROR_GLACIER_OBJECT_NOT_AVAILABLE (-22) +#define ERROR_INVALID_PLIST_XML (-23) +#define ERROR_NOT_DOWNLOADABLE (-24) +#define ERROR_MISSING_SECRET (-25) +#define ERROR_ACCESS_REVOKED (-26) +#define ERROR_MULTIPLE_ERRORS (-27) +#define ERROR_USAGE (-1001) diff --git a/shared/NSError_extra.h b/cocoastack/shared/NSError_extra.h similarity index 98% rename from shared/NSError_extra.h rename to cocoastack/shared/NSError_extra.h index 186d069..7cefb78 100644 --- a/shared/NSError_extra.h +++ b/cocoastack/shared/NSError_extra.h @@ -39,5 +39,4 @@ - (BOOL)isConnectionResetError; - (BOOL)isTransientError; - (BOOL)isSSLError; -- (void)logSSLCerts; @end diff --git a/shared/NSError_extra.m b/cocoastack/shared/NSError_extra.m similarity index 53% rename from shared/NSError_extra.m rename to cocoastack/shared/NSError_extra.m index 0353e37..3e0c942 100644 --- a/shared/NSError_extra.m +++ b/cocoastack/shared/NSError_extra.m @@ -13,7 +13,7 @@ notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of their contributors may be used to endorse or promote products derived from this software without specific prior written permission. @@ -28,12 +28,15 @@ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ + */ +#include "libssh2.h" +#include "libssh2_sftp.h" #import #import #import "NSError_extra.h" -#import "NSErrorCodes.h" +#import "S3Service.h" +#import "GoogleDrive.h" @implementation NSError (extra) @@ -72,8 +75,9 @@ || [self code] == EISCONN || [self code] == ENOTCONN || [self code] == ETIMEDOUT - || [self code] == EHOSTUNREACH + || [self code] == ECONNREFUSED || [self code] == EHOSTDOWN + || [self code] == EHOSTUNREACH || [self code] == EPIPE) { return YES; } @@ -92,6 +96,12 @@ return YES; } } + if ([[self domain] isEqualToString:[S3Service errorDomain]] && [self code] == S3SERVICE_ERROR_AMAZON_ERROR) { + return YES; + } + if ([[self domain] isEqualToString:[GoogleDrive errorDomain]] && [self code] == 500) { + return YES; + } if ([[self domain] isEqualToString:@"NSOSStatusErrorDomain"] && [self code] <= errSSLProtocol && [self code] >= errSSLLast) { return YES; } @@ -101,46 +111,78 @@ if ([self code] == ERROR_TIMEOUT) { return YES; } + NSNumber *theSessionError = [[self userInfo] objectForKey:@"libssh2SessionError"]; + if (theSessionError != nil) { + int sessionError = [theSessionError intValue]; + if (sessionError == LIBSSH2_ERROR_SFTP_PROTOCOL) { + NSNumber *theSFTPError = [[self userInfo] objectForKey:@"libssh2SFTPError"]; + if (theSFTPError != nil) { + int sftpError = [theSFTPError intValue]; + switch (sftpError) { + case LIBSSH2_FX_BAD_MESSAGE: + case LIBSSH2_FX_NO_CONNECTION: + case LIBSSH2_FX_CONNECTION_LOST: + return YES; + } + } + } else { + switch (sessionError) { + case LIBSSH2_ERROR_BANNER_RECV: + case LIBSSH2_ERROR_BANNER_SEND: + case LIBSSH2_ERROR_INVALID_MAC: + case LIBSSH2_ERROR_KEX_FAILURE: + // case LIBSSH2_ERROR_ALLOC: + case LIBSSH2_ERROR_SOCKET_SEND: + case LIBSSH2_ERROR_KEY_EXCHANGE_FAILURE: + case LIBSSH2_ERROR_TIMEOUT: + // case LIBSSH2_ERROR_HOSTKEY_INIT: + // case LIBSSH2_ERROR_HOSTKEY_SIGN: + // case LIBSSH2_ERROR_DECRYPT: + case LIBSSH2_ERROR_SOCKET_DISCONNECT: + // case LIBSSH2_ERROR_PROTO: + // case LIBSSH2_ERROR_PASSWORD_EXPIRED: + // case LIBSSH2_ERROR_FILE: + // case LIBSSH2_ERROR_METHOD_NONE: + // case LIBSSH2_ERROR_AUTHENTICATION_FAILED: + // case LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED: + case LIBSSH2_ERROR_CHANNEL_OUTOFORDER: + case LIBSSH2_ERROR_CHANNEL_FAILURE: + case LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED: + case LIBSSH2_ERROR_CHANNEL_UNKNOWN: + case LIBSSH2_ERROR_CHANNEL_WINDOW_EXCEEDED: + case LIBSSH2_ERROR_CHANNEL_PACKET_EXCEEDED: + case LIBSSH2_ERROR_CHANNEL_CLOSED: + case LIBSSH2_ERROR_CHANNEL_EOF_SENT: + // case LIBSSH2_ERROR_SCP_PROTOCOL: + // case LIBSSH2_ERROR_ZLIB: + case LIBSSH2_ERROR_SOCKET_TIMEOUT: + // case LIBSSH2_ERROR_SFTP_PROTOCOL: + // case LIBSSH2_ERROR_REQUEST_DENIED: + // case LIBSSH2_ERROR_METHOD_NOT_SUPPORTED: + // case LIBSSH2_ERROR_INVAL: + // case LIBSSH2_ERROR_INVALID_POLL_TYPE: + // case LIBSSH2_ERROR_PUBLICKEY_PROTOCOL: + case LIBSSH2_ERROR_EAGAIN: + // case LIBSSH2_ERROR_BUFFER_TOO_SMALL: + // case LIBSSH2_ERROR_BAD_USE: + // case LIBSSH2_ERROR_COMPRESS: + // case LIBSSH2_ERROR_OUT_OF_BOUNDARY: + case LIBSSH2_ERROR_AGENT_PROTOCOL: + case LIBSSH2_ERROR_SOCKET_RECV: + // case LIBSSH2_ERROR_ENCRYPT: + case LIBSSH2_ERROR_BAD_SOCKET: + case LIBSSH2_ERROR_KNOWN_HOSTS: + return YES; + } + } + } + HSLogDebug(@"%@ not a transient error", self); return NO; } - (BOOL)isSSLError { - return [[self domain] isEqualToString:NSURLErrorDomain] - && [self code] <= NSURLErrorSecureConnectionFailed + return [[self domain] isEqualToString:NSURLErrorDomain] + && [self code] <= NSURLErrorSecureConnectionFailed && [self code] >= NSURLErrorClientCertificateRejected; } -- (void)logSSLCerts { - NSArray *certs = [[self userInfo] objectForKey:@"NSErrorPeerCertificateChainKey"]; - for (NSUInteger index = 0; index < [certs count]; index++) { - SecCertificateRef secCert = (SecCertificateRef)[certs objectAtIndex:index]; - CFStringRef commonName = NULL; - if (SecCertificateCopyCommonName(secCert, &commonName) == 0 && commonName != NULL) { - HSLog(@"SSL cert common name: %@", (NSString *)commonName); - CFRelease(commonName); - } else { - HSLog(@"error getting SSL cert's common name"); - } - SecKeyRef key = NULL; - if (SecCertificateCopyPublicKey(secCert, &key) == 0 && key != NULL) { - CSSM_CSP_HANDLE cspHandle; - if (SecKeyGetCSPHandle(key, &cspHandle) == 0) { - HSLog(@"SSL cert CSP handle: %ld", (long)cspHandle); - } else { - HSLog(@"error getting SSL cert's key's CSP handle"); - } - const CSSM_KEY *cssmKey; - if (SecKeyGetCSSMKey(key, &cssmKey) == 0) { - NSData *keyHeaderData = [NSData dataWithBytes:(const unsigned char *)&(cssmKey->KeyHeader) length:sizeof(CSSM_KEYHEADER)]; - HSLog(@"SSL cert CSSM key header: %@", keyHeaderData); - NSData *keyData = [NSData dataWithBytes:(const unsigned char *)cssmKey->KeyData.Data length:cssmKey->KeyData.Length]; - HSLog(@"SSL cert CSSM key data: %@", keyData); - } else { - HSLog(@"error getting SSL cert's key's CSM key"); - } - CFRelease(key); - } else { - HSLog(@"error getting SSL cert's public key"); - } - } -} @end diff --git a/shared/NSObject_extra.h b/cocoastack/shared/NSObject_extra.h similarity index 73% rename from shared/NSObject_extra.h rename to cocoastack/shared/NSObject_extra.h index 0086311..f448b92 100644 --- a/shared/NSObject_extra.h +++ b/cocoastack/shared/NSObject_extra.h @@ -3,7 +3,7 @@ // Arq // // Created by Stefan Reitshamer on 7/4/11. -// Copyright 2011 __MyCompanyName__. All rights reserved. +// Copyright 2011 Haystack Software. All rights reserved. // diff --git a/shared/NSObject_extra.m b/cocoastack/shared/NSObject_extra.m similarity index 83% rename from shared/NSObject_extra.m rename to cocoastack/shared/NSObject_extra.m index f8dae46..908b13b 100644 --- a/shared/NSObject_extra.m +++ b/cocoastack/shared/NSObject_extra.m @@ -3,7 +3,7 @@ // Arq // // Created by Stefan Reitshamer on 7/4/11. -// Copyright 2011 __MyCompanyName__. All rights reserved. +// Copyright 2011 Haystack Software. All rights reserved. // #import "NSObject_extra.h" diff --git a/shared/NSString_extra.h b/cocoastack/shared/NSString_extra.h similarity index 89% rename from shared/NSString_extra.h rename to cocoastack/shared/NSString_extra.h index 56d0363..9af7e33 100644 --- a/shared/NSString_extra.h +++ b/cocoastack/shared/NSString_extra.h @@ -33,9 +33,13 @@ @interface NSString (extra) ++ (NSString *)hexStringWithData:(NSData *)data; + (NSString *)hexStringWithBytes:(const unsigned char *)bytes length:(unsigned int)length; ++ (NSString *)stringWithRandomUUID; - (NSString *)stringWithUniquePath; -- (NSData *)hexStringToData; +- (NSData *)hexStringToData:(NSError **)error; - (NSData *)decodeBase64; - (NSComparisonResult)compareByLength:(NSString *)value; +- (NSString *)stringByEscapingURLCharacters; +- (NSString *)stringByDeletingTrailingSlash; @end diff --git a/shared/NSString_extra.m b/cocoastack/shared/NSString_extra.m similarity index 69% rename from shared/NSString_extra.m rename to cocoastack/shared/NSString_extra.m index 31d8960..a7c69ef 100644 --- a/shared/NSString_extra.m +++ b/cocoastack/shared/NSString_extra.m @@ -36,7 +36,6 @@ #include static const char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; -static const int BASE64_INPUT_SIZE = 57; static BOOL isbase64(char c) { @@ -91,12 +90,12 @@ static int UnBase64(unsigned char *dest, const unsigned char *src, int srclen) } while(srclen-= 4); *p = 0; - return p-dest; + return (int)(p-dest); } static NSString *PATH_PATTERN = @"^(.+)(\\.\\w+)$"; -static unsigned char hexCharToInt(char c1) { +static char hexCharToInt(char c1) { if (c1 >= '0' && c1 <= '9') { return c1 - '0'; } @@ -106,20 +105,49 @@ static unsigned char hexCharToInt(char c1) { if (c1 >= 'A' && c1 <= 'F') { return c1 - 'A' + 10; } - @throw [NSException exceptionWithName:@"Invalid hex char" reason:@"not a hex char" userInfo:nil]; + return -1; } @implementation NSString (extra) ++ (NSString *)hexStringWithData:(NSData *)data { + return [NSString hexStringWithBytes:[data bytes] length:(unsigned int)[data length]]; +} + (NSString *)hexStringWithBytes:(const unsigned char *)bytes length:(unsigned int)length { + if (length == 0) { + return [NSString string]; + } + char *buf = (char *)malloc(length * 2 + 1); for (unsigned int i = 0; i < length; i++) { unsigned char c = bytes[i]; - snprintf(buf + i*2, 3, "%02x", (unsigned int)c); + + unsigned char c1 = (c >> 4) & 0x0f; + if (c1 > 9) { + c1 = 'a' + c1 - 10; + } else { + c1 = '0' + c1; + } + + unsigned char c2 = (c & 0xf); + if (c2 > 9) { + c2 = 'a' + c2 - 10; + } else { + c2 = '0' + c2; + } + + buf[i*2] = c1; + buf[i*2+1] = c2; } NSString *ret = [[[NSString alloc] initWithBytes:buf length:length*2 encoding:NSUTF8StringEncoding] autorelease]; free(buf); return ret; } ++ (NSString *)stringWithRandomUUID { + CFUUIDRef uuidObj = CFUUIDCreate(nil);//create a new UUID + NSString *uuidString = (NSString*)CFUUIDCreateString(nil, uuidObj); + CFRelease(uuidObj); + return [uuidString autorelease]; +} - (NSString *)stringWithUniquePath { NSString *left = self; NSString *right = @""; @@ -131,19 +159,23 @@ static unsigned char hexCharToInt(char c1) { NSUInteger index = 2; NSString *path = [NSString stringWithString:self]; while ([fm fileExistsAtPath:path]) { - path = [NSString stringWithFormat:@"%@_%lu%@", left, index++, right]; + path = [NSString stringWithFormat:@"%@_%lu%@", left, (unsigned long)index++, right]; } return path; } -- (NSData *)hexStringToData { +- (NSData *)hexStringToData:(NSError **)error { const char *ascii = [self cStringUsingEncoding:NSASCIIStringEncoding]; size_t len = strlen(ascii) / 2; NSMutableData *data = [NSMutableData dataWithLength:len]; char *bytes = (char *)[data mutableBytes]; for (size_t i = 0; i < len; i++) { - unsigned char c1 = hexCharToInt(ascii[i*2]) << 4; - unsigned char c2 = hexCharToInt(ascii[i*2 + 1]); - unsigned char tmp = c1 | c2; + char c1 = hexCharToInt(ascii[i*2]); + char c2 = hexCharToInt(ascii[i*2 + 1]); + if (c1 < 0 || c2 < 0) { + SETNSERROR(@"NSStringExtraErrorDomain", -1, @"invalid hex string %@", self); + return nil; + } + unsigned char tmp = ((unsigned char)c1 << 4) | (unsigned char)c2; bytes[i] = tmp; } return data; @@ -152,8 +184,10 @@ static unsigned char hexCharToInt(char c1) { NSData *encodedData = [self dataUsingEncoding:NSASCIIStringEncoding]; const unsigned char *encoded = (const unsigned char *)[encodedData bytes]; unsigned char *decoded = (unsigned char *)malloc([encodedData length]); - int ret = UnBase64(decoded, encoded, [encodedData length]); - return [[[NSData alloc] initWithBytes:decoded length:ret] autorelease]; + int ret = UnBase64(decoded, encoded, (int)[encodedData length]); + NSData *decodedData = [[[NSData alloc] initWithBytes:decoded length:ret] autorelease]; + free(decoded); + return decodedData; } - (NSComparisonResult)compareByLength:(NSString *)value { NSUInteger myLength = [self length]; @@ -166,4 +200,17 @@ static unsigned char hexCharToInt(char c1) { } return NSOrderedDescending; } +- (NSString *)stringByEscapingURLCharacters { + return [(NSString *)CFURLCreateStringByAddingPercentEscapes(NULL, + (CFStringRef)self, + NULL, + (CFStringRef)@"!*'();:@&=+$,/?%#[]", + kCFStringEncodingUTF8) autorelease]; +} +- (NSString *)stringByDeletingTrailingSlash { + if ([self isEqualToString:@"/"] || ![self hasSuffix:@"/"]) { + return self; + } + return [self substringToIndex:[self length] - 1]; +} @end diff --git a/shared/NSXMLNode_extra.h b/cocoastack/shared/NSXMLNode_extra.h similarity index 100% rename from shared/NSXMLNode_extra.h rename to cocoastack/shared/NSXMLNode_extra.h diff --git a/shared/NSXMLNode_extra.m b/cocoastack/shared/NSXMLNode_extra.m similarity index 100% rename from shared/NSXMLNode_extra.m rename to cocoastack/shared/NSXMLNode_extra.m diff --git a/shared/RFC822.h b/cocoastack/shared/RFC822.h similarity index 100% rename from shared/RFC822.h rename to cocoastack/shared/RFC822.h diff --git a/shared/RFC822.m b/cocoastack/shared/RFC822.m similarity index 96% rename from shared/RFC822.m rename to cocoastack/shared/RFC822.m index bd68ca5..4283535 100644 --- a/shared/RFC822.m +++ b/cocoastack/shared/RFC822.m @@ -31,16 +31,16 @@ */ #import "RFC822.h" -#import "SetNSError.h" + #import "RegexKitLite.h" #import "S3Service.h" -#define FMT822 (@"^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})\\.(\\d{3})Z$") +#define FMT822 (@"^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})\\.(\\d+)Z$") @implementation RFC822 + (NSDate *)dateFromString:(NSString *)dateString error:(NSError **)error { if ([dateString rangeOfRegex:FMT822].location == NSNotFound) { - SETNSERROR([S3Service errorDomain], S3SERVICE_INVALID_PARAMETERS, @"invalid date '%@'", dateString); + SETNSERROR([S3Service errorDomain], S3SERVICE_INVALID_PARAMETERS, @"invalid RFC822 date '%@'", dateString); return nil; } return [NSCalendarDate dateWithYear:[[dateString stringByMatching:FMT822 capture:1] intValue] diff --git a/shared/RegexKitLite.h b/cocoastack/shared/RegexKitLite.h similarity index 100% rename from shared/RegexKitLite.h rename to cocoastack/shared/RegexKitLite.h diff --git a/shared/RegexKitLite.m b/cocoastack/shared/RegexKitLite.m similarity index 100% rename from shared/RegexKitLite.m rename to cocoastack/shared/RegexKitLite.m diff --git a/shared/SetNSError.h b/cocoastack/shared/SetNSError.h similarity index 96% rename from shared/SetNSError.h rename to cocoastack/shared/SetNSError.h index 0180e38..5c583b7 100644 --- a/shared/SetNSError.h +++ b/cocoastack/shared/SetNSError.h @@ -38,6 +38,11 @@ if (error != NULL) {\ HSLogTrace(@"%@", [*error localizedDescription]);\ } +#define SETERRORFROMMYERROR \ +if (error != NULL) {\ +*error = myError;\ +} + #define LOGERROR \ if (error != NULL) {\ HSLogError(@"%@", *error); \ diff --git a/shared/Sysctl.h b/cocoastack/shared/Sysctl.h similarity index 100% rename from shared/Sysctl.h rename to cocoastack/shared/Sysctl.h diff --git a/shared/Sysctl.m b/cocoastack/shared/Sysctl.m similarity index 99% rename from shared/Sysctl.m rename to cocoastack/shared/Sysctl.m index 31eed33..5cb12ef 100644 --- a/shared/Sysctl.m +++ b/cocoastack/shared/Sysctl.m @@ -35,7 +35,7 @@ #include #include #import "Sysctl.h" -#import "SetNSError.h" + @implementation Sysctl + (BOOL)networkBytesIn:(unsigned long long *)bytesIn bytesOut:(unsigned long long *)bytesOut error:(NSError **)error { @@ -55,6 +55,7 @@ } char *buf = (char *)malloc(len); if (sysctl(mib, 6, buf, &len, NULL, 0) < 0) { + free(buf); int errnum = errno; SETNSERROR(@"UnixErrorDomain", errnum, @"sysctl: %s", strerror(errnum)); return NO; diff --git a/shared/UserLibrary.h b/cocoastack/shared/UserLibrary.h similarity index 100% rename from shared/UserLibrary.h rename to cocoastack/shared/UserLibrary.h diff --git a/shared/UserLibrary.m b/cocoastack/shared/UserLibrary.m similarity index 100% rename from shared/UserLibrary.m rename to cocoastack/shared/UserLibrary.m diff --git a/cocoastack/sns/CreateTopicResponse.h b/cocoastack/sns/CreateTopicResponse.h new file mode 100644 index 0000000..6e08c2d --- /dev/null +++ b/cocoastack/sns/CreateTopicResponse.h @@ -0,0 +1,15 @@ +// +// CreateTopicResponse.h +// +// Created by Stefan Reitshamer on 9/16/12. +// +// + + +@interface CreateTopicResponse : NSObject { + NSString *topicArn; + NSMutableString *currentStringBuffer; +} +- (id)initWithData:(NSData *)theData; +- (NSString *)topicArn; +@end diff --git a/cocoastack/sns/CreateTopicResponse.m b/cocoastack/sns/CreateTopicResponse.m new file mode 100644 index 0000000..0bff775 --- /dev/null +++ b/cocoastack/sns/CreateTopicResponse.m @@ -0,0 +1,60 @@ +// +// CreateTopicResponse.m +// +// Created by Stefan Reitshamer on 9/16/12. +// +// + +#import "CreateTopicResponse.h" + +@implementation CreateTopicResponse +- (id)initWithData:(NSData *)theData { + if (self = [super init]) { + HSLogDebug(@"createtopicresponse: %@", [[[NSString alloc] initWithData:theData encoding:NSUTF8StringEncoding] autorelease]); + NSXMLParser *parser = [[NSXMLParser alloc] initWithData:theData]; + [parser setDelegate:self]; + [parser parse]; + [parser release]; + } + return self; +} +- (void)dealloc { + [topicArn release]; + [currentStringBuffer release]; + [super dealloc]; +} + +- (NSString *)topicArn { + return topicArn; +} + + +#pragma mark - NSXMLParserDelegate +- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName + namespaceURI:(NSString *)namespaceURI + qualifiedName:(NSString *)qualifiedName + attributes:(NSDictionary *)attributeDict { + [currentStringBuffer release]; + currentStringBuffer = nil; +} +- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { + if (currentStringBuffer == nil) { + currentStringBuffer = [[NSMutableString alloc] init]; + } + [currentStringBuffer appendString:string]; +} +- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { + if (currentStringBuffer != nil) { + if ([elementName isEqualToString:@"TopicArn"]) { + [topicArn release]; + topicArn = [currentStringBuffer copy]; + } + } +} +- (void)parser:(NSXMLParser *)theParser parseErrorOccurred:(NSError *)parseError { + HSLogError(@"error parsing amazon response: %@", parseError); +} +- (void)parserDidEndDocument:(NSXMLParser *)parser { +} + +@end diff --git a/cocoastack/sns/ListTopicsResponse.h b/cocoastack/sns/ListTopicsResponse.h new file mode 100644 index 0000000..5ae694b --- /dev/null +++ b/cocoastack/sns/ListTopicsResponse.h @@ -0,0 +1,16 @@ +// +// ListTopicsResponse.h +// Arq +// +// Created by Stefan Reitshamer on 9/29/12. +// +// + + +@interface ListTopicsResponse : NSObject { + NSMutableArray *topicArns; + NSMutableString *currentStringBuffer; +} +- (id)initWithData:(NSData *)theData; +- (NSArray *)topicArns; +@end diff --git a/cocoastack/sns/ListTopicsResponse.m b/cocoastack/sns/ListTopicsResponse.m new file mode 100644 index 0000000..d6488a8 --- /dev/null +++ b/cocoastack/sns/ListTopicsResponse.m @@ -0,0 +1,59 @@ +// +// ListTopicsResponse.m +// Arq +// +// Created by Stefan Reitshamer on 9/29/12. +// +// + +#import "ListTopicsResponse.h" + +@implementation ListTopicsResponse +- (id)initWithData:(NSData *)theData { + if (self = [super init]) { + topicArns = [[NSMutableArray alloc] init]; + HSLogDebug(@"createtopicresponse: %@", [[[NSString alloc] initWithData:theData encoding:NSUTF8StringEncoding] autorelease]); + NSXMLParser *parser = [[NSXMLParser alloc] initWithData:theData]; + [parser setDelegate:self]; + [parser parse]; + [parser release]; + } + return self; +} +- (void)dealloc { + [topicArns release]; + [currentStringBuffer release]; + [super dealloc]; +} +- (NSArray *)topicArns { + return topicArns; +} + +#pragma mark - NSXMLParserDelegate +- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName + namespaceURI:(NSString *)namespaceURI + qualifiedName:(NSString *)qualifiedName + attributes:(NSDictionary *)attributeDict { + [currentStringBuffer release]; + currentStringBuffer = nil; +} +- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { + if (currentStringBuffer == nil) { + currentStringBuffer = [[NSMutableString alloc] init]; + } + [currentStringBuffer appendString:string]; +} +- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { + if (currentStringBuffer != nil) { + if ([elementName isEqualToString:@"TopicArn"]) { + [topicArns addObject:[[currentStringBuffer copy] autorelease]]; + } + } +} +- (void)parser:(NSXMLParser *)theParser parseErrorOccurred:(NSError *)parseError { + HSLogError(@"error parsing amazon response: %@", parseError); +} +- (void)parserDidEndDocument:(NSXMLParser *)parser { +} + +@end diff --git a/cocoastack/sns/SNS.h b/cocoastack/sns/SNS.h new file mode 100644 index 0000000..b9d3a56 --- /dev/null +++ b/cocoastack/sns/SNS.h @@ -0,0 +1,27 @@ +// +// SNS.h +// Arq +// +// Created by Stefan Reitshamer on 9/16/12. +// +// + +@class SignatureV2Provider; +@class AWSRegion; + + +@interface SNS : NSObject { + NSString *accessKey; + SignatureV2Provider *sap; + AWSRegion *awsRegion; + BOOL retryOnTransientError; +} ++ (NSString *)errorDomain; + +- (id)initWithAccessKey:(NSString *)access secretKey:(NSString *)secret awsRegion:(AWSRegion *)theAWSRegion retryOnTransientError:(BOOL)retry; + +- (NSString *)createTopic:(NSString *)theName error:(NSError **)error; +- (NSString *)subscribeQueueArn:(NSString *)theQueueArn toTopicArn:(NSString *)theTopicArn error:(NSError **)error; +- (NSArray *)topicArns:(NSError **)error; +- (BOOL)deleteTopicWithArn:(NSString *)theTopicArn error:(NSError **)error; +@end diff --git a/cocoastack/sns/SNS.m b/cocoastack/sns/SNS.m new file mode 100644 index 0000000..bed2cb6 --- /dev/null +++ b/cocoastack/sns/SNS.m @@ -0,0 +1,156 @@ +// +// SNS.m +// Arq +// +// Created by Stefan Reitshamer on 9/16/12. +// +// + +#import "SNS.h" +#import "AWSRegion.h" +#import "NSString_extra.h" +#import "SignatureV2Provider.h" +#import "AWSQueryRequest.h" +#import "AWSQueryResponse.h" +#import "CreateTopicResponse.h" +#import "SubscribeResponse.h" +#import "ListTopicsResponse.h" + + +@implementation SNS ++ (NSString *)errorDomain { + return @"SNSErrorDomain"; +} + +- (id)initWithAccessKey:(NSString *)theAccessKey secretKey:(NSString *)secret awsRegion:(AWSRegion *)theAWSRegion retryOnTransientError:(BOOL)retry { + if (self = [super init]) { + accessKey = [theAccessKey retain]; + sap = [[SignatureV2Provider alloc] initWithSecretKey:secret]; + awsRegion = [theAWSRegion retain]; + retryOnTransientError = retry; + } + return self; +} +- (void)dealloc { + [accessKey release]; + [sap release]; + [awsRegion release]; + [super dealloc]; +} + +- (NSString *)createTopic:(NSString *)theName error:(NSError **)error { + NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease]; + [formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss'Z'"]; + [formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; + + NSMutableString *str = [NSMutableString stringWithFormat:@"%@/?", [awsRegion snsEndpointWithSSL:NO]]; + [str appendFormat:@"AWSAccessKeyId=%@", [accessKey stringByEscapingURLCharacters]]; + [str appendFormat:@"&Action=CreateTopic"]; +// [str appendFormat:@"&Expires=%ld", (NSUInteger)[[[NSDate date] dateByAddingTimeInterval:30.0] timeIntervalSince1970]]; + [str appendFormat:@"&Name=%@", [theName stringByEscapingURLCharacters]]; + [str appendFormat:@"&SignatureMethod=HmacSHA256"]; + [str appendFormat:@"&SignatureVersion=2"]; + [str appendFormat:@"&Timestamp=%@", [[formatter stringFromDate:[NSDate date]] stringByEscapingURLCharacters]]; + NSURL *url = [NSURL URLWithString:str]; + NSAssert(url != nil, @"url may not be nil!"); + NSString *signature = [sap signatureForHTTPMethod:@"GET" url:url]; + [str appendFormat:@"&Signature=%@", [signature stringByEscapingURLCharacters]]; + + NSURL *urlWithSignature = [NSURL URLWithString:str]; + AWSQueryRequest *req = [[[AWSQueryRequest alloc] initWithMethod:@"GET" url:urlWithSignature retryOnTransientError:retryOnTransientError] autorelease]; + AWSQueryResponse *response = [req execute:error]; + if (response == nil) { + return nil; + } + CreateTopicResponse *ctr = [[[CreateTopicResponse alloc] initWithData:[response body]] autorelease]; + NSString *ret = [ctr topicArn]; + if (ret == nil) { + SETNSERROR([SNS errorDomain], -1, @"TopicArn not found in CreateTopic response"); + } + return ret; +} +- (NSString *)subscribeQueueArn:(NSString *)theQueueArn toTopicArn:(NSString *)theTopicArn error:(NSError **)error { + NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease]; + [formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss'Z'"]; + [formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; + + NSMutableString *str = [NSMutableString stringWithFormat:@"%@/?", [awsRegion snsEndpointWithSSL:NO]]; + [str appendFormat:@"AWSAccessKeyId=%@", [accessKey stringByEscapingURLCharacters]]; + [str appendFormat:@"&Action=Subscribe"]; + [str appendFormat:@"&Endpoint=%@", [theQueueArn stringByEscapingURLCharacters]]; + [str appendFormat:@"&Protocol=sqs"]; + [str appendFormat:@"&SignatureMethod=HmacSHA256"]; + [str appendFormat:@"&SignatureVersion=2"]; + [str appendFormat:@"&Timestamp=%@", [[formatter stringFromDate:[NSDate date]] stringByEscapingURLCharacters]]; + [str appendFormat:@"&TopicArn=%@", [theTopicArn stringByEscapingURLCharacters]]; + + NSURL *url = [NSURL URLWithString:str]; + NSAssert(url != nil, @"url may not be nil!"); + NSString *signature = [sap signatureForHTTPMethod:@"GET" url:url]; + [str appendFormat:@"&Signature=%@", [signature stringByEscapingURLCharacters]]; + + NSURL *urlWithSignature = [NSURL URLWithString:str]; + AWSQueryRequest *req = [[[AWSQueryRequest alloc] initWithMethod:@"GET" url:urlWithSignature retryOnTransientError:retryOnTransientError] autorelease]; + AWSQueryResponse *response = [req execute:error]; + if (response == nil) { + return nil; + } + SubscribeResponse *sr = [[[SubscribeResponse alloc] initWithData:[response body]] autorelease]; + NSString *subscriptionArn = [sr subscriptionArn]; + if (subscriptionArn == nil) { + SETNSERROR([SNS errorDomain], -1, @"SubscriptionArn not found in Subscribe response"); + } + return subscriptionArn; +} +- (NSArray *)topicArns:(NSError **)error { + NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease]; + [formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss'Z'"]; + [formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; + + NSMutableString *str = [NSMutableString stringWithFormat:@"%@/?", [awsRegion snsEndpointWithSSL:NO]]; + [str appendFormat:@"AWSAccessKeyId=%@", [accessKey stringByEscapingURLCharacters]]; + [str appendFormat:@"&Action=ListTopics"]; + [str appendFormat:@"&SignatureMethod=HmacSHA256"]; + [str appendFormat:@"&SignatureVersion=2"]; + [str appendFormat:@"&Timestamp=%@", [[formatter stringFromDate:[NSDate date]] stringByEscapingURLCharacters]]; + NSURL *url = [NSURL URLWithString:str]; + NSAssert(url != nil, @"url may not be nil!"); + NSString *signature = [sap signatureForHTTPMethod:@"GET" url:url]; + [str appendFormat:@"&Signature=%@", [signature stringByEscapingURLCharacters]]; + + NSURL *urlWithSignature = [NSURL URLWithString:str]; + AWSQueryRequest *req = [[[AWSQueryRequest alloc] initWithMethod:@"GET" url:urlWithSignature retryOnTransientError:retryOnTransientError] autorelease]; + AWSQueryResponse *response = [req execute:error]; + if (response == nil) { + return nil; + } + ListTopicsResponse *ltr = [[[ListTopicsResponse alloc] initWithData:[response body]] autorelease]; + return [ltr topicArns]; +} +- (BOOL)deleteTopicWithArn:(NSString *)theTopicArn error:(NSError **)error { + NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease]; + [formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss'Z'"]; + [formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; + + NSMutableString *str = [NSMutableString stringWithFormat:@"%@/?", [awsRegion snsEndpointWithSSL:NO]]; + [str appendFormat:@"AWSAccessKeyId=%@", [accessKey stringByEscapingURLCharacters]]; + [str appendFormat:@"&Action=DeleteTopic"]; + // [str appendFormat:@"&Expires=%ld", (NSUInteger)[[[NSDate date] dateByAddingTimeInterval:30.0] timeIntervalSince1970]]; + [str appendFormat:@"&SignatureMethod=HmacSHA256"]; + [str appendFormat:@"&SignatureVersion=2"]; + [str appendFormat:@"&Timestamp=%@", [[formatter stringFromDate:[NSDate date]] stringByEscapingURLCharacters]]; + [str appendFormat:@"&TopicArn=%@", [theTopicArn stringByEscapingURLCharacters]]; + NSURL *url = [NSURL URLWithString:str]; + NSAssert(url != nil, @"url may not be nil!"); + NSString *signature = [sap signatureForHTTPMethod:@"GET" url:url]; + [str appendFormat:@"&Signature=%@", [signature stringByEscapingURLCharacters]]; + + NSURL *urlWithSignature = [NSURL URLWithString:str]; + AWSQueryRequest *req = [[[AWSQueryRequest alloc] initWithMethod:@"GET" url:urlWithSignature retryOnTransientError:retryOnTransientError] autorelease]; + AWSQueryResponse *response = [req execute:error]; + if (response == nil) { + return NO; + } + return YES; +} +@end diff --git a/cocoastack/sns/SubscribeResponse.h b/cocoastack/sns/SubscribeResponse.h new file mode 100644 index 0000000..9681335 --- /dev/null +++ b/cocoastack/sns/SubscribeResponse.h @@ -0,0 +1,16 @@ +// +// SubscribeResponse.h +// Arq +// +// Created by Stefan Reitshamer on 9/17/12. +// +// + + +@interface SubscribeResponse : NSObject { + NSString *subscriptionArn; + NSMutableString *currentStringBuffer; +} +- (id)initWithData:(NSData *)theData; +- (NSString *)subscriptionArn; +@end diff --git a/cocoastack/sns/SubscribeResponse.m b/cocoastack/sns/SubscribeResponse.m new file mode 100644 index 0000000..d50fe2d --- /dev/null +++ b/cocoastack/sns/SubscribeResponse.m @@ -0,0 +1,60 @@ +// +// SubscribeResponse.m +// Arq +// +// Created by Stefan Reitshamer on 9/17/12. +// +// + +#import "SubscribeResponse.h" + +@implementation SubscribeResponse +- (id)initWithData:(NSData *)theData { + if (self = [super init]) { + NSXMLParser *parser = [[NSXMLParser alloc] initWithData:theData]; + [parser setDelegate:self]; + [parser parse]; + [parser release]; + } + return self; +} +- (void)dealloc { + [subscriptionArn release]; + [currentStringBuffer release]; + [super dealloc]; +} + +- (NSString *)subscriptionArn { + return subscriptionArn; +} + + +#pragma mark - NSXMLParserDelegate +- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName + namespaceURI:(NSString *)namespaceURI + qualifiedName:(NSString *)qualifiedName + attributes:(NSDictionary *)attributeDict { + [currentStringBuffer release]; + currentStringBuffer = nil; +} +- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { + if (currentStringBuffer == nil) { + currentStringBuffer = [[NSMutableString alloc] init]; + } + [currentStringBuffer appendString:string]; +} +- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { + if (currentStringBuffer != nil) { + if ([elementName isEqualToString:@"SubscriptionArn"]) { + [subscriptionArn release]; + subscriptionArn = [currentStringBuffer copy]; + } + } +} +- (void)parser:(NSXMLParser *)theParser parseErrorOccurred:(NSError *)parseError { + HSLogError(@"error parsing amazon response: %@", parseError); +} +- (void)parserDidEndDocument:(NSXMLParser *)parser { +} + +@end diff --git a/cocoastack/sqs/CreateQueueResponse.h b/cocoastack/sqs/CreateQueueResponse.h new file mode 100644 index 0000000..e0bd251 --- /dev/null +++ b/cocoastack/sqs/CreateQueueResponse.h @@ -0,0 +1,15 @@ +// +// CreateQueueResponse.h +// +// Created by Stefan Reitshamer on 9/16/12. +// +// + + +@interface CreateQueueResponse : NSObject { + NSMutableString *currentStringBuffer; + NSURL *queueURL; +} +- (id)initWithData:(NSData *)theData; +- (NSURL *)queueURL; +@end diff --git a/cocoastack/sqs/CreateQueueResponse.m b/cocoastack/sqs/CreateQueueResponse.m new file mode 100644 index 0000000..2ee0724 --- /dev/null +++ b/cocoastack/sqs/CreateQueueResponse.m @@ -0,0 +1,58 @@ +// +// CreateQueueResponse.m +// +// Created by Stefan Reitshamer on 9/16/12. +// +// + +#import "CreateQueueResponse.h" + +@implementation CreateQueueResponse +- (id)initWithData:(NSData *)theData { + if (self = [super init]) { + NSXMLParser *parser = [[NSXMLParser alloc] initWithData:theData]; + [parser setDelegate:self]; + [parser parse]; + [parser release]; + } + return self; +} +- (void)dealloc { + [queueURL release]; + [currentStringBuffer release]; + [super dealloc]; +} + +- (NSURL *)queueURL { + return queueURL; +} + + +#pragma mark - NSXMLParserDelegate +- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName + namespaceURI:(NSString *)namespaceURI + qualifiedName:(NSString *)qualifiedName + attributes:(NSDictionary *)attributeDict { + [currentStringBuffer release]; + currentStringBuffer = nil; +} +- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { + if (currentStringBuffer == nil) { + currentStringBuffer = [[NSMutableString alloc] init]; + } + [currentStringBuffer appendString:string]; +} +- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { + if (currentStringBuffer != nil) { + if ([elementName isEqualToString:@"QueueUrl"]) { + [queueURL release]; + queueURL = [[NSURL alloc] initWithString:currentStringBuffer]; + } + } +} +- (void)parser:(NSXMLParser *)theParser parseErrorOccurred:(NSError *)parseError { + HSLogError(@"error parsing amazon response: %@", parseError); +} +- (void)parserDidEndDocument:(NSXMLParser *)parser { +} +@end diff --git a/cocoastack/sqs/GetQueueAttributesResponse.h b/cocoastack/sqs/GetQueueAttributesResponse.h new file mode 100644 index 0000000..93d98ca --- /dev/null +++ b/cocoastack/sqs/GetQueueAttributesResponse.h @@ -0,0 +1,18 @@ +// +// GetQueueAttributesResponse.h +// Arq +// +// Created by Stefan Reitshamer on 9/17/12. +// +// + + +@interface GetQueueAttributesResponse : NSObject { + BOOL inAttribute; + NSMutableString *currentStringBuffer; + NSString *lastAttributeName; + NSString *queueArn; +} +- (id)initWithData:(NSData *)theData; +- (NSString *)queueArn; +@end diff --git a/cocoastack/sqs/GetQueueAttributesResponse.m b/cocoastack/sqs/GetQueueAttributesResponse.m new file mode 100644 index 0000000..d617f74 --- /dev/null +++ b/cocoastack/sqs/GetQueueAttributesResponse.m @@ -0,0 +1,73 @@ +// +// GetQueueAttributesResponse.m +// Arq +// +// Created by Stefan Reitshamer on 9/17/12. +// +// + +#import "GetQueueAttributesResponse.h" + +@implementation GetQueueAttributesResponse +- (id)initWithData:(NSData *)theData { + if (self = [super init]) { + NSXMLParser *parser = [[NSXMLParser alloc] initWithData:theData]; + [parser setDelegate:self]; + [parser parse]; + [parser release]; + } + return self; +} +- (void)dealloc { + [currentStringBuffer release]; + [lastAttributeName release]; + [queueArn release]; + [super dealloc]; +} + +- (NSString *)queueArn { + return queueArn; +} + + +#pragma mark - NSXMLParserDelegate +- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName + namespaceURI:(NSString *)namespaceURI + qualifiedName:(NSString *)qualifiedName + attributes:(NSDictionary *)attributeDict { + [currentStringBuffer release]; + currentStringBuffer = nil; + if ([elementName isEqualToString:@"Attribute"]) { + inAttribute = YES; + } +} +- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { + if (currentStringBuffer == nil) { + currentStringBuffer = [[NSMutableString alloc] init]; + } + [currentStringBuffer appendString:string]; +} +- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { + if (currentStringBuffer != nil) { + if (inAttribute) { + if ([elementName isEqualToString:@"Name"]) { + [lastAttributeName release]; + lastAttributeName = [currentStringBuffer copy]; + } else if ([elementName isEqualToString:@"Value"]) { + if ([lastAttributeName isEqualToString:@"QueueArn"]) { + [queueArn release]; + queueArn = [currentStringBuffer copy]; + } + } + } + } + if ([elementName isEqualToString:@"Attribute"]) { + inAttribute = NO; + } +} +- (void)parser:(NSXMLParser *)theParser parseErrorOccurred:(NSError *)parseError { + HSLogError(@"error parsing amazon response: %@", parseError); +} +- (void)parserDidEndDocument:(NSXMLParser *)parser { +} +@end diff --git a/cocoastack/sqs/ListQueuesResponse.h b/cocoastack/sqs/ListQueuesResponse.h new file mode 100644 index 0000000..a1efdfa --- /dev/null +++ b/cocoastack/sqs/ListQueuesResponse.h @@ -0,0 +1,16 @@ +// +// ListQueuesResponse.h +// Arq +// +// Created by Stefan Reitshamer on 10/12/12. +// Copyright (c) 2012 Stefan Reitshamer. All rights reserved. +// + + +@interface ListQueuesResponse : NSObject { + NSMutableArray *queueURLs; + NSMutableString *currentStringBuffer; +} +- (id)initWithData:(NSData *)theData; +- (NSArray *)queueURLs; +@end diff --git a/cocoastack/sqs/ListQueuesResponse.m b/cocoastack/sqs/ListQueuesResponse.m new file mode 100644 index 0000000..c5626cc --- /dev/null +++ b/cocoastack/sqs/ListQueuesResponse.m @@ -0,0 +1,65 @@ +// +// ListQueuesResponse.m +// Arq +// +// Created by Stefan Reitshamer on 10/12/12. +// Copyright (c) 2012 Stefan Reitshamer. All rights reserved. +// + +#import "ListQueuesResponse.h" + +@implementation ListQueuesResponse +- (id)initWithData:(NSData *)theData { + if (self = [super init]) { + queueURLs = [[NSMutableArray alloc] init]; + HSLogDebug(@"listqueuesresponse: %@", [[[NSString alloc] initWithData:theData encoding:NSUTF8StringEncoding] autorelease]); + NSXMLParser *parser = [[NSXMLParser alloc] initWithData:theData]; + [parser setDelegate:self]; + [parser parse]; + [parser release]; + } + return self; +} +- (void)dealloc { + [queueURLs release]; + [currentStringBuffer release]; + [super dealloc]; +} +- (NSArray *)queueURLs { + return queueURLs; +} + +#pragma mark - NSXMLParserDelegate +- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName + namespaceURI:(NSString *)namespaceURI + qualifiedName:(NSString *)qualifiedName + attributes:(NSDictionary *)attributeDict { + [currentStringBuffer release]; + currentStringBuffer = nil; +} +- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { + if (currentStringBuffer == nil) { + currentStringBuffer = [[NSMutableString alloc] init]; + } + [currentStringBuffer appendString:string]; +} +- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { + if (currentStringBuffer != nil) { + if ([elementName isEqualToString:@"QueueUrl"]) { + NSString *currentString = [[currentStringBuffer copy] autorelease]; + NSURL *theURL = [NSURL URLWithString:currentString]; + if (theURL == nil) { + HSLogError(@"QueueUrl is invalid: %@", currentString); + } else { + [queueURLs addObject:theURL]; + } + } + } +} +- (void)parser:(NSXMLParser *)theParser parseErrorOccurred:(NSError *)parseError { + HSLogError(@"error parsing amazon response: %@", parseError); +} +- (void)parserDidEndDocument:(NSXMLParser *)parser { +} + +@end diff --git a/cocoastack/sqs/ReceiveMessageResponse.h b/cocoastack/sqs/ReceiveMessageResponse.h new file mode 100644 index 0000000..3811712 --- /dev/null +++ b/cocoastack/sqs/ReceiveMessageResponse.h @@ -0,0 +1,20 @@ +// +// SQSMessage.h +// Arq +// +// Created by Stefan Reitshamer on 9/17/12. +// +// + + +@interface ReceiveMessageResponse : NSObject { + NSURL *queueURL; + NSMutableArray *messages; + NSMutableString *currentStringBuffer; + BOOL inMessage; + NSString *receiptHandle; + NSString *body; +} +- (id)initWithQueueURL:(NSURL *)theQueueURL data:(NSData *)theData; +- (NSArray *)messages; +@end diff --git a/cocoastack/sqs/ReceiveMessageResponse.m b/cocoastack/sqs/ReceiveMessageResponse.m new file mode 100644 index 0000000..3795b1a --- /dev/null +++ b/cocoastack/sqs/ReceiveMessageResponse.m @@ -0,0 +1,72 @@ +// +// SQSMessage.m +// Arq +// +// Created by Stefan Reitshamer on 9/17/12. +// +// + +#import "ReceiveMessageResponse.h" +#import "SQSMessage.h" + + +@implementation ReceiveMessageResponse +- (id)initWithQueueURL:(NSURL *)theQueueURL data:(NSData *)theData { + if (self = [super init]) { + queueURL = [theQueueURL retain]; + messages = [[NSMutableArray alloc] init]; + NSXMLParser *parser = [[NSXMLParser alloc] initWithData:theData]; + [parser setDelegate:self]; + [parser parse]; + [parser release]; + } + return self; +} +- (void)dealloc { + [queueURL release]; + [messages release]; + [currentStringBuffer release]; + [body release]; + [receiptHandle release]; + [super dealloc]; +} +- (NSArray *)messages { + return messages; +} + +#pragma mark - NSXMLParserDelegate +- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName + namespaceURI:(NSString *)namespaceURI + qualifiedName:(NSString *)qualifiedName + attributes:(NSDictionary *)attributeDict { + [currentStringBuffer release]; + currentStringBuffer = nil; + if ([elementName isEqualToString:@"Message"]) { + inMessage = YES; + } +} +- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { + if (currentStringBuffer == nil) { + currentStringBuffer = [[NSMutableString alloc] init]; + } + [currentStringBuffer appendString:string]; +} +- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { + if ([elementName isEqualToString:@"Body"]) { + [body release]; + body = [currentStringBuffer copy]; + } else if ([elementName isEqualToString:@"ReceiptHandle"]) { + [receiptHandle release]; + receiptHandle = [currentStringBuffer copy]; + } else if ([elementName isEqualToString:@"Message"]) { + inMessage = NO; + SQSMessage *msg = [[[SQSMessage alloc] initWithQueueURL:queueURL body:body receiptHandle:receiptHandle] autorelease]; + [messages addObject:msg]; + } +} +- (void)parser:(NSXMLParser *)theParser parseErrorOccurred:(NSError *)parseError { + HSLogError(@"error parsing amazon response: %@", parseError); +} +- (void)parserDidEndDocument:(NSXMLParser *)parser { +} +@end diff --git a/cocoastack/sqs/SQS.h b/cocoastack/sqs/SQS.h new file mode 100644 index 0000000..49ab252 --- /dev/null +++ b/cocoastack/sqs/SQS.h @@ -0,0 +1,31 @@ +// +// SQS.h +// Arq +// +// Created by Stefan Reitshamer on 9/16/12. +// +// + +@class SignatureV2Provider; +@class AWSRegion; +@class ReceiveMessageResponse; + + +@interface SQS : NSObject { + NSString *accessKey; + SignatureV2Provider *sap; + AWSRegion *awsRegion; + BOOL retryOnTransientError; +} ++ (NSString *)errorDomain; + +- (id)initWithAccessKey:(NSString *)access secretKey:(NSString *)secret awsRegion:(AWSRegion *)theAWSRegion retryOnTransientError:(BOOL)retry; + +- (NSURL *)createQueueWithName:(NSString *)theName error:(NSError **)error; +- (NSString *)queueArnForQueueURL:(NSURL *)theURL error:(NSError **)error; +- (BOOL)setSendMessagePermissionToQueueURL:(NSURL *)theQueueURL queueArn:(NSString *)theQueueArn forSourceArn:(NSString *)theSourceArn error:(NSError **)error; +- (ReceiveMessageResponse *)receiveMessagesForQueueURL:(NSURL *)theURL maxMessages:(NSUInteger)theMaxMessages error:(NSError **)error; +- (BOOL)deleteMessageWithQueueURL:(NSURL *)theURL receiptHandle:(NSString *)theReceiptHandle error:(NSError **)error; +- (NSArray *)queueURLs:(NSError **)error; +- (BOOL)deleteQueue:(NSURL *)theQueueURL error:(NSError **)error; +@end diff --git a/cocoastack/sqs/SQS.m b/cocoastack/sqs/SQS.m new file mode 100644 index 0000000..1eb5cef --- /dev/null +++ b/cocoastack/sqs/SQS.m @@ -0,0 +1,250 @@ +// +// SQS.m +// Arq +// +// Created by Stefan Reitshamer on 9/16/12. +// +// + +#import "SQS.h" +#import "SignatureV2Provider.h" +#import "AWSRegion.h" +#import "NSString_extra.h" +#import "AWSQueryRequest.h" +#import "AWSQueryResponse.h" +#import "CreateQueueResponse.h" +#import "GetQueueAttributesResponse.h" +#import "ReceiveMessageResponse.h" +#import "ListQueuesResponse.h" + + +@implementation SQS ++ (NSString *)errorDomain { + return @"SQSErrorDomain"; +} + +- (id)initWithAccessKey:(NSString *)theAccessKey secretKey:(NSString *)secret awsRegion:(AWSRegion *)theAWSRegion retryOnTransientError:(BOOL)retry { + if (self = [super init]) { + accessKey = [theAccessKey retain]; + sap = [[SignatureV2Provider alloc] initWithSecretKey:secret]; + awsRegion = [theAWSRegion retain]; + retryOnTransientError = retry; + } + return self; +} +- (void)dealloc { + [accessKey release]; + [sap release]; + [awsRegion release]; + [super dealloc]; +} + +- (NSURL *)createQueueWithName:(NSString *)theName error:(NSError **)error { + NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease]; + [formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss'Z'"]; + [formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; + + NSMutableString *str = [NSMutableString stringWithFormat:@"%@/?", [awsRegion sqsEndpointWithSSL:NO]]; + [str appendFormat:@"AWSAccessKeyId=%@", [accessKey stringByEscapingURLCharacters]]; + [str appendFormat:@"&Action=CreateQueue"]; + // [str appendFormat:@"&Expires=%ld", (NSUInteger)[[[NSDate date] dateByAddingTimeInterval:30.0] timeIntervalSince1970]]; + [str appendFormat:@"&QueueName=%@", [theName stringByEscapingURLCharacters]]; + [str appendFormat:@"&SignatureMethod=HmacSHA256"]; + [str appendFormat:@"&SignatureVersion=2"]; + [str appendFormat:@"&Timestamp=%@", [[formatter stringFromDate:[NSDate date]] stringByEscapingURLCharacters]]; + [str appendFormat:@"&Version=2011-10-01"]; + NSURL *url = [NSURL URLWithString:str]; + NSAssert(url != nil, @"url may not be nil!"); + NSString *signature = [sap signatureForHTTPMethod:@"GET" url:url]; + [str appendFormat:@"&Signature=%@", [signature stringByEscapingURLCharacters]]; + + NSURL *urlWithSignature = [NSURL URLWithString:str]; + AWSQueryRequest *req = [[[AWSQueryRequest alloc] initWithMethod:@"GET" url:urlWithSignature retryOnTransientError:retryOnTransientError] autorelease]; + AWSQueryResponse *response = [req execute:error]; + if (response == nil) { + return nil; + } + CreateQueueResponse *cqr = [[[CreateQueueResponse alloc] initWithData:[response body]] autorelease]; + NSURL *ret = [cqr queueURL]; + if (ret == nil) { + SETNSERROR([SQS errorDomain], -1, @"QueueURL not found in CreateQueue response"); + } + return ret; +} +- (NSString *)queueArnForQueueURL:(NSURL *)theURL error:(NSError **)error { + NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease]; + [formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss'Z'"]; + [formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; + + NSMutableString *str = [NSMutableString stringWithFormat:@"%@?", [theURL description]]; + [str appendFormat:@"AWSAccessKeyId=%@", [accessKey stringByEscapingURLCharacters]]; + [str appendFormat:@"&Action=GetQueueAttributes"]; + [str appendFormat:@"&AttributeName.1=QueueArn"]; + // [str appendFormat:@"&Expires=%ld", (NSUInteger)[[[NSDate date] dateByAddingTimeInterval:30.0] timeIntervalSince1970]]; + [str appendFormat:@"&SignatureMethod=HmacSHA256"]; + [str appendFormat:@"&SignatureVersion=2"]; + [str appendFormat:@"&Timestamp=%@", [[formatter stringFromDate:[NSDate date]] stringByEscapingURLCharacters]]; + [str appendFormat:@"&Version=2009-02-01"]; + NSURL *url = [NSURL URLWithString:str]; + NSAssert(url != nil, @"url may not be nil!"); + NSString *signature = [sap signatureForHTTPMethod:@"GET" url:url]; + [str appendFormat:@"&Signature=%@", [signature stringByEscapingURLCharacters]]; + + NSURL *urlWithSignature = [NSURL URLWithString:str]; + AWSQueryRequest *req = [[[AWSQueryRequest alloc] initWithMethod:@"GET" url:urlWithSignature retryOnTransientError:retryOnTransientError] autorelease]; + AWSQueryResponse *response = [req execute:error]; + if (response == nil) { + return nil; + } + + GetQueueAttributesResponse *gqar = [[[GetQueueAttributesResponse alloc] initWithData:[response body]] autorelease]; + NSString *ret = [gqar queueArn]; + if (ret == nil) { + SETNSERROR([SQS errorDomain], -1, @"QueueArn not found in GetQueueAttributes response"); + } + return ret; +} +- (BOOL)setSendMessagePermissionToQueueURL:(NSURL *)theQueueURL queueArn:(NSString *)theQueueArn forSourceArn:(NSString *)theSourceArn error:(NSError **)error { + NSString *theQueueName = [[theQueueArn componentsSeparatedByString:@":"] lastObject]; + NSString *policy = [NSString stringWithFormat:@"{\"Statement\": [{\"Sid\": \"%@-policy\", \"Effect\": \"Allow\", \"Principal\": {\"AWS\": \"*\"}, \"Action\": \"sqs:SendMessage\", \"Resource\": \"%@\", \"Condition\": { \"ArnEquals\": { \"aws:sourceArn\": \"%@\" } } }]}", theQueueName, theQueueArn, theSourceArn]; + HSLogDebug(@"policy = %@", policy); + NSString *escapedPolicy = [(NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)policy, NULL, CFSTR("?=&+?:,*"), kCFStringEncodingUTF8) autorelease]; + + NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease]; + [formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss'Z'"]; + [formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; + + NSMutableString *str = [NSMutableString stringWithFormat:@"%@?", [theQueueURL description]]; + [str appendFormat:@"AWSAccessKeyId=%@", [accessKey stringByEscapingURLCharacters]]; + [str appendFormat:@"&Action=SetQueueAttributes"]; + [str appendFormat:@"&Attribute.Name=Policy"]; + [str appendFormat:@"&Attribute.Value=%@", escapedPolicy]; + [str appendFormat:@"&SignatureMethod=HmacSHA256"]; + [str appendFormat:@"&SignatureVersion=2"]; + [str appendFormat:@"&Timestamp=%@", [[formatter stringFromDate:[NSDate date]] stringByEscapingURLCharacters]]; + [str appendFormat:@"&Version=2009-02-01"]; + NSURL *url = [NSURL URLWithString:str]; + NSAssert(url != nil, @"url may not be nil!"); + NSString *signature = [sap signatureForHTTPMethod:@"GET" url:url]; + [str appendFormat:@"&Signature=%@", [signature stringByEscapingURLCharacters]]; + + NSURL *urlWithSignature = [NSURL URLWithString:str]; + AWSQueryRequest *req = [[[AWSQueryRequest alloc] initWithMethod:@"GET" url:urlWithSignature retryOnTransientError:retryOnTransientError] autorelease]; + AWSQueryResponse *response = [req execute:error]; + if (response == nil) { + return NO; + } + return YES; +} +- (ReceiveMessageResponse *)receiveMessagesForQueueURL:(NSURL *)theURL maxMessages:(NSUInteger)theMaxMessages error:(NSError **)error { + if (theMaxMessages > 10) { + // SQS only accepts a value between 1 and 10. + theMaxMessages = 10; + } + NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease]; + [formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss'Z'"]; + [formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; + + NSMutableString *str = [NSMutableString stringWithFormat:@"%@?", [theURL description]]; + [str appendFormat:@"AWSAccessKeyId=%@", [accessKey stringByEscapingURLCharacters]]; + [str appendFormat:@"&Action=ReceiveMessage"]; + [str appendFormat:@"&AttributeName=All"]; + [str appendFormat:@"&MaxNumberOfMessages=%lu", (unsigned long)theMaxMessages]; + [str appendFormat:@"&SignatureMethod=HmacSHA256"]; + [str appendFormat:@"&SignatureVersion=2"]; + [str appendFormat:@"&Timestamp=%@", [[formatter stringFromDate:[NSDate date]] stringByEscapingURLCharacters]]; + [str appendFormat:@"&Version=2009-02-01"]; + NSURL *url = [NSURL URLWithString:str]; + NSAssert(url != nil, @"url may not be nil!"); + NSString *signature = [sap signatureForHTTPMethod:@"GET" url:url]; + [str appendFormat:@"&Signature=%@", [signature stringByEscapingURLCharacters]]; + + NSURL *urlWithSignature = [NSURL URLWithString:str]; + AWSQueryRequest *req = [[[AWSQueryRequest alloc] initWithMethod:@"GET" url:urlWithSignature retryOnTransientError:retryOnTransientError] autorelease]; + AWSQueryResponse *response = [req execute:error]; + if (response == nil) { + return nil; + } + ReceiveMessageResponse *msg = [[[ReceiveMessageResponse alloc] initWithQueueURL:theURL data:[response body]] autorelease]; + return msg; +} +- (BOOL)deleteMessageWithQueueURL:(NSURL *)theURL receiptHandle:(NSString *)theReceiptHandle error:(NSError **)error { + NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease]; + [formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss'Z'"]; + [formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; + + NSMutableString *str = [NSMutableString stringWithFormat:@"%@?", [theURL description]]; + [str appendFormat:@"AWSAccessKeyId=%@", [accessKey stringByEscapingURLCharacters]]; + [str appendFormat:@"&Action=DeleteMessage"]; + [str appendFormat:@"&ReceiptHandle=%@", [theReceiptHandle stringByEscapingURLCharacters]]; + [str appendFormat:@"&SignatureMethod=HmacSHA256"]; + [str appendFormat:@"&SignatureVersion=2"]; + [str appendFormat:@"&Timestamp=%@", [[formatter stringFromDate:[NSDate date]] stringByEscapingURLCharacters]]; + [str appendFormat:@"&Version=2009-02-01"]; + NSURL *url = [NSURL URLWithString:str]; + NSAssert(url != nil, @"url may not be nil!"); + NSString *signature = [sap signatureForHTTPMethod:@"GET" url:url]; + [str appendFormat:@"&Signature=%@", [signature stringByEscapingURLCharacters]]; + + NSURL *urlWithSignature = [NSURL URLWithString:str]; + AWSQueryRequest *req = [[[AWSQueryRequest alloc] initWithMethod:@"GET" url:urlWithSignature retryOnTransientError:retryOnTransientError] autorelease]; + AWSQueryResponse *response = [req execute:error]; + if (response == nil) { + return NO; + } + return YES; +} +- (NSArray *)queueURLs:(NSError **)error { + //FIXME: This only returns up to 1000 queues. + + NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease]; + [formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss'Z'"]; + [formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; + + NSMutableString *str = [NSMutableString stringWithFormat:@"%@/?", [awsRegion sqsEndpointWithSSL:NO]]; + [str appendFormat:@"AWSAccessKeyId=%@", [accessKey stringByEscapingURLCharacters]]; + [str appendFormat:@"&Action=ListQueues"]; + [str appendFormat:@"&SignatureMethod=HmacSHA256"]; + [str appendFormat:@"&SignatureVersion=2"]; + [str appendFormat:@"&Timestamp=%@", [[formatter stringFromDate:[NSDate date]] stringByEscapingURLCharacters]]; + [str appendFormat:@"&Version=2011-10-01"]; + NSURL *url = [NSURL URLWithString:str]; + NSAssert(url != nil, @"url may not be nil!"); + NSString *signature = [sap signatureForHTTPMethod:@"GET" url:url]; + [str appendFormat:@"&Signature=%@", [signature stringByEscapingURLCharacters]]; + + NSURL *urlWithSignature = [NSURL URLWithString:str]; + AWSQueryRequest *req = [[[AWSQueryRequest alloc] initWithMethod:@"GET" url:urlWithSignature retryOnTransientError:retryOnTransientError] autorelease]; + AWSQueryResponse *response = [req execute:error]; + if (response == nil) { + return nil; + } + ListQueuesResponse *msg = [[[ListQueuesResponse alloc] initWithData:[response body]] autorelease]; + return [msg queueURLs]; +} +- (BOOL)deleteQueue:(NSURL *)theQueueURL error:(NSError **)error { + NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease]; + [formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss'Z'"]; + [formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; + + NSMutableString *str = [NSMutableString stringWithFormat:@"%@?", [theQueueURL description]]; + [str appendFormat:@"AWSAccessKeyId=%@", [accessKey stringByEscapingURLCharacters]]; + [str appendFormat:@"&Action=DeleteQueue"]; + [str appendFormat:@"&SignatureMethod=HmacSHA256"]; + [str appendFormat:@"&SignatureVersion=2"]; + [str appendFormat:@"&Timestamp=%@", [[formatter stringFromDate:[NSDate date]] stringByEscapingURLCharacters]]; + [str appendFormat:@"&Version=2009-02-01"]; + NSURL *url = [NSURL URLWithString:str]; + NSAssert(url != nil, @"url may not be nil!"); + NSString *signature = [sap signatureForHTTPMethod:@"GET" url:url]; + [str appendFormat:@"&Signature=%@", [signature stringByEscapingURLCharacters]]; + + NSURL *urlWithSignature = [NSURL URLWithString:str]; + AWSQueryRequest *req = [[[AWSQueryRequest alloc] initWithMethod:@"GET" url:urlWithSignature retryOnTransientError:retryOnTransientError] autorelease]; + AWSQueryResponse *response = [req execute:error]; + if (response == nil) { + return NO; + } + return YES; +} +@end diff --git a/cocoastack/sqs/SQSMessage.h b/cocoastack/sqs/SQSMessage.h new file mode 100644 index 0000000..688be09 --- /dev/null +++ b/cocoastack/sqs/SQSMessage.h @@ -0,0 +1,20 @@ +// +// SQSMessage.h +// Arq +// +// Created by Stefan Reitshamer on 9/17/12. +// +// + + +@interface SQSMessage : NSObject { + NSURL *queueURL; + NSString *body; + NSString *receiptHandle; +} +- (id)initWithQueueURL:(NSURL *)theQueueURL body:(NSString *)theBody receiptHandle:(NSString *)theReceiptHandle; + +- (NSURL *)queueURL; +- (NSString *)body; +- (NSString *)receiptHandle; +@end diff --git a/cocoastack/sqs/SQSMessage.m b/cocoastack/sqs/SQSMessage.m new file mode 100644 index 0000000..0c1875a --- /dev/null +++ b/cocoastack/sqs/SQSMessage.m @@ -0,0 +1,36 @@ +// +// SQSMessage.m +// Arq +// +// Created by Stefan Reitshamer on 9/17/12. +// +// + +#import "SQSMessage.h" + +@implementation SQSMessage +- (id)initWithQueueURL:(NSURL *)theQueueURL body:(NSString *)theBody receiptHandle:(NSString *)theReceiptHandle { + if (self = [super init]) { + queueURL = [theQueueURL retain]; + body = [theBody retain]; + receiptHandle = [theReceiptHandle retain]; + } + return self; +} +- (void)dealloc { + [queueURL release]; + [body release]; + [receiptHandle release]; + [super dealloc]; +} + +- (NSURL *)queueURL { + return queueURL; +} +- (NSString *)body { + return body; +} +- (NSString *)receiptHandle { + return receiptHandle; +} +@end diff --git a/storage/StorageType.h b/cocoastack/storage/StorageType.h similarity index 52% rename from storage/StorageType.h rename to cocoastack/storage/StorageType.h index 1a480bd..0a3f5ad 100644 --- a/storage/StorageType.h +++ b/cocoastack/storage/StorageType.h @@ -1,5 +1,6 @@ enum { StorageTypeS3 = 1, - StorageTypeGlacier = 2 + StorageTypeGlacier = 2, + StorageTypeS3Glacier = 3 }; typedef uint32_t StorageType; diff --git a/crypto/Encryption.h b/crypto/Encryption.h deleted file mode 100644 index 3a15b1e..0000000 --- a/crypto/Encryption.h +++ /dev/null @@ -1,16 +0,0 @@ -// -// Encryption.h -// Arq -// -// Created by Stefan Reitshamer on 7/15/11. -// Copyright 2011 __MyCompanyName__. All rights reserved. -// - - - - -@interface Encryption : NSObject { - -} -+ (NSString *)errorDomain; -@end diff --git a/crypto/Encryption.m b/crypto/Encryption.m deleted file mode 100644 index 4471815..0000000 --- a/crypto/Encryption.m +++ /dev/null @@ -1,16 +0,0 @@ -// -// Encryption.m -// Arq -// -// Created by Stefan Reitshamer on 7/15/11. -// Copyright 2011 __MyCompanyName__. All rights reserved. -// - -#import "Encryption.h" - - -@implementation Encryption -+ (NSString *)errorDomain { - return @"EncryptionErrorDomain"; -} -@end diff --git a/crypto/NSData-Base64Extensions.m b/crypto/NSData-Base64Extensions.m deleted file mode 100644 index 7169996..0000000 --- a/crypto/NSData-Base64Extensions.m +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) 2006 Dave Dribin (http://www.dribin.org/dave/) -// 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 "NSData-Base64Extensions.h" -#include -#include - -@implementation NSData (Base64) - -- (NSString *) encodeBase64; -{ - return [self encodeBase64WithNewlines: YES]; -} - -- (NSString *) encodeBase64WithNewlines: (BOOL) encodeWithNewlines; -{ - // Create a memory buffer which will contain the Base64 encoded string - BIO * mem = BIO_new(BIO_s_mem()); - - // Push on a Base64 filter so that writing to the buffer encodes the data - BIO * b64 = BIO_new(BIO_f_base64()); - if (!encodeWithNewlines) - BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); - mem = BIO_push(b64, mem); - - // Encode all the data - BIO_write(mem, [self bytes], [self length]); - if (BIO_flush(mem) < 1) { - HSLogError(@"BIO_flush error"); - } - - // Create a new string from the data in the memory buffer - char * base64Pointer; - long base64Length = BIO_get_mem_data(mem, &base64Pointer); - NSString *base64String = [[[NSString alloc] initWithCString:base64Pointer length:base64Length] autorelease]; - - // Clean up and go home - BIO_free_all(mem); - return base64String; -} -@end diff --git a/crypto/NSData-Encrypt.h b/crypto/NSData-Encrypt.h deleted file mode 100644 index f7d532a..0000000 --- a/crypto/NSData-Encrypt.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - - -@class CryptoKey; - -@interface NSData (Encrypt) -- (NSData *)encryptWithCryptoKey:(CryptoKey *)theCryptoKey error:(NSError **)error; -- (NSData *)decryptWithCryptoKey:(CryptoKey *)theCryptoKey error:(NSError **)error; - -@end diff --git a/crypto/NSData-Encrypt.m b/crypto/NSData-Encrypt.m deleted file mode 100644 index ff0e9fa..0000000 --- a/crypto/NSData-Encrypt.m +++ /dev/null @@ -1,63 +0,0 @@ -/* - Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#import - -#import "NSData-Encrypt.h" -#import "DataInputStream.h" -#import "EncryptedInputStream.h" -#import "DecryptedInputStream.h" - -@implementation NSData (Encrypt) -- (NSData *)encryptWithCryptoKey:(CryptoKey *)theCryptoKey error:(NSError **)error { - DataInputStream *dis = [[DataInputStream alloc] initWithData:self]; - EncryptedInputStream *encrypted = [[EncryptedInputStream alloc] initWithInputStream:dis cryptoKey:theCryptoKey error:error]; - [dis release]; - if (encrypted == nil) { - return nil; - } - NSData *ret = [encrypted slurp:error]; - [encrypted release]; - return ret; -} -- (NSData *)decryptWithCryptoKey:(CryptoKey *)theCryptoKey error:(NSError **)error { - DataInputStream *dis = [[DataInputStream alloc] initWithData:self]; - DecryptedInputStream *decrypted = [[DecryptedInputStream alloc] initWithInputStream:dis cryptoKey:theCryptoKey error:error]; - [dis release]; - if (decrypted == nil) { - return nil; - } - NSData *ret = [decrypted slurp:error]; - [decrypted release]; - return ret; -} -@end diff --git a/crypto/OpenSSL.m b/crypto/OpenSSL.m deleted file mode 100644 index 611061a..0000000 --- a/crypto/OpenSSL.m +++ /dev/null @@ -1,78 +0,0 @@ -/* - Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#import -#import -#import "OpenSSL.h" -#import "SetNSError.h" - -static BOOL initialized = NO; -static SSL_CTX *ctx; - -@implementation OpenSSL -+ (BOOL)initializeSSL:(NSError **)error { - if (!initialized) { - SSL_library_init(); - OpenSSL_add_all_algorithms(); - SSL_load_error_strings(); - ERR_load_crypto_strings(); - ctx = SSL_CTX_new(SSLv23_method()); - if (ctx == NULL) { - SETNSERROR(@"SSLErrorDomain", -1, @"SSL_CTX_new: %@", [OpenSSL errorMessage]); - return NO; - } - initialized = YES; - } - return YES; -} -+ (SSL_CTX *)context { - return ctx; -} -+ (NSString *)errorMessage { - NSMutableString *msg = [NSMutableString string]; - for (;;) { - unsigned long err = ERR_get_error(); - if (err == 0) { - break; - } - if ([msg length] > 0) { - [msg appendString:@"; "]; - } - HSLogTrace(@"%s", ERR_error_string(err, NULL)); - [msg appendFormat:@"%s", ERR_reason_error_string(err)]; - } - if ([msg length] == 0) { - [msg appendString:@"(no error)"]; - } - return msg; -} -@end diff --git a/crypto/SHA1Hash.m b/crypto/SHA1Hash.m deleted file mode 100644 index c575d46..0000000 --- a/crypto/SHA1Hash.m +++ /dev/null @@ -1,174 +0,0 @@ -/* - Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#import "SHA1Hash.h" -#include -#include -#import "SetNSError.h" -#import "FileInputStream.h" -#import "NSErrorCodes.h" -#import "Blob.h" -#import "BufferedInputStream.h" - -#define MY_BUF_SIZE (4096) - -@interface SHA1Hash (internal) -+ (NSString *)hashStream:(id )is error:(NSError **)error; -+ (NSString *)hashStream:(id )is streamLength:(unsigned long long *)streamLength error:(NSError **)error; -+ (BOOL)updateSHA1:(SHA_CTX *)ctx fromStream:(id )is length:(unsigned long long)theLength error:(NSError **)error; -@end - -static NSString *digest2String(unsigned char *digest) { - char *str = (char *)calloc(SHA_DIGEST_LENGTH, 2); - for (int i = 0; i < SHA_DIGEST_LENGTH; i++) { - sprintf(&(str[i*2]), "%02x", digest[i]); - } - NSString *ret = [[[NSString alloc] initWithCString:str length:SHA_DIGEST_LENGTH*2] autorelease]; - free(str); - return ret; -} - -@implementation SHA1Hash -+ (NSString *)errorDomain { - return @"SHA1HashErrorDomain"; -} -+ (NSString *)hashData:(NSData *)data { - SHA_CTX ctx; - SHA1_Init(&ctx); - SHA1_Update(&ctx, [data bytes], (unsigned long)[data length]); - unsigned char md[SHA_DIGEST_LENGTH]; - SHA1_Final(md, &ctx); - return digest2String(md); -} -+ (NSString *)hashBlob:(Blob *)blob blobLength:(unsigned long long *)blobLength error:(NSError **)error { - id is = [[blob inputStreamFactory] newInputStream]; - if (is == nil) { - return nil; - } - NSString *sha1 = [SHA1Hash hashStream:is streamLength:blobLength error:error]; - [is release]; - return sha1; -} -+ (NSString *)hashFile:(NSString *)path error:(NSError **)error { - struct stat st; - if (lstat([path fileSystemRepresentation], &st) == -1) { - int errnum = errno; - HSLogError(@"lstat(%@) error %d: %s", path, errnum, strerror(errnum)); - SETNSERROR(@"UnixErrorDomain", errnum, @"%@: %s", path, strerror(errnum)); - return NO; - } - unsigned long long length = (unsigned long long)st.st_size; - FileInputStream *fis = [[FileInputStream alloc] initWithPath:path offset:0 length:length]; - NSString *sha1 = [SHA1Hash hashStream:fis error:error]; - [fis release]; - return sha1; -} -+ (NSString *)hashStream:(id )is withLength:(uint64_t)length error:(NSError **)error { - SHA_CTX ctx; - SHA1_Init(&ctx); - uint64_t received = 0; - unsigned char *buf = (unsigned char *)malloc(MY_BUF_SIZE); - NSInteger ret = 0; - while (received < length) { - uint64_t toRead = length - received; - uint64_t toReadThisTime = toRead > MY_BUF_SIZE ? MY_BUF_SIZE : toRead; - ret = [is read:buf bufferLength:toReadThisTime error:error]; - if (ret < 0) { - break; - } - if (ret == 0) { - SETNSERROR([SHA1Hash errorDomain], ERROR_EOF, @"unexpected EOF in %@ after %qu of %qu bytes", is, received, length); - break; - } - SHA1_Update(&ctx, buf, ret); - received += (uint64_t)ret; - } - free(buf); - if (ret <= 0) { - return nil; - } - unsigned char md[SHA_DIGEST_LENGTH]; - SHA1_Final(md, &ctx); - return digest2String(md); -} -@end - -@implementation SHA1Hash (internal) -+ (NSString *)hashStream:(id )is error:(NSError **)error { - unsigned long long length; - return [SHA1Hash hashStream:is streamLength:&length error:error]; -} -+ (NSString *)hashStream:(id )is streamLength:(unsigned long long *)streamLength error:(NSError **)error { - SHA_CTX ctx; - SHA1_Init(&ctx); - *streamLength = 0; - unsigned char *buf = (unsigned char *)malloc(MY_BUF_SIZE); - NSInteger ret = 0; - for (;;) { - ret = [is read:buf bufferLength:MY_BUF_SIZE error:error]; - if (ret <= 0) { - break; - } - SHA1_Update(&ctx, buf, (unsigned long)ret); - *streamLength += (unsigned long long)ret; - } - free(buf); - if (ret < 0) { - return nil; - } - unsigned char md[SHA_DIGEST_LENGTH]; - SHA1_Final(md, &ctx); - return digest2String(md); -} -+ (BOOL)updateSHA1:(SHA_CTX *)ctx fromStream:(id )is length:(unsigned long long)theLength error:(NSError **)error { - unsigned char *imageBuf = (unsigned char *)malloc(MY_BUF_SIZE); - uint64_t recvd = 0; - NSInteger ret = 0; - while (recvd < theLength) { - uint64_t needed = theLength - recvd; - uint64_t toRead = needed > MY_BUF_SIZE ? MY_BUF_SIZE : needed; - ret = [is read:imageBuf bufferLength:toRead error:error]; - if (ret < 0) { - break; - } - if (ret == 0) { - SETNSERROR([SHA1Hash errorDomain], -1, @"unexpected EOF reading image data from %@", is); - break; - } - SHA1_Update( - ctx, imageBuf, ret); - recvd += (uint64_t)ret; - } - free(imageBuf); - return ret > 0; -} -@end diff --git a/http/CFHTTPConnection.h b/http/CFHTTPConnection.h deleted file mode 100644 index de815c6..0000000 --- a/http/CFHTTPConnection.h +++ /dev/null @@ -1,83 +0,0 @@ -/* - Copyright (c) 2009-2010, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#import "HTTPConnection.h" -@protocol HTTPConnectionDelegate; -#import "InputStream.h" -@class RFC2616DateFormatter; -@class HTTPTimeoutSetting; - -@interface CFHTTPConnection : NSObject { - RFC2616DateFormatter *dateFormatter; - NSURL *url; - NSString *requestMethod; - id httpConnectionDelegate; - NSMutableDictionary *requestHeaders; - CFHTTPMessageRef request; - NSInputStream *readStream; - BOOL errorOccurred; - NSError *_error; - BOOL complete; - BOOL hasBytesAvailable; - int responseStatusCode; - NSDictionary *responseHeaders; - NSTimeInterval createTime; - BOOL closeRequested; - CFHTTPConnection *previous; - NSDate *sendTimeout; - unsigned long long totalSent; - HTTPTimeoutSetting *httpTimeoutSetting; -} -+ (NSString *)errorDomain; -- (id)initWithURL:(NSURL *)theURL method:(NSString *)theMethod httpTimeoutSetting:(HTTPTimeoutSetting *)theHTTPTimeoutSetting httpConnectionDelegate:(id )theDelegate; -- (id)initWithURL:(NSURL *)theURL method:(NSString *)theMethod httpTimeoutSetting:(HTTPTimeoutSetting *)theHTTPTimeoutSetting httpConnectionDelegate:(id )theDelegate previousConnection:(CFHTTPConnection *)thePrevious; -- (void)setRequestHeader:(NSString *)value forKey:(NSString *)key; -- (void)setRequestHostHeader; -- (void)setRequestContentDispositionHeader:(NSString *)downloadName; -- (void)setRFC822DateRequestHeader; -- (NSString *)requestMethod; -- (NSString *)requestPathInfo; -- (NSString *)requestQueryString; -- (NSArray *)requestHeaderKeys; -- (NSString *)requestHeaderForKey:(NSString *)theKey; -- (BOOL)executeRequest:(NSError **)error; -- (BOOL)executeRequestWithBody:(NSData *)requestBody error:(NSError **)error; -- (int)responseCode; -- (NSString *)responseHeaderForKey:(NSString *)key; -- (NSString *)responseContentType; -- (NSString *)responseDownloadName; -- (id )newResponseBodyStream:(NSError **)error; -- (void)setCloseRequested; -- (BOOL)isCloseRequested; -- (NSTimeInterval)createTime; -- (void)releasePreviousConnection; -@end diff --git a/http/CFHTTPConnection.m b/http/CFHTTPConnection.m deleted file mode 100644 index 6e74185..0000000 --- a/http/CFHTTPConnection.m +++ /dev/null @@ -1,540 +0,0 @@ -/* - Copyright (c) 2009-2010, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#import "CFHTTPConnection.h" -#import "BufferedInputStream.h" -#import "HSLog.h" -#import "OutputStream.h" -#import "RFC2616DateFormatter.h" -#import "RegexKitLite.h" -#import "HTTP.h" -#import "SetNSError.h" -#import "ChunkedInputStream.h" -#import "FixedLengthInputStream.h" -#import "NSData-InputStream.h" -#import "DataInputStream.h" -#import "CFNetwork.h" -#import -#import "CFNetwork.h" -#import "InputStreams.h" -#import "CFHTTPInputStream.h" -#import "NSErrorCodes.h" -#import "HTTPConnectionDelegate.h" -#import "HTTPTimeoutSetting.h" - - -#define DEFAULT_TIMEOUT_SECONDS (30.0) -#define MY_BUF_SIZE (8192) -static NSString *runLoopMode = @"HTTPConnectionRunLoopMode"; - -@interface CFHTTPConnection (usability) -- (BOOL)isUsable; -@end - -@interface CFHTTPConnection (internal) -- (void)doExecuteRequestWithBody:(NSData *)requestBody; -- (void)setProxiesOnReadStream; -- (void)handleNetworkEvent:(CFStreamEventType)theType; -- (void)handleBytesAvailable; -- (void)handleStreamComplete; -- (void)handleStreamError; -- (void)readResponseHeaders; -- (void)destroyReadStream; -- (void)resetSendTimeout; -- (id )doNewResponseBodyStream:(NSError **)error; -- (NSInteger)doRead:(unsigned char *)buf bufferLength:(NSUInteger)length error:(NSError **)error; -@end - -@interface CFHTTPConnection (callback) -- (void)sentRequestBytes:(NSInteger)count; -@end - - -static void ReadStreamClientCallback(CFReadStreamRef readStream, CFStreamEventType type, void *clientCallBackInfo) { - [(CFHTTPConnection *)clientCallBackInfo handleNetworkEvent:type]; -} - -@implementation CFHTTPConnection -+ (NSString *)errorDomain { - return @"HTTPConnectionErrorDomain"; -} - -- (id)initWithURL:(NSURL *)theURL method:(NSString *)theMethod httpTimeoutSetting:(HTTPTimeoutSetting *)theHTTPTimeoutSetting httpConnectionDelegate:(id )theDelegate { - return [self initWithURL:theURL method:theMethod httpTimeoutSetting:theHTTPTimeoutSetting httpConnectionDelegate:theDelegate previousConnection:nil]; -} -- (id)initWithURL:(NSURL *)theURL method:(NSString *)theMethod httpTimeoutSetting:(HTTPTimeoutSetting *)theHTTPTimeoutSetting httpConnectionDelegate:(id )theDelegate previousConnection:(CFHTTPConnection *)thePrevious { - if (self = [super init]) { - dateFormatter = [[RFC2616DateFormatter alloc] init]; - url = [theURL retain]; - requestMethod = [theMethod retain]; - httpTimeoutSetting = [theHTTPTimeoutSetting retain]; - httpConnectionDelegate = theDelegate; - requestHeaders = [[NSMutableDictionary alloc] init]; - if (thePrevious != nil) { - previous = [thePrevious retain]; - createTime = [thePrevious createTime]; - } else { - createTime = [NSDate timeIntervalSinceReferenceDate]; - } - } - return self; -} -- (void)dealloc { - [dateFormatter release]; - [url release]; - [requestMethod release]; - [httpTimeoutSetting release]; - [requestHeaders release]; - if (request) { - CFRelease(request); - } - [runLoopMode release]; - [responseHeaders release]; - [self destroyReadStream]; - [previous release]; - [sendTimeout release]; - [super dealloc]; -} - -- (NSString *)errorDomain { - return @"HTTPConnectionErrorDomain"; -} - -- (void)setRequestHeader:(NSString *)value forKey:(NSString *)key { - [requestHeaders setObject:value forKey:key]; -} -- (void)setRequestHostHeader { - [self setRequestHeader:[url host] forKey:@"Host"]; -} -- (void)setRequestContentDispositionHeader:(NSString *)downloadName { - if (downloadName != nil) { - NSString *encodedFilename = [NSString stringWithFormat:@"\"%@\"", [downloadName stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\\\""]]; - encodedFilename = [encodedFilename stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"]; - NSString *contentDisposition = [NSString stringWithFormat:@"attachment;filename=%@", encodedFilename]; - [self setRequestHeader:contentDisposition forKey:@"Content-Disposition"]; - } -} -- (void)setRFC822DateRequestHeader { - [self setRequestHeader:[dateFormatter rfc2616StringFromDate:[NSDate date]] forKey:@"Date"]; -} -- (NSString *)requestMethod { - return requestMethod; -} -//- (NSString *)requestPathInfo { -// return [[url path] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; -//} -- (NSString *)requestPathInfo { - NSString *urlDescription = [url description]; - NSRange rangeBeforeQueryString = [urlDescription rangeOfRegex:@"^([^?]+)"]; - NSString *stringBeforeQueryString = [urlDescription substringWithRange:rangeBeforeQueryString]; - NSString *path = [url path]; - if ([stringBeforeQueryString hasSuffix:@"/"] && ![path hasSuffix:@"/"]) { - // NSURL's path method strips trailing slashes. Add it back in. - path = [path stringByAppendingString:@"/"]; - } - return [path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; -} -- (NSString *)requestQueryString { - return [url query]; -} -- (NSArray *)requestHeaderKeys { - return [requestHeaders allKeys]; -} -- (NSString *)requestHeaderForKey:(NSString *)theKey { - return [requestHeaders objectForKey:theKey]; -} -- (BOOL)executeRequest:(NSError **)error { - return [self executeRequestWithBody:nil error:error]; -} -- (BOOL)executeRequestWithBody:(NSData *)requestBody error:(NSError **)error { - if (closeRequested) { - SETNSERROR([CFHTTPConnection errorDomain], -1, @"close was requested; can't reuse this connection"); - return NO; - } - [self doExecuteRequestWithBody:requestBody]; - if (errorOccurred) { - if (error != NULL) { - *error = _error; - } - if ([httpConnectionDelegate respondsToSelector:@selector(httpConnection:subtractSentBytes:)]) { - [httpConnectionDelegate httpConnection:self subtractSentBytes:totalSent]; - } - return NO; - } - return YES; -} -- (int)responseCode { - if (responseHeaders == nil) { - // User probably canceled before we received the response header. - return HTTP_INTERNAL_SERVER_ERROR; - } - return responseStatusCode; -} -- (NSString *)responseHeaderForKey:(NSString *)key { - return [responseHeaders objectForKey:key]; -} -- (NSString *)responseContentType { - return [self responseHeaderForKey:@"Content-Type"]; -} -- (NSString *)responseDownloadName { - NSString *downloadName = nil; - NSString *contentDisposition = [self responseHeaderForKey:@"Content-Disposition"]; - if (contentDisposition != nil) { - NSRange filenameRange = [contentDisposition rangeOfRegex:@"attachment;filename=(.+)" capture:1]; - if (filenameRange.location != NSNotFound) { - downloadName = [contentDisposition substringWithRange:filenameRange]; - } - } - return downloadName; -} -- (id )newResponseBodyStream:(NSError **)error { - id ret = [self doNewResponseBodyStream:error]; - if (ret == nil && [httpConnectionDelegate respondsToSelector:@selector(httpConnection:subtractSentBytes:)]) { - [httpConnectionDelegate httpConnection:self subtractSentBytes:totalSent]; - } - return ret; -} -- (void)setCloseRequested { - closeRequested = YES; -} -- (BOOL)isCloseRequested { - return closeRequested || errorOccurred; -} -- (NSTimeInterval)createTime { - return createTime; -} -- (void)releasePreviousConnection { - [previous release]; - previous = nil; -} - -#pragma mark InputStream -- (NSInteger)read:(unsigned char *)buf bufferLength:(NSUInteger)length error:(NSError **)error { - NSInteger ret = [self doRead:buf bufferLength:length error:error]; - if (ret < 0 && [httpConnectionDelegate respondsToSelector:@selector(httpConnection:subtractSentBytes:)]) { - [httpConnectionDelegate httpConnection:self subtractSentBytes:totalSent]; - } - return ret; -} -- (NSData *)slurp:(NSError **)error { - return [InputStreams slurp:self error:error]; -} - -#pragma mark NSObject -- (NSString *)description { - return [NSString stringWithFormat:@"", url, requestMethod]; -} -@end - -@implementation CFHTTPConnection (internal) -- (void)doExecuteRequestWithBody:(NSData *)requestBody { - request = CFHTTPMessageCreateRequest(kCFAllocatorDefault, (CFStringRef)requestMethod, (CFURLRef)url, kCFHTTPVersion1_1); - if (!request) { - errorOccurred = YES; - _error = [[NSError errorWithDomain:[CFNetwork errorDomain] code:-1 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Error creating request", NSLocalizedDescriptionKey, nil]] retain]; - return; - } - - // Add headers. - for (NSString *header in [requestHeaders allKeys]) { - CFHTTPMessageSetHeaderFieldValue(request, (CFStringRef)header, (CFStringRef)[requestHeaders objectForKey:header]); - } - - // Add keep-alive header every time: - CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Connection"), CFSTR("Keep-Alive")); - - if ([requestBody length] > 0) { - CFHTTPInputStream *bodyStream = [[[CFHTTPInputStream alloc] initWithCFHTTPConnection:self data:requestBody httpConnectionDelegate:httpConnectionDelegate] autorelease]; - readStream = (NSInputStream *)CFReadStreamCreateForStreamedHTTPRequest(kCFAllocatorDefault, request, (CFReadStreamRef)bodyStream); - } else { - readStream = (NSInputStream *)CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, request); - } - - HSLogTrace(@"new readStream: %p", readStream); - if (!readStream) { - errorOccurred = YES; - _error = [[NSError errorWithDomain:[CFNetwork errorDomain] code:-1 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Error creating read stream", NSLocalizedDescriptionKey, nil]] retain]; - return; - } - - if ([[[url scheme] lowercaseString] isEqualToString:@"https"]) { - NSMutableDictionary *sslProperties = [NSDictionary dictionaryWithObjectsAndKeys: - (NSString *)kCFStreamSocketSecurityLevelNegotiatedSSL, kCFStreamSSLLevel, - kCFBooleanTrue, kCFStreamSSLAllowsExpiredCertificates, - kCFBooleanTrue, kCFStreamSSLAllowsExpiredRoots, - kCFBooleanTrue, kCFStreamSSLAllowsAnyRoot, - kCFBooleanFalse, kCFStreamSSLValidatesCertificateChain, - kCFNull, kCFStreamSSLPeerName, - nil]; - CFReadStreamSetProperty((CFReadStreamRef)readStream, kCFStreamPropertySSLSettings, sslProperties); - } - [self setProxiesOnReadStream]; - - // Attempt to reuse this connection. - CFReadStreamSetProperty((CFReadStreamRef)readStream, kCFStreamPropertyHTTPAttemptPersistentConnection, kCFBooleanTrue); - - CFStreamClientContext ctxt = { 0, self, NULL, NULL, NULL }; - CFReadStreamSetClient((CFReadStreamRef)readStream, kCFStreamEventHasBytesAvailable | kCFStreamEventEndEncountered | kCFStreamEventErrorOccurred, ReadStreamClientCallback, &ctxt); - [readStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:runLoopMode]; - if (!CFReadStreamOpen((CFReadStreamRef)readStream)) { - errorOccurred = YES; - _error = [[NSError errorWithDomain:[CFNetwork errorDomain] code:-1 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Error opening read stream", NSLocalizedDescriptionKey, nil]] retain]; - return; - } - [previous releasePreviousConnection]; -} -- (void)setProxiesOnReadStream { - NSDictionary *proxySettings = (NSDictionary *)SCDynamicStoreCopyProxies(NULL); - NSArray *proxies = (NSArray *)CFNetworkCopyProxiesForURL((CFURLRef)url, (CFDictionaryRef)proxySettings); - if ([proxies count] > 0) { - NSDictionary *proxy = [proxies objectAtIndex:0]; - NSString *proxyType = [proxy objectForKey:(NSString *)kCFProxyTypeKey]; - if (![proxyType isEqualToString:(NSString *)kCFProxyTypeNone]) { - NSString *proxyHost = [proxy objectForKey:(NSString *)kCFProxyHostNameKey]; - int proxyPort = [[proxy objectForKey:(NSString *)kCFProxyPortNumberKey] intValue]; - NSString *hostKey; - NSString *portKey; - if ([proxyType isEqualToString:(NSString *)kCFProxyTypeSOCKS]) { - hostKey = (NSString *)kCFStreamPropertySOCKSProxyHost; - portKey = (NSString *)kCFStreamPropertySOCKSProxyPort; - } else { - hostKey = (NSString *)kCFStreamPropertyHTTPProxyHost; - portKey = (NSString *)kCFStreamPropertyHTTPProxyPort; - if ([[[url scheme] lowercaseString] isEqualToString:@"https"]) { - hostKey = (NSString *)kCFStreamPropertyHTTPSProxyHost; - portKey = (NSString *)kCFStreamPropertyHTTPSProxyPort; - } - } - // FIXME: Support proxy autconfiguration files (kCFProxyTypeAutoConfigurationURL) too! - NSDictionary *proxyToUse = [NSDictionary dictionaryWithObjectsAndKeys: - proxyHost, hostKey, - [NSNumber numberWithInt:proxyPort], portKey, - nil]; - if ([proxyType isEqualToString:(NSString *)kCFProxyTypeSOCKS]) { - CFReadStreamSetProperty((CFReadStreamRef)readStream, kCFStreamPropertySOCKSProxy, proxyToUse); - } else { - CFReadStreamSetProperty((CFReadStreamRef)readStream, kCFStreamPropertyHTTPProxy, proxyToUse); - } - } - } - [proxies release]; - [proxySettings release]; -} -- (void)handleNetworkEvent:(CFStreamEventType)theType { - switch (theType) { - case kCFStreamEventHasBytesAvailable: - [self handleBytesAvailable]; - break; - case kCFStreamEventEndEncountered: - [self handleStreamComplete]; - break; - case kCFStreamEventErrorOccurred: - [self handleStreamError]; - break; - default: - break; - } -} -- (void)handleBytesAvailable { - HSLogTrace(@"%@: handleBytesAvailable", self); - [self readResponseHeaders]; - - // Rarely, there aren't any data actually available. - if (!CFReadStreamHasBytesAvailable((CFReadStreamRef)readStream)) { - return; - } - hasBytesAvailable = YES; -} -- (void)handleStreamComplete { - HSLogTrace(@"%@: handleStreamComplete", self); - [self readResponseHeaders]; - complete = YES; -} -- (void)handleStreamError { - HSLogTrace(@"%@: handleStreamError", self); - if (errorOccurred) { - NSLog(@"already have an error!"); - return; - } - errorOccurred = YES; - _error = (NSError *)CFReadStreamCopyError((CFReadStreamRef)readStream); - complete = YES; - closeRequested = YES; -} -- (void)readResponseHeaders { - if (responseHeaders) { - return; - } - CFHTTPMessageRef message = (CFHTTPMessageRef)CFReadStreamCopyProperty((CFReadStreamRef)readStream, kCFStreamPropertyHTTPResponseHeader); - if (message) { - if (!CFHTTPMessageIsHeaderComplete(message)) { - HSLogDebug(@"%@: header not complete!", self); - } else { - [responseHeaders release]; - responseHeaders = (NSDictionary *)CFHTTPMessageCopyAllHeaderFields(message); - responseStatusCode = (int)CFHTTPMessageGetResponseStatusCode(message); - } - CFRelease(message); - } -} -- (void)destroyReadStream { - if (readStream != nil) { - HSLogTrace(@"destroying readStream: %p", readStream); - CFReadStreamSetClient((CFReadStreamRef)readStream, kCFStreamEventNone, NULL, NULL); - [readStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:runLoopMode]; - [readStream close]; - [readStream release]; - readStream = nil; - } -} -- (void)resetSendTimeout { - NSTimeInterval timeoutSeconds = [httpTimeoutSetting timeoutSeconds]; - if (timeoutSeconds == 0) { - timeoutSeconds = DEFAULT_TIMEOUT_SECONDS; - } - [sendTimeout release]; - sendTimeout = [[[NSDate date] addTimeInterval:timeoutSeconds] retain]; -} -- (id )doNewResponseBodyStream:(NSError **)error { - while (!complete && !hasBytesAvailable) { - [self resetSendTimeout]; - HSLogTrace(@"newResponseBodyStream: running the runloop until %f", [sendTimeout timeIntervalSinceReferenceDate]); - [[NSRunLoop currentRunLoop] runMode:runLoopMode beforeDate:sendTimeout]; - NSTimeInterval current = [NSDate timeIntervalSinceReferenceDate]; - if ((current + 0.1) > [sendTimeout timeIntervalSinceReferenceDate]) { - HSLogWarn(@"timeout waiting for response to %@ %@", requestMethod, url); - SETNSERROR([self errorDomain], ERROR_TIMEOUT, @"timeout while attempting to send HTTP request"); - closeRequested = YES; - return nil; - } else { - HSLogTrace(@"current time %f is not later than timeout %f; continuing", current, [sendTimeout timeIntervalSinceReferenceDate]); - } - } - if (errorOccurred) { - if ([httpConnectionDelegate abortRequestedForHTTPConnection:self]) { - [_error release]; - _error = [[NSError alloc] initWithDomain:[self errorDomain] code:ERROR_ABORT_REQUESTED userInfo:[NSDictionary dictionaryWithObject:@"abort requested" forKey:NSLocalizedDescriptionKey]]; - } - if (error != NULL) { - *error = _error; - } - return nil; - } - hasBytesAvailable = NO; - id ret = nil; - if (complete) { - HSLogTrace(@"%@: empty response body", self); - ret = [[NSData data] newInputStream]; - } else { - NSAssert(responseHeaders != nil, @"responseHeaders can't be nil"); - - NSString *transferEncoding = [self responseHeaderForKey:@"Transfer-Encoding"]; - // NSString *contentLength = [self responseHeaderForKey:@"Content-Length"]; - HSLogTrace(@"Content-Length = %@", [self responseHeaderForKey:@"Content-Length"]); - HSLogTrace(@"Transfer-Encoding = %@", transferEncoding); - if (transferEncoding != nil && ![transferEncoding isEqualToString:@"Identity"]) { - if ([[transferEncoding lowercaseString] isEqualToString:@"chunked"]) { - HSLogTrace(@"%@: chunked response body", self); - BufferedInputStream *bis = [[BufferedInputStream alloc] initWithUnderlyingStream:self]; - ret = [[ChunkedInputStream alloc] initWithUnderlyingStream:bis]; - } else { - SETNSERROR(@"StreamErrorDomain", -1, @"unknown Transfer-Encoding '%@'", transferEncoding); - return nil; - } - // } else if (contentLength != nil) { - // int length = [contentLength intValue]; - // BufferedInputStream *bis = [[BufferedInputStream alloc] initWithUnderlyingStream:self]; - // HSLogTrace(@"%@: fixed-length response body (%d bytes)", self, length); - //// ret = [[FixedLengthInputStream alloc] initWithUnderlyingStream:bis length:(NSUInteger)length]; - //// [bis release]; - // ret = bis; - } else { - /* - * FIXME: handle multipart/byteranges media type. - * See rfc2616 section 4.4 ("message length"). - */ - HSLogTrace(@"%@: response body with no content-length", self); - ret = [self retain]; - } - } - return ret; -} -- (NSInteger)doRead:(unsigned char *)buf bufferLength:(NSUInteger)length error:(NSError **)error { - NSInteger recvd = 0; - for (;;) { - NSTimeInterval timeoutSeconds = [httpTimeoutSetting timeoutSeconds]; - if (timeoutSeconds == 0) { - timeoutSeconds = DEFAULT_TIMEOUT_SECONDS; - } - NSDate *timeout = [[NSDate date] addTimeInterval:timeoutSeconds]; - while (!complete && !hasBytesAvailable) { - if ([timeout earlierDate:[NSDate date]] == timeout) { - HSLogWarn(@"timed out after %0.2f seconds waiting for response data from %@ %@", timeoutSeconds, requestMethod, url); - SETNSERROR([self errorDomain], ERROR_TIMEOUT, @"timeout after %0.2f seconds", timeoutSeconds); - closeRequested = YES; - return -1; - } - HSLogTrace(@"read: running the runloop until %@", timeout); - [[NSRunLoop currentRunLoop] runMode:runLoopMode beforeDate:timeout]; - } - hasBytesAvailable = NO; - if (errorOccurred) { - if (error != NULL) { - *error = _error; - } - return -1; - } - if (complete) { - return 0; - } - recvd = [readStream read:buf maxLength:length]; - HSLogTrace(@"received %ld bytes", recvd); - if (recvd < 0) { - [self handleStreamError]; - if (error != NULL) { - *error = _error; - } - return -1; - } - if (recvd > 0) { - break; - } - } - return recvd; -} -@end - -@implementation CFHTTPConnection (callback) -- (void)sentRequestBytes:(NSInteger)count { - totalSent += count; -} -@end diff --git a/http/CFHTTPInputStream.h b/http/CFHTTPInputStream.h deleted file mode 100644 index 5c886bb..0000000 --- a/http/CFHTTPInputStream.h +++ /dev/null @@ -1,28 +0,0 @@ -// -// CFHTTPInputStream.h -// Arq -// -// Created by Stefan Reitshamer on 3/16/12. -// Copyright 2012 __MyCompanyName__. All rights reserved. -// - - -@class CFHTTPConnection; -@protocol HTTPConnectionDelegate; -@class NetMonitor; - - -@interface CFHTTPInputStream : NSObject { - CFHTTPConnection *conn; - NSInputStream *inputStream; - id httpConnectionDelegate; - int throttleType; - int throttleKBPS; - NSTimeInterval lastReceivedTime; - NSUInteger lastReceivedLength; - NSUInteger totalReceivedLength; - NetMonitor *netMonitor; -} -- (id)initWithCFHTTPConnection:(CFHTTPConnection *)theConn data:(NSData *)theData httpConnectionDelegate:(id )theHTTPConnectionDelegate; - -@end diff --git a/http/CFNetwork.h b/http/CFNetwork.h deleted file mode 100644 index a5e8f89..0000000 --- a/http/CFNetwork.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - Copyright (c) 2009-2010, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - - - - -@interface CFNetwork : NSObject { - -} -+ (NSString *)errorDomain; -+ (NSError *)NSErrorWithNetworkError:(CFErrorRef)err; -@end diff --git a/http/CFNetwork.m b/http/CFNetwork.m deleted file mode 100644 index 851d6fe..0000000 --- a/http/CFNetwork.m +++ /dev/null @@ -1,130 +0,0 @@ -/* - Copyright (c) 2009-2010, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#import "CFNetwork.h" -#import "DNS_SDErrors.h" - -@implementation CFNetwork -+ (NSString *)errorDomain { - return @"CFNetworkErrorDomain"; -} -+ (NSError *)NSErrorWithNetworkError:(CFErrorRef)err { - NSString *localizedDescription = @"Network error"; - NSString *domain = (NSString *)CFErrorGetDomain(err); - CFIndex code = CFErrorGetCode(err); - CFDictionaryRef userInfo = CFErrorCopyUserInfo(err); - if ([domain isEqualToString:(NSString *)kCFErrorDomainCFNetwork]) { - if (code == kCFHostErrorHostNotFound) { - localizedDescription = @"host not found"; - } else if (code == kCFHostErrorUnknown) { - int gaiCode = 0; - if (CFNumberGetValue((CFNumberRef)CFDictionaryGetValue(userInfo, kCFGetAddrInfoFailureKey), kCFNumberIntType, &gaiCode)) { - HSLogDebug(@"Host lookup error: %s", gai_strerror(gaiCode)); - localizedDescription = @"Could not connect to the Internet"; - } - } else if (code == kCFSOCKSErrorUnknownClientVersion) { - localizedDescription = @"Unknown SOCKS client version"; - } else if (code == kCFSOCKSErrorUnsupportedServerVersion) { - localizedDescription = [NSString stringWithFormat:@"Unsupported SOCKS server version (server requested version %@)", (NSString *)CFDictionaryGetValue(userInfo, kCFSOCKSVersionKey)]; - } else if (code == kCFSOCKS4ErrorRequestFailed) { - localizedDescription = @"SOCKS4 request rejected or failed"; - } else if (code == kCFSOCKS4ErrorIdentdFailed) { - localizedDescription = @"SOCKS4 server cannot connect to identd on the client"; - } else if (code == kCFSOCKS4ErrorIdConflict) { - localizedDescription = @"SOCKS4 client and identd report different user IDs"; - } else if (code == kCFSOCKS4ErrorUnknownStatusCode) { - localizedDescription = [NSString stringWithFormat:@"SOCKS4 error %@", (NSString *)CFDictionaryGetValue(userInfo, kCFSOCKSStatusCodeKey)]; - } else if (code == kCFSOCKS5ErrorBadState) { - localizedDescription = @"SOCKS5 bad state"; - } else if (code == kCFSOCKS5ErrorBadResponseAddr) { - localizedDescription = @"SOCKS5 bad credentials"; - } else if (code == kCFSOCKS5ErrorBadCredentials) { - localizedDescription = @"SOCKS5 unsupported negotiation method"; - } else if (code == kCFSOCKS5ErrorUnsupportedNegotiationMethod) { - localizedDescription = @"SOCKS5 unsupported negotiation method"; - } else if (code == kCFSOCKS5ErrorNoAcceptableMethod) { - localizedDescription = @"SOCKS5 no acceptable method"; - } else if (code == kCFNetServiceErrorUnknown) { - localizedDescription = @"Unknown Net Services error"; - } else if (code == kCFNetServiceErrorCollision) { - localizedDescription = @"Net Services: collision"; - } else if (code == kCFNetServiceErrorNotFound) { - localizedDescription = @"Net Services: not found"; - } else if (code == kCFNetServiceErrorInProgress) { - localizedDescription = @"Net Services: in progress"; - } else if (code == kCFNetServiceErrorBadArgument) { - localizedDescription = @"Net Services: bad argument"; - } else if (code == kCFNetServiceErrorCancel) { - localizedDescription = @"Net Services: cancelled"; - } else if (code == kCFNetServiceErrorInvalid) { - localizedDescription = @"Net Services: invalid"; - } else if (code == kCFNetServiceErrorTimeout) { - localizedDescription = @"Net Services timeout"; - } else if (code == kCFNetServiceErrorDNSServiceFailure) { - localizedDescription = @"Net Services DNS failure"; - int dns_sdCode = 0; - if (CFNumberGetValue((CFNumberRef)CFDictionaryGetValue(userInfo, kCFDNSServiceFailureKey), kCFNumberIntType, &dns_sdCode)) { - localizedDescription = [NSString stringWithFormat:@"Net Services DNS failure: %@", [DNS_SDErrors descriptionForDNS_SDError:dns_sdCode]]; - } - } else if (code == kCFFTPErrorUnexpectedStatusCode) { - localizedDescription = [NSString stringWithFormat:@"FTP error %@", (NSString *)CFDictionaryGetValue(userInfo, kCFFTPStatusCodeKey)]; - } else if (code == kCFErrorHTTPAuthenticationTypeUnsupported) { - localizedDescription = @"HTTP authentication type unsupported"; - } else if (code == kCFErrorHTTPBadCredentials) { - localizedDescription = @"bad HTTP credentials"; - } else if (code == kCFErrorHTTPConnectionLost) { - localizedDescription = @"HTTP connection lost"; - } else if (code == kCFErrorHTTPParseFailure) { - localizedDescription = @"HTTP parse failure"; - } else if (code == kCFErrorHTTPRedirectionLoopDetected) { - localizedDescription = @"HTTP redirection loop detected"; - } else if (code == kCFErrorHTTPBadURL) { - localizedDescription = @"bad HTTP URL"; - } else if (code == kCFErrorHTTPProxyConnectionFailure) { - localizedDescription = @"HTTP proxy connection failure"; - } else if (code == kCFErrorHTTPBadProxyCredentials) { - localizedDescription = @"bad HTTP proxy credentials"; - } else if (code == kCFErrorPACFileError) { - localizedDescription = @"HTTP PAC file error"; - } - } else if ([domain isEqualToString:@"NSPOSIXErrorDomain"] && code == ENOTCONN) { - localizedDescription = @"Lost connection to the Internet"; - } else { - localizedDescription = [(NSString *)CFErrorCopyDescription(err) autorelease]; - } - CFRelease(userInfo); - return [NSError errorWithDomain:[CFNetwork errorDomain] code:code userInfo:[NSDictionary dictionaryWithObjectsAndKeys:localizedDescription, NSLocalizedDescriptionKey, nil]]; -} -@end diff --git a/http/HTTPConnectionDelegate.h b/http/HTTPConnectionDelegate.h deleted file mode 100644 index 08542e6..0000000 --- a/http/HTTPConnectionDelegate.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// HTTPConnectionDelegate.h -// Arq -// -// Created by Stefan Reitshamer on 3/16/12. -// Copyright 2012 __MyCompanyName__. All rights reserved. -// - -@protocol HTTPConnection; - -#define THROTTLE_NONE 0 -#define THROTTLE_AUTOMATIC 1 -#define THROTTLE_FIXED 2 - -@protocol HTTPConnectionDelegate -- (void)httpConnection:(id )theHTTPConnection sentBytes:(unsigned long long)sent throttleType:(int *)theThrottleType throttleKBPS:(int *)theThrottleKBPS pauseRequested:(BOOL *)isPauseRequested abortRequested:(BOOL *)isAbortRequested; -- (void)httpConnection:(id )theHTTPConnection subtractSentBytes:(unsigned long long)sent; -- (BOOL)abortRequestedForHTTPConnection:(id )theHTTPConnection; -@end diff --git a/http/HTTPConnectionFactory.h b/http/HTTPConnectionFactory.h deleted file mode 100644 index 34eca58..0000000 --- a/http/HTTPConnectionFactory.h +++ /dev/null @@ -1,27 +0,0 @@ -// -// HTTPConnectionFactory.h -// Arq -// -// Created by Stefan Reitshamer on 3/15/12. -// Copyright 2012 __MyCompanyName__. All rights reserved. -// - - -@protocol HTTPConnection; -@protocol HTTPConnectionDelegate; -@class HTTPTimeoutSetting; - - -@interface HTTPConnectionFactory : NSObject { - NSTimeInterval maxConnectionLifetime; - NSLock *lock; - NSMutableDictionary *connectionMapsByThreadId; -} - -+ (HTTPConnectionFactory *)theFactory; -- (id )newHTTPConnectionToURL:(NSURL *)theURL - method:(NSString *)theMethod - httpTimeoutSetting:(HTTPTimeoutSetting *)theHTTPTimeoutSetting - httpConnectionDelegate:(id )theHTTPConnectionDelegate; - -@end diff --git a/http/HTTPConnectionFactory.m b/http/HTTPConnectionFactory.m deleted file mode 100644 index 68facae..0000000 --- a/http/HTTPConnectionFactory.m +++ /dev/null @@ -1,151 +0,0 @@ -// -// HTTPConnectionFactory.m -// Arq -// -// Created by Stefan Reitshamer on 3/15/12. -// Copyright 2012 __MyCompanyName__. All rights reserved. -// - -#import "HTTPConnectionFactory.h" -#import "CFHTTPConnection.h" -#import "URLConnection.h" - -#define DEFAULT_MAX_HTTPCONNECTION_LIFETIME_SECONDS (20) -#define CLEANUP_THREAD_SLEEP_SECONDS (5) - - -@interface ConnectionMap : NSObject { - NSMutableDictionary *connections; -} -- (CFHTTPConnection *)newConnectionToURL:(NSURL *)theURL - method:(NSString *)theMethod - maxConnectionLifetime:(NSTimeInterval)theMaxConnectionLifetime - httpTimeoutSetting:(HTTPTimeoutSetting *)theHTTPTimeoutSetting - httpConnectionDelegate:(id )theDelegate; -- (void)dropUnusableConnections:(NSTimeInterval)theMaxConnectionLifetime; -@end - -@implementation ConnectionMap -- (id)init { - if (self = [super init]) { - connections = [[NSMutableDictionary alloc] init]; - } - return self; -} -- (void)dealloc { - [connections release]; - [super dealloc]; -} -- (CFHTTPConnection *)newConnectionToURL:(NSURL *)theURL - method:(NSString *)theMethod - maxConnectionLifetime:(NSTimeInterval)theMaxConnectionLifetime - httpTimeoutSetting:(HTTPTimeoutSetting *)theHTTPTimeoutSetting - httpConnectionDelegate:(id )theHTTPConnectionDelegate { - NSString *key = [NSString stringWithFormat:@"%@ %@://%@:%d", theMethod, [theURL scheme], [theURL host], [[theURL port] intValue]]; - CFHTTPConnection *conn = [connections objectForKey:key]; - if (conn != nil) { - if ([conn isCloseRequested] || (([NSDate timeIntervalSinceReferenceDate] - [conn createTime]) > theMaxConnectionLifetime)) { - [connections removeObjectForKey:key]; - HSLogTrace(@"removing connection %p", conn); - conn = nil; - } else { - HSLogTrace(@"reusing connection %p", conn); - conn = [[CFHTTPConnection alloc] initWithURL:theURL method:theMethod httpTimeoutSetting:theHTTPTimeoutSetting httpConnectionDelegate:theHTTPConnectionDelegate previousConnection:conn]; - [connections setObject:conn forKey:key]; - } - } - if (conn == nil) { - HSLogTrace(@"new connection %p", conn); - conn = [[CFHTTPConnection alloc] initWithURL:theURL method:theMethod httpTimeoutSetting:theHTTPTimeoutSetting httpConnectionDelegate:theHTTPConnectionDelegate]; -// [connections setObject:conn forKey:key]; - } - return conn; -} - -- (void)dropUnusableConnections:(NSTimeInterval)theMaxConnectionLifetime { - NSMutableArray *keysToDrop = [NSMutableArray array]; - for (NSString *key in [connections allKeys]) { - CFHTTPConnection *conn = [connections objectForKey:key]; - if ([conn isCloseRequested] || (([NSDate timeIntervalSinceReferenceDate] - [conn createTime]) > theMaxConnectionLifetime)) { // FIXME: Duplicate logic to newConnectionToURL: method - [keysToDrop addObject:key]; - } - } - if ([keysToDrop count] > 0) { - HSLogTrace(@"dropping %@", keysToDrop); - [connections removeObjectsForKeys:keysToDrop]; - } -} -@end - -static HTTPConnectionFactory *theFactory = nil; - -@implementation HTTPConnectionFactory -+ (HTTPConnectionFactory *)theFactory { - if (theFactory == nil) { - theFactory = [[super allocWithZone:NULL] init]; - } - return theFactory; -} - -/* Singleton recipe: */ -+ (id)allocWithZone:(NSZone *)zone { - return [[HTTPConnectionFactory theFactory] retain]; -} -- (id)copyWithZone:(NSZone *)zone { - return self; -} - -- (id)init { - if (self = [super init]) { - lock = [[NSLock alloc] init]; - [lock setName:@"HTTPConnectionFactory lock"]; - connectionMapsByThreadId = [[NSMutableDictionary alloc] init]; - maxConnectionLifetime = DEFAULT_MAX_HTTPCONNECTION_LIFETIME_SECONDS; - [NSThread detachNewThreadSelector:@selector(dropUnusableConnections) toTarget:self withObject:nil]; - } - return self; -} -- (void)dealloc { - [lock release]; - [connectionMapsByThreadId release]; - [super dealloc]; -} -- (id )newHTTPConnectionToURL:(NSURL *)theURL method:(NSString *)theMethod httpTimeoutSetting:(HTTPTimeoutSetting *)theHTTPTimeoutSetting httpConnectionDelegate:(id )theHTTPConnectionDelegate { - id ret = nil; - void *pthreadPtr = pthread_self(); -#ifdef __LP64__ - NSNumber *threadID = [NSNumber numberWithUnsignedLongLong:(uint64_t)pthreadPtr]; -#else - NSNumber *threadID = [NSNumber numberWithUnsignedLong:(uint32_t)pthreadPtr]; -#endif - [lock lock]; - ConnectionMap *connMap = [connectionMapsByThreadId objectForKey:threadID]; - if (connMap == nil) { - connMap = [[ConnectionMap alloc] init]; - [connectionMapsByThreadId setObject:connMap forKey:threadID]; - [connMap release]; - } - ret = [connMap newConnectionToURL:theURL method:theMethod maxConnectionLifetime:maxConnectionLifetime httpTimeoutSetting:theHTTPTimeoutSetting httpConnectionDelegate:theHTTPConnectionDelegate]; - [lock unlock]; - return ret; -} - -#pragma mark cleanup thread -- (void)dropUnusableConnections { - [self retain]; - for (;;) { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - [NSThread sleepForTimeInterval:CLEANUP_THREAD_SLEEP_SECONDS]; - [lock lock]; - for (ConnectionMap *connMap in [connectionMapsByThreadId allValues]) { - @try { - [connMap dropUnusableConnections:maxConnectionLifetime]; - } @catch(NSException *e) { - HSLogError(@"unexpected exception in HTTPConnectionFactory cleanup thread: %@", [e description]); - } - } - [lock unlock]; - [pool drain]; - } -} -@end diff --git a/http/HTTPTimeoutSetting.h b/http/HTTPTimeoutSetting.h deleted file mode 100644 index 9e067a9..0000000 --- a/http/HTTPTimeoutSetting.h +++ /dev/null @@ -1,16 +0,0 @@ -// -// HTTPTimeoutSetting.h -// Arq -// -// Created by Stefan Reitshamer on 4/6/12. -// Copyright 2012 __MyCompanyName__. All rights reserved. -// - - -@interface HTTPTimeoutSetting : NSObject { - NSTimeInterval timeoutSeconds; -} -- (id)init; -- (id)initWithTimeoutSeconds:(NSTimeInterval)theTimeoutSeconds; -- (NSTimeInterval)timeoutSeconds; -@end diff --git a/http/HTTPTimeoutSetting.m b/http/HTTPTimeoutSetting.m deleted file mode 100644 index 24c42dd..0000000 --- a/http/HTTPTimeoutSetting.m +++ /dev/null @@ -1,35 +0,0 @@ -// -// HTTPTimeoutSetting.m -// Arq -// -// Created by Stefan Reitshamer on 4/6/12. -// Copyright 2012 __MyCompanyName__. All rights reserved. -// - -#import "HTTPTimeoutSetting.h" - -#define DEFAULT_TIMEOUT_SECONDS 30.0 - -@implementation HTTPTimeoutSetting -- (id)init { - self = [super init]; - return self; -} -- (id)initWithTimeoutSeconds:(NSTimeInterval)theTimeoutSeconds { - if (self = [super init]) { - timeoutSeconds = theTimeoutSeconds; - } - return self; -} -- (NSTimeInterval)timeoutSeconds { - NSTimeInterval ret = timeoutSeconds; - if (ret == 0) { - [[NSUserDefaults standardUserDefaults] synchronize]; - ret = (NSTimeInterval)[[NSUserDefaults standardUserDefaults] doubleForKey:@"HTTPTimeoutSeconds"]; - } - if (ret == 0) { - ret = DEFAULT_TIMEOUT_SECONDS; - } - return ret; -} -@end diff --git a/http/URLConnection.h b/http/URLConnection.h deleted file mode 100644 index d534bee..0000000 --- a/http/URLConnection.h +++ /dev/null @@ -1,42 +0,0 @@ -// -// URLConnection.h -// Arq -// -// Created by Stefan Reitshamer on 5/3/11. -// Copyright 2011 __MyCompanyName__. All rights reserved. -// - - -@class RFC2616DateFormatter; -#import "HTTPConnection.h" - -#define THROTTLE_NONE 0 -#define THROTTLE_AUTOMATIC 1 -#define THROTTLE_FIXED 2 - -@interface URLConnection : NSObject { - NSString *method; - id delegate; - NSMutableURLRequest *mutableURLRequest; - NSURLConnection *urlConnection; - NSHTTPURLResponse *httpURLResponse; - RFC2616DateFormatter *dateFormatter; - BOOL complete; - unsigned long long totalSent; - NSData *receivedData; - NSUInteger offset; - NSUInteger totalReceived; - NSError *_error; - BOOL responseHasContentLength; - NSUInteger contentLength; - NSTimeInterval lastSentTime; -} -+ (NSString *)errorDomain; - -- (id)initWithURL:(NSURL *)theURL method:(NSString *)theMethod delegate:(id)theDelegate; -@end - -@interface NSObject (URLConnectionDelegate) -- (void)urlConnection:(URLConnection *)theURLConnection sentBytes:(unsigned long long)sent throttleType:(int *)theThrottleType throttleKBPS:(int *)theThrottleKBPS pauseRequested:(BOOL *)isPauseRequested abortRequested:(BOOL *)isAbortRequested; -- (void)urlConnection:(URLConnection *)theURLConnection subtractSentBytes:(unsigned long long)sent; -@end diff --git a/http/URLConnection.m b/http/URLConnection.m deleted file mode 100644 index 9d57ab4..0000000 --- a/http/URLConnection.m +++ /dev/null @@ -1,276 +0,0 @@ -// -// URLConnection.m -// Arq -// -// Created by Stefan Reitshamer on 5/3/11. -// Copyright 2011 __MyCompanyName__. All rights reserved. -// - -#import "URLConnection.h" -#import "RFC2616DateFormatter.h" -#import "InputStream.h" -#import "RegexKitLite.h" -#import "NSData-InputStream.h" -#import "ChunkedInputStream.h" -#import "SetNSError.h" -#import "DataInputStream.h" -#import "BufferedInputStream.h" -#import "InputStreams.h" -#import "NSErrorCodes.h" -#import "NSError_extra.h" -#import "Streams.h" - - -static NSString *RUN_LOOP_MODE = @"HTTPConnectionRunLoopMode"; - -@interface NSURLRequest (privateInterface) -+ (void)setAllowsAnyHTTPSCertificate:(BOOL)allow forHost:(NSString *)host; -@end - -@interface URLConnection (internal) -- (void)subtractBytes; -@end - -@implementation URLConnection -+ (NSString *)errorDomain { - return @"URLConnectionErrorDomain"; -} - -- (id)initWithURL:(NSURL *)theURL method:(NSString *)theMethod delegate:(id)theDelegate { - if (self = [super init]) { - // Don't retain the delegate. - delegate = theDelegate; - method = [theMethod retain]; - mutableURLRequest = [[NSMutableURLRequest alloc] initWithURL:theURL]; - [mutableURLRequest setHTTPMethod:theMethod]; - [NSURLRequest setAllowsAnyHTTPSCertificate:YES forHost:[theURL host]]; - dateFormatter = [[RFC2616DateFormatter alloc] init]; - HSLogTrace(@"%@ %@", theMethod, theURL); - } - return self; -} -- (void)dealloc { - [method release]; - [urlConnection unscheduleFromRunLoop:[NSRunLoop currentRunLoop] forMode:RUN_LOOP_MODE]; - [mutableURLRequest release]; - [urlConnection release]; - [httpURLResponse release]; - [dateFormatter release]; - [super dealloc]; -} -- (void)setRequestHeader:(NSString *)value forKey:(NSString *)key { - HSLogTrace(@"request header %@ = %@", key, value); - [mutableURLRequest setValue:value forHTTPHeaderField:key]; -} -- (void)setRequestHostHeader { - [self setRequestHeader:[[mutableURLRequest URL] host] forKey:@"Host"]; -} -- (void)setRequestContentDispositionHeader:(NSString *)downloadName { - if (downloadName != nil) { - NSString *encodedFilename = [NSString stringWithFormat:@"\"%@\"", [downloadName stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\\\""]]; - encodedFilename = [encodedFilename stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"]; - NSString *contentDisposition = [NSString stringWithFormat:@"attachment;filename=%@", encodedFilename]; - [self setRequestHeader:contentDisposition forKey:@"Content-Disposition"]; - } -} -- (void)setRFC822DateRequestHeader { - [self setRequestHeader:[dateFormatter rfc2616StringFromDate:[NSDate date]] forKey:@"Date"]; -} -- (NSString *)requestMethod { - return [mutableURLRequest HTTPMethod]; -} -- (NSString *)requestPathInfo { - return [[[mutableURLRequest URL] path] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; -} -- (NSString *)requestQueryString { - return [[mutableURLRequest URL] query]; -} -- (NSArray *)requestHeaderKeys { - return [[mutableURLRequest allHTTPHeaderFields] allKeys]; -} -- (NSString *)requestHeaderForKey:(NSString *)theKey { - return [[mutableURLRequest allHTTPHeaderFields] objectForKey:theKey]; -} -- (BOOL)executeRequest:(NSError **)error { - return [self executeRequestWithBody:nil error:error]; -} -- (BOOL)executeRequestWithBody:(NSData *)theBody error:(NSError **)error { - if (theBody != nil) { - [mutableURLRequest setHTTPBody:theBody]; - } - totalSent = 0; - NSAssert(urlConnection == nil, @"can't call this method more than once!"); - urlConnection = [[NSURLConnection alloc] initWithRequest:mutableURLRequest delegate:self startImmediately:NO]; - [urlConnection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:RUN_LOOP_MODE]; - [urlConnection start]; - return YES; -} -- (int)responseCode { - return [httpURLResponse statusCode]; -} -- (NSString *)responseHeaderForKey:(NSString *)key { - return [[httpURLResponse allHeaderFields] objectForKey:key]; -} -- (NSString *)responseContentType { - return [self responseHeaderForKey:@"Content-Type"]; -} -- (NSString *)responseDownloadName { - NSString *downloadName = nil; - NSString *contentDisposition = [self responseHeaderForKey:@"Content-Disposition"]; - if (contentDisposition != nil) { - NSRange filenameRange = [contentDisposition rangeOfRegex:@"attachment;filename=(.+)" capture:1]; - if (filenameRange.location != NSNotFound) { - downloadName = [contentDisposition substringWithRange:filenameRange]; - } - } - return downloadName; -} -- (id )newResponseBodyStream:(NSError **)error { - while (!complete && offset >= [receivedData length]) { - [[NSRunLoop currentRunLoop] runMode:RUN_LOOP_MODE beforeDate:[NSDate distantFuture]]; - } - if (_error) { - if (error != NULL) { - *error = _error; - } - [self subtractBytes]; - return nil; - } - - id ret = nil; - if ([method isEqualToString:@"HEAD"] && complete) { - HSLogTrace(@"%@: empty response body", self); - ret = [[NSData data] newInputStream]; - } else { - NSAssert(httpURLResponse != nil, @"httpURLResponse can't be nil"); - NSString *transferEncoding = [self responseHeaderForKey:@"Transfer-Encoding"]; - HSLogTrace(@"Content-Length = %@", [self responseHeaderForKey:@"Content-Length"]); - HSLogTrace(@"Transfer-Encoding = %@", transferEncoding); - if (transferEncoding != nil && ![transferEncoding isEqualToString:@"Identity"]) { - if ([[transferEncoding lowercaseString] isEqualToString:@"chunked"]) { - HSLogTrace(@"%@: chunked response body", self); - id underlying = self; - BufferedInputStream *bis = [[BufferedInputStream alloc] initWithUnderlyingStream:underlying]; - ret = [[ChunkedInputStream alloc] initWithUnderlyingStream:bis]; - [bis release]; - } else { - SETNSERROR(@"StreamErrorDomain", -1, @"unknown Transfer-Encoding '%@'", transferEncoding); - [self subtractBytes]; - return nil; - } - } else { - /* - * FIXME: handle multipart/byteranges media type. - * See rfc2616 section 4.4 ("message length"). - */ - HSLogTrace(@"%@: response body with no content-length", self); - ret = [self retain]; - } - } - return ret; -} - -#pragma mark InputStream -- (NSInteger)read:(unsigned char *)buf bufferLength:(NSUInteger)length error:(NSError **)error { - NSInteger recvd = 0; - while (!complete && offset >= [receivedData length]) { - [[NSRunLoop currentRunLoop] runMode:RUN_LOOP_MODE beforeDate:[NSDate distantFuture]]; - } - if (_error) { - if (error != NULL) { - *error = _error; - } - [self subtractBytes]; - return -1; - } - - NSUInteger bytesRemaining = [receivedData length] - offset; - recvd = (length < bytesRemaining) ? length : bytesRemaining; - totalReceived += recvd; - memcpy(buf, (unsigned char *)[receivedData bytes] + offset, recvd); - offset += recvd; - if (offset == [receivedData length]) { - [receivedData release]; - receivedData = nil; - offset = 0; - } - HSLogTrace(@"received %ld bytes", recvd); - - return recvd; -} -- (NSData *)slurp:(NSError **)error { - NSMutableData *ret = [NSMutableData data]; - - if (receivedData != nil) { - [ret appendData:receivedData]; - [receivedData release]; - receivedData = nil; - offset = 0; - } - for (;;) { - while (!complete && offset >= [receivedData length]) { - [[NSRunLoop currentRunLoop] runMode:RUN_LOOP_MODE beforeDate:[NSDate distantFuture]]; - } - if (_error) { - if (error != NULL) { *error = _error; } - return nil; - } - if (receivedData != nil) { - [ret appendData:receivedData]; - [receivedData release]; - receivedData = nil; - } - if (complete) { - break; - } - } - return ret; -} - -#pragma mark NSURLConnection delegate -- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)myError { - HSLogDebug(@"%@ %@: %@", method, [mutableURLRequest URL], myError); - [_error release]; - _error = [myError retain]; - complete = YES; -} -- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { - NSAssert(receivedData == nil, @"must not discard unread bytes"); - HSLogTrace(@"received %lu bytes", [data length]); - [receivedData release]; - receivedData = [data retain]; - offset = 0; -} -- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { - if ([response isKindOfClass:[NSHTTPURLResponse class]]) { - [httpURLResponse release]; - httpURLResponse = (NSHTTPURLResponse *)[response retain]; - } -} -- (void)connection:(NSURLConnection *)connection didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite { - HSLogTrace(@"%@: sent so far %lu of %lu", self, totalBytesWritten, totalBytesExpectedToWrite); -} -- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse { - return cachedResponse; -} -- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse { - return request; -} -- (void)connectionDidFinishLoading:(NSURLConnection *)connection { - complete = YES; -} - -#pragma mark NSObject -- (NSString *)description { - return [NSString stringWithFormat:@"", [mutableURLRequest URL]]; -} -@end - -@implementation URLConnection (internal) -- (void)subtractBytes { - if (totalSent > 0 && [delegate respondsToSelector:@selector(urlConnection:subtractSentBytes:)]) { - [delegate urlConnection:self subtractSentBytes:totalSent]; - totalSent = 0; - } -} -@end diff --git a/io/CryptInputStream.h b/io/CryptInputStream.h deleted file mode 100644 index 5deb344..0000000 --- a/io/CryptInputStream.h +++ /dev/null @@ -1,66 +0,0 @@ -/* - Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - - -#include -#import "InputStream.h" -@class CryptoKey; - -typedef int (*CryptInitFunc)(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type, unsigned char *key, unsigned char *iv); -typedef int (*CryptUpdateFunc)(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl, unsigned char *in, int inl); -typedef int (*CryptFinalFunc)(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl); - -@interface CryptInputStream : NSObject { - CryptInitFunc cryptInit; - CryptUpdateFunc cryptUpdate; - CryptFinalFunc cryptFinal; - id is; - NSString *label; - unsigned char *inBuf; - NSUInteger inBufSize; - unsigned char *outBuf; - NSInteger outBufLen; - NSUInteger outBufSize; - NSUInteger outBufPos; - EVP_CIPHER_CTX cipherContext; - size_t blockSize; - BOOL initialized; - BOOL finalized; -} -- (id)initWithCryptInitFunc:(void *)theCryptInit - cryptUpdateFunc:(void *)theCryptUpdate - cryptFinalFunc:(void *)theCryptFinal - inputStream:(id )theIS - cryptoKey:(CryptoKey *)theCryptoKey - label:(NSString *)theLabel - error:(NSError **)error; -@end diff --git a/io/CryptInputStream.m b/io/CryptInputStream.m deleted file mode 100644 index c031aae..0000000 --- a/io/CryptInputStream.m +++ /dev/null @@ -1,161 +0,0 @@ -/* - Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - - - -#import "CryptInputStream.h" -#import "SetNSError.h" -#import "OpenSSL.h" -#import "InputStreams.h" -#import "NSErrorCodes.h" -#import "CryptoKey.h" -#import "Encryption.h" - -#define MY_BUF_SIZE (4096) - -@interface CryptInputStream (internal) -- (BOOL)fillOutBuf:(NSError **)error; -@end - -@implementation CryptInputStream -- (id)initWithCryptInitFunc:(void *)theCryptInit - cryptUpdateFunc:(void *)theCryptUpdate - cryptFinalFunc:(void *)theCryptFinal - inputStream:(id )theIS - cryptoKey:(CryptoKey *)theCryptoKey - label:(NSString *)theLabel - error:(NSError **)error { - if (self = [super init]) { - label = [theLabel retain]; - cryptInit = (CryptInitFunc)theCryptInit; - cryptUpdate = (CryptUpdateFunc)theCryptUpdate; - cryptFinal = (CryptFinalFunc)theCryptFinal; - BOOL ret = NO; - do { - is = [theIS retain]; - EVP_CIPHER_CTX_init(&cipherContext); - if (!(*cryptInit)(&cipherContext, [theCryptoKey cipher], [theCryptoKey evpKey], [theCryptoKey iv])) { - SETNSERROR([Encryption errorDomain], -1, @"%@ initialization error: %@", label, [OpenSSL errorMessage]); - break; - } - EVP_CIPHER_CTX_set_key_length(&cipherContext, EVP_MAX_KEY_LENGTH); - blockSize = (unsigned long long)EVP_CIPHER_CTX_block_size(&cipherContext); - inBufSize = MY_BUF_SIZE; - inBuf = (unsigned char *)malloc(inBufSize); - outBufSize = MY_BUF_SIZE + blockSize - 1; - outBuf = (unsigned char *)malloc(outBufSize); - initialized = YES; - ret = YES; - } while(0); - if (!ret) { - [self release]; - self = nil; - } - } - return self; -} -- (void)dealloc { - [label release]; - if (initialized) { - EVP_CIPHER_CTX_cleanup(&cipherContext); - } - if (inBuf != NULL) { - free(inBuf); - } - if (outBuf != NULL) { - free(outBuf); - } - [is release]; - [super dealloc]; -} -- (BOOL)cryptUpdate:(int *)outLen inBuf:(unsigned char *)inBuf inLen:(NSUInteger)inLen { - @throw [NSException exceptionWithName:@"PureVirtualMethod" reason:@"don't call this" userInfo:nil]; -} -- (BOOL)cryptFinal:(int *)outLen { - @throw [NSException exceptionWithName:@"PureVirtualMethod" reason:@"don't call this" userInfo:nil]; -} - -#pragma mark InputStream -- (NSInteger)read:(unsigned char *)buf bufferLength:(NSUInteger)bufferLength error:(NSError **)error { - while (outBufPos >= outBufLen && !finalized) { - if (![self fillOutBuf:error]) { - return -1; - } - } - NSUInteger available = outBufLen - outBufPos; - NSUInteger ret = 0; - if (available > 0) { - NSUInteger toCopy = available > bufferLength ? bufferLength : available; - memcpy(buf, outBuf + outBufPos, toCopy); - outBufPos += toCopy; - ret = toCopy; - } - return ret; -} -- (NSData *)slurp:(NSError **)error { - return [InputStreams slurp:self error:error]; -} -@end - -@implementation CryptInputStream (internal) -- (BOOL)fillOutBuf:(NSError **)error { - if (finalized) { - SETNSERROR([Encryption errorDomain], ERROR_EOF, @"EOF"); - return NO; - } - outBufLen = 0; - outBufPos = 0; - NSInteger recvd = [is read:inBuf bufferLength:inBufSize error:error]; - if (recvd == -1) { - return NO; - } - if (recvd == 0) { - finalized = YES; - int theBufLen = 0; - if (!(cryptFinal)(&cipherContext, outBuf, &theBufLen)) { - SETNSERROR([Encryption errorDomain], -1, @"%@ error: %@", label, [OpenSSL errorMessage]); - return NO; - } - HSLogTrace(@"%@ final: outBufLen = %ld", label, (NSInteger)outBufLen); - outBufLen = (NSInteger)theBufLen; - } else { - int theBufLen = 0; - if (!(*cryptUpdate)(&cipherContext, outBuf, &theBufLen, inBuf, recvd)) { - SETNSERROR([Encryption errorDomain], -1, @"%@ error: %@", label, [OpenSSL errorMessage]); - return NO; - } - HSLogTrace(@"%@ update: inBufLen = %ld, outBufLen = %ld", label, recvd, (NSInteger)outBufLen); - outBufLen = (NSInteger)theBufLen; - } - return YES; -} -@end diff --git a/io/CryptoKey.m b/io/CryptoKey.m deleted file mode 100644 index edded61..0000000 --- a/io/CryptoKey.m +++ /dev/null @@ -1,84 +0,0 @@ -// -// CryptoKey.m -// Arq -// -// Created by Stefan Reitshamer on 6/9/11. -// Copyright 2011 __MyCompanyName__. All rights reserved. -// - -#import "CryptoKey.h" -#import "SetNSError.h" -#import "OpenSSL.h" -#import "Encryption.h" - -#define ITERATIONS (1000) -#define KEYLEN (48) - -@implementation CryptoKey -+ (NSString *)errorDomain { - return @"CryptoKeyErrorDomain"; -} - -- (id)init { - @throw [NSException exceptionWithName:@"InvalidInitializerException" reason:@"can't call CryptoKey init" userInfo:nil]; -} -- (id)initWithPassword:(NSString *)thePassword salt:(NSData *)theSalt error:(NSError **)error { - if (self = [super init]) { - if (![OpenSSL initializeSSL:error]) { - [self release]; - return nil; - } - if ([thePassword length] == 0) { - SETNSERROR([Encryption errorDomain], -1, @"missing encryption password"); - [self release]; - return nil; - } - if (theSalt != nil && [theSalt length] != 8) { - SETNSERROR([Encryption errorDomain], -1, @"salt must be 8 bytes or nil"); - [self release]; - return nil; - } - cipher = EVP_aes_256_cbc(); - const char *cPassword = [thePassword UTF8String]; - unsigned char *cSaltCopy = NULL; - if (theSalt != nil) { - cSaltCopy = (unsigned char *)malloc([theSalt length]); - memcpy(cSaltCopy, [theSalt bytes], [theSalt length]); - } else { - HSLogWarn(@"NULL salt value for CryptoKey"); - } - unsigned char buf[KEYLEN]; - memset(buf, 0, KEYLEN); - PKCS5_PBKDF2_HMAC_SHA1(cPassword, strlen(cPassword), cSaltCopy, [theSalt length], ITERATIONS, KEYLEN, buf); - evpKey[0] = 0; - EVP_BytesToKey(cipher, EVP_sha1(), cSaltCopy, buf, KEYLEN, ITERATIONS, evpKey, iv); - if (cSaltCopy != NULL) { - free(cSaltCopy); - } - } - return self; -} -- (id)initLegacyWithPassword:(NSString *)thePassword error:(NSError **)error { - if (self = [super init]) { - if (![OpenSSL initializeSSL:error]) { - [self release]; - return nil; - } - - cipher = EVP_aes_256_cbc(); - evpKey[0] = 0; - NSData *passwordData = [thePassword dataUsingEncoding:NSUTF8StringEncoding]; - EVP_BytesToKey(cipher, EVP_md5(), NULL, [passwordData bytes], [passwordData length], 1, evpKey, iv); - } - return self; -} -- (const EVP_CIPHER *)cipher { - return cipher; -} -- (unsigned char *)evpKey { - return evpKey; -} -- (unsigned char *)iv { - return iv; -} -@end diff --git a/io/DataInputStreamFactory.h b/io/DataInputStreamFactory.h deleted file mode 100644 index 3df74b9..0000000 --- a/io/DataInputStreamFactory.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - - - - -#import "InputStreamFactory.h" - -@interface DataInputStreamFactory : NSObject { - NSData *data; - NSString *dataDescription; -} -- (id)initWithData:(NSData *)theData dataDescription:(NSString *)theDataDescription; -@end diff --git a/io/DecryptedInputStream.h b/io/DecryptedInputStream.h deleted file mode 100644 index 30d90b8..0000000 --- a/io/DecryptedInputStream.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - - - - -#import "CryptInputStream.h" -@class CryptoKey; - -@interface DecryptedInputStream : CryptInputStream { -} -- (id)initWithInputStream:(id )theIS cryptoKey:(CryptoKey *)theCryptoKey error:(NSError **)error; -@end diff --git a/io/DecryptedInputStream.m b/io/DecryptedInputStream.m deleted file mode 100644 index 4e0a805..0000000 --- a/io/DecryptedInputStream.m +++ /dev/null @@ -1,46 +0,0 @@ -/* - Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - - - -#import "DecryptedInputStream.h" -#include - -@implementation DecryptedInputStream -- (id)initWithInputStream:(id )theIS cryptoKey:(CryptoKey *)theCryptoKey error:(NSError **)error { - self = [super initWithCryptInitFunc:&EVP_DecryptInit cryptUpdateFunc:&EVP_DecryptUpdate cryptFinalFunc:&EVP_DecryptFinal inputStream:theIS cryptoKey:theCryptoKey label:@"decrypt" error:error]; - return self; -} -- (NSString *)description { - return [NSString stringWithFormat:@"", is]; -} -@end diff --git a/io/EncryptedInputStream.h b/io/EncryptedInputStream.h deleted file mode 100644 index 53eaa30..0000000 --- a/io/EncryptedInputStream.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - - - - -#import "CryptInputStream.h" -@class CryptoKey; - -@interface EncryptedInputStream : CryptInputStream { -} -- (id)initWithInputStream:(id )theIS cryptoKey:(CryptoKey *)theCryptoKey error:(NSError **)error; -@end diff --git a/io/EncryptedInputStream.m b/io/EncryptedInputStream.m deleted file mode 100644 index 6cca750..0000000 --- a/io/EncryptedInputStream.m +++ /dev/null @@ -1,48 +0,0 @@ -/* - Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - - - -#import "EncryptedInputStream.h" -#import - -@implementation EncryptedInputStream -- (id)initWithInputStream:(id )theIS cryptoKey:(CryptoKey *)theCryptoKey error:(NSError **)error { - self = [super initWithCryptInitFunc:&EVP_EncryptInit cryptUpdateFunc:&EVP_EncryptUpdate cryptFinalFunc:&EVP_EncryptFinal inputStream:theIS cryptoKey:theCryptoKey label:@"encrypt" error:error]; - return self; -} - -#pragma mark NSObject -- (NSString *)description { - return [NSString stringWithFormat:@"", is]; -} -@end diff --git a/io/EncryptedInputStreamFactory.h b/io/EncryptedInputStreamFactory.h deleted file mode 100644 index 3f154a6..0000000 --- a/io/EncryptedInputStreamFactory.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - - - - -#import "InputStreamFactory.h" -@class CryptoKey; - -@interface EncryptedInputStreamFactory : NSObject { - CryptoKey *cryptoKey; - id underlyingFactory; -} -- (id)initWithCryptoKey:(CryptoKey *)theCryptoKey underlyingFactory:(id )theUnderlyingFactory; -@end diff --git a/io/EncryptedInputStreamFactory.m b/io/EncryptedInputStreamFactory.m deleted file mode 100644 index db11d23..0000000 --- a/io/EncryptedInputStreamFactory.m +++ /dev/null @@ -1,69 +0,0 @@ -/* - Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - - - -#import "EncryptedInputStreamFactory.h" -#import "EncryptedInputStream.h" -#import "NSData-Encrypt.h" - -@implementation EncryptedInputStreamFactory -- (id)initWithCryptoKey:(CryptoKey *)theCryptoKey underlyingFactory:(id )theUnderlyingFactory { - if (self = [super init]) { - cryptoKey = [theCryptoKey retain]; - underlyingFactory = [theUnderlyingFactory retain]; - } - return self; -} -- (void)dealloc { - [cryptoKey release]; - [underlyingFactory release]; - [super dealloc]; -} - -#pragma mark InputStreamFactory -- (id ) newInputStream { - id underlying = [underlyingFactory newInputStream]; - NSError *error = nil; - EncryptedInputStream *eis = [[EncryptedInputStream alloc] initWithInputStream:underlying cryptoKey:cryptoKey error:&error]; - [underlying release]; - if (eis == nil) { - @throw [NSException exceptionWithName:@"EncryptedInputStreamInitException" reason:[error localizedDescription] userInfo:nil]; - } - return eis; -} - -#pragma mark NSObject -- (NSString *)description { - return [NSString stringWithFormat:@"", [underlyingFactory description]]; -} -@end diff --git a/io/FileInputStreamFactory.h b/io/FileInputStreamFactory.h deleted file mode 100644 index a0bca75..0000000 --- a/io/FileInputStreamFactory.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - - - - -#import "InputStreamFactory.h" - -@interface FileInputStreamFactory : NSObject { - NSString *path; - unsigned long long offset; - unsigned long long length; -} -- (id)initWithPath:(NSString *)thePath offset:(unsigned long long)theOffset length:(unsigned long long)theLength; -- (id)initWithPath:(NSString *)thePath error:(NSError **)error; -@end diff --git a/io/FileInputStreamFactory.m b/io/FileInputStreamFactory.m deleted file mode 100644 index eff2e45..0000000 --- a/io/FileInputStreamFactory.m +++ /dev/null @@ -1,71 +0,0 @@ -/* - Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - - - -#include -#import "FileInputStreamFactory.h" -#import "FileInputStream.h" -#import "SetNSError.h" - -@implementation FileInputStreamFactory -- (id)initWithPath:(NSString *)thePath offset:(unsigned long long)theOffset length:(unsigned long long)theLength { - if (self = [super init]) { - path = [thePath copy]; - offset = theOffset; - length = theLength; - } - return self; -} -- (id)initWithPath:(NSString *)thePath error:(NSError **)error { - struct stat st; - if (lstat([thePath fileSystemRepresentation], &st) == -1) { - int errnum = errno; - HSLogError(@"lstat(%@) error %d: %s", thePath, errnum, strerror(errnum)); - SETNSERROR(@"UnixErrorDomain", errnum, @"%@: %s", thePath, strerror(errnum)); - return nil; - } - return [self initWithPath:thePath offset:0 length:(unsigned long long)st.st_size]; -} -- (void)dealloc { - [path release]; - [super dealloc]; -} -- (id ) newInputStream { - return [[FileInputStream alloc] initWithPath:path offset:offset length:length]; -} - -#pragma mark NSObject -- (NSString *)description { - return [NSString stringWithFormat:@"", path, offset, length, (offset+length)]; -} -@end diff --git a/io/FixedLengthInputStream.h b/io/FixedLengthInputStream.h deleted file mode 100644 index 57190fa..0000000 --- a/io/FixedLengthInputStream.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - - - - -#import "InputStream.h" -@class BufferedInputStream; - -@interface FixedLengthInputStream : NSObject { - BufferedInputStream *underlyingStream; - unsigned long long fixedLength; - unsigned long long totalReceived; -} -- (id)initWithUnderlyingStream:(BufferedInputStream *)is length:(unsigned long long)theLength; -@end diff --git a/io/FixedLengthInputStream.m b/io/FixedLengthInputStream.m deleted file mode 100644 index d2cd9d3..0000000 --- a/io/FixedLengthInputStream.m +++ /dev/null @@ -1,79 +0,0 @@ -/* - Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - - - -#import "FixedLengthInputStream.h" -#import "InputStreams.h" -#import "SetNSError.h" -#import "NSErrorCodes.h" -#import "BufferedInputStream.h" - -@implementation FixedLengthInputStream -- (id)initWithUnderlyingStream:(BufferedInputStream *)is length:(unsigned long long)theLength { - if (self = [super init]) { - underlyingStream = [is retain]; - fixedLength = theLength; - } - return self; -} -- (void)dealloc { - [underlyingStream release]; - [super dealloc]; -} - -#pragma mark InputStream -- (NSInteger)read:(unsigned char *)buf bufferLength:(NSUInteger)bufferLength error:(NSError **)error { - NSInteger ret = 0; - unsigned long long remaining = fixedLength - totalReceived; - if (remaining > 0) { - NSUInteger toRead = remaining; - if (toRead > bufferLength) { - toRead = bufferLength; - } - ret = [underlyingStream read:buf bufferLength:toRead error:error]; - if (ret < 0) { - return -1; - } - } - totalReceived += ret; - return ret; -} -- (NSData *)slurp:(NSError **)error { - return [InputStreams slurp:self error:error]; -} - -#pragma mark NSObject -- (NSString *)description { - return [NSString stringWithFormat:@"", fixedLength, underlyingStream]; -} -@end diff --git a/io/InputStreamFactory.h b/io/InputStreamFactory.h deleted file mode 100644 index 070ce99..0000000 --- a/io/InputStreamFactory.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - - - - -#import "InputStream.h" - -@protocol InputStreamFactory - -// Returns an object that must be released by the caller. -- (id ) newInputStream; -@end diff --git a/io/MonitoredInputStream.h b/io/MonitoredInputStream.h deleted file mode 100644 index c541bcc..0000000 --- a/io/MonitoredInputStream.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - Copyright (c) 2009-2010, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - - -#import "InputStream.h" - -@interface MonitoredInputStream : NSObject { - id underlyingStream; - unsigned long long bytesReceived; - id delegate; -} -- (id)initWithUnderlyingStream:(id )theUnderlyingStream delegate:(id)theDelegate; -@end - -@interface NSObject (MonitoredInputStreamDelegate) -- (BOOL)monitoredInputStream:(MonitoredInputStream *)stream receivedBytes:(unsigned long long)length error:(NSError **)error; -@end diff --git a/io/MonitoredInputStream.m b/io/MonitoredInputStream.m deleted file mode 100644 index 5baee8e..0000000 --- a/io/MonitoredInputStream.m +++ /dev/null @@ -1,71 +0,0 @@ -/* - Copyright (c) 2009-2010, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#import "MonitoredInputStream.h" -#import "SetNSError.h" - -@implementation MonitoredInputStream -- (id)initWithUnderlyingStream:(id )theUnderlyingStream delegate:(id)theDelegate { - if (self = [super init]) { - underlyingStream = [theUnderlyingStream retain]; - delegate = [theDelegate retain]; - NSNotification *notif = [NSNotification notificationWithName:@"MonitoredInputStreamCreated" object:nil]; - [[NSNotificationCenter defaultCenter] performSelectorOnMainThread:@selector(postNotification:) withObject:notif waitUntilDone:NO]; - } - return self; -} -- (void)dealloc { - [underlyingStream release]; - [delegate release]; - [super dealloc]; -} - -#pragma mark InputStream -- (NSInteger)read:(unsigned char *)buf bufferLength:(NSUInteger)bufferLength error:(NSError **)error { - NSInteger ret = [underlyingStream read:buf bufferLength:bufferLength error:error]; - if (ret < 0) { - return ret; - } - bytesReceived += (unsigned long long)ret; - return ret; -} -- (NSData *)slurp:(NSError **)error { - NSData *data = [underlyingStream slurp:error]; - if (data != nil) { - if (![delegate monitoredInputStream:self receivedBytes:(unsigned long long)[data length] error:error]) { - data = nil; - } - bytesReceived += (unsigned long long)[data length]; - } - return data; -} -@end diff --git a/io/StreamPair.h b/io/StreamPair.h deleted file mode 100644 index 2e8a386..0000000 --- a/io/StreamPair.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - - -#import "InputStream.h" -#import "OutputStream.h" - -@protocol StreamPair -- (void)setCloseRequested; -- (BOOL)isUsable; - -@end diff --git a/io/Writer.h b/io/Writer.h deleted file mode 100644 index 7af4f25..0000000 --- a/io/Writer.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - Copyright (c) 2009-2010, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - - -#import "OutputStream.h" - -@interface Writer : NSObject { - id os; -} -- (id)initWithOutputStream:(id )theOS; -- (BOOL)write:(NSString *)text error:(NSError **)error; -@end diff --git a/io/Writer.m b/io/Writer.m deleted file mode 100644 index cce0768..0000000 --- a/io/Writer.m +++ /dev/null @@ -1,52 +0,0 @@ -/* - Copyright (c) 2009-2010, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#import "Writer.h" - -@implementation Writer -- (id)initWithOutputStream:(id )theOS { - if (self = [super init]) { - os = [theOS retain]; - } - return self; -} -- (void)dealloc { - [os release]; - [super dealloc]; -} -- (BOOL)write:(NSString *)text error:(NSError **)error { - NSAssert(text != nil, @"text must not be nil"); - const char *utf8 = [text UTF8String]; - return [os write:(const unsigned char *)utf8 length:strlen(utf8) error:error]; -} - -@end diff --git a/libssh2/include/channel.h b/libssh2/include/channel.h new file mode 100644 index 0000000..dc0ee37 --- /dev/null +++ b/libssh2/include/channel.h @@ -0,0 +1,141 @@ +#ifndef __LIBSSH2_CHANNEL_H +#define __LIBSSH2_CHANNEL_H +/* Copyright (c) 2008-2010 by Daniel Stenberg + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +/* + * _libssh2_channel_receive_window_adjust + * + * Adjust the receive window for a channel by adjustment bytes. If the amount + * to be adjusted is less than LIBSSH2_CHANNEL_MINADJUST and force is 0 the + * adjustment amount will be queued for a later packet. + * + * Always non-blocking. + */ +int _libssh2_channel_receive_window_adjust(LIBSSH2_CHANNEL * channel, + uint32_t adjustment, + unsigned char force, + unsigned int *store); + +/* + * _libssh2_channel_flush + * + * Flush data from one (or all) stream + * Returns number of bytes flushed, or negative on failure + */ +int _libssh2_channel_flush(LIBSSH2_CHANNEL *channel, int streamid); + +/* + * _libssh2_channel_free + * + * Make sure a channel is closed, then remove the channel from the session + * and free its resource(s) + * + * Returns 0 on success, negative on failure + */ +int _libssh2_channel_free(LIBSSH2_CHANNEL *channel); + +int +_libssh2_channel_extended_data(LIBSSH2_CHANNEL *channel, int ignore_mode); + +/* + * _libssh2_channel_write + * + * Send data to a channel + */ +ssize_t +_libssh2_channel_write(LIBSSH2_CHANNEL *channel, int stream_id, + const unsigned char *buf, size_t buflen); + +/* + * _libssh2_channel_open + * + * Establish a generic session channel + */ +LIBSSH2_CHANNEL * +_libssh2_channel_open(LIBSSH2_SESSION * session, const char *channel_type, + uint32_t channel_type_len, + uint32_t window_size, + uint32_t packet_size, + const unsigned char *message, size_t message_len); + + +/* + * _libssh2_channel_process_startup + * + * Primitive for libssh2_channel_(shell|exec|subsystem) + */ +int +_libssh2_channel_process_startup(LIBSSH2_CHANNEL *channel, + const char *request, size_t request_len, + const char *message, size_t message_len); + +/* + * _libssh2_channel_read + * + * Read data from a channel + * + * It is important to not return 0 until the currently read channel is + * complete. If we read stuff from the wire but it was no payload data to fill + * in the buffer with, we MUST make sure to return PACKET_EAGAIN. + */ +ssize_t _libssh2_channel_read(LIBSSH2_CHANNEL *channel, int stream_id, + char *buf, size_t buflen); + +uint32_t _libssh2_channel_nextid(LIBSSH2_SESSION * session); + +LIBSSH2_CHANNEL *_libssh2_channel_locate(LIBSSH2_SESSION * session, + uint32_t channel_id); + +size_t _libssh2_channel_packet_data_len(LIBSSH2_CHANNEL * channel, + int stream_id); + +int _libssh2_channel_close(LIBSSH2_CHANNEL * channel); + +/* + * _libssh2_channel_forward_cancel + * + * Stop listening on a remote port and free the listener + * Toss out any pending (un-accept()ed) connections + * + * Return 0 on success, LIBSSH2_ERROR_EAGAIN if would block, -1 on error + */ +int _libssh2_channel_forward_cancel(LIBSSH2_LISTENER *listener); + +#endif /* __LIBSSH2_CHANNEL_H */ + diff --git a/libssh2/include/comp.h b/libssh2/include/comp.h new file mode 100644 index 0000000..8edc150 --- /dev/null +++ b/libssh2/include/comp.h @@ -0,0 +1,45 @@ +#ifndef __LIBSSH2_COMP_H +#define __LIBSSH2_COMP_H + +/* Copyright (C) 2009-2010 by Daniel Stenberg + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + */ + +#include "libssh2_priv.h" + +const LIBSSH2_COMP_METHOD **_libssh2_comp_methods(LIBSSH2_SESSION *session); + +#endif /* __LIBSSH2_COMP_H */ diff --git a/libssh2/include/crypto.h b/libssh2/include/crypto.h new file mode 100644 index 0000000..fb576b6 --- /dev/null +++ b/libssh2/include/crypto.h @@ -0,0 +1,120 @@ +/* Copyright (C) 2009, 2010 Simon Josefsson + * Copyright (C) 2006, 2007 The Written Word, Inc. All rights reserved. + * Copyright (C) 2010 Daniel Stenberg + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ +#ifndef LIBSSH2_CRYPTO_H +#define LIBSSH2_CRYPTO_H + +#ifdef LIBSSH2_LIBGCRYPT +#include "libgcrypt.h" +#else +#include "openssl.h" +#endif + +int _libssh2_rsa_new(libssh2_rsa_ctx ** rsa, + const unsigned char *edata, + unsigned long elen, + const unsigned char *ndata, + unsigned long nlen, + const unsigned char *ddata, + unsigned long dlen, + const unsigned char *pdata, + unsigned long plen, + const unsigned char *qdata, + unsigned long qlen, + const unsigned char *e1data, + unsigned long e1len, + const unsigned char *e2data, + unsigned long e2len, + const unsigned char *coeffdata, unsigned long coefflen); +int _libssh2_rsa_new_private(libssh2_rsa_ctx ** rsa, + LIBSSH2_SESSION * session, + const char *filename, + unsigned const char *passphrase); +int _libssh2_rsa_sha1_verify(libssh2_rsa_ctx * rsa, + const unsigned char *sig, + unsigned long sig_len, + const unsigned char *m, unsigned long m_len); +int _libssh2_rsa_sha1_sign(LIBSSH2_SESSION * session, + libssh2_rsa_ctx * rsactx, + const unsigned char *hash, + size_t hash_len, + unsigned char **signature, + size_t *signature_len); + +#if LIBSSH2_DSA +int _libssh2_dsa_new(libssh2_dsa_ctx ** dsa, + const unsigned char *pdata, + unsigned long plen, + const unsigned char *qdata, + unsigned long qlen, + const unsigned char *gdata, + unsigned long glen, + const unsigned char *ydata, + unsigned long ylen, + const unsigned char *x, unsigned long x_len); +int _libssh2_dsa_new_private(libssh2_dsa_ctx ** dsa, + LIBSSH2_SESSION * session, + const char *filename, + unsigned const char *passphrase); +int _libssh2_dsa_sha1_verify(libssh2_dsa_ctx * dsactx, + const unsigned char *sig, + const unsigned char *m, unsigned long m_len); +int _libssh2_dsa_sha1_sign(libssh2_dsa_ctx * dsactx, + const unsigned char *hash, + unsigned long hash_len, unsigned char *sig); +#endif + +int _libssh2_cipher_init(_libssh2_cipher_ctx * h, + _libssh2_cipher_type(algo), + unsigned char *iv, + unsigned char *secret, int encrypt); + +int _libssh2_cipher_crypt(_libssh2_cipher_ctx * ctx, + _libssh2_cipher_type(algo), + int encrypt, unsigned char *block, size_t blocksize); + +int _libssh2_pub_priv_keyfile(LIBSSH2_SESSION *session, + unsigned char **method, + size_t *method_len, + unsigned char **pubkeydata, + size_t *pubkeydata_len, + const char *privatekey, + const char *passphrase); + +void _libssh2_init_aes_ctr(void); + +#endif diff --git a/libssh2/include/libgcrypt.h b/libssh2/include/libgcrypt.h new file mode 100644 index 0000000..1f0276e --- /dev/null +++ b/libssh2/include/libgcrypt.h @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2008, 2009, 2010 Simon Josefsson + * Copyright (C) 2006, 2007, The Written Word, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include + +#define LIBSSH2_MD5 1 + +#define LIBSSH2_HMAC_RIPEMD 1 + +#define LIBSSH2_AES 1 +#define LIBSSH2_AES_CTR 1 +#define LIBSSH2_BLOWFISH 1 +#define LIBSSH2_RC4 1 +#define LIBSSH2_CAST 1 +#define LIBSSH2_3DES 1 + +#define LIBSSH2_RSA 1 +#define LIBSSH2_DSA 1 + +#define MD5_DIGEST_LENGTH 16 +#define SHA_DIGEST_LENGTH 20 + +#define _libssh2_random(buf, len) \ + (gcry_randomize ((buf), (len), GCRY_STRONG_RANDOM), 1) + +#define libssh2_sha1_ctx gcry_md_hd_t +#define libssh2_sha1_init(ctx) gcry_md_open (ctx, GCRY_MD_SHA1, 0); +#define libssh2_sha1_update(ctx, data, len) gcry_md_write (ctx, data, len) +#define libssh2_sha1_final(ctx, out) \ + memcpy (out, gcry_md_read (ctx, 0), SHA_DIGEST_LENGTH), gcry_md_close (ctx) +#define libssh2_sha1(message, len, out) \ + gcry_md_hash_buffer (GCRY_MD_SHA1, out, message, len) + +#define libssh2_md5_ctx gcry_md_hd_t + +/* returns 0 in case of failure */ +#define libssh2_md5_init(ctx) \ + (GPG_ERR_NO_ERROR == gcry_md_open (ctx, GCRY_MD_MD5, 0)) + +#define libssh2_md5_update(ctx, data, len) gcry_md_write (ctx, data, len) +#define libssh2_md5_final(ctx, out) \ + memcpy (out, gcry_md_read (ctx, 0), MD5_DIGEST_LENGTH), gcry_md_close (ctx) +#define libssh2_md5(message, len, out) \ + gcry_md_hash_buffer (GCRY_MD_MD5, out, message, len) + +#define libssh2_hmac_ctx gcry_md_hd_t +#define libssh2_hmac_sha1_init(ctx, key, keylen) \ + gcry_md_open (ctx, GCRY_MD_SHA1, GCRY_MD_FLAG_HMAC), \ + gcry_md_setkey (*ctx, key, keylen) +#define libssh2_hmac_md5_init(ctx, key, keylen) \ + gcry_md_open (ctx, GCRY_MD_MD5, GCRY_MD_FLAG_HMAC), \ + gcry_md_setkey (*ctx, key, keylen) +#define libssh2_hmac_ripemd160_init(ctx, key, keylen) \ + gcry_md_open (ctx, GCRY_MD_RMD160, GCRY_MD_FLAG_HMAC), \ + gcry_md_setkey (*ctx, key, keylen) +#define libssh2_hmac_update(ctx, data, datalen) \ + gcry_md_write (ctx, data, datalen) +#define libssh2_hmac_final(ctx, data) \ + memcpy (data, gcry_md_read (ctx, 0), \ + gcry_md_get_algo_dlen (gcry_md_get_algo (ctx))) +#define libssh2_hmac_cleanup(ctx) gcry_md_close (*ctx); + +#define libssh2_crypto_init() gcry_control (GCRYCTL_DISABLE_SECMEM) +#define libssh2_crypto_exit() + +#define libssh2_rsa_ctx struct gcry_sexp + +#define _libssh2_rsa_free(rsactx) gcry_sexp_release (rsactx) + +#define libssh2_dsa_ctx struct gcry_sexp + +#define _libssh2_dsa_free(dsactx) gcry_sexp_release (dsactx) + +#define _libssh2_cipher_type(name) int name +#define _libssh2_cipher_ctx gcry_cipher_hd_t + +#define _libssh2_gcry_ciphermode(c,m) ((c << 8) | m) +#define _libssh2_gcry_cipher(c) (c >> 8) +#define _libssh2_gcry_mode(m) (m & 0xFF) + +#define _libssh2_cipher_aes256ctr \ + _libssh2_gcry_ciphermode(GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_CTR) +#define _libssh2_cipher_aes192ctr \ + _libssh2_gcry_ciphermode(GCRY_CIPHER_AES192, GCRY_CIPHER_MODE_CTR) +#define _libssh2_cipher_aes128ctr \ + _libssh2_gcry_ciphermode(GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_CTR) +#define _libssh2_cipher_aes256 \ + _libssh2_gcry_ciphermode(GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_CBC) +#define _libssh2_cipher_aes192 \ + _libssh2_gcry_ciphermode(GCRY_CIPHER_AES192, GCRY_CIPHER_MODE_CBC) +#define _libssh2_cipher_aes128 \ + _libssh2_gcry_ciphermode(GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_CBC) +#define _libssh2_cipher_blowfish \ + _libssh2_gcry_ciphermode(GCRY_CIPHER_BLOWFISH, GCRY_CIPHER_MODE_CBC) +#define _libssh2_cipher_arcfour \ + _libssh2_gcry_ciphermode(GCRY_CIPHER_ARCFOUR, GCRY_CIPHER_MODE_STREAM) +#define _libssh2_cipher_cast5 \ + _libssh2_gcry_ciphermode(GCRY_CIPHER_CAST5, GCRY_CIPHER_MODE_CBC) +#define _libssh2_cipher_3des \ + _libssh2_gcry_ciphermode(GCRY_CIPHER_3DES, GCRY_CIPHER_MODE_CBC) + + +#define _libssh2_cipher_dtor(ctx) gcry_cipher_close(*(ctx)) + +#define _libssh2_bn struct gcry_mpi +#define _libssh2_bn_ctx int +#define _libssh2_bn_ctx_new() 0 +#define _libssh2_bn_ctx_free(bnctx) ((void)0) +#define _libssh2_bn_init() gcry_mpi_new(0) +#define _libssh2_bn_rand(bn, bits, top, bottom) gcry_mpi_randomize (bn, bits, GCRY_WEAK_RANDOM) +#define _libssh2_bn_mod_exp(r, a, p, m, ctx) gcry_mpi_powm (r, a, p, m) +#define _libssh2_bn_set_word(bn, val) gcry_mpi_set_ui(bn, val) +#define _libssh2_bn_from_bin(bn, len, val) gcry_mpi_scan(&((bn)), GCRYMPI_FMT_USG, val, len, NULL) +#define _libssh2_bn_to_bin(bn, val) gcry_mpi_print (GCRYMPI_FMT_USG, val, _libssh2_bn_bytes(bn), NULL, bn) +#define _libssh2_bn_bytes(bn) (gcry_mpi_get_nbits (bn) / 8 + ((gcry_mpi_get_nbits (bn) % 8 == 0) ? 0 : 1)) +#define _libssh2_bn_bits(bn) gcry_mpi_get_nbits (bn) +#define _libssh2_bn_free(bn) gcry_mpi_release(bn) + diff --git a/libssh2/include/libssh2.h b/libssh2/include/libssh2.h new file mode 100644 index 0000000..9b1a6e1 --- /dev/null +++ b/libssh2/include/libssh2.h @@ -0,0 +1,1189 @@ +/* Copyright (c) 2004-2009, Sara Golemon + * Copyright (c) 2009-2012 Daniel Stenberg + * Copyright (c) 2010 Simon Josefsson + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#ifndef LIBSSH2_H +#define LIBSSH2_H 1 + +#define LIBSSH2_COPYRIGHT "2004-2012 The libssh2 project and its contributors." + +/* We use underscore instead of dash when appending DEV in dev versions just + to make the BANNER define (used by src/session.c) be a valid SSH + banner. Release versions have no appended strings and may of course not + have dashes either. */ +#define LIBSSH2_VERSION "1.4.3" + +/* The numeric version number is also available "in parts" by using these + defines: */ +#define LIBSSH2_VERSION_MAJOR 1 +#define LIBSSH2_VERSION_MINOR 4 +#define LIBSSH2_VERSION_PATCH 3 + +/* This is the numeric version of the libssh2 version number, meant for easier + parsing and comparions by programs. The LIBSSH2_VERSION_NUM define will + always follow this syntax: + + 0xXXYYZZ + + Where XX, YY and ZZ are the main version, release and patch numbers in + hexadecimal (using 8 bits each). All three numbers are always represented + using two digits. 1.2 would appear as "0x010200" while version 9.11.7 + appears as "0x090b07". + + This 6-digit (24 bits) hexadecimal number does not show pre-release number, + and it is always a greater number in a more recent release. It makes + comparisons with greater than and less than work. +*/ +#define LIBSSH2_VERSION_NUM 0x010403 + +/* + * This is the date and time when the full source package was created. The + * timestamp is not stored in the source code repo, as the timestamp is + * properly set in the tarballs by the maketgz script. + * + * The format of the date should follow this template: + * + * "Mon Feb 12 11:35:33 UTC 2007" + */ +#define LIBSSH2_TIMESTAMP "Tue Nov 27 21:45:20 UTC 2012" + +#ifndef RC_INVOKED + +#ifdef __cplusplus +extern "C" { +#endif +#ifdef _WIN32 +# include +# include +#endif + +#include +#include +#include +#include + +/* Allow alternate API prefix from CFLAGS or calling app */ +#ifndef LIBSSH2_API +# ifdef LIBSSH2_WIN32 +# ifdef LIBSSH2_LIBRARY +# define LIBSSH2_API __declspec(dllexport) +# else +# define LIBSSH2_API __declspec(dllimport) +# endif /* LIBSSH2_LIBRARY */ +# else /* !LIBSSH2_WIN32 */ +# define LIBSSH2_API +# endif /* LIBSSH2_WIN32 */ +#endif /* LIBSSH2_API */ + +#if defined(LIBSSH2_DARWIN) +# include +#endif + +#if (defined(NETWARE) && !defined(__NOVELL_LIBC__)) +# include +typedef unsigned char uint8_t; +typedef unsigned int uint32_t; +#endif + +#ifdef _MSC_VER +typedef unsigned char uint8_t; +typedef unsigned int uint32_t; +typedef unsigned __int64 libssh2_uint64_t; +typedef __int64 libssh2_int64_t; +#ifndef ssize_t +typedef SSIZE_T ssize_t; +#endif +#else +typedef unsigned long long libssh2_uint64_t; +typedef long long libssh2_int64_t; +#endif + +#ifdef WIN32 +typedef SOCKET libssh2_socket_t; +#define LIBSSH2_INVALID_SOCKET INVALID_SOCKET +#else /* !WIN32 */ +typedef int libssh2_socket_t; +#define LIBSSH2_INVALID_SOCKET -1 +#endif /* WIN32 */ + +/* Part of every banner, user specified or not */ +#define LIBSSH2_SSH_BANNER "SSH-2.0-libssh2_" LIBSSH2_VERSION + +/* We *could* add a comment here if we so chose */ +#define LIBSSH2_SSH_DEFAULT_BANNER LIBSSH2_SSH_BANNER +#define LIBSSH2_SSH_DEFAULT_BANNER_WITH_CRLF LIBSSH2_SSH_DEFAULT_BANNER "\r\n" + +/* Default generate and safe prime sizes for diffie-hellman-group-exchange-sha1 */ +#define LIBSSH2_DH_GEX_MINGROUP 1024 +#define LIBSSH2_DH_GEX_OPTGROUP 1536 +#define LIBSSH2_DH_GEX_MAXGROUP 2048 + +/* Defaults for pty requests */ +#define LIBSSH2_TERM_WIDTH 80 +#define LIBSSH2_TERM_HEIGHT 24 +#define LIBSSH2_TERM_WIDTH_PX 0 +#define LIBSSH2_TERM_HEIGHT_PX 0 + +/* 1/4 second */ +#define LIBSSH2_SOCKET_POLL_UDELAY 250000 +/* 0.25 * 120 == 30 seconds */ +#define LIBSSH2_SOCKET_POLL_MAXLOOPS 120 + +/* Maximum size to allow a payload to compress to, plays it safe by falling + short of spec limits */ +#define LIBSSH2_PACKET_MAXCOMP 32000 + +/* Maximum size to allow a payload to deccompress to, plays it safe by + allowing more than spec requires */ +#define LIBSSH2_PACKET_MAXDECOMP 40000 + +/* Maximum size for an inbound compressed payload, plays it safe by + overshooting spec limits */ +#define LIBSSH2_PACKET_MAXPAYLOAD 40000 + +/* Malloc callbacks */ +#define LIBSSH2_ALLOC_FUNC(name) void *name(size_t count, void **abstract) +#define LIBSSH2_REALLOC_FUNC(name) void *name(void *ptr, size_t count, \ + void **abstract) +#define LIBSSH2_FREE_FUNC(name) void name(void *ptr, void **abstract) + +typedef struct _LIBSSH2_USERAUTH_KBDINT_PROMPT +{ + char* text; + unsigned int length; + unsigned char echo; +} LIBSSH2_USERAUTH_KBDINT_PROMPT; + +typedef struct _LIBSSH2_USERAUTH_KBDINT_RESPONSE +{ + char* text; + unsigned int length; +} LIBSSH2_USERAUTH_KBDINT_RESPONSE; + +/* 'publickey' authentication callback */ +#define LIBSSH2_USERAUTH_PUBLICKEY_SIGN_FUNC(name) \ + int name(LIBSSH2_SESSION *session, unsigned char **sig, size_t *sig_len, \ + const unsigned char *data, size_t data_len, void **abstract) + +/* 'keyboard-interactive' authentication callback */ +#define LIBSSH2_USERAUTH_KBDINT_RESPONSE_FUNC(name_) \ + void name_(const char* name, int name_len, const char* instruction, \ + int instruction_len, int num_prompts, \ + const LIBSSH2_USERAUTH_KBDINT_PROMPT* prompts, \ + LIBSSH2_USERAUTH_KBDINT_RESPONSE* responses, void **abstract) + +/* Callbacks for special SSH packets */ +#define LIBSSH2_IGNORE_FUNC(name) \ + void name(LIBSSH2_SESSION *session, const char *message, int message_len, \ + void **abstract) + +#define LIBSSH2_DEBUG_FUNC(name) \ + void name(LIBSSH2_SESSION *session, int always_display, const char *message, \ + int message_len, const char *language, int language_len, \ + void **abstract) + +#define LIBSSH2_DISCONNECT_FUNC(name) \ + void name(LIBSSH2_SESSION *session, int reason, const char *message, \ + int message_len, const char *language, int language_len, \ + void **abstract) + +#define LIBSSH2_PASSWD_CHANGEREQ_FUNC(name) \ + void name(LIBSSH2_SESSION *session, char **newpw, int *newpw_len, \ + void **abstract) + +#define LIBSSH2_MACERROR_FUNC(name) \ + int name(LIBSSH2_SESSION *session, const char *packet, int packet_len, \ + void **abstract) + +#define LIBSSH2_X11_OPEN_FUNC(name) \ + void name(LIBSSH2_SESSION *session, LIBSSH2_CHANNEL *channel, \ + const char *shost, int sport, void **abstract) + +#define LIBSSH2_CHANNEL_CLOSE_FUNC(name) \ + void name(LIBSSH2_SESSION *session, void **session_abstract, \ + LIBSSH2_CHANNEL *channel, void **channel_abstract) + +/* I/O callbacks */ +#define LIBSSH2_RECV_FUNC(name) ssize_t name(libssh2_socket_t socket, \ + void *buffer, size_t length, \ + int flags, void **abstract) +#define LIBSSH2_SEND_FUNC(name) ssize_t name(libssh2_socket_t socket, \ + const void *buffer, size_t length,\ + int flags, void **abstract) + +/* libssh2_session_callback_set() constants */ +#define LIBSSH2_CALLBACK_IGNORE 0 +#define LIBSSH2_CALLBACK_DEBUG 1 +#define LIBSSH2_CALLBACK_DISCONNECT 2 +#define LIBSSH2_CALLBACK_MACERROR 3 +#define LIBSSH2_CALLBACK_X11 4 +#define LIBSSH2_CALLBACK_SEND 5 +#define LIBSSH2_CALLBACK_RECV 6 + +/* libssh2_session_method_pref() constants */ +#define LIBSSH2_METHOD_KEX 0 +#define LIBSSH2_METHOD_HOSTKEY 1 +#define LIBSSH2_METHOD_CRYPT_CS 2 +#define LIBSSH2_METHOD_CRYPT_SC 3 +#define LIBSSH2_METHOD_MAC_CS 4 +#define LIBSSH2_METHOD_MAC_SC 5 +#define LIBSSH2_METHOD_COMP_CS 6 +#define LIBSSH2_METHOD_COMP_SC 7 +#define LIBSSH2_METHOD_LANG_CS 8 +#define LIBSSH2_METHOD_LANG_SC 9 + +/* flags */ +#define LIBSSH2_FLAG_SIGPIPE 1 +#define LIBSSH2_FLAG_COMPRESS 2 + +typedef struct _LIBSSH2_SESSION LIBSSH2_SESSION; +typedef struct _LIBSSH2_CHANNEL LIBSSH2_CHANNEL; +typedef struct _LIBSSH2_LISTENER LIBSSH2_LISTENER; +typedef struct _LIBSSH2_KNOWNHOSTS LIBSSH2_KNOWNHOSTS; +typedef struct _LIBSSH2_AGENT LIBSSH2_AGENT; + +typedef struct _LIBSSH2_POLLFD { + unsigned char type; /* LIBSSH2_POLLFD_* below */ + + union { + int socket; /* File descriptors -- examined with system select() call */ + LIBSSH2_CHANNEL *channel; /* Examined by checking internal state */ + LIBSSH2_LISTENER *listener; /* Read polls only -- are inbound + connections waiting to be accepted? */ + } fd; + + unsigned long events; /* Requested Events */ + unsigned long revents; /* Returned Events */ +} LIBSSH2_POLLFD; + +/* Poll FD Descriptor Types */ +#define LIBSSH2_POLLFD_SOCKET 1 +#define LIBSSH2_POLLFD_CHANNEL 2 +#define LIBSSH2_POLLFD_LISTENER 3 + +/* Note: Win32 Doesn't actually have a poll() implementation, so some of these + values are faked with select() data */ +/* Poll FD events/revents -- Match sys/poll.h where possible */ +#define LIBSSH2_POLLFD_POLLIN 0x0001 /* Data available to be read or + connection available -- + All */ +#define LIBSSH2_POLLFD_POLLPRI 0x0002 /* Priority data available to + be read -- Socket only */ +#define LIBSSH2_POLLFD_POLLEXT 0x0002 /* Extended data available to + be read -- Channel only */ +#define LIBSSH2_POLLFD_POLLOUT 0x0004 /* Can may be written -- + Socket/Channel */ +/* revents only */ +#define LIBSSH2_POLLFD_POLLERR 0x0008 /* Error Condition -- Socket */ +#define LIBSSH2_POLLFD_POLLHUP 0x0010 /* HangUp/EOF -- Socket */ +#define LIBSSH2_POLLFD_SESSION_CLOSED 0x0010 /* Session Disconnect */ +#define LIBSSH2_POLLFD_POLLNVAL 0x0020 /* Invalid request -- Socket + Only */ +#define LIBSSH2_POLLFD_POLLEX 0x0040 /* Exception Condition -- + Socket/Win32 */ +#define LIBSSH2_POLLFD_CHANNEL_CLOSED 0x0080 /* Channel Disconnect */ +#define LIBSSH2_POLLFD_LISTENER_CLOSED 0x0080 /* Listener Disconnect */ + +#define HAVE_LIBSSH2_SESSION_BLOCK_DIRECTION +/* Block Direction Types */ +#define LIBSSH2_SESSION_BLOCK_INBOUND 0x0001 +#define LIBSSH2_SESSION_BLOCK_OUTBOUND 0x0002 + +/* Hash Types */ +#define LIBSSH2_HOSTKEY_HASH_MD5 1 +#define LIBSSH2_HOSTKEY_HASH_SHA1 2 + +/* Hostkey Types */ +#define LIBSSH2_HOSTKEY_TYPE_UNKNOWN 0 +#define LIBSSH2_HOSTKEY_TYPE_RSA 1 +#define LIBSSH2_HOSTKEY_TYPE_DSS 2 + +/* Disconnect Codes (defined by SSH protocol) */ +#define SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT 1 +#define SSH_DISCONNECT_PROTOCOL_ERROR 2 +#define SSH_DISCONNECT_KEY_EXCHANGE_FAILED 3 +#define SSH_DISCONNECT_RESERVED 4 +#define SSH_DISCONNECT_MAC_ERROR 5 +#define SSH_DISCONNECT_COMPRESSION_ERROR 6 +#define SSH_DISCONNECT_SERVICE_NOT_AVAILABLE 7 +#define SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED 8 +#define SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE 9 +#define SSH_DISCONNECT_CONNECTION_LOST 10 +#define SSH_DISCONNECT_BY_APPLICATION 11 +#define SSH_DISCONNECT_TOO_MANY_CONNECTIONS 12 +#define SSH_DISCONNECT_AUTH_CANCELLED_BY_USER 13 +#define SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE 14 +#define SSH_DISCONNECT_ILLEGAL_USER_NAME 15 + +/* Error Codes (defined by libssh2) */ +#define LIBSSH2_ERROR_NONE 0 + +/* The library once used -1 as a generic error return value on numerous places + through the code, which subsequently was converted to + LIBSSH2_ERROR_SOCKET_NONE uses over time. As this is a generic error code, + the goal is to never ever return this code but instead make sure that a + more accurate and descriptive error code is used. */ +#define LIBSSH2_ERROR_SOCKET_NONE -1 + +#define LIBSSH2_ERROR_BANNER_RECV -2 +#define LIBSSH2_ERROR_BANNER_SEND -3 +#define LIBSSH2_ERROR_INVALID_MAC -4 +#define LIBSSH2_ERROR_KEX_FAILURE -5 +#define LIBSSH2_ERROR_ALLOC -6 +#define LIBSSH2_ERROR_SOCKET_SEND -7 +#define LIBSSH2_ERROR_KEY_EXCHANGE_FAILURE -8 +#define LIBSSH2_ERROR_TIMEOUT -9 +#define LIBSSH2_ERROR_HOSTKEY_INIT -10 +#define LIBSSH2_ERROR_HOSTKEY_SIGN -11 +#define LIBSSH2_ERROR_DECRYPT -12 +#define LIBSSH2_ERROR_SOCKET_DISCONNECT -13 +#define LIBSSH2_ERROR_PROTO -14 +#define LIBSSH2_ERROR_PASSWORD_EXPIRED -15 +#define LIBSSH2_ERROR_FILE -16 +#define LIBSSH2_ERROR_METHOD_NONE -17 +#define LIBSSH2_ERROR_AUTHENTICATION_FAILED -18 +#define LIBSSH2_ERROR_PUBLICKEY_UNRECOGNIZED LIBSSH2_ERROR_AUTHENTICATION_FAILED +#define LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED -19 +#define LIBSSH2_ERROR_CHANNEL_OUTOFORDER -20 +#define LIBSSH2_ERROR_CHANNEL_FAILURE -21 +#define LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED -22 +#define LIBSSH2_ERROR_CHANNEL_UNKNOWN -23 +#define LIBSSH2_ERROR_CHANNEL_WINDOW_EXCEEDED -24 +#define LIBSSH2_ERROR_CHANNEL_PACKET_EXCEEDED -25 +#define LIBSSH2_ERROR_CHANNEL_CLOSED -26 +#define LIBSSH2_ERROR_CHANNEL_EOF_SENT -27 +#define LIBSSH2_ERROR_SCP_PROTOCOL -28 +#define LIBSSH2_ERROR_ZLIB -29 +#define LIBSSH2_ERROR_SOCKET_TIMEOUT -30 +#define LIBSSH2_ERROR_SFTP_PROTOCOL -31 +#define LIBSSH2_ERROR_REQUEST_DENIED -32 +#define LIBSSH2_ERROR_METHOD_NOT_SUPPORTED -33 +#define LIBSSH2_ERROR_INVAL -34 +#define LIBSSH2_ERROR_INVALID_POLL_TYPE -35 +#define LIBSSH2_ERROR_PUBLICKEY_PROTOCOL -36 +#define LIBSSH2_ERROR_EAGAIN -37 +#define LIBSSH2_ERROR_BUFFER_TOO_SMALL -38 +#define LIBSSH2_ERROR_BAD_USE -39 +#define LIBSSH2_ERROR_COMPRESS -40 +#define LIBSSH2_ERROR_OUT_OF_BOUNDARY -41 +#define LIBSSH2_ERROR_AGENT_PROTOCOL -42 +#define LIBSSH2_ERROR_SOCKET_RECV -43 +#define LIBSSH2_ERROR_ENCRYPT -44 +#define LIBSSH2_ERROR_BAD_SOCKET -45 +#define LIBSSH2_ERROR_KNOWN_HOSTS -46 + +/* this is a define to provide the old (<= 1.2.7) name */ +#define LIBSSH2_ERROR_BANNER_NONE LIBSSH2_ERROR_BANNER_RECV + +/* Global API */ +#define LIBSSH2_INIT_NO_CRYPTO 0x0001 + +/* + * libssh2_init() + * + * Initialize the libssh2 functions. This typically initialize the + * crypto library. It uses a global state, and is not thread safe -- + * you must make sure this function is not called concurrently. + * + * Flags can be: + * 0: Normal initialize + * LIBSSH2_INIT_NO_CRYPTO: Do not initialize the crypto library (ie. + * OPENSSL_add_cipher_algoritms() for OpenSSL + * + * Returns 0 if succeeded, or a negative value for error. + */ +LIBSSH2_API int libssh2_init(int flags); + +/* + * libssh2_exit() + * + * Exit the libssh2 functions and free's all memory used internal. + */ +LIBSSH2_API void libssh2_exit(void); + +/* + * libssh2_free() + * + * Deallocate memory allocated by earlier call to libssh2 functions. + */ +LIBSSH2_API void libssh2_free(LIBSSH2_SESSION *session, void *ptr); + +/* + * libssh2_session_supported_algs() + * + * Fills algs with a list of supported acryptographic algorithms. Returns a + * non-negative number (number of supported algorithms) on success or a + * negative number (an eror code) on failure. + * + * NOTE: on success, algs must be deallocated (by calling libssh2_free) when + * not needed anymore + */ +LIBSSH2_API int libssh2_session_supported_algs(LIBSSH2_SESSION* session, + int method_type, + const char*** algs); + +/* Session API */ +LIBSSH2_API LIBSSH2_SESSION * +libssh2_session_init_ex(LIBSSH2_ALLOC_FUNC((*my_alloc)), + LIBSSH2_FREE_FUNC((*my_free)), + LIBSSH2_REALLOC_FUNC((*my_realloc)), void *abstract); +#define libssh2_session_init() libssh2_session_init_ex(NULL, NULL, NULL, NULL) + +LIBSSH2_API void **libssh2_session_abstract(LIBSSH2_SESSION *session); + +LIBSSH2_API void *libssh2_session_callback_set(LIBSSH2_SESSION *session, + int cbtype, void *callback); +LIBSSH2_API int libssh2_session_banner_set(LIBSSH2_SESSION *session, + const char *banner); +LIBSSH2_API int libssh2_banner_set(LIBSSH2_SESSION *session, + const char *banner); + +LIBSSH2_API int libssh2_session_startup(LIBSSH2_SESSION *session, int sock); +LIBSSH2_API int libssh2_session_handshake(LIBSSH2_SESSION *session, + libssh2_socket_t sock); +LIBSSH2_API int libssh2_session_disconnect_ex(LIBSSH2_SESSION *session, + int reason, + const char *description, + const char *lang); +#define libssh2_session_disconnect(session, description) \ + libssh2_session_disconnect_ex((session), SSH_DISCONNECT_BY_APPLICATION, \ + (description), "") + +LIBSSH2_API int libssh2_session_free(LIBSSH2_SESSION *session); + +LIBSSH2_API const char *libssh2_hostkey_hash(LIBSSH2_SESSION *session, + int hash_type); + +LIBSSH2_API const char *libssh2_session_hostkey(LIBSSH2_SESSION *session, + size_t *len, int *type); + +LIBSSH2_API int libssh2_session_method_pref(LIBSSH2_SESSION *session, + int method_type, + const char *prefs); +LIBSSH2_API const char *libssh2_session_methods(LIBSSH2_SESSION *session, + int method_type); +LIBSSH2_API int libssh2_session_last_error(LIBSSH2_SESSION *session, + char **errmsg, + int *errmsg_len, int want_buf); +LIBSSH2_API int libssh2_session_last_errno(LIBSSH2_SESSION *session); +LIBSSH2_API int libssh2_session_block_directions(LIBSSH2_SESSION *session); + +LIBSSH2_API int libssh2_session_flag(LIBSSH2_SESSION *session, int flag, + int value); +LIBSSH2_API const char *libssh2_session_banner_get(LIBSSH2_SESSION *session); + +/* Userauth API */ +LIBSSH2_API char *libssh2_userauth_list(LIBSSH2_SESSION *session, + const char *username, + unsigned int username_len); +LIBSSH2_API int libssh2_userauth_authenticated(LIBSSH2_SESSION *session); + +LIBSSH2_API int libssh2_userauth_password_ex(LIBSSH2_SESSION *session, + const char *username, + unsigned int username_len, + const char *password, + unsigned int password_len, + LIBSSH2_PASSWD_CHANGEREQ_FUNC((*passwd_change_cb))); + +#define libssh2_userauth_password(session, username, password) \ + libssh2_userauth_password_ex((session), (username), strlen(username), \ + (password), strlen(password), NULL) + +LIBSSH2_API int +libssh2_userauth_publickey_fromfile_ex(LIBSSH2_SESSION *session, + const char *username, + unsigned int username_len, + const char *publickey, + const char *privatekey, + const char *passphrase); + +#define libssh2_userauth_publickey_fromfile(session, username, publickey, \ + privatekey, passphrase) \ + libssh2_userauth_publickey_fromfile_ex((session), (username), \ + strlen(username), (publickey), \ + (privatekey), (passphrase)) + +LIBSSH2_API int +libssh2_userauth_publickey(LIBSSH2_SESSION *session, + const char *username, + const unsigned char *pubkeydata, + size_t pubkeydata_len, + LIBSSH2_USERAUTH_PUBLICKEY_SIGN_FUNC((*sign_callback)), + void **abstract); + +LIBSSH2_API int +libssh2_userauth_hostbased_fromfile_ex(LIBSSH2_SESSION *session, + const char *username, + unsigned int username_len, + const char *publickey, + const char *privatekey, + const char *passphrase, + const char *hostname, + unsigned int hostname_len, + const char *local_username, + unsigned int local_username_len); + +#define libssh2_userauth_hostbased_fromfile(session, username, publickey, \ + privatekey, passphrase, hostname) \ + libssh2_userauth_hostbased_fromfile_ex((session), (username), \ + strlen(username), (publickey), \ + (privatekey), (passphrase), \ + (hostname), strlen(hostname), \ + (username), strlen(username)) + +/* + * response_callback is provided with filled by library prompts array, + * but client must allocate and fill individual responses. Responses + * array is already allocated. Responses data will be freed by libssh2 + * after callback return, but before subsequent callback invokation. + */ +LIBSSH2_API int +libssh2_userauth_keyboard_interactive_ex(LIBSSH2_SESSION* session, + const char *username, + unsigned int username_len, + LIBSSH2_USERAUTH_KBDINT_RESPONSE_FUNC((*response_callback))); + +#define libssh2_userauth_keyboard_interactive(session, username, \ + response_callback) \ + libssh2_userauth_keyboard_interactive_ex((session), (username), \ + strlen(username), (response_callback)) + +LIBSSH2_API int libssh2_poll(LIBSSH2_POLLFD *fds, unsigned int nfds, + long timeout); + +/* Channel API */ +#define LIBSSH2_CHANNEL_WINDOW_DEFAULT (256*1024) +#define LIBSSH2_CHANNEL_PACKET_DEFAULT 32768 +#define LIBSSH2_CHANNEL_MINADJUST 1024 + +/* Extended Data Handling */ +#define LIBSSH2_CHANNEL_EXTENDED_DATA_NORMAL 0 +#define LIBSSH2_CHANNEL_EXTENDED_DATA_IGNORE 1 +#define LIBSSH2_CHANNEL_EXTENDED_DATA_MERGE 2 + +#define SSH_EXTENDED_DATA_STDERR 1 + +/* Returned by any function that would block during a read/write opperation */ +#define LIBSSH2CHANNEL_EAGAIN LIBSSH2_ERROR_EAGAIN + +LIBSSH2_API LIBSSH2_CHANNEL * +libssh2_channel_open_ex(LIBSSH2_SESSION *session, const char *channel_type, + unsigned int channel_type_len, + unsigned int window_size, unsigned int packet_size, + const char *message, unsigned int message_len); + +#define libssh2_channel_open_session(session) \ + libssh2_channel_open_ex((session), "session", sizeof("session") - 1, \ + LIBSSH2_CHANNEL_WINDOW_DEFAULT, \ + LIBSSH2_CHANNEL_PACKET_DEFAULT, NULL, 0) + +LIBSSH2_API LIBSSH2_CHANNEL * +libssh2_channel_direct_tcpip_ex(LIBSSH2_SESSION *session, const char *host, + int port, const char *shost, int sport); +#define libssh2_channel_direct_tcpip(session, host, port) \ + libssh2_channel_direct_tcpip_ex((session), (host), (port), "127.0.0.1", 22) + +LIBSSH2_API LIBSSH2_LISTENER * +libssh2_channel_forward_listen_ex(LIBSSH2_SESSION *session, const char *host, + int port, int *bound_port, int queue_maxsize); +#define libssh2_channel_forward_listen(session, port) \ + libssh2_channel_forward_listen_ex((session), NULL, (port), NULL, 16) + +LIBSSH2_API int libssh2_channel_forward_cancel(LIBSSH2_LISTENER *listener); + +LIBSSH2_API LIBSSH2_CHANNEL * +libssh2_channel_forward_accept(LIBSSH2_LISTENER *listener); + +LIBSSH2_API int libssh2_channel_setenv_ex(LIBSSH2_CHANNEL *channel, + const char *varname, + unsigned int varname_len, + const char *value, + unsigned int value_len); + +#define libssh2_channel_setenv(channel, varname, value) \ + libssh2_channel_setenv_ex((channel), (varname), strlen(varname), (value), \ + strlen(value)) + +LIBSSH2_API int libssh2_channel_request_pty_ex(LIBSSH2_CHANNEL *channel, + const char *term, + unsigned int term_len, + const char *modes, + unsigned int modes_len, + int width, int height, + int width_px, int height_px); +#define libssh2_channel_request_pty(channel, term) \ + libssh2_channel_request_pty_ex((channel), (term), strlen(term), NULL, 0, \ + LIBSSH2_TERM_WIDTH, LIBSSH2_TERM_HEIGHT, \ + LIBSSH2_TERM_WIDTH_PX, LIBSSH2_TERM_HEIGHT_PX) + +LIBSSH2_API int libssh2_channel_request_pty_size_ex(LIBSSH2_CHANNEL *channel, + int width, int height, + int width_px, + int height_px); +#define libssh2_channel_request_pty_size(channel, width, height) \ + libssh2_channel_request_pty_size_ex( (channel), (width), (height), 0, 0) + +LIBSSH2_API int libssh2_channel_x11_req_ex(LIBSSH2_CHANNEL *channel, + int single_connection, + const char *auth_proto, + const char *auth_cookie, + int screen_number); +#define libssh2_channel_x11_req(channel, screen_number) \ + libssh2_channel_x11_req_ex((channel), 0, NULL, NULL, (screen_number)) + +LIBSSH2_API int libssh2_channel_process_startup(LIBSSH2_CHANNEL *channel, + const char *request, + unsigned int request_len, + const char *message, + unsigned int message_len); +#define libssh2_channel_shell(channel) \ + libssh2_channel_process_startup((channel), "shell", sizeof("shell") - 1, \ + NULL, 0) +#define libssh2_channel_exec(channel, command) \ + libssh2_channel_process_startup((channel), "exec", sizeof("exec") - 1, \ + (command), strlen(command)) +#define libssh2_channel_subsystem(channel, subsystem) \ + libssh2_channel_process_startup((channel), "subsystem", \ + sizeof("subsystem") - 1, (subsystem), \ + strlen(subsystem)) + +LIBSSH2_API ssize_t libssh2_channel_read_ex(LIBSSH2_CHANNEL *channel, + int stream_id, char *buf, + size_t buflen); +#define libssh2_channel_read(channel, buf, buflen) \ + libssh2_channel_read_ex((channel), 0, (buf), (buflen)) +#define libssh2_channel_read_stderr(channel, buf, buflen) \ + libssh2_channel_read_ex((channel), SSH_EXTENDED_DATA_STDERR, (buf), (buflen)) + +LIBSSH2_API int libssh2_poll_channel_read(LIBSSH2_CHANNEL *channel, + int extended); + +LIBSSH2_API unsigned long +libssh2_channel_window_read_ex(LIBSSH2_CHANNEL *channel, + unsigned long *read_avail, + unsigned long *window_size_initial); +#define libssh2_channel_window_read(channel) \ + libssh2_channel_window_read_ex((channel), NULL, NULL) + +/* libssh2_channel_receive_window_adjust is DEPRECATED, do not use! */ +LIBSSH2_API unsigned long +libssh2_channel_receive_window_adjust(LIBSSH2_CHANNEL *channel, + unsigned long adjustment, + unsigned char force); + +LIBSSH2_API int +libssh2_channel_receive_window_adjust2(LIBSSH2_CHANNEL *channel, + unsigned long adjustment, + unsigned char force, + unsigned int *storewindow); + +LIBSSH2_API ssize_t libssh2_channel_write_ex(LIBSSH2_CHANNEL *channel, + int stream_id, const char *buf, + size_t buflen); + +#define libssh2_channel_write(channel, buf, buflen) \ + libssh2_channel_write_ex((channel), 0, (buf), (buflen)) +#define libssh2_channel_write_stderr(channel, buf, buflen) \ + libssh2_channel_write_ex((channel), SSH_EXTENDED_DATA_STDERR, (buf), (buflen)) + +LIBSSH2_API unsigned long +libssh2_channel_window_write_ex(LIBSSH2_CHANNEL *channel, + unsigned long *window_size_initial); +#define libssh2_channel_window_write(channel) \ + libssh2_channel_window_write_ex((channel), NULL) + +LIBSSH2_API void libssh2_session_set_blocking(LIBSSH2_SESSION* session, + int blocking); +LIBSSH2_API int libssh2_session_get_blocking(LIBSSH2_SESSION* session); + +LIBSSH2_API void libssh2_channel_set_blocking(LIBSSH2_CHANNEL *channel, + int blocking); + +LIBSSH2_API void libssh2_session_set_timeout(LIBSSH2_SESSION* session, + long timeout); +LIBSSH2_API long libssh2_session_get_timeout(LIBSSH2_SESSION* session); + +/* libssh2_channel_handle_extended_data is DEPRECATED, do not use! */ +LIBSSH2_API void libssh2_channel_handle_extended_data(LIBSSH2_CHANNEL *channel, + int ignore_mode); +LIBSSH2_API int libssh2_channel_handle_extended_data2(LIBSSH2_CHANNEL *channel, + int ignore_mode); + +/* libssh2_channel_ignore_extended_data() is defined below for BC with version + * 0.1 + * + * Future uses should use libssh2_channel_handle_extended_data() directly if + * LIBSSH2_CHANNEL_EXTENDED_DATA_MERGE is passed, extended data will be read + * (FIFO) from the standard data channel + */ +/* DEPRECATED */ +#define libssh2_channel_ignore_extended_data(channel, ignore) \ + libssh2_channel_handle_extended_data((channel), \ + (ignore) ? \ + LIBSSH2_CHANNEL_EXTENDED_DATA_IGNORE : \ + LIBSSH2_CHANNEL_EXTENDED_DATA_NORMAL ) + +#define LIBSSH2_CHANNEL_FLUSH_EXTENDED_DATA -1 +#define LIBSSH2_CHANNEL_FLUSH_ALL -2 +LIBSSH2_API int libssh2_channel_flush_ex(LIBSSH2_CHANNEL *channel, + int streamid); +#define libssh2_channel_flush(channel) libssh2_channel_flush_ex((channel), 0) +#define libssh2_channel_flush_stderr(channel) \ + libssh2_channel_flush_ex((channel), SSH_EXTENDED_DATA_STDERR) + +LIBSSH2_API int libssh2_channel_get_exit_status(LIBSSH2_CHANNEL* channel); +LIBSSH2_API int libssh2_channel_get_exit_signal(LIBSSH2_CHANNEL* channel, + char **exitsignal, + size_t *exitsignal_len, + char **errmsg, + size_t *errmsg_len, + char **langtag, + size_t *langtag_len); +LIBSSH2_API int libssh2_channel_send_eof(LIBSSH2_CHANNEL *channel); +LIBSSH2_API int libssh2_channel_eof(LIBSSH2_CHANNEL *channel); +LIBSSH2_API int libssh2_channel_wait_eof(LIBSSH2_CHANNEL *channel); +LIBSSH2_API int libssh2_channel_close(LIBSSH2_CHANNEL *channel); +LIBSSH2_API int libssh2_channel_wait_closed(LIBSSH2_CHANNEL *channel); +LIBSSH2_API int libssh2_channel_free(LIBSSH2_CHANNEL *channel); + +LIBSSH2_API LIBSSH2_CHANNEL *libssh2_scp_recv(LIBSSH2_SESSION *session, + const char *path, + struct stat *sb); +LIBSSH2_API LIBSSH2_CHANNEL *libssh2_scp_send_ex(LIBSSH2_SESSION *session, + const char *path, int mode, + size_t size, long mtime, + long atime); +LIBSSH2_API LIBSSH2_CHANNEL * +libssh2_scp_send64(LIBSSH2_SESSION *session, const char *path, int mode, + libssh2_int64_t size, time_t mtime, time_t atime); + +#define libssh2_scp_send(session, path, mode, size) \ + libssh2_scp_send_ex((session), (path), (mode), (size), 0, 0) + +LIBSSH2_API int libssh2_base64_decode(LIBSSH2_SESSION *session, char **dest, + unsigned int *dest_len, + const char *src, unsigned int src_len); + +LIBSSH2_API +const char *libssh2_version(int req_version_num); + +#define HAVE_LIBSSH2_KNOWNHOST_API 0x010101 /* since 1.1.1 */ +#define HAVE_LIBSSH2_VERSION_API 0x010100 /* libssh2_version since 1.1 */ + +struct libssh2_knownhost { + unsigned int magic; /* magic stored by the library */ + void *node; /* handle to the internal representation of this host */ + char *name; /* this is NULL if no plain text host name exists */ + char *key; /* key in base64/printable format */ + int typemask; +}; + +/* + * libssh2_knownhost_init + * + * Init a collection of known hosts. Returns the pointer to a collection. + * + */ +LIBSSH2_API LIBSSH2_KNOWNHOSTS * +libssh2_knownhost_init(LIBSSH2_SESSION *session); + +/* + * libssh2_knownhost_add + * + * Add a host and its associated key to the collection of known hosts. + * + * The 'type' argument specifies on what format the given host and keys are: + * + * plain - ascii "hostname.domain.tld" + * sha1 - SHA1( ) base64-encoded! + * custom - another hash + * + * If 'sha1' is selected as type, the salt must be provided to the salt + * argument. This too base64 encoded. + * + * The SHA-1 hash is what OpenSSH can be told to use in known_hosts files. If + * a custom type is used, salt is ignored and you must provide the host + * pre-hashed when checking for it in the libssh2_knownhost_check() function. + * + * The keylen parameter may be omitted (zero) if the key is provided as a + * NULL-terminated base64-encoded string. + */ + +/* host format (2 bits) */ +#define LIBSSH2_KNOWNHOST_TYPE_MASK 0xffff +#define LIBSSH2_KNOWNHOST_TYPE_PLAIN 1 +#define LIBSSH2_KNOWNHOST_TYPE_SHA1 2 /* always base64 encoded */ +#define LIBSSH2_KNOWNHOST_TYPE_CUSTOM 3 + +/* key format (2 bits) */ +#define LIBSSH2_KNOWNHOST_KEYENC_MASK (3<<16) +#define LIBSSH2_KNOWNHOST_KEYENC_RAW (1<<16) +#define LIBSSH2_KNOWNHOST_KEYENC_BASE64 (2<<16) + +/* type of key (2 bits) */ +#define LIBSSH2_KNOWNHOST_KEY_MASK (3<<18) +#define LIBSSH2_KNOWNHOST_KEY_SHIFT 18 +#define LIBSSH2_KNOWNHOST_KEY_RSA1 (1<<18) +#define LIBSSH2_KNOWNHOST_KEY_SSHRSA (2<<18) +#define LIBSSH2_KNOWNHOST_KEY_SSHDSS (3<<18) + +LIBSSH2_API int +libssh2_knownhost_add(LIBSSH2_KNOWNHOSTS *hosts, + const char *host, + const char *salt, + const char *key, size_t keylen, int typemask, + struct libssh2_knownhost **store); + +/* + * libssh2_knownhost_addc + * + * Add a host and its associated key to the collection of known hosts. + * + * Takes a comment argument that may be NULL. A NULL comment indicates + * there is no comment and the entry will end directly after the key + * when written out to a file. An empty string "" comment will indicate an + * empty comment which will cause a single space to be written after the key. + * + * The 'type' argument specifies on what format the given host and keys are: + * + * plain - ascii "hostname.domain.tld" + * sha1 - SHA1( ) base64-encoded! + * custom - another hash + * + * If 'sha1' is selected as type, the salt must be provided to the salt + * argument. This too base64 encoded. + * + * The SHA-1 hash is what OpenSSH can be told to use in known_hosts files. If + * a custom type is used, salt is ignored and you must provide the host + * pre-hashed when checking for it in the libssh2_knownhost_check() function. + * + * The keylen parameter may be omitted (zero) if the key is provided as a + * NULL-terminated base64-encoded string. + */ + +LIBSSH2_API int +libssh2_knownhost_addc(LIBSSH2_KNOWNHOSTS *hosts, + const char *host, + const char *salt, + const char *key, size_t keylen, + const char *comment, size_t commentlen, int typemask, + struct libssh2_knownhost **store); + +/* + * libssh2_knownhost_check + * + * Check a host and its associated key against the collection of known hosts. + * + * The type is the type/format of the given host name. + * + * plain - ascii "hostname.domain.tld" + * custom - prehashed base64 encoded. Note that this cannot use any salts. + * + * + * 'knownhost' may be set to NULL if you don't care about that info. + * + * Returns: + * + * LIBSSH2_KNOWNHOST_CHECK_* values, see below + * + */ + +#define LIBSSH2_KNOWNHOST_CHECK_MATCH 0 +#define LIBSSH2_KNOWNHOST_CHECK_MISMATCH 1 +#define LIBSSH2_KNOWNHOST_CHECK_NOTFOUND 2 +#define LIBSSH2_KNOWNHOST_CHECK_FAILURE 3 + +LIBSSH2_API int +libssh2_knownhost_check(LIBSSH2_KNOWNHOSTS *hosts, + const char *host, const char *key, size_t keylen, + int typemask, + struct libssh2_knownhost **knownhost); + +/* this function is identital to the above one, but also takes a port + argument that allows libssh2 to do a better check */ +LIBSSH2_API int +libssh2_knownhost_checkp(LIBSSH2_KNOWNHOSTS *hosts, + const char *host, int port, + const char *key, size_t keylen, + int typemask, + struct libssh2_knownhost **knownhost); + +/* + * libssh2_knownhost_del + * + * Remove a host from the collection of known hosts. The 'entry' struct is + * retrieved by a call to libssh2_knownhost_check(). + * + */ +LIBSSH2_API int +libssh2_knownhost_del(LIBSSH2_KNOWNHOSTS *hosts, + struct libssh2_knownhost *entry); + +/* + * libssh2_knownhost_free + * + * Free an entire collection of known hosts. + * + */ +LIBSSH2_API void +libssh2_knownhost_free(LIBSSH2_KNOWNHOSTS *hosts); + +/* + * libssh2_knownhost_readline() + * + * Pass in a line of a file of 'type'. It makes libssh2 read this line. + * + * LIBSSH2_KNOWNHOST_FILE_OPENSSH is the only supported type. + * + */ +LIBSSH2_API int +libssh2_knownhost_readline(LIBSSH2_KNOWNHOSTS *hosts, + const char *line, size_t len, int type); + +/* + * libssh2_knownhost_readfile + * + * Add hosts+key pairs from a given file. + * + * Returns a negative value for error or number of successfully added hosts. + * + * This implementation currently only knows one 'type' (openssh), all others + * are reserved for future use. + */ + +#define LIBSSH2_KNOWNHOST_FILE_OPENSSH 1 + +LIBSSH2_API int +libssh2_knownhost_readfile(LIBSSH2_KNOWNHOSTS *hosts, + const char *filename, int type); + +/* + * libssh2_knownhost_writeline() + * + * Ask libssh2 to convert a known host to an output line for storage. + * + * Note that this function returns LIBSSH2_ERROR_BUFFER_TOO_SMALL if the given + * output buffer is too small to hold the desired output. + * + * This implementation currently only knows one 'type' (openssh), all others + * are reserved for future use. + * + */ +LIBSSH2_API int +libssh2_knownhost_writeline(LIBSSH2_KNOWNHOSTS *hosts, + struct libssh2_knownhost *known, + char *buffer, size_t buflen, + size_t *outlen, /* the amount of written data */ + int type); + +/* + * libssh2_knownhost_writefile + * + * Write hosts+key pairs to a given file. + * + * This implementation currently only knows one 'type' (openssh), all others + * are reserved for future use. + */ + +LIBSSH2_API int +libssh2_knownhost_writefile(LIBSSH2_KNOWNHOSTS *hosts, + const char *filename, int type); + +/* + * libssh2_knownhost_get() + * + * Traverse the internal list of known hosts. Pass NULL to 'prev' to get + * the first one. Or pass a poiner to the previously returned one to get the + * next. + * + * Returns: + * 0 if a fine host was stored in 'store' + * 1 if end of hosts + * [negative] on errors + */ +LIBSSH2_API int +libssh2_knownhost_get(LIBSSH2_KNOWNHOSTS *hosts, + struct libssh2_knownhost **store, + struct libssh2_knownhost *prev); + +#define HAVE_LIBSSH2_AGENT_API 0x010202 /* since 1.2.2 */ + +struct libssh2_agent_publickey { + unsigned int magic; /* magic stored by the library */ + void *node; /* handle to the internal representation of key */ + unsigned char *blob; /* public key blob */ + size_t blob_len; /* length of the public key blob */ + char *comment; /* comment in printable format */ +}; + +/* + * libssh2_agent_init + * + * Init an ssh-agent handle. Returns the pointer to the handle. + * + */ +LIBSSH2_API LIBSSH2_AGENT * +libssh2_agent_init(LIBSSH2_SESSION *session); + +/* + * libssh2_agent_connect() + * + * Connect to an ssh-agent. + * + * Returns 0 if succeeded, or a negative value for error. + */ +LIBSSH2_API int +libssh2_agent_connect(LIBSSH2_AGENT *agent); + +/* + * libssh2_agent_list_identities() + * + * Request an ssh-agent to list identities. + * + * Returns 0 if succeeded, or a negative value for error. + */ +LIBSSH2_API int +libssh2_agent_list_identities(LIBSSH2_AGENT *agent); + +/* + * libssh2_agent_get_identity() + * + * Traverse the internal list of public keys. Pass NULL to 'prev' to get + * the first one. Or pass a poiner to the previously returned one to get the + * next. + * + * Returns: + * 0 if a fine public key was stored in 'store' + * 1 if end of public keys + * [negative] on errors + */ +LIBSSH2_API int +libssh2_agent_get_identity(LIBSSH2_AGENT *agent, + struct libssh2_agent_publickey **store, + struct libssh2_agent_publickey *prev); + +/* + * libssh2_agent_userauth() + * + * Do publickey user authentication with the help of ssh-agent. + * + * Returns 0 if succeeded, or a negative value for error. + */ +LIBSSH2_API int +libssh2_agent_userauth(LIBSSH2_AGENT *agent, + const char *username, + struct libssh2_agent_publickey *identity); + +/* + * libssh2_agent_disconnect() + * + * Close a connection to an ssh-agent. + * + * Returns 0 if succeeded, or a negative value for error. + */ +LIBSSH2_API int +libssh2_agent_disconnect(LIBSSH2_AGENT *agent); + +/* + * libssh2_agent_free() + * + * Free an ssh-agent handle. This function also frees the internal + * collection of public keys. + */ +LIBSSH2_API void +libssh2_agent_free(LIBSSH2_AGENT *agent); + + +/* + * libssh2_keepalive_config() + * + * Set how often keepalive messages should be sent. WANT_REPLY + * indicates whether the keepalive messages should request a response + * from the server. INTERVAL is number of seconds that can pass + * without any I/O, use 0 (the default) to disable keepalives. To + * avoid some busy-loop corner-cases, if you specify an interval of 1 + * it will be treated as 2. + * + * Note that non-blocking applications are responsible for sending the + * keepalive messages using libssh2_keepalive_send(). + */ +LIBSSH2_API void libssh2_keepalive_config (LIBSSH2_SESSION *session, + int want_reply, + unsigned interval); + +/* + * libssh2_keepalive_send() + * + * Send a keepalive message if needed. SECONDS_TO_NEXT indicates how + * many seconds you can sleep after this call before you need to call + * it again. Returns 0 on success, or LIBSSH2_ERROR_SOCKET_SEND on + * I/O errors. + */ +LIBSSH2_API int libssh2_keepalive_send (LIBSSH2_SESSION *session, + int *seconds_to_next); + +/* NOTE NOTE NOTE + libssh2_trace() has no function in builds that aren't built with debug + enabled + */ +LIBSSH2_API int libssh2_trace(LIBSSH2_SESSION *session, int bitmask); +#define LIBSSH2_TRACE_TRANS (1<<1) +#define LIBSSH2_TRACE_KEX (1<<2) +#define LIBSSH2_TRACE_AUTH (1<<3) +#define LIBSSH2_TRACE_CONN (1<<4) +#define LIBSSH2_TRACE_SCP (1<<5) +#define LIBSSH2_TRACE_SFTP (1<<6) +#define LIBSSH2_TRACE_ERROR (1<<7) +#define LIBSSH2_TRACE_PUBLICKEY (1<<8) +#define LIBSSH2_TRACE_SOCKET (1<<9) + +typedef void (*libssh2_trace_handler_func)(LIBSSH2_SESSION*, + void*, + const char *, + size_t); +LIBSSH2_API int libssh2_trace_sethandler(LIBSSH2_SESSION *session, + void* context, + libssh2_trace_handler_func callback); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* !RC_INVOKED */ + +#endif /* LIBSSH2_H */ diff --git a/libssh2/include/libssh2_config.h b/libssh2/include/libssh2_config.h new file mode 100644 index 0000000..4d0ca7d --- /dev/null +++ b/libssh2/include/libssh2_config.h @@ -0,0 +1,232 @@ +/* src/libssh2_config.h. Generated from libssh2_config.h.in by configure. */ +/* src/libssh2_config.h.in. Generated from configure.ac by autoheader. */ + +/* Define if building universal (internal helper macro) */ +/* #undef AC_APPLE_UNIVERSAL_BUILD */ + +/* Define to one of `_getb67', `GETB67', `getb67' for Cray-2 and Cray-YMP + systems. This function is required for `alloca.c' support on those systems. + */ +/* #undef CRAY_STACKSEG_END */ + +/* Define to 1 if using `alloca.c'. */ +/* #undef C_ALLOCA */ + +/* Define to 1 if you have `alloca', as a function or macro. */ +#define HAVE_ALLOCA 1 + +/* Define to 1 if you have and it should be used (not on Ultrix). + */ +#define HAVE_ALLOCA_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_ARPA_INET_H 1 + +/* disabled non-blocking sockets */ +/* #undef HAVE_DISABLED_NONBLOCKING */ + +/* Define to 1 if you have the header file. */ +#define HAVE_DLFCN_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_ERRNO_H 1 + +/* Define to 1 if you have the `EVP_aes_128_ctr' function. */ +#define HAVE_EVP_AES_128_CTR 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_FCNTL_H 1 + +/* use FIONBIO for non-blocking sockets */ +/* #undef HAVE_FIONBIO */ + +/* Define to 1 if you have the `gettimeofday' function. */ +#define HAVE_GETTIMEOFDAY 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* use ioctlsocket() for non-blocking sockets */ +/* #undef HAVE_IOCTLSOCKET */ + +/* use Ioctlsocket() for non-blocking sockets */ +/* #undef HAVE_IOCTLSOCKET_CASE */ + +/* Define if you have the gcrypt library. */ +/* #undef HAVE_LIBGCRYPT */ + +/* Define if you have the ssl library. */ +#define HAVE_LIBSSL 1 + +/* Define if you have the z library. */ +#define HAVE_LIBZ 1 + +/* Define to 1 if the compiler supports the 'long long' data type. */ +#define HAVE_LONGLONG 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_NETINET_IN_H 1 + +/* use O_NONBLOCK for non-blocking sockets */ +#define HAVE_O_NONBLOCK 1 + +/* Define to 1 if you have the `poll' function. */ +/* #undef HAVE_POLL */ + +/* Define to 1 if you have the select function. */ +#define HAVE_SELECT 1 + +/* use SO_NONBLOCK for non-blocking sockets */ +/* #undef HAVE_SO_NONBLOCK */ + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDIO_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the `strtoll' function. */ +#define HAVE_STRTOLL 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_IOCTL_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SELECT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SOCKET_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TIME_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_UIO_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_UN_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_UNISTD_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_WINDOWS_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_WINSOCK2_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_WS2TCPIP_H */ + +/* to make a symbol visible */ +/* #undef LIBSSH2_API */ + +/* Enable "none" cipher -- NOT RECOMMENDED */ +/* #undef LIBSSH2_CRYPT_NONE */ + +/* Enable newer diffie-hellman-group-exchange-sha1 syntax */ +#define LIBSSH2_DH_GEX_NEW 1 + +/* Compile in zlib support */ +#define LIBSSH2_HAVE_ZLIB 1 + +/* Use libgcrypt */ +/* #undef LIBSSH2_LIBGCRYPT */ + +/* Enable "none" MAC -- NOT RECOMMENDED */ +/* #undef LIBSSH2_MAC_NONE */ + +/* Define to the sub-directory in which libtool stores uninstalled libraries. + */ +#define LT_OBJDIR ".libs/" + +/* Define to 1 if _REENTRANT preprocessor symbol must be defined. */ +/* #undef NEED_REENTRANT */ + +/* Name of package */ +#define PACKAGE "libssh2" + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "libssh2-devel@cool.haxx.se" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "libssh2" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "libssh2 -" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "libssh2" + +/* Define to the home page for this package. */ +#define PACKAGE_URL "" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "-" + +/* If using the C implementation of alloca, define if you know the + direction of stack growth for your system; otherwise it will be + automatically deduced at runtime. + STACK_DIRECTION > 0 => grows toward higher addresses + STACK_DIRECTION < 0 => grows toward lower addresses + STACK_DIRECTION = 0 => direction of growth unknown */ +/* #undef STACK_DIRECTION */ + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Version number of package */ +#define VERSION "-" + +/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most + significant byte first (like Motorola and SPARC, unlike Intel). */ +#if defined AC_APPLE_UNIVERSAL_BUILD +# if defined __BIG_ENDIAN__ +# define WORDS_BIGENDIAN 1 +# endif +#else +# ifndef WORDS_BIGENDIAN +/* # undef WORDS_BIGENDIAN */ +# endif +#endif + +/* Enable large inode numbers on Mac OS X 10.5. */ +#ifndef _DARWIN_USE_64_BIT_INODE +# define _DARWIN_USE_64_BIT_INODE 1 +#endif + +/* Number of bits in a file offset, on hosts where this is settable. */ +/* #undef _FILE_OFFSET_BITS */ + +/* Define for large files, on AIX-style hosts. */ +/* #undef _LARGE_FILES */ + +/* Define to empty if `const' does not conform to ANSI C. */ +/* #undef const */ + +/* Define to `__inline__' or `__inline' if that's what the C compiler + calls it, or to nothing if 'inline' is not supported under any name. */ +#ifndef __cplusplus +/* #undef inline */ +#endif + +/* Define to `unsigned int' if does not define. */ +/* #undef size_t */ diff --git a/libssh2/include/libssh2_priv.h b/libssh2/include/libssh2_priv.h new file mode 100644 index 0000000..4ec9f73 --- /dev/null +++ b/libssh2/include/libssh2_priv.h @@ -0,0 +1,1040 @@ +/* Copyright (c) 2004-2008, 2010, Sara Golemon + * Copyright (c) 2009-2011 by Daniel Stenberg + * Copyright (c) 2010 Simon Josefsson + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#ifndef LIBSSH2_PRIV_H +#define LIBSSH2_PRIV_H 1 + +#define LIBSSH2_LIBRARY +#include "libssh2_config.h" + +#ifdef HAVE_WINDOWS_H +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#undef WIN32_LEAN_AND_MEAN +#endif + +#ifdef HAVE_WS2TCPIP_H +#include +#endif + +#include +#include + +/* The following CPP block should really only be in session.c and + packet.c. However, AIX have #define's for 'events' and 'revents' + and we are using those names in libssh2.h, so we need to include + the AIX headers first, to make sure all code is compiled with + consistent names of these fields. While arguable the best would to + change libssh2.h to use other names, that would break backwards + compatibility. For more information, see: + http://www.mail-archive.com/libssh2-devel%40lists.sourceforge.net/msg00003.html + http://www.mail-archive.com/libssh2-devel%40lists.sourceforge.net/msg00224.html +*/ +#ifdef HAVE_POLL +# include +#else +# if defined(HAVE_SELECT) && !defined(WIN32) +# ifdef HAVE_SYS_SELECT_H +# include +# else +# include +# include +# endif +# endif +#endif + +/* Needed for struct iovec on some platforms */ +#ifdef HAVE_SYS_UIO_H +#include +#endif + +#ifdef HAVE_SYS_SOCKET_H +# include +#endif +#ifdef HAVE_SYS_IOCTL_H +# include +#endif +#ifdef HAVE_INTTYPES_H +#include +#endif + +#include "libssh2.h" +#include "libssh2_publickey.h" +#include "libssh2_sftp.h" +#include "misc.h" /* for the linked list stuff */ + +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef TRUE +#define TRUE 1 +#endif + +/* Provide iovec / writev on WIN32 platform. */ +#ifdef WIN32 + +struct iovec { + size_t iov_len; + void * iov_base; +}; + +#define inline __inline + +static inline int writev(int sock, struct iovec *iov, int nvecs) +{ + DWORD ret; + if (WSASend(sock, (LPWSABUF)iov, nvecs, &ret, 0, NULL, NULL) == 0) { + return ret; + } + return -1; +} + +#endif /* WIN32 */ + +#include "crypto.h" + +#ifdef HAVE_WINSOCK2_H + +#include +#include +#include + +#ifdef _MSC_VER +/* "inline" keyword is valid only with C++ engine! */ +#define inline __inline +#endif + +#endif + +/* RFC4253 section 6.1 Maximum Packet Length says: + * + * "All implementations MUST be able to process packets with + * uncompressed payload length of 32768 bytes or less and + * total packet size of 35000 bytes or less (including length, + * padding length, payload, padding, and MAC.)." + */ +#define MAX_SSH_PACKET_LEN 35000 + +#define LIBSSH2_ALLOC(session, count) \ + session->alloc((count), &(session)->abstract) +#define LIBSSH2_REALLOC(session, ptr, count) \ + ((ptr) ? session->realloc((ptr), (count), &(session)->abstract) : \ + session->alloc((count), &(session)->abstract)) +#define LIBSSH2_FREE(session, ptr) \ + session->free((ptr), &(session)->abstract) +#define LIBSSH2_IGNORE(session, data, datalen) \ + session->ssh_msg_ignore((session), (data), (datalen), &(session)->abstract) +#define LIBSSH2_DEBUG(session, always_display, message, message_len, \ + language, language_len) \ + session->ssh_msg_debug((session), (always_display), (message), \ + (message_len), (language), (language_len), \ + &(session)->abstract) +#define LIBSSH2_DISCONNECT(session, reason, message, message_len, \ + language, language_len) \ + session->ssh_msg_disconnect((session), (reason), (message), \ + (message_len), (language), (language_len), \ + &(session)->abstract) + +#define LIBSSH2_MACERROR(session, data, datalen) \ + session->macerror((session), (data), (datalen), &(session)->abstract) +#define LIBSSH2_X11_OPEN(channel, shost, sport) \ + channel->session->x11(((channel)->session), (channel), \ + (shost), (sport), (&(channel)->session->abstract)) + +#define LIBSSH2_CHANNEL_CLOSE(session, channel) \ + channel->close_cb((session), &(session)->abstract, \ + (channel), &(channel)->abstract) + +#define LIBSSH2_SEND_FD(session, fd, buffer, length, flags) \ + session->send(fd, buffer, length, flags, &session->abstract) +#define LIBSSH2_RECV_FD(session, fd, buffer, length, flags) \ + session->recv(fd, buffer, length, flags, &session->abstract) + +#define LIBSSH2_SEND(session, buffer, length, flags) \ + LIBSSH2_SEND_FD(session, session->socket_fd, buffer, length, flags) +#define LIBSSH2_RECV(session, buffer, length, flags) \ + LIBSSH2_RECV_FD(session, session->socket_fd, buffer, length, flags) + +typedef struct _LIBSSH2_KEX_METHOD LIBSSH2_KEX_METHOD; +typedef struct _LIBSSH2_HOSTKEY_METHOD LIBSSH2_HOSTKEY_METHOD; +typedef struct _LIBSSH2_CRYPT_METHOD LIBSSH2_CRYPT_METHOD; +typedef struct _LIBSSH2_COMP_METHOD LIBSSH2_COMP_METHOD; + +typedef struct _LIBSSH2_PACKET LIBSSH2_PACKET; + +typedef enum +{ + libssh2_NB_state_idle = 0, + libssh2_NB_state_allocated, + libssh2_NB_state_created, + libssh2_NB_state_sent, + libssh2_NB_state_sent1, + libssh2_NB_state_sent2, + libssh2_NB_state_sent3, + libssh2_NB_state_sent4, + libssh2_NB_state_sent5, + libssh2_NB_state_sent6, + libssh2_NB_state_sent7, + libssh2_NB_state_jump1, + libssh2_NB_state_jump2, + libssh2_NB_state_jump3, + libssh2_NB_state_jump4, + libssh2_NB_state_jump5 +} libssh2_nonblocking_states; + +typedef struct packet_require_state_t +{ + libssh2_nonblocking_states state; + time_t start; +} packet_require_state_t; + +typedef struct packet_requirev_state_t +{ + time_t start; +} packet_requirev_state_t; + +typedef struct kmdhgGPsha1kex_state_t +{ + libssh2_nonblocking_states state; + unsigned char *e_packet; + unsigned char *s_packet; + unsigned char *tmp; + unsigned char h_sig_comp[SHA_DIGEST_LENGTH]; + unsigned char c; + size_t e_packet_len; + size_t s_packet_len; + size_t tmp_len; + _libssh2_bn_ctx *ctx; + _libssh2_bn *x; + _libssh2_bn *e; + _libssh2_bn *f; + _libssh2_bn *k; + unsigned char *s; + unsigned char *f_value; + unsigned char *k_value; + unsigned char *h_sig; + size_t f_value_len; + size_t k_value_len; + size_t h_sig_len; + libssh2_sha1_ctx exchange_hash; + packet_require_state_t req_state; + libssh2_nonblocking_states burn_state; +} kmdhgGPsha1kex_state_t; + +typedef struct key_exchange_state_low_t +{ + libssh2_nonblocking_states state; + packet_require_state_t req_state; + kmdhgGPsha1kex_state_t exchange_state; + _libssh2_bn *p; /* SSH2 defined value (p_value) */ + _libssh2_bn *g; /* SSH2 defined value (2) */ + unsigned char request[13]; + unsigned char *data; + size_t request_len; + size_t data_len; +} key_exchange_state_low_t; + +typedef struct key_exchange_state_t +{ + libssh2_nonblocking_states state; + packet_require_state_t req_state; + key_exchange_state_low_t key_state_low; + unsigned char *data; + size_t data_len; + unsigned char *oldlocal; + size_t oldlocal_len; +} key_exchange_state_t; + +#define FwdNotReq "Forward not requested" + +typedef struct packet_queue_listener_state_t +{ + libssh2_nonblocking_states state; + unsigned char packet[17 + (sizeof(FwdNotReq) - 1)]; + unsigned char *host; + unsigned char *shost; + uint32_t sender_channel; + uint32_t initial_window_size; + uint32_t packet_size; + uint32_t port; + uint32_t sport; + uint32_t host_len; + uint32_t shost_len; + LIBSSH2_CHANNEL *channel; +} packet_queue_listener_state_t; + +#define X11FwdUnAvil "X11 Forward Unavailable" + +typedef struct packet_x11_open_state_t +{ + libssh2_nonblocking_states state; + unsigned char packet[17 + (sizeof(X11FwdUnAvil) - 1)]; + unsigned char *shost; + uint32_t sender_channel; + uint32_t initial_window_size; + uint32_t packet_size; + uint32_t sport; + uint32_t shost_len; + LIBSSH2_CHANNEL *channel; +} packet_x11_open_state_t; + +struct _LIBSSH2_PACKET +{ + struct list_node node; /* linked list header */ + + /* the raw unencrypted payload */ + unsigned char *data; + size_t data_len; + + /* Where to start reading data from, + * used for channel data that's been partially consumed */ + size_t data_head; +}; + +typedef struct _libssh2_channel_data +{ + /* Identifier */ + uint32_t id; + + /* Limits and restrictions */ + uint32_t window_size_initial, window_size, packet_size; + + /* Set to 1 when CHANNEL_CLOSE / CHANNEL_EOF sent/received */ + char close, eof, extended_data_ignore_mode; +} libssh2_channel_data; + +struct _LIBSSH2_CHANNEL +{ + struct list_node node; + + unsigned char *channel_type; + unsigned channel_type_len; + + /* channel's program exit status */ + int exit_status; + + /* channel's program exit signal (without the SIG prefix) */ + char *exit_signal; + + libssh2_channel_data local, remote; + /* Amount of bytes to be refunded to receive window (but not yet sent) */ + uint32_t adjust_queue; + + LIBSSH2_SESSION *session; + + void *abstract; + LIBSSH2_CHANNEL_CLOSE_FUNC((*close_cb)); + + /* State variables used in libssh2_channel_setenv_ex() */ + libssh2_nonblocking_states setenv_state; + unsigned char *setenv_packet; + size_t setenv_packet_len; + unsigned char setenv_local_channel[4]; + packet_requirev_state_t setenv_packet_requirev_state; + + /* State variables used in libssh2_channel_request_pty_ex() + libssh2_channel_request_pty_size_ex() */ + libssh2_nonblocking_states reqPTY_state; + unsigned char reqPTY_packet[41 + 256]; + size_t reqPTY_packet_len; + unsigned char reqPTY_local_channel[4]; + packet_requirev_state_t reqPTY_packet_requirev_state; + + /* State variables used in libssh2_channel_x11_req_ex() */ + libssh2_nonblocking_states reqX11_state; + unsigned char *reqX11_packet; + size_t reqX11_packet_len; + unsigned char reqX11_local_channel[4]; + packet_requirev_state_t reqX11_packet_requirev_state; + + /* State variables used in libssh2_channel_process_startup() */ + libssh2_nonblocking_states process_state; + unsigned char *process_packet; + size_t process_packet_len; + unsigned char process_local_channel[4]; + packet_requirev_state_t process_packet_requirev_state; + + /* State variables used in libssh2_channel_flush_ex() */ + libssh2_nonblocking_states flush_state; + size_t flush_refund_bytes; + size_t flush_flush_bytes; + + /* State variables used in libssh2_channel_receive_window_adjust() */ + libssh2_nonblocking_states adjust_state; + unsigned char adjust_adjust[9]; /* packet_type(1) + channel(4) + adjustment(4) */ + + /* State variables used in libssh2_channel_read_ex() */ + libssh2_nonblocking_states read_state; + + uint32_t read_local_id; + + /* State variables used in libssh2_channel_write_ex() */ + libssh2_nonblocking_states write_state; + unsigned char write_packet[13]; + size_t write_packet_len; + size_t write_bufwrite; + + /* State variables used in libssh2_channel_close() */ + libssh2_nonblocking_states close_state; + unsigned char close_packet[5]; + + /* State variables used in libssh2_channel_wait_closedeof() */ + libssh2_nonblocking_states wait_eof_state; + + /* State variables used in libssh2_channel_wait_closed() */ + libssh2_nonblocking_states wait_closed_state; + + /* State variables used in libssh2_channel_free() */ + libssh2_nonblocking_states free_state; + + /* State variables used in libssh2_channel_handle_extended_data2() */ + libssh2_nonblocking_states extData2_state; + +}; + +struct _LIBSSH2_LISTENER +{ + struct list_node node; /* linked list header */ + + LIBSSH2_SESSION *session; + + char *host; + int port; + + /* a list of CHANNELs for this listener */ + struct list_head queue; + + int queue_size; + int queue_maxsize; + + /* State variables used in libssh2_channel_forward_cancel() */ + libssh2_nonblocking_states chanFwdCncl_state; + unsigned char *chanFwdCncl_data; + size_t chanFwdCncl_data_len; +}; + +typedef struct _libssh2_endpoint_data +{ + unsigned char *banner; + + unsigned char *kexinit; + size_t kexinit_len; + + const LIBSSH2_CRYPT_METHOD *crypt; + void *crypt_abstract; + + const struct _LIBSSH2_MAC_METHOD *mac; + uint32_t seqno; + void *mac_abstract; + + const LIBSSH2_COMP_METHOD *comp; + void *comp_abstract; + + /* Method Preferences -- NULL yields "load order" */ + char *crypt_prefs; + char *mac_prefs; + char *comp_prefs; + char *lang_prefs; +} libssh2_endpoint_data; + +#define PACKETBUFSIZE (1024*16) + +struct transportpacket +{ + /* ------------- for incoming data --------------- */ + unsigned char buf[PACKETBUFSIZE]; + unsigned char init[5]; /* first 5 bytes of the incoming data stream, + still encrypted */ + size_t writeidx; /* at what array index we do the next write into + the buffer */ + size_t readidx; /* at what array index we do the next read from + the buffer */ + uint32_t packet_length; /* the most recent packet_length as read from the + network data */ + uint8_t padding_length; /* the most recent padding_length as read from the + network data */ + size_t data_num; /* How much of the total package that has been read + so far. */ + size_t total_num; /* How much a total package is supposed to be, in + number of bytes. A full package is + packet_length + padding_length + 4 + + mac_length. */ + unsigned char *payload; /* this is a pointer to a LIBSSH2_ALLOC() + area to which we write decrypted data */ + unsigned char *wptr; /* write pointer into the payload to where we + are currently writing decrypted data */ + + /* ------------- for outgoing data --------------- */ + unsigned char outbuf[MAX_SSH_PACKET_LEN]; /* area for the outgoing data */ + + int ototal_num; /* size of outbuf in number of bytes */ + const unsigned char *odata; /* original pointer to the data */ + size_t olen; /* original size of the data we stored in + outbuf */ + size_t osent; /* number of bytes already sent */ +}; + +struct _LIBSSH2_PUBLICKEY +{ + LIBSSH2_CHANNEL *channel; + uint32_t version; + + /* State variables used in libssh2_publickey_packet_receive() */ + libssh2_nonblocking_states receive_state; + unsigned char *receive_packet; + size_t receive_packet_len; + + /* State variables used in libssh2_publickey_add_ex() */ + libssh2_nonblocking_states add_state; + unsigned char *add_packet; + unsigned char *add_s; + + /* State variables used in libssh2_publickey_remove_ex() */ + libssh2_nonblocking_states remove_state; + unsigned char *remove_packet; + unsigned char *remove_s; + + /* State variables used in libssh2_publickey_list_fetch() */ + libssh2_nonblocking_states listFetch_state; + unsigned char *listFetch_s; + unsigned char listFetch_buffer[12]; + unsigned char *listFetch_data; + size_t listFetch_data_len; +}; + +#define LIBSSH2_SCP_RESPONSE_BUFLEN 256 + +struct flags { + int sigpipe; /* LIBSSH2_FLAG_SIGPIPE */ + int compress; /* LIBSSH2_FLAG_COMPRESS */ +}; + +struct _LIBSSH2_SESSION +{ + /* Memory management callbacks */ + void *abstract; + LIBSSH2_ALLOC_FUNC((*alloc)); + LIBSSH2_REALLOC_FUNC((*realloc)); + LIBSSH2_FREE_FUNC((*free)); + + /* Other callbacks */ + LIBSSH2_IGNORE_FUNC((*ssh_msg_ignore)); + LIBSSH2_DEBUG_FUNC((*ssh_msg_debug)); + LIBSSH2_DISCONNECT_FUNC((*ssh_msg_disconnect)); + LIBSSH2_MACERROR_FUNC((*macerror)); + LIBSSH2_X11_OPEN_FUNC((*x11)); + LIBSSH2_SEND_FUNC((*send)); + LIBSSH2_RECV_FUNC((*recv)); + + /* Method preferences -- NULL yields "load order" */ + char *kex_prefs; + char *hostkey_prefs; + + int state; + + /* Flag options */ + struct flags flag; + + /* Agreed Key Exchange Method */ + const LIBSSH2_KEX_METHOD *kex; + int burn_optimistic_kexinit:1; + + unsigned char *session_id; + uint32_t session_id_len; + + /* this is set to TRUE if a blocking API behavior is requested */ + int api_block_mode; + + /* Timeout used when blocking API behavior is active */ + long api_timeout; + + /* Server's public key */ + const LIBSSH2_HOSTKEY_METHOD *hostkey; + void *server_hostkey_abstract; + + /* Either set with libssh2_session_hostkey() (for server mode) + * Or read from server in (eg) KEXDH_INIT (for client mode) + */ + unsigned char *server_hostkey; + uint32_t server_hostkey_len; +#if LIBSSH2_MD5 + unsigned char server_hostkey_md5[MD5_DIGEST_LENGTH]; + int server_hostkey_md5_valid; +#endif /* ! LIBSSH2_MD5 */ + unsigned char server_hostkey_sha1[SHA_DIGEST_LENGTH]; + + /* (remote as source of data -- packet_read ) */ + libssh2_endpoint_data remote; + + /* (local as source of data -- packet_write ) */ + libssh2_endpoint_data local; + + /* Inbound Data linked list -- Sometimes the packet that comes in isn't the + packet we're ready for */ + struct list_head packets; + + /* Active connection channels */ + struct list_head channels; + + uint32_t next_channel; + + struct list_head listeners; /* list of LIBSSH2_LISTENER structs */ + + /* Actual I/O socket */ + libssh2_socket_t socket_fd; + int socket_state; + int socket_block_directions; + int socket_prev_blockstate; /* stores the state of the socket blockiness + when libssh2_session_startup() is called */ + + /* Error tracking */ + const char *err_msg; + int err_code; + + /* struct members for packet-level reading */ + struct transportpacket packet; +#ifdef LIBSSH2DEBUG + int showmask; /* what debug/trace messages to display */ + libssh2_trace_handler_func tracehandler; /* callback to display trace messages */ + void* tracehandler_context; /* context for the trace handler */ +#endif + + /* State variables used in libssh2_banner_send() */ + libssh2_nonblocking_states banner_TxRx_state; + char banner_TxRx_banner[256]; + ssize_t banner_TxRx_total_send; + + /* State variables used in libssh2_kexinit() */ + libssh2_nonblocking_states kexinit_state; + unsigned char *kexinit_data; + size_t kexinit_data_len; + + /* State variables used in libssh2_session_startup() */ + libssh2_nonblocking_states startup_state; + unsigned char *startup_data; + size_t startup_data_len; + unsigned char startup_service[sizeof("ssh-userauth") + 5 - 1]; + size_t startup_service_length; + packet_require_state_t startup_req_state; + key_exchange_state_t startup_key_state; + + /* State variables used in libssh2_session_free() */ + libssh2_nonblocking_states free_state; + + /* State variables used in libssh2_session_disconnect_ex() */ + libssh2_nonblocking_states disconnect_state; + unsigned char disconnect_data[256 + 13]; + size_t disconnect_data_len; + + /* State variables used in libssh2_packet_read() */ + libssh2_nonblocking_states readPack_state; + int readPack_encrypted; + + /* State variables used in libssh2_userauth_list() */ + libssh2_nonblocking_states userauth_list_state; + unsigned char *userauth_list_data; + size_t userauth_list_data_len; + packet_requirev_state_t userauth_list_packet_requirev_state; + + /* State variables used in libssh2_userauth_password_ex() */ + libssh2_nonblocking_states userauth_pswd_state; + unsigned char *userauth_pswd_data; + unsigned char userauth_pswd_data0; + size_t userauth_pswd_data_len; + char *userauth_pswd_newpw; + int userauth_pswd_newpw_len; + packet_requirev_state_t userauth_pswd_packet_requirev_state; + + /* State variables used in libssh2_userauth_hostbased_fromfile_ex() */ + libssh2_nonblocking_states userauth_host_state; + unsigned char *userauth_host_data; + size_t userauth_host_data_len; + unsigned char *userauth_host_packet; + size_t userauth_host_packet_len; + unsigned char *userauth_host_method; + size_t userauth_host_method_len; + unsigned char *userauth_host_s; + packet_requirev_state_t userauth_host_packet_requirev_state; + + /* State variables used in libssh2_userauth_publickey_fromfile_ex() */ + libssh2_nonblocking_states userauth_pblc_state; + unsigned char *userauth_pblc_data; + size_t userauth_pblc_data_len; + unsigned char *userauth_pblc_packet; + size_t userauth_pblc_packet_len; + unsigned char *userauth_pblc_method; + size_t userauth_pblc_method_len; + unsigned char *userauth_pblc_s; + unsigned char *userauth_pblc_b; + packet_requirev_state_t userauth_pblc_packet_requirev_state; + + /* State variables used in libssh2_userauth_keyboard_interactive_ex() */ + libssh2_nonblocking_states userauth_kybd_state; + unsigned char *userauth_kybd_data; + size_t userauth_kybd_data_len; + unsigned char *userauth_kybd_packet; + size_t userauth_kybd_packet_len; + unsigned int userauth_kybd_auth_name_len; + char *userauth_kybd_auth_name; + unsigned userauth_kybd_auth_instruction_len; + char *userauth_kybd_auth_instruction; + unsigned int userauth_kybd_num_prompts; + int userauth_kybd_auth_failure; + LIBSSH2_USERAUTH_KBDINT_PROMPT *userauth_kybd_prompts; + LIBSSH2_USERAUTH_KBDINT_RESPONSE *userauth_kybd_responses; + packet_requirev_state_t userauth_kybd_packet_requirev_state; + + /* State variables used in libssh2_channel_open_ex() */ + libssh2_nonblocking_states open_state; + packet_requirev_state_t open_packet_requirev_state; + LIBSSH2_CHANNEL *open_channel; + unsigned char *open_packet; + size_t open_packet_len; + unsigned char *open_data; + size_t open_data_len; + uint32_t open_local_channel; + + /* State variables used in libssh2_channel_direct_tcpip_ex() */ + libssh2_nonblocking_states direct_state; + unsigned char *direct_message; + size_t direct_host_len; + size_t direct_shost_len; + size_t direct_message_len; + + /* State variables used in libssh2_channel_forward_listen_ex() */ + libssh2_nonblocking_states fwdLstn_state; + unsigned char *fwdLstn_packet; + uint32_t fwdLstn_host_len; + uint32_t fwdLstn_packet_len; + packet_requirev_state_t fwdLstn_packet_requirev_state; + + /* State variables used in libssh2_publickey_init() */ + libssh2_nonblocking_states pkeyInit_state; + LIBSSH2_PUBLICKEY *pkeyInit_pkey; + LIBSSH2_CHANNEL *pkeyInit_channel; + unsigned char *pkeyInit_data; + size_t pkeyInit_data_len; + /* 19 = packet_len(4) + version_len(4) + "version"(7) + version_num(4) */ + unsigned char pkeyInit_buffer[19]; + size_t pkeyInit_buffer_sent; /* how much of buffer that has been sent */ + + /* State variables used in libssh2_packet_add() */ + libssh2_nonblocking_states packAdd_state; + LIBSSH2_CHANNEL *packAdd_channelp; /* keeper of the channel during EAGAIN + states */ + packet_queue_listener_state_t packAdd_Qlstn_state; + packet_x11_open_state_t packAdd_x11open_state; + + /* State variables used in fullpacket() */ + libssh2_nonblocking_states fullpacket_state; + int fullpacket_macstate; + size_t fullpacket_payload_len; + int fullpacket_packet_type; + + /* State variables used in libssh2_sftp_init() */ + libssh2_nonblocking_states sftpInit_state; + LIBSSH2_SFTP *sftpInit_sftp; + LIBSSH2_CHANNEL *sftpInit_channel; + unsigned char sftpInit_buffer[9]; /* sftp_header(5){excludes request_id} + + version_id(4) */ + int sftpInit_sent; /* number of bytes from the buffer that have been + sent */ + + /* State variables used in libssh2_scp_recv() */ + libssh2_nonblocking_states scpRecv_state; + unsigned char *scpRecv_command; + size_t scpRecv_command_len; + unsigned char scpRecv_response[LIBSSH2_SCP_RESPONSE_BUFLEN]; + size_t scpRecv_response_len; + long scpRecv_mode; +#if defined(HAVE_LONGLONG) && defined(HAVE_STRTOLL) + /* we have the type and we can parse such numbers */ + long long scpRecv_size; +#define scpsize_strtol strtoll +#else + long scpRecv_size; +#define scpsize_strtol strtol +#endif + long scpRecv_mtime; + long scpRecv_atime; + LIBSSH2_CHANNEL *scpRecv_channel; + + /* State variables used in libssh2_scp_send_ex() */ + libssh2_nonblocking_states scpSend_state; + unsigned char *scpSend_command; + size_t scpSend_command_len; + unsigned char scpSend_response[LIBSSH2_SCP_RESPONSE_BUFLEN]; + size_t scpSend_response_len; + LIBSSH2_CHANNEL *scpSend_channel; + + /* Keepalive variables used by keepalive.c. */ + int keepalive_interval; + int keepalive_want_reply; + time_t keepalive_last_sent; +}; + +/* session.state bits */ +#define LIBSSH2_STATE_EXCHANGING_KEYS 0x00000001 +#define LIBSSH2_STATE_NEWKEYS 0x00000002 +#define LIBSSH2_STATE_AUTHENTICATED 0x00000004 +#define LIBSSH2_STATE_KEX_ACTIVE 0x00000008 + +/* session.flag helpers */ +#ifdef MSG_NOSIGNAL +#define LIBSSH2_SOCKET_SEND_FLAGS(session) \ + (((session)->flag.sigpipe) ? 0 : MSG_NOSIGNAL) +#define LIBSSH2_SOCKET_RECV_FLAGS(session) \ + (((session)->flag.sigpipe) ? 0 : MSG_NOSIGNAL) +#else +/* If MSG_NOSIGNAL isn't defined we're SOL on blocking SIGPIPE */ +#define LIBSSH2_SOCKET_SEND_FLAGS(session) 0 +#define LIBSSH2_SOCKET_RECV_FLAGS(session) 0 +#endif + +/* --------- */ + +/* libssh2 extensible ssh api, ultimately I'd like to allow loading additional + methods via .so/.dll */ + +struct _LIBSSH2_KEX_METHOD +{ + const char *name; + + /* Key exchange, populates session->* and returns 0 on success, non-0 on error */ + int (*exchange_keys) (LIBSSH2_SESSION * session, + key_exchange_state_low_t * key_state); + + long flags; +}; + +struct _LIBSSH2_HOSTKEY_METHOD +{ + const char *name; + unsigned long hash_len; + + int (*init) (LIBSSH2_SESSION * session, const unsigned char *hostkey_data, + size_t hostkey_data_len, void **abstract); + int (*initPEM) (LIBSSH2_SESSION * session, const char *privkeyfile, + unsigned const char *passphrase, void **abstract); + int (*sig_verify) (LIBSSH2_SESSION * session, const unsigned char *sig, + size_t sig_len, const unsigned char *m, + size_t m_len, void **abstract); + int (*signv) (LIBSSH2_SESSION * session, unsigned char **signature, + size_t *signature_len, int veccount, + const struct iovec datavec[], void **abstract); + int (*encrypt) (LIBSSH2_SESSION * session, unsigned char **dst, + size_t *dst_len, const unsigned char *src, + size_t src_len, void **abstract); + int (*dtor) (LIBSSH2_SESSION * session, void **abstract); +}; + +struct _LIBSSH2_CRYPT_METHOD +{ + const char *name; + + int blocksize; + + /* iv and key sizes (-1 for variable length) */ + int iv_len; + int secret_len; + + long flags; + + int (*init) (LIBSSH2_SESSION * session, + const LIBSSH2_CRYPT_METHOD * method, unsigned char *iv, + int *free_iv, unsigned char *secret, int *free_secret, + int encrypt, void **abstract); + int (*crypt) (LIBSSH2_SESSION * session, unsigned char *block, + size_t blocksize, void **abstract); + int (*dtor) (LIBSSH2_SESSION * session, void **abstract); + + _libssh2_cipher_type(algo); +}; + +struct _LIBSSH2_COMP_METHOD +{ + const char *name; + int compress; /* 1 if it does compress, 0 if it doesn't */ + int use_in_auth; /* 1 if compression should be used in userauth */ + int (*init) (LIBSSH2_SESSION *session, int compress, void **abstract); + int (*comp) (LIBSSH2_SESSION *session, + unsigned char *dest, + size_t *dest_len, + const unsigned char *src, + size_t src_len, + void **abstract); + int (*decomp) (LIBSSH2_SESSION *session, + unsigned char **dest, + size_t *dest_len, + size_t payload_limit, + const unsigned char *src, + size_t src_len, + void **abstract); + int (*dtor) (LIBSSH2_SESSION * session, int compress, void **abstract); +}; + +#ifdef LIBSSH2DEBUG +void _libssh2_debug(LIBSSH2_SESSION * session, int context, const char *format, + ...); +#else +#if (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || defined(__GNUC__) +/* C99 supported and also by older GCC */ +#define _libssh2_debug(x,y,z,...) do {} while (0) +#else +/* no gcc and not C99, do static and hopefully inline */ +static inline void +_libssh2_debug(LIBSSH2_SESSION * session, int context, const char *format, ...) +{ +} +#endif +#endif + +#define LIBSSH2_SOCKET_UNKNOWN 1 +#define LIBSSH2_SOCKET_CONNECTED 0 +#define LIBSSH2_SOCKET_DISCONNECTED -1 + +/* Initial packet state, prior to MAC check */ +#define LIBSSH2_MAC_UNCONFIRMED 1 +/* When MAC type is "none" (proto initiation phase) all packets are deemed "confirmed" */ +#define LIBSSH2_MAC_CONFIRMED 0 +/* Something very bad is going on */ +#define LIBSSH2_MAC_INVALID -1 + +/* SSH Packet Types -- Defined by internet draft */ +/* Transport Layer */ +#define SSH_MSG_DISCONNECT 1 +#define SSH_MSG_IGNORE 2 +#define SSH_MSG_UNIMPLEMENTED 3 +#define SSH_MSG_DEBUG 4 +#define SSH_MSG_SERVICE_REQUEST 5 +#define SSH_MSG_SERVICE_ACCEPT 6 + +#define SSH_MSG_KEXINIT 20 +#define SSH_MSG_NEWKEYS 21 + +/* diffie-hellman-group1-sha1 */ +#define SSH_MSG_KEXDH_INIT 30 +#define SSH_MSG_KEXDH_REPLY 31 + +/* diffie-hellman-group-exchange-sha1 */ +#define SSH_MSG_KEX_DH_GEX_REQUEST_OLD 30 +#define SSH_MSG_KEX_DH_GEX_REQUEST 34 +#define SSH_MSG_KEX_DH_GEX_GROUP 31 +#define SSH_MSG_KEX_DH_GEX_INIT 32 +#define SSH_MSG_KEX_DH_GEX_REPLY 33 + +/* User Authentication */ +#define SSH_MSG_USERAUTH_REQUEST 50 +#define SSH_MSG_USERAUTH_FAILURE 51 +#define SSH_MSG_USERAUTH_SUCCESS 52 +#define SSH_MSG_USERAUTH_BANNER 53 + +/* "public key" method */ +#define SSH_MSG_USERAUTH_PK_OK 60 +/* "password" method */ +#define SSH_MSG_USERAUTH_PASSWD_CHANGEREQ 60 +/* "keyboard-interactive" method */ +#define SSH_MSG_USERAUTH_INFO_REQUEST 60 +#define SSH_MSG_USERAUTH_INFO_RESPONSE 61 + +/* Channels */ +#define SSH_MSG_GLOBAL_REQUEST 80 +#define SSH_MSG_REQUEST_SUCCESS 81 +#define SSH_MSG_REQUEST_FAILURE 82 + +#define SSH_MSG_CHANNEL_OPEN 90 +#define SSH_MSG_CHANNEL_OPEN_CONFIRMATION 91 +#define SSH_MSG_CHANNEL_OPEN_FAILURE 92 +#define SSH_MSG_CHANNEL_WINDOW_ADJUST 93 +#define SSH_MSG_CHANNEL_DATA 94 +#define SSH_MSG_CHANNEL_EXTENDED_DATA 95 +#define SSH_MSG_CHANNEL_EOF 96 +#define SSH_MSG_CHANNEL_CLOSE 97 +#define SSH_MSG_CHANNEL_REQUEST 98 +#define SSH_MSG_CHANNEL_SUCCESS 99 +#define SSH_MSG_CHANNEL_FAILURE 100 + +/* Error codes returned in SSH_MSG_CHANNEL_OPEN_FAILURE message + (see RFC4254) */ +#define SSH_OPEN_ADMINISTRATIVELY_PROHIBITED 1 +#define SSH_OPEN_CONNECT_FAILED 2 +#define SSH_OPEN_UNKNOWN_CHANNELTYPE 3 +#define SSH_OPEN_RESOURCE_SHORTAGE 4 + +ssize_t _libssh2_recv(libssh2_socket_t socket, void *buffer, + size_t length, int flags, void **abstract); +ssize_t _libssh2_send(libssh2_socket_t socket, const void *buffer, + size_t length, int flags, void **abstract); + +#define LIBSSH2_READ_TIMEOUT 60 /* generic timeout in seconds used when + waiting for more data to arrive */ + + +int _libssh2_kex_exchange(LIBSSH2_SESSION * session, int reexchange, + key_exchange_state_t * state); + +/* Let crypt.c/hostkey.c expose their method structs */ +const LIBSSH2_CRYPT_METHOD **libssh2_crypt_methods(void); +const LIBSSH2_HOSTKEY_METHOD **libssh2_hostkey_methods(void); + +/* pem.c */ +int _libssh2_pem_parse(LIBSSH2_SESSION * session, + const char *headerbegin, + const char *headerend, + FILE * fp, unsigned char **data, unsigned int *datalen); +int _libssh2_pem_decode_sequence(unsigned char **data, unsigned int *datalen); +int _libssh2_pem_decode_integer(unsigned char **data, unsigned int *datalen, + unsigned char **i, unsigned int *ilen); + +/* global.c */ +void _libssh2_init_if_needed (void); + + +#define ARRAY_SIZE(a) (sizeof ((a)) / sizeof ((a)[0])) + +/* define to output the libssh2_int64_t type in a *printf() */ +#if defined( __BORLANDC__ ) || defined( _MSC_VER ) || defined( __MINGW32__ ) +#define LIBSSH2_INT64_T_FORMAT "I64d" +#else +#define LIBSSH2_INT64_T_FORMAT "lld" +#endif + +#endif /* LIBSSH2_H */ diff --git a/libssh2/include/libssh2_publickey.h b/libssh2/include/libssh2_publickey.h new file mode 100644 index 0000000..7350e9f --- /dev/null +++ b/libssh2/include/libssh2_publickey.h @@ -0,0 +1,118 @@ +/* Copyright (c) 2004-2006, Sara Golemon + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +/* Note: This include file is only needed for using the + * publickey SUBSYSTEM which is not the same as publickey + * authentication. For authentication you only need libssh2.h + * + * For more information on the publickey subsystem, + * refer to IETF draft: secsh-publickey + */ + +#ifndef LIBSSH2_PUBLICKEY_H +#define LIBSSH2_PUBLICKEY_H 1 + +#include "libssh2.h" + +typedef struct _LIBSSH2_PUBLICKEY LIBSSH2_PUBLICKEY; + +typedef struct _libssh2_publickey_attribute { + const char *name; + unsigned long name_len; + const char *value; + unsigned long value_len; + char mandatory; +} libssh2_publickey_attribute; + +typedef struct _libssh2_publickey_list { + unsigned char *packet; /* For freeing */ + + const unsigned char *name; + unsigned long name_len; + const unsigned char *blob; + unsigned long blob_len; + unsigned long num_attrs; + libssh2_publickey_attribute *attrs; /* free me */ +} libssh2_publickey_list; + +/* Generally use the first macro here, but if both name and value are string literals, you can use _fast() to take advantage of preprocessing */ +#define libssh2_publickey_attribute(name, value, mandatory) \ + { (name), strlen(name), (value), strlen(value), (mandatory) }, +#define libssh2_publickey_attribute_fast(name, value, mandatory) \ + { (name), sizeof(name) - 1, (value), sizeof(value) - 1, (mandatory) }, + +#ifdef __cplusplus +extern "C" { +#endif + +/* Publickey Subsystem */ +LIBSSH2_API LIBSSH2_PUBLICKEY *libssh2_publickey_init(LIBSSH2_SESSION *session); + +LIBSSH2_API int libssh2_publickey_add_ex(LIBSSH2_PUBLICKEY *pkey, + const unsigned char *name, + unsigned long name_len, + const unsigned char *blob, + unsigned long blob_len, char overwrite, + unsigned long num_attrs, + const libssh2_publickey_attribute attrs[]); +#define libssh2_publickey_add(pkey, name, blob, blob_len, overwrite, \ + num_attrs, attrs) \ + libssh2_publickey_add_ex((pkey), (name), strlen(name), (blob), (blob_len), \ + (overwrite), (num_attrs), (attrs)) + +LIBSSH2_API int libssh2_publickey_remove_ex(LIBSSH2_PUBLICKEY *pkey, + const unsigned char *name, + unsigned long name_len, + const unsigned char *blob, + unsigned long blob_len); +#define libssh2_publickey_remove(pkey, name, blob, blob_len) \ + libssh2_publickey_remove_ex((pkey), (name), strlen(name), (blob), (blob_len)) + +LIBSSH2_API int +libssh2_publickey_list_fetch(LIBSSH2_PUBLICKEY *pkey, + unsigned long *num_keys, + libssh2_publickey_list **pkey_list); +LIBSSH2_API void libssh2_publickey_list_free(LIBSSH2_PUBLICKEY *pkey, + libssh2_publickey_list *pkey_list); + +LIBSSH2_API int libssh2_publickey_shutdown(LIBSSH2_PUBLICKEY *pkey); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* ifndef: LIBSSH2_PUBLICKEY_H */ diff --git a/libssh2/include/libssh2_sftp.h b/libssh2/include/libssh2_sftp.h new file mode 100644 index 0000000..74884fb --- /dev/null +++ b/libssh2/include/libssh2_sftp.h @@ -0,0 +1,345 @@ +/* Copyright (c) 2004-2008, Sara Golemon + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#ifndef LIBSSH2_SFTP_H +#define LIBSSH2_SFTP_H 1 + +#include "libssh2.h" + +#ifndef WIN32 +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* Note: Version 6 was documented at the time of writing + * However it was marked as "DO NOT IMPLEMENT" due to pending changes + * + * Let's start with Version 3 (The version found in OpenSSH) and go from there + */ +#define LIBSSH2_SFTP_VERSION 3 + +typedef struct _LIBSSH2_SFTP LIBSSH2_SFTP; +typedef struct _LIBSSH2_SFTP_HANDLE LIBSSH2_SFTP_HANDLE; +typedef struct _LIBSSH2_SFTP_ATTRIBUTES LIBSSH2_SFTP_ATTRIBUTES; +typedef struct _LIBSSH2_SFTP_STATVFS LIBSSH2_SFTP_STATVFS; + +/* Flags for open_ex() */ +#define LIBSSH2_SFTP_OPENFILE 0 +#define LIBSSH2_SFTP_OPENDIR 1 + +/* Flags for rename_ex() */ +#define LIBSSH2_SFTP_RENAME_OVERWRITE 0x00000001 +#define LIBSSH2_SFTP_RENAME_ATOMIC 0x00000002 +#define LIBSSH2_SFTP_RENAME_NATIVE 0x00000004 + +/* Flags for stat_ex() */ +#define LIBSSH2_SFTP_STAT 0 +#define LIBSSH2_SFTP_LSTAT 1 +#define LIBSSH2_SFTP_SETSTAT 2 + +/* Flags for symlink_ex() */ +#define LIBSSH2_SFTP_SYMLINK 0 +#define LIBSSH2_SFTP_READLINK 1 +#define LIBSSH2_SFTP_REALPATH 2 + +/* SFTP attribute flag bits */ +#define LIBSSH2_SFTP_ATTR_SIZE 0x00000001 +#define LIBSSH2_SFTP_ATTR_UIDGID 0x00000002 +#define LIBSSH2_SFTP_ATTR_PERMISSIONS 0x00000004 +#define LIBSSH2_SFTP_ATTR_ACMODTIME 0x00000008 +#define LIBSSH2_SFTP_ATTR_EXTENDED 0x80000000 + +/* SFTP statvfs flag bits */ +#define LIBSSH2_SFTP_ST_RDONLY 0x00000001 +#define LIBSSH2_SFTP_ST_NOSUID 0x00000002 + +struct _LIBSSH2_SFTP_ATTRIBUTES { + /* If flags & ATTR_* bit is set, then the value in this struct will be + * meaningful Otherwise it should be ignored + */ + unsigned long flags; + + libssh2_uint64_t filesize; + unsigned long uid, gid; + unsigned long permissions; + unsigned long atime, mtime; +}; + +struct _LIBSSH2_SFTP_STATVFS { + libssh2_uint64_t f_bsize; /* file system block size */ + libssh2_uint64_t f_frsize; /* fragment size */ + libssh2_uint64_t f_blocks; /* size of fs in f_frsize units */ + libssh2_uint64_t f_bfree; /* # free blocks */ + libssh2_uint64_t f_bavail; /* # free blocks for non-root */ + libssh2_uint64_t f_files; /* # inodes */ + libssh2_uint64_t f_ffree; /* # free inodes */ + libssh2_uint64_t f_favail; /* # free inodes for non-root */ + libssh2_uint64_t f_fsid; /* file system ID */ + libssh2_uint64_t f_flag; /* mount flags */ + libssh2_uint64_t f_namemax; /* maximum filename length */ +}; + +/* SFTP filetypes */ +#define LIBSSH2_SFTP_TYPE_REGULAR 1 +#define LIBSSH2_SFTP_TYPE_DIRECTORY 2 +#define LIBSSH2_SFTP_TYPE_SYMLINK 3 +#define LIBSSH2_SFTP_TYPE_SPECIAL 4 +#define LIBSSH2_SFTP_TYPE_UNKNOWN 5 +#define LIBSSH2_SFTP_TYPE_SOCKET 6 +#define LIBSSH2_SFTP_TYPE_CHAR_DEVICE 7 +#define LIBSSH2_SFTP_TYPE_BLOCK_DEVICE 8 +#define LIBSSH2_SFTP_TYPE_FIFO 9 + +/* + * Reproduce the POSIX file modes here for systems that are not POSIX + * compliant. + * + * These is used in "permissions" of "struct _LIBSSH2_SFTP_ATTRIBUTES" + */ +/* File type */ +#define LIBSSH2_SFTP_S_IFMT 0170000 /* type of file mask */ +#define LIBSSH2_SFTP_S_IFIFO 0010000 /* named pipe (fifo) */ +#define LIBSSH2_SFTP_S_IFCHR 0020000 /* character special */ +#define LIBSSH2_SFTP_S_IFDIR 0040000 /* directory */ +#define LIBSSH2_SFTP_S_IFBLK 0060000 /* block special */ +#define LIBSSH2_SFTP_S_IFREG 0100000 /* regular */ +#define LIBSSH2_SFTP_S_IFLNK 0120000 /* symbolic link */ +#define LIBSSH2_SFTP_S_IFSOCK 0140000 /* socket */ + +/* File mode */ +/* Read, write, execute/search by owner */ +#define LIBSSH2_SFTP_S_IRWXU 0000700 /* RWX mask for owner */ +#define LIBSSH2_SFTP_S_IRUSR 0000400 /* R for owner */ +#define LIBSSH2_SFTP_S_IWUSR 0000200 /* W for owner */ +#define LIBSSH2_SFTP_S_IXUSR 0000100 /* X for owner */ +/* Read, write, execute/search by group */ +#define LIBSSH2_SFTP_S_IRWXG 0000070 /* RWX mask for group */ +#define LIBSSH2_SFTP_S_IRGRP 0000040 /* R for group */ +#define LIBSSH2_SFTP_S_IWGRP 0000020 /* W for group */ +#define LIBSSH2_SFTP_S_IXGRP 0000010 /* X for group */ +/* Read, write, execute/search by others */ +#define LIBSSH2_SFTP_S_IRWXO 0000007 /* RWX mask for other */ +#define LIBSSH2_SFTP_S_IROTH 0000004 /* R for other */ +#define LIBSSH2_SFTP_S_IWOTH 0000002 /* W for other */ +#define LIBSSH2_SFTP_S_IXOTH 0000001 /* X for other */ + +/* macros to check for specific file types, added in 1.2.5 */ +#define LIBSSH2_SFTP_S_ISLNK(m) \ + (((m) & LIBSSH2_SFTP_S_IFMT) == LIBSSH2_SFTP_S_IFLNK) +#define LIBSSH2_SFTP_S_ISREG(m) \ + (((m) & LIBSSH2_SFTP_S_IFMT) == LIBSSH2_SFTP_S_IFREG) +#define LIBSSH2_SFTP_S_ISDIR(m) \ + (((m) & LIBSSH2_SFTP_S_IFMT) == LIBSSH2_SFTP_S_IFDIR) +#define LIBSSH2_SFTP_S_ISCHR(m) \ + (((m) & LIBSSH2_SFTP_S_IFMT) == LIBSSH2_SFTP_S_IFCHR) +#define LIBSSH2_SFTP_S_ISBLK(m) \ + (((m) & LIBSSH2_SFTP_S_IFMT) == LIBSSH2_SFTP_S_IFBLK) +#define LIBSSH2_SFTP_S_ISFIFO(m) \ + (((m) & LIBSSH2_SFTP_S_IFMT) == LIBSSH2_SFTP_S_IFIFO) +#define LIBSSH2_SFTP_S_ISSOCK(m) \ + (((m) & LIBSSH2_SFTP_S_IFMT) == LIBSSH2_SFTP_S_IFSOCK) + +/* SFTP File Transfer Flags -- (e.g. flags parameter to sftp_open()) + * Danger will robinson... APPEND doesn't have any effect on OpenSSH servers */ +#define LIBSSH2_FXF_READ 0x00000001 +#define LIBSSH2_FXF_WRITE 0x00000002 +#define LIBSSH2_FXF_APPEND 0x00000004 +#define LIBSSH2_FXF_CREAT 0x00000008 +#define LIBSSH2_FXF_TRUNC 0x00000010 +#define LIBSSH2_FXF_EXCL 0x00000020 + +/* SFTP Status Codes (returned by libssh2_sftp_last_error() ) */ +#define LIBSSH2_FX_OK 0 +#define LIBSSH2_FX_EOF 1 +#define LIBSSH2_FX_NO_SUCH_FILE 2 +#define LIBSSH2_FX_PERMISSION_DENIED 3 +#define LIBSSH2_FX_FAILURE 4 +#define LIBSSH2_FX_BAD_MESSAGE 5 +#define LIBSSH2_FX_NO_CONNECTION 6 +#define LIBSSH2_FX_CONNECTION_LOST 7 +#define LIBSSH2_FX_OP_UNSUPPORTED 8 +#define LIBSSH2_FX_INVALID_HANDLE 9 +#define LIBSSH2_FX_NO_SUCH_PATH 10 +#define LIBSSH2_FX_FILE_ALREADY_EXISTS 11 +#define LIBSSH2_FX_WRITE_PROTECT 12 +#define LIBSSH2_FX_NO_MEDIA 13 +#define LIBSSH2_FX_NO_SPACE_ON_FILESYSTEM 14 +#define LIBSSH2_FX_QUOTA_EXCEEDED 15 +#define LIBSSH2_FX_UNKNOWN_PRINCIPLE 16 /* Initial mis-spelling */ +#define LIBSSH2_FX_UNKNOWN_PRINCIPAL 16 +#define LIBSSH2_FX_LOCK_CONFlICT 17 /* Initial mis-spelling */ +#define LIBSSH2_FX_LOCK_CONFLICT 17 +#define LIBSSH2_FX_DIR_NOT_EMPTY 18 +#define LIBSSH2_FX_NOT_A_DIRECTORY 19 +#define LIBSSH2_FX_INVALID_FILENAME 20 +#define LIBSSH2_FX_LINK_LOOP 21 + +/* Returned by any function that would block during a read/write opperation */ +#define LIBSSH2SFTP_EAGAIN LIBSSH2_ERROR_EAGAIN + +/* SFTP API */ +LIBSSH2_API LIBSSH2_SFTP *libssh2_sftp_init(LIBSSH2_SESSION *session); +LIBSSH2_API int libssh2_sftp_shutdown(LIBSSH2_SFTP *sftp); +LIBSSH2_API unsigned long libssh2_sftp_last_error(LIBSSH2_SFTP *sftp); +LIBSSH2_API LIBSSH2_CHANNEL *libssh2_sftp_get_channel(LIBSSH2_SFTP *sftp); + +/* File / Directory Ops */ +LIBSSH2_API LIBSSH2_SFTP_HANDLE *libssh2_sftp_open_ex(LIBSSH2_SFTP *sftp, + const char *filename, + unsigned int filename_len, + unsigned long flags, + long mode, int open_type); +#define libssh2_sftp_open(sftp, filename, flags, mode) \ + libssh2_sftp_open_ex((sftp), (filename), strlen(filename), (flags), \ + (mode), LIBSSH2_SFTP_OPENFILE) +#define libssh2_sftp_opendir(sftp, path) \ + libssh2_sftp_open_ex((sftp), (path), strlen(path), 0, 0, \ + LIBSSH2_SFTP_OPENDIR) + +LIBSSH2_API ssize_t libssh2_sftp_read(LIBSSH2_SFTP_HANDLE *handle, + char *buffer, size_t buffer_maxlen); + +LIBSSH2_API int libssh2_sftp_readdir_ex(LIBSSH2_SFTP_HANDLE *handle, \ + char *buffer, size_t buffer_maxlen, + char *longentry, + size_t longentry_maxlen, + LIBSSH2_SFTP_ATTRIBUTES *attrs); +#define libssh2_sftp_readdir(handle, buffer, buffer_maxlen, attrs) \ + libssh2_sftp_readdir_ex((handle), (buffer), (buffer_maxlen), NULL, 0, \ + (attrs)) + +LIBSSH2_API ssize_t libssh2_sftp_write(LIBSSH2_SFTP_HANDLE *handle, + const char *buffer, size_t count); + +LIBSSH2_API int libssh2_sftp_close_handle(LIBSSH2_SFTP_HANDLE *handle); +#define libssh2_sftp_close(handle) libssh2_sftp_close_handle(handle) +#define libssh2_sftp_closedir(handle) libssh2_sftp_close_handle(handle) + +LIBSSH2_API void libssh2_sftp_seek(LIBSSH2_SFTP_HANDLE *handle, size_t offset); +LIBSSH2_API void libssh2_sftp_seek64(LIBSSH2_SFTP_HANDLE *handle, + libssh2_uint64_t offset); +#define libssh2_sftp_rewind(handle) libssh2_sftp_seek64((handle), 0) + +LIBSSH2_API size_t libssh2_sftp_tell(LIBSSH2_SFTP_HANDLE *handle); +LIBSSH2_API libssh2_uint64_t libssh2_sftp_tell64(LIBSSH2_SFTP_HANDLE *handle); + +LIBSSH2_API int libssh2_sftp_fstat_ex(LIBSSH2_SFTP_HANDLE *handle, + LIBSSH2_SFTP_ATTRIBUTES *attrs, + int setstat); +#define libssh2_sftp_fstat(handle, attrs) \ + libssh2_sftp_fstat_ex((handle), (attrs), 0) +#define libssh2_sftp_fsetstat(handle, attrs) \ + libssh2_sftp_fstat_ex((handle), (attrs), 1) + +/* Miscellaneous Ops */ +LIBSSH2_API int libssh2_sftp_rename_ex(LIBSSH2_SFTP *sftp, + const char *source_filename, + unsigned int srouce_filename_len, + const char *dest_filename, + unsigned int dest_filename_len, + long flags); +#define libssh2_sftp_rename(sftp, sourcefile, destfile) \ + libssh2_sftp_rename_ex((sftp), (sourcefile), strlen(sourcefile), \ + (destfile), strlen(destfile), \ + LIBSSH2_SFTP_RENAME_OVERWRITE | \ + LIBSSH2_SFTP_RENAME_ATOMIC | \ + LIBSSH2_SFTP_RENAME_NATIVE) + +LIBSSH2_API int libssh2_sftp_unlink_ex(LIBSSH2_SFTP *sftp, + const char *filename, + unsigned int filename_len); +#define libssh2_sftp_unlink(sftp, filename) \ + libssh2_sftp_unlink_ex((sftp), (filename), strlen(filename)) + +LIBSSH2_API int libssh2_sftp_fstatvfs(LIBSSH2_SFTP_HANDLE *handle, + LIBSSH2_SFTP_STATVFS *st); + +LIBSSH2_API int libssh2_sftp_statvfs(LIBSSH2_SFTP *sftp, + const char *path, + size_t path_len, + LIBSSH2_SFTP_STATVFS *st); + +LIBSSH2_API int libssh2_sftp_mkdir_ex(LIBSSH2_SFTP *sftp, + const char *path, + unsigned int path_len, long mode); +#define libssh2_sftp_mkdir(sftp, path, mode) \ + libssh2_sftp_mkdir_ex((sftp), (path), strlen(path), (mode)) + +LIBSSH2_API int libssh2_sftp_rmdir_ex(LIBSSH2_SFTP *sftp, + const char *path, + unsigned int path_len); +#define libssh2_sftp_rmdir(sftp, path) \ + libssh2_sftp_rmdir_ex((sftp), (path), strlen(path)) + +LIBSSH2_API int libssh2_sftp_stat_ex(LIBSSH2_SFTP *sftp, + const char *path, + unsigned int path_len, + int stat_type, + LIBSSH2_SFTP_ATTRIBUTES *attrs); +#define libssh2_sftp_stat(sftp, path, attrs) \ + libssh2_sftp_stat_ex((sftp), (path), strlen(path), LIBSSH2_SFTP_STAT, \ + (attrs)) +#define libssh2_sftp_lstat(sftp, path, attrs) \ + libssh2_sftp_stat_ex((sftp), (path), strlen(path), LIBSSH2_SFTP_LSTAT, \ + (attrs)) +#define libssh2_sftp_setstat(sftp, path, attrs) \ + libssh2_sftp_stat_ex((sftp), (path), strlen(path), LIBSSH2_SFTP_SETSTAT, \ + (attrs)) + +LIBSSH2_API int libssh2_sftp_symlink_ex(LIBSSH2_SFTP *sftp, + const char *path, + unsigned int path_len, + char *target, + unsigned int target_len, int link_type); +#define libssh2_sftp_symlink(sftp, orig, linkpath) \ + libssh2_sftp_symlink_ex((sftp), (orig), strlen(orig), (linkpath), \ + strlen(linkpath), LIBSSH2_SFTP_SYMLINK) +#define libssh2_sftp_readlink(sftp, path, target, maxlen) \ + libssh2_sftp_symlink_ex((sftp), (path), strlen(path), (target), (maxlen), \ + LIBSSH2_SFTP_READLINK) +#define libssh2_sftp_realpath(sftp, path, target, maxlen) \ + libssh2_sftp_symlink_ex((sftp), (path), strlen(path), (target), (maxlen), \ + LIBSSH2_SFTP_REALPATH) + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* LIBSSH2_SFTP_H */ diff --git a/libssh2/include/mac.h b/libssh2/include/mac.h new file mode 100644 index 0000000..66d3e61 --- /dev/null +++ b/libssh2/include/mac.h @@ -0,0 +1,67 @@ +#ifndef __LIBSSH2_MAC_H +#define __LIBSSH2_MAC_H + +/* Copyright (C) 2009-2010 by Daniel Stenberg + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + */ + +#include "libssh2_priv.h" + +struct _LIBSSH2_MAC_METHOD +{ + const char *name; + + /* The length of a given MAC packet */ + int mac_len; + + /* integrity key length */ + int key_len; + + /* Message Authentication Code Hashing algo */ + int (*init) (LIBSSH2_SESSION * session, unsigned char *key, int *free_key, + void **abstract); + int (*hash) (LIBSSH2_SESSION * session, unsigned char *buf, + uint32_t seqno, const unsigned char *packet, + uint32_t packet_len, const unsigned char *addtl, + uint32_t addtl_len, void **abstract); + int (*dtor) (LIBSSH2_SESSION * session, void **abstract); +}; + +typedef struct _LIBSSH2_MAC_METHOD LIBSSH2_MAC_METHOD; + +const LIBSSH2_MAC_METHOD **_libssh2_mac_methods(void); + +#endif /* __LIBSSH2_MAC_H */ diff --git a/libssh2/include/misc.h b/libssh2/include/misc.h new file mode 100644 index 0000000..e25248d --- /dev/null +++ b/libssh2/include/misc.h @@ -0,0 +1,94 @@ +#ifndef __LIBSSH2_MISC_H +#define __LIBSSH2_MISC_H +/* Copyright (c) 2009-2011 by Daniel Stenberg + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +struct list_head { + struct list_node *last; + struct list_node *first; +}; + +struct list_node { + struct list_node *next; + struct list_node *prev; + struct list_head *head; +}; + +int _libssh2_error(LIBSSH2_SESSION* session, int errcode, const char* errmsg); + +void _libssh2_list_init(struct list_head *head); + +/* add a node last in the list */ +void _libssh2_list_add(struct list_head *head, + struct list_node *entry); + +/* return the "first" node in the list this head points to */ +void *_libssh2_list_first(struct list_head *head); + +/* return the next node in the list */ +void *_libssh2_list_next(struct list_node *node); + +/* return the prev node in the list */ +void *_libssh2_list_prev(struct list_node *node); + +/* remove this node from the list */ +void _libssh2_list_remove(struct list_node *entry); + +size_t _libssh2_base64_encode(struct _LIBSSH2_SESSION *session, + const char *inp, size_t insize, char **outptr); + +unsigned int _libssh2_ntohu32(const unsigned char *buf); +libssh2_uint64_t _libssh2_ntohu64(const unsigned char *buf); +void _libssh2_htonu32(unsigned char *buf, uint32_t val); +void _libssh2_store_u32(unsigned char **buf, uint32_t value); +void _libssh2_store_str(unsigned char **buf, const char *str, size_t len); + +#if defined(LIBSSH2_WIN32) && !defined(__MINGW32__) && !defined(__CYGWIN__) +/* provide a private one */ +#undef HAVE_GETTIMEOFDAY +int __cdecl _libssh2_gettimeofday(struct timeval *tp, void *tzp); +#define HAVE_LIBSSH2_GETTIMEOFDAY +#define LIBSSH2_GETTIMEOFDAY_WIN32 /* enable the win32 implementation */ +#else +#ifdef HAVE_GETTIMEOFDAY +#define _libssh2_gettimeofday(x,y) gettimeofday(x,y) +#define HAVE_LIBSSH2_GETTIMEOFDAY +#endif +#endif + +#endif /* _LIBSSH2_MISC_H */ diff --git a/libssh2/include/openssl.h b/libssh2/include/openssl.h new file mode 100644 index 0000000..6f21a1a --- /dev/null +++ b/libssh2/include/openssl.h @@ -0,0 +1,187 @@ +/* Copyright (C) 2009, 2010 Simon Josefsson + * Copyright (C) 2006, 2007 The Written Word, Inc. All rights reserved. + * + * Author: Simon Josefsson + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include +#include +#ifndef OPENSSL_NO_MD5 +#include +#endif +#include +#include +#include +#include +#include + +#ifdef OPENSSL_NO_RSA +# define LIBSSH2_RSA 0 +#else +# define LIBSSH2_RSA 1 +#endif + +#ifdef OPENSSL_NO_DSA +# define LIBSSH2_DSA 0 +#else +# define LIBSSH2_DSA 1 +#endif + +#ifdef OPENSSL_NO_MD5 +# define LIBSSH2_MD5 0 +#else +# define LIBSSH2_MD5 1 +#endif + +#ifdef OPENSSL_NO_RIPEMD +# define LIBSSH2_HMAC_RIPEMD 0 +#else +# define LIBSSH2_HMAC_RIPEMD 1 +#endif + +#if OPENSSL_VERSION_NUMBER >= 0x00907000L && !defined(OPENSSL_NO_AES) +# define LIBSSH2_AES_CTR 1 +# define LIBSSH2_AES 1 +#else +# define LIBSSH2_AES_CTR 0 +# define LIBSSH2_AES 0 +#endif + +#ifdef OPENSSL_NO_BLOWFISH +# define LIBSSH2_BLOWFISH 0 +#else +# define LIBSSH2_BLOWFISH 1 +#endif + +#ifdef OPENSSL_NO_RC4 +# define LIBSSH2_RC4 0 +#else +# define LIBSSH2_RC4 1 +#endif + +#ifdef OPENSSL_NO_CAST +# define LIBSSH2_CAST 0 +#else +# define LIBSSH2_CAST 1 +#endif + +#ifdef OPENSSL_NO_DES +# define LIBSSH2_3DES 0 +#else +# define LIBSSH2_3DES 1 +#endif + +#define _libssh2_random(buf, len) RAND_bytes ((buf), (len)) + +#define libssh2_sha1_ctx EVP_MD_CTX +#define libssh2_sha1_init(ctx) EVP_DigestInit(ctx, EVP_get_digestbyname("sha1")) +#define libssh2_sha1_update(ctx, data, len) EVP_DigestUpdate(&(ctx), data, len) +#define libssh2_sha1_final(ctx, out) EVP_DigestFinal(&(ctx), out, NULL) +void libssh2_sha1(const unsigned char *message, unsigned long len, unsigned char *out); + +#define libssh2_md5_ctx EVP_MD_CTX + +/* returns 0 in case of failure */ +#define libssh2_md5_init(ctx) EVP_DigestInit(ctx, EVP_get_digestbyname("md5")) + +#define libssh2_md5_update(ctx, data, len) EVP_DigestUpdate(&(ctx), data, len) +#define libssh2_md5_final(ctx, out) EVP_DigestFinal(&(ctx), out, NULL) +void libssh2_md5(const unsigned char *message, unsigned long len, unsigned char *out); + +#define libssh2_hmac_ctx HMAC_CTX +#define libssh2_hmac_sha1_init(ctx, key, keylen) \ + HMAC_Init(ctx, key, keylen, EVP_sha1()) +#define libssh2_hmac_md5_init(ctx, key, keylen) \ + HMAC_Init(ctx, key, keylen, EVP_md5()) +#define libssh2_hmac_ripemd160_init(ctx, key, keylen) \ + HMAC_Init(ctx, key, keylen, EVP_ripemd160()) +#define libssh2_hmac_update(ctx, data, datalen) \ + HMAC_Update(&(ctx), data, datalen) +#define libssh2_hmac_final(ctx, data) HMAC_Final(&(ctx), data, NULL) +#define libssh2_hmac_cleanup(ctx) HMAC_cleanup(ctx) + +#define libssh2_crypto_init() OpenSSL_add_all_algorithms() +#define libssh2_crypto_exit() + +#define libssh2_rsa_ctx RSA + +#define _libssh2_rsa_free(rsactx) RSA_free(rsactx) + +#define libssh2_dsa_ctx DSA + + +#define _libssh2_dsa_free(dsactx) DSA_free(dsactx) + +#define _libssh2_cipher_type(name) const EVP_CIPHER *(*name)(void) +#define _libssh2_cipher_ctx EVP_CIPHER_CTX + +#define _libssh2_cipher_aes256 EVP_aes_256_cbc +#define _libssh2_cipher_aes192 EVP_aes_192_cbc +#define _libssh2_cipher_aes128 EVP_aes_128_cbc +#ifdef HAVE_EVP_AES_128_CTR +#define _libssh2_cipher_aes128ctr EVP_aes_128_ctr +#define _libssh2_cipher_aes192ctr EVP_aes_192_ctr +#define _libssh2_cipher_aes256ctr EVP_aes_256_ctr +#else +#define _libssh2_cipher_aes128ctr _libssh2_EVP_aes_128_ctr +#define _libssh2_cipher_aes192ctr _libssh2_EVP_aes_192_ctr +#define _libssh2_cipher_aes256ctr _libssh2_EVP_aes_256_ctr +#endif +#define _libssh2_cipher_blowfish EVP_bf_cbc +#define _libssh2_cipher_arcfour EVP_rc4 +#define _libssh2_cipher_cast5 EVP_cast5_cbc +#define _libssh2_cipher_3des EVP_des_ede3_cbc + +#define _libssh2_cipher_dtor(ctx) EVP_CIPHER_CTX_cleanup(ctx) + +#define _libssh2_bn BIGNUM +#define _libssh2_bn_ctx BN_CTX +#define _libssh2_bn_ctx_new() BN_CTX_new() +#define _libssh2_bn_ctx_free(bnctx) BN_CTX_free(bnctx) +#define _libssh2_bn_init() BN_new() +#define _libssh2_bn_rand(bn, bits, top, bottom) BN_rand(bn, bits, top, bottom) +#define _libssh2_bn_mod_exp(r, a, p, m, ctx) BN_mod_exp(r, a, p, m, ctx) +#define _libssh2_bn_set_word(bn, val) BN_set_word(bn, val) +#define _libssh2_bn_from_bin(bn, len, val) BN_bin2bn(val, len, bn) +#define _libssh2_bn_to_bin(bn, val) BN_bn2bin(bn, val) +#define _libssh2_bn_bytes(bn) BN_num_bytes(bn) +#define _libssh2_bn_bits(bn) BN_num_bits(bn) +#define _libssh2_bn_free(bn) BN_clear_free(bn) + +const EVP_CIPHER *_libssh2_EVP_aes_128_ctr(void); +const EVP_CIPHER *_libssh2_EVP_aes_192_ctr(void); +const EVP_CIPHER *_libssh2_EVP_aes_256_ctr(void); + diff --git a/libssh2/include/packet.h b/libssh2/include/packet.h new file mode 100644 index 0000000..d66b15b --- /dev/null +++ b/libssh2/include/packet.h @@ -0,0 +1,76 @@ +#ifndef LIBSSH2_PACKET_H +#define LIBSSH2_PACKET_H +/* + * Copyright (C) 2010 by Daniel Stenberg + * Author: Daniel Stenberg + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + */ + +int _libssh2_packet_read(LIBSSH2_SESSION * session); + +int _libssh2_packet_ask(LIBSSH2_SESSION * session, unsigned char packet_type, + unsigned char **data, size_t *data_len, + int match_ofs, + const unsigned char *match_buf, + size_t match_len); + +int _libssh2_packet_askv(LIBSSH2_SESSION * session, + const unsigned char *packet_types, + unsigned char **data, size_t *data_len, + int match_ofs, + const unsigned char *match_buf, + size_t match_len); +int _libssh2_packet_require(LIBSSH2_SESSION * session, + unsigned char packet_type, unsigned char **data, + size_t *data_len, int match_ofs, + const unsigned char *match_buf, + size_t match_len, + packet_require_state_t * state); +int _libssh2_packet_requirev(LIBSSH2_SESSION *session, + const unsigned char *packet_types, + unsigned char **data, size_t *data_len, + int match_ofs, + const unsigned char *match_buf, + size_t match_len, + packet_requirev_state_t * state); +int _libssh2_packet_burn(LIBSSH2_SESSION * session, + libssh2_nonblocking_states * state); +int _libssh2_packet_write(LIBSSH2_SESSION * session, unsigned char *data, + unsigned long data_len); +int _libssh2_packet_add(LIBSSH2_SESSION * session, unsigned char *data, + size_t datalen, int macstate); + +#endif /* LIBSSH2_PACKET_H */ diff --git a/libssh2/include/session.h b/libssh2/include/session.h new file mode 100644 index 0000000..aff4f2c --- /dev/null +++ b/libssh2/include/session.h @@ -0,0 +1,93 @@ +#ifndef LIBSSH2_SESSION_H +#define LIBSSH2_SESSION_H +/* Copyright (c) 2004-2007 Sara Golemon + * Copyright (c) 2009-2010 by Daniel Stenberg + * Copyright (c) 2010 Simon Josefsson + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +/* Conveniance-macros to allow code like this; + + int rc = BLOCK_ADJUST(rc, session, session_startup(session, sock) ); + + int rc = BLOCK_ADJUST_ERRNO(ptr, session, session_startup(session, sock) ); + + The point of course being to make sure that while in non-blocking mode + these always return no matter what the return code is, but in blocking mode + it blocks if EAGAIN is the reason for the return from the underlying + function. + +*/ +#define BLOCK_ADJUST(rc,sess,x) \ + do { \ + time_t entry_time = time (NULL); \ + do { \ + rc = x; \ + /* the order of the check below is important to properly deal with \ + the case when the 'sess' is freed */ \ + if((rc != LIBSSH2_ERROR_EAGAIN) || !sess->api_block_mode) \ + break; \ + rc = _libssh2_wait_socket(sess, entry_time); \ + } while(!rc); \ + } while(0) + +/* + * For functions that returns a pointer, we need to check if the API is + * non-blocking and return immediately. If the pointer is non-NULL we return + * immediately. If the API is blocking and we get a NULL we check the errno + * and *only* if that is EAGAIN we loop and wait for socket action. + */ +#define BLOCK_ADJUST_ERRNO(ptr,sess,x) \ + do { \ + time_t entry_time = time (NULL); \ + int rc; \ + do { \ + ptr = x; \ + if(!sess->api_block_mode || \ + (ptr != NULL) || \ + (libssh2_session_last_errno(sess) != LIBSSH2_ERROR_EAGAIN) ) \ + break; \ + rc = _libssh2_wait_socket(sess, entry_time); \ + } while(!rc); \ + } while(0) + + +int _libssh2_wait_socket(LIBSSH2_SESSION *session, time_t entry_time); + +/* this is the lib-internal set blocking function */ +int _libssh2_session_set_blocking(LIBSSH2_SESSION * session, int blocking); + +#endif /* LIBSSH2_SESSION_H */ diff --git a/libssh2/include/sftp.h b/libssh2/include/sftp.h new file mode 100644 index 0000000..55bdb46 --- /dev/null +++ b/libssh2/include/sftp.h @@ -0,0 +1,230 @@ +#ifndef _LIBSSH2_SFTP_H +#define _LIBSSH2_SFTP_H +/* + * Copyright (C) 2010 - 2012 by Daniel Stenberg + * Author: Daniel Stenberg + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + */ + +/* + * MAX_SFTP_OUTGOING_SIZE MUST not be larger than 32500 or so. This is the + * amount of data sent in each FXP_WRITE packet + */ +#define MAX_SFTP_OUTGOING_SIZE 30000 + +/* MAX_SFTP_READ_SIZE is how much data is asked for at max in each FXP_READ + * packets. + */ +#define MAX_SFTP_READ_SIZE 2000 + +struct sftp_pipeline_chunk { + struct list_node node; + size_t len; /* WRITE: size of the data to write + READ: how many bytes that was asked for */ + size_t sent; + ssize_t lefttosend; /* if 0, the entire packet has been sent off */ + uint32_t request_id; + unsigned char packet[1]; /* data */ +}; + +struct sftp_zombie_requests { + struct list_node node; + uint32_t request_id; +}; + +#ifndef MIN +#define MIN(x,y) ((x)<(y)?(x):(y)) +#endif + +struct _LIBSSH2_SFTP_PACKET +{ + struct list_node node; /* linked list header */ + uint32_t request_id; + unsigned char *data; + size_t data_len; /* payload size */ +}; + +typedef struct _LIBSSH2_SFTP_PACKET LIBSSH2_SFTP_PACKET; + +#define SFTP_HANDLE_MAXLEN 256 /* according to spec! */ + +struct _LIBSSH2_SFTP_HANDLE +{ + struct list_node node; + + LIBSSH2_SFTP *sftp; + + char handle[SFTP_HANDLE_MAXLEN]; + size_t handle_len; + + enum { + LIBSSH2_SFTP_HANDLE_FILE, + LIBSSH2_SFTP_HANDLE_DIR + } handle_type; + + union _libssh2_sftp_handle_data + { + struct _libssh2_sftp_handle_file_data + { + libssh2_uint64_t offset; + libssh2_uint64_t offset_sent; + size_t acked; /* container for acked data that hasn't been + returned to caller yet, used for sftp_write */ + + /* 'data' is used by sftp_read() and is allocated data that has + been received already from the server but wasn't returned to + the caller yet. It is of size 'data_len' and 'data_left is the + number of bytes not yet returned, counted from the end of the + buffer. */ + unsigned char *data; + size_t data_len; + size_t data_left; + + char eof; /* we have read to the end */ + } file; + struct _libssh2_sftp_handle_dir_data + { + uint32_t names_left; + void *names_packet; + char *next_name; + } dir; + } u; + + /* State variables used in libssh2_sftp_close_handle() */ + libssh2_nonblocking_states close_state; + uint32_t close_request_id; + unsigned char *close_packet; + + /* list of outstanding packets sent to server */ + struct list_head packet_list; + +}; + +struct _LIBSSH2_SFTP +{ + LIBSSH2_CHANNEL *channel; + + uint32_t request_id, version; + + struct list_head packets; + + /* List of FXP_READ responses to ignore because EOF already received. */ + struct list_head zombie_requests; + + /* a list of _LIBSSH2_SFTP_HANDLE structs */ + struct list_head sftp_handles; + + uint32_t last_errno; + + /* Holder for partial packet, use in libssh2_sftp_packet_read() */ + unsigned char partial_size[4]; /* buffer for size field */ + size_t partial_size_len; /* size field length */ + unsigned char *partial_packet; /* The data */ + uint32_t partial_len; /* Desired number of bytes */ + size_t partial_received; /* Bytes received so far */ + + /* Time that libssh2_sftp_packet_requirev() started reading */ + time_t requirev_start; + + /* State variables used in libssh2_sftp_open_ex() */ + libssh2_nonblocking_states open_state; + unsigned char *open_packet; + uint32_t open_packet_len; /* 32 bit on the wire */ + size_t open_packet_sent; + uint32_t open_request_id; + + /* State variable used in sftp_read() */ + libssh2_nonblocking_states read_state; + + /* State variable used in sftp_packet_read() */ + libssh2_nonblocking_states packet_state; + + /* State variable used in sftp_write() */ + libssh2_nonblocking_states write_state; + + /* State variables used in libssh2_sftp_readdir() */ + libssh2_nonblocking_states readdir_state; + unsigned char *readdir_packet; + uint32_t readdir_request_id; + + /* State variables used in libssh2_sftp_fstat_ex() */ + libssh2_nonblocking_states fstat_state; + unsigned char *fstat_packet; + uint32_t fstat_request_id; + + /* State variables used in libssh2_sftp_unlink_ex() */ + libssh2_nonblocking_states unlink_state; + unsigned char *unlink_packet; + uint32_t unlink_request_id; + + /* State variables used in libssh2_sftp_rename_ex() */ + libssh2_nonblocking_states rename_state; + unsigned char *rename_packet; + unsigned char *rename_s; + uint32_t rename_request_id; + + /* State variables used in libssh2_sftp_fstatvfs() */ + libssh2_nonblocking_states fstatvfs_state; + unsigned char *fstatvfs_packet; + uint32_t fstatvfs_request_id; + + /* State variables used in libssh2_sftp_statvfs() */ + libssh2_nonblocking_states statvfs_state; + unsigned char *statvfs_packet; + uint32_t statvfs_request_id; + + /* State variables used in libssh2_sftp_mkdir() */ + libssh2_nonblocking_states mkdir_state; + unsigned char *mkdir_packet; + uint32_t mkdir_request_id; + + /* State variables used in libssh2_sftp_rmdir() */ + libssh2_nonblocking_states rmdir_state; + unsigned char *rmdir_packet; + uint32_t rmdir_request_id; + + /* State variables used in libssh2_sftp_stat() */ + libssh2_nonblocking_states stat_state; + unsigned char *stat_packet; + uint32_t stat_request_id; + + /* State variables used in libssh2_sftp_symlink() */ + libssh2_nonblocking_states symlink_state; + unsigned char *symlink_packet; + uint32_t symlink_request_id; +}; + +#endif diff --git a/libssh2/include/transport.h b/libssh2/include/transport.h new file mode 100644 index 0000000..89982a6 --- /dev/null +++ b/libssh2/include/transport.h @@ -0,0 +1,87 @@ +#ifndef __LIBSSH2_TRANSPORT_H +#define __LIBSSH2_TRANSPORT_H + +/* Copyright (C) 2007 The Written Word, Inc. All rights reserved. + * Copyright (C) 2009-2010 by Daniel Stenberg + * Author: Daniel Stenberg + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * This file handles reading and writing to the SECSH transport layer. RFC4253. + */ + +#include "libssh2_priv.h" +#include "packet.h" + + +/* + * libssh2_transport_send + * + * Send a packet, encrypting it and adding a MAC code if necessary + * Returns 0 on success, non-zero on failure. + * + * The data is provided as _two_ data areas that are combined by this + * function. The 'data' part is sent immediately before 'data2'. 'data2' can + * be set to NULL (or data2_len to 0) to only use a single part. + * + * Returns LIBSSH2_ERROR_EAGAIN if it would block or if the whole packet was + * not sent yet. If it does so, the caller should call this function again as + * soon as it is likely that more data can be sent, and this function MUST + * then be called with the same argument set (same data pointer and same + * data_len) until ERROR_NONE or failure is returned. + * + * This function DOES NOT call _libssh2_error() on any errors. + */ +int _libssh2_transport_send(LIBSSH2_SESSION *session, + const unsigned char *data, size_t data_len, + const unsigned char *data2, size_t data2_len); + +/* + * _libssh2_transport_read + * + * Collect a packet into the input brigade block only controls whether or not + * to wait for a packet to start. + * + * Returns packet type added to input brigade (PACKET_NONE if nothing added), + * or PACKET_FAIL on failure and PACKET_EAGAIN if it couldn't process a full + * packet. + */ + +/* + * This function reads the binary stream as specified in chapter 6 of RFC4253 + * "The Secure Shell (SSH) Transport Layer Protocol" + */ +int _libssh2_transport_read(LIBSSH2_SESSION * session); + +#endif /* __LIBSSH2_TRANSPORT_H */ diff --git a/libssh2/include/userauth.h b/libssh2/include/userauth.h new file mode 100644 index 0000000..c0442ae --- /dev/null +++ b/libssh2/include/userauth.h @@ -0,0 +1,50 @@ +#ifndef LIBSSH2_USERAUTH_H +#define LIBSSH2_USERAUTH_H +/* Copyright (c) 2004-2007, Sara Golemon + * Copyright (c) 2009-2010 by Daniel Stenberg + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +int +_libssh2_userauth_publickey(LIBSSH2_SESSION *session, + const char *username, + unsigned int username_len, + const unsigned char *pubkeydata, + unsigned long pubkeydata_len, + LIBSSH2_USERAUTH_PUBLICKEY_SIGN_FUNC((*sign_callback)), + void *abstract); + +#endif /* LIBSSH2_USERAUTH_H */ diff --git a/libssh2/lib/libssh2.1.dylib b/libssh2/lib/libssh2.1.dylib new file mode 100755 index 0000000..ecd7431 Binary files /dev/null and b/libssh2/lib/libssh2.1.dylib differ diff --git a/libssh2/src/agent.c b/libssh2/src/agent.c new file mode 100644 index 0000000..1c65149 --- /dev/null +++ b/libssh2/src/agent.c @@ -0,0 +1,793 @@ +/* + * Copyright (c) 2009 by Daiki Ueno + * Copyright (C) 2010 by Daniel Stenberg + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include "libssh2_priv.h" +#include "misc.h" +#include +#ifdef HAVE_SYS_UN_H +#include +#else +/* Use the existence of sys/un.h as a test if Unix domain socket is + supported. winsock*.h define PF_UNIX/AF_UNIX but do not actually + support them. */ +#undef PF_UNIX +#endif +#include "userauth.h" +#include "session.h" + +/* Requests from client to agent for protocol 1 key operations */ +#define SSH_AGENTC_REQUEST_RSA_IDENTITIES 1 +#define SSH_AGENTC_RSA_CHALLENGE 3 +#define SSH_AGENTC_ADD_RSA_IDENTITY 7 +#define SSH_AGENTC_REMOVE_RSA_IDENTITY 8 +#define SSH_AGENTC_REMOVE_ALL_RSA_IDENTITIES 9 +#define SSH_AGENTC_ADD_RSA_ID_CONSTRAINED 24 + +/* Requests from client to agent for protocol 2 key operations */ +#define SSH2_AGENTC_REQUEST_IDENTITIES 11 +#define SSH2_AGENTC_SIGN_REQUEST 13 +#define SSH2_AGENTC_ADD_IDENTITY 17 +#define SSH2_AGENTC_REMOVE_IDENTITY 18 +#define SSH2_AGENTC_REMOVE_ALL_IDENTITIES 19 +#define SSH2_AGENTC_ADD_ID_CONSTRAINED 25 + +/* Key-type independent requests from client to agent */ +#define SSH_AGENTC_ADD_SMARTCARD_KEY 20 +#define SSH_AGENTC_REMOVE_SMARTCARD_KEY 21 +#define SSH_AGENTC_LOCK 22 +#define SSH_AGENTC_UNLOCK 23 +#define SSH_AGENTC_ADD_SMARTCARD_KEY_CONSTRAINED 26 + +/* Generic replies from agent to client */ +#define SSH_AGENT_FAILURE 5 +#define SSH_AGENT_SUCCESS 6 + +/* Replies from agent to client for protocol 1 key operations */ +#define SSH_AGENT_RSA_IDENTITIES_ANSWER 2 +#define SSH_AGENT_RSA_RESPONSE 4 + +/* Replies from agent to client for protocol 2 key operations */ +#define SSH2_AGENT_IDENTITIES_ANSWER 12 +#define SSH2_AGENT_SIGN_RESPONSE 14 + +/* Key constraint identifiers */ +#define SSH_AGENT_CONSTRAIN_LIFETIME 1 +#define SSH_AGENT_CONSTRAIN_CONFIRM 2 + +/* non-blocking mode on agent connection is not yet implemented, but + for future use. */ +typedef enum { + agent_NB_state_init = 0, + agent_NB_state_request_created, + agent_NB_state_request_length_sent, + agent_NB_state_request_sent, + agent_NB_state_response_length_received, + agent_NB_state_response_received +} agent_nonblocking_states; + +typedef struct agent_transaction_ctx { + unsigned char *request; + size_t request_len; + unsigned char *response; + size_t response_len; + agent_nonblocking_states state; +} *agent_transaction_ctx_t; + +typedef int (*agent_connect_func)(LIBSSH2_AGENT *agent); +typedef int (*agent_transact_func)(LIBSSH2_AGENT *agent, + agent_transaction_ctx_t transctx); +typedef int (*agent_disconnect_func)(LIBSSH2_AGENT *agent); + +struct agent_publickey { + struct list_node node; + + /* this is the struct we expose externally */ + struct libssh2_agent_publickey external; +}; + +struct agent_ops { + agent_connect_func connect; + agent_transact_func transact; + agent_disconnect_func disconnect; +}; + +struct _LIBSSH2_AGENT +{ + LIBSSH2_SESSION *session; /* the session this "belongs to" */ + + libssh2_socket_t fd; + + struct agent_ops *ops; + + struct agent_transaction_ctx transctx; + struct agent_publickey *identity; + struct list_head head; /* list of public keys */ +}; + +#ifdef PF_UNIX +static int +agent_connect_unix(LIBSSH2_AGENT *agent) +{ + const char *path; + struct sockaddr_un s_un; + + path = getenv("SSH_AUTH_SOCK"); + if (!path) + return _libssh2_error(agent->session, LIBSSH2_ERROR_BAD_USE, + "no auth sock variable"); + + agent->fd = socket(PF_UNIX, SOCK_STREAM, 0); + if (agent->fd < 0) + return _libssh2_error(agent->session, LIBSSH2_ERROR_BAD_SOCKET, + "failed creating socket"); + + s_un.sun_family = AF_UNIX; + strncpy (s_un.sun_path, path, sizeof s_un.sun_path); + if (connect(agent->fd, (struct sockaddr*)(&s_un), sizeof s_un) != 0) { + close (agent->fd); + return _libssh2_error(agent->session, LIBSSH2_ERROR_AGENT_PROTOCOL, + "failed connecting with agent"); + } + + return LIBSSH2_ERROR_NONE; +} + +static int +agent_transact_unix(LIBSSH2_AGENT *agent, agent_transaction_ctx_t transctx) +{ + unsigned char buf[4]; + int rc; + + /* Send the length of the request */ + if (transctx->state == agent_NB_state_request_created) { + _libssh2_htonu32(buf, transctx->request_len); + rc = LIBSSH2_SEND_FD(agent->session, agent->fd, buf, sizeof buf, 0); + if (rc == -EAGAIN) + return LIBSSH2_ERROR_EAGAIN; + else if (rc < 0) + return _libssh2_error(agent->session, LIBSSH2_ERROR_SOCKET_SEND, + "agent send failed"); + transctx->state = agent_NB_state_request_length_sent; + } + + /* Send the request body */ + if (transctx->state == agent_NB_state_request_length_sent) { + rc = LIBSSH2_SEND_FD(agent->session, agent->fd, transctx->request, + transctx->request_len, 0); + if (rc == -EAGAIN) + return LIBSSH2_ERROR_EAGAIN; + else if (rc < 0) + return _libssh2_error(agent->session, LIBSSH2_ERROR_SOCKET_SEND, + "agent send failed"); + transctx->state = agent_NB_state_request_sent; + } + + /* Receive the length of a response */ + if (transctx->state == agent_NB_state_request_sent) { + rc = LIBSSH2_RECV_FD(agent->session, agent->fd, buf, sizeof buf, 0); + if (rc < 0) { + if (rc == -EAGAIN) + return LIBSSH2_ERROR_EAGAIN; + return _libssh2_error(agent->session, LIBSSH2_ERROR_SOCKET_RECV, + "agent recv failed"); + } + transctx->response_len = _libssh2_ntohu32(buf); + transctx->response = LIBSSH2_ALLOC(agent->session, + transctx->response_len); + if (!transctx->response) + return LIBSSH2_ERROR_ALLOC; + + transctx->state = agent_NB_state_response_length_received; + } + + /* Receive the response body */ + if (transctx->state == agent_NB_state_response_length_received) { + rc = LIBSSH2_RECV_FD(agent->session, agent->fd, transctx->response, + transctx->response_len, 0); + if (rc < 0) { + if (rc == -EAGAIN) + return LIBSSH2_ERROR_EAGAIN; + return _libssh2_error(agent->session, LIBSSH2_ERROR_SOCKET_SEND, + "agent recv failed"); + } + transctx->state = agent_NB_state_response_received; + } + + return 0; +} + +static int +agent_disconnect_unix(LIBSSH2_AGENT *agent) +{ + int ret; + ret = close(agent->fd); + + if(ret == -1) + return _libssh2_error(agent->session, LIBSSH2_ERROR_SOCKET_DISCONNECT, + "failed closing the agent socket"); + return LIBSSH2_ERROR_NONE; +} + +struct agent_ops agent_ops_unix = { + agent_connect_unix, + agent_transact_unix, + agent_disconnect_unix +}; +#endif /* PF_UNIX */ + +#ifdef WIN32 +/* Code to talk to Pageant was taken from PuTTY. + * + * Portions copyright Robert de Bath, Joris van Rantwijk, Delian + * Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas + * Barry, Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa, + * Markus Kuhn, Colin Watson, and CORE SDI S.A. + */ +#define PAGEANT_COPYDATA_ID 0x804e50ba /* random goop */ +#define PAGEANT_MAX_MSGLEN 8192 + +static int +agent_connect_pageant(LIBSSH2_AGENT *agent) +{ + HWND hwnd; + hwnd = FindWindow("Pageant", "Pageant"); + if (!hwnd) + return _libssh2_error(agent->session, LIBSSH2_ERROR_AGENT_PROTOCOL, + "failed connecting agent"); + agent->fd = 0; /* Mark as the connection has been established */ + return LIBSSH2_ERROR_NONE; +} + +static int +agent_transact_pageant(LIBSSH2_AGENT *agent, agent_transaction_ctx_t transctx) +{ + HWND hwnd; + char mapname[23]; + HANDLE filemap; + unsigned char *p; + unsigned char *p2; + int id; + COPYDATASTRUCT cds; + + if (!transctx || 4 + transctx->request_len > PAGEANT_MAX_MSGLEN) + return _libssh2_error(agent->session, LIBSSH2_ERROR_INVAL, + "illegal input"); + + hwnd = FindWindow("Pageant", "Pageant"); + if (!hwnd) + return _libssh2_error(agent->session, LIBSSH2_ERROR_AGENT_PROTOCOL, + "found no pageant"); + + sprintf(mapname, "PageantRequest%08x", (unsigned)GetCurrentThreadId()); + filemap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, + 0, PAGEANT_MAX_MSGLEN, mapname); + + if (filemap == NULL || filemap == INVALID_HANDLE_VALUE) + return _libssh2_error(agent->session, LIBSSH2_ERROR_AGENT_PROTOCOL, + "failed setting up pageant filemap"); + + p2 = p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0); + _libssh2_store_str(&p2, (const char *)transctx->request, + transctx->request_len); + + cds.dwData = PAGEANT_COPYDATA_ID; + cds.cbData = 1 + strlen(mapname); + cds.lpData = mapname; + + id = SendMessage(hwnd, WM_COPYDATA, (WPARAM) NULL, (LPARAM) &cds); + if (id > 0) { + transctx->response_len = _libssh2_ntohu32(p); + if (transctx->response_len > PAGEANT_MAX_MSGLEN) { + UnmapViewOfFile(p); + CloseHandle(filemap); + return _libssh2_error(agent->session, LIBSSH2_ERROR_AGENT_PROTOCOL, + "agent setup fail"); + } + transctx->response = LIBSSH2_ALLOC(agent->session, + transctx->response_len); + if (!transctx->response) { + UnmapViewOfFile(p); + CloseHandle(filemap); + return _libssh2_error(agent->session, LIBSSH2_ERROR_ALLOC, + "agent malloc"); + } + memcpy(transctx->response, p + 4, transctx->response_len); + } + + UnmapViewOfFile(p); + CloseHandle(filemap); + return 0; +} + +static int +agent_disconnect_pageant(LIBSSH2_AGENT *agent) +{ + agent->fd = LIBSSH2_INVALID_SOCKET; + return 0; +} + +struct agent_ops agent_ops_pageant = { + agent_connect_pageant, + agent_transact_pageant, + agent_disconnect_pageant +}; +#endif /* WIN32 */ + +static struct { + const char *name; + struct agent_ops *ops; +} supported_backends[] = { +#ifdef WIN32 + {"Pageant", &agent_ops_pageant}, +#endif /* WIN32 */ +#ifdef PF_UNIX + {"Unix", &agent_ops_unix}, +#endif /* PF_UNIX */ + {NULL, NULL} +}; + +static int +agent_sign(LIBSSH2_SESSION *session, unsigned char **sig, size_t *sig_len, + const unsigned char *data, size_t data_len, void **abstract) +{ + LIBSSH2_AGENT *agent = (LIBSSH2_AGENT *) (*abstract); + agent_transaction_ctx_t transctx = &agent->transctx; + struct agent_publickey *identity = agent->identity; + ssize_t len = 1 + 4 + identity->external.blob_len + 4 + data_len + 4; + ssize_t method_len; + unsigned char *s; + int rc; + + /* Create a request to sign the data */ + if (transctx->state == agent_NB_state_init) { + s = transctx->request = LIBSSH2_ALLOC(session, len); + if (!transctx->request) + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "out of memory"); + + *s++ = SSH2_AGENTC_SIGN_REQUEST; + /* key blob */ + _libssh2_store_str(&s, (const char *)identity->external.blob, + identity->external.blob_len); + /* data */ + _libssh2_store_str(&s, (const char *)data, data_len); + + /* flags */ + _libssh2_store_u32(&s, 0); + + transctx->request_len = s - transctx->request; + transctx->state = agent_NB_state_request_created; + } + + /* Make sure to be re-called as a result of EAGAIN. */ + if (*transctx->request != SSH2_AGENTC_SIGN_REQUEST) + return _libssh2_error(session, LIBSSH2_ERROR_BAD_USE, + "illegal request"); + + if (!agent->ops) + /* if no agent has been connected, bail out */ + return _libssh2_error(session, LIBSSH2_ERROR_BAD_USE, + "agent not connected"); + + rc = agent->ops->transact(agent, transctx); + if (rc) { + goto error; + } + LIBSSH2_FREE(session, transctx->request); + transctx->request = NULL; + + len = transctx->response_len; + s = transctx->response; + len--; + if (len < 0) { + rc = LIBSSH2_ERROR_AGENT_PROTOCOL; + goto error; + } + if (*s != SSH2_AGENT_SIGN_RESPONSE) { + rc = LIBSSH2_ERROR_AGENT_PROTOCOL; + goto error; + } + s++; + + /* Skip the entire length of the signature */ + len -= 4; + if (len < 0) { + rc = LIBSSH2_ERROR_AGENT_PROTOCOL; + goto error; + } + s += 4; + + /* Skip signing method */ + len -= 4; + if (len < 0) { + rc = LIBSSH2_ERROR_AGENT_PROTOCOL; + goto error; + } + method_len = _libssh2_ntohu32(s); + s += 4; + len -= method_len; + if (len < 0) { + rc = LIBSSH2_ERROR_AGENT_PROTOCOL; + goto error; + } + s += method_len; + + /* Read the signature */ + len -= 4; + if (len < 0) { + rc = LIBSSH2_ERROR_AGENT_PROTOCOL; + goto error; + } + *sig_len = _libssh2_ntohu32(s); + s += 4; + len -= *sig_len; + if (len < 0) { + rc = LIBSSH2_ERROR_AGENT_PROTOCOL; + goto error; + } + + *sig = LIBSSH2_ALLOC(session, *sig_len); + if (!*sig) { + rc = LIBSSH2_ERROR_ALLOC; + goto error; + } + memcpy(*sig, s, *sig_len); + + error: + LIBSSH2_FREE(session, transctx->request); + transctx->request = NULL; + + LIBSSH2_FREE(session, transctx->response); + transctx->response = NULL; + + return _libssh2_error(session, rc, "agent sign failure"); +} + +static int +agent_list_identities(LIBSSH2_AGENT *agent) +{ + agent_transaction_ctx_t transctx = &agent->transctx; + ssize_t len, num_identities; + unsigned char *s; + int rc; + unsigned char c = SSH2_AGENTC_REQUEST_IDENTITIES; + + /* Create a request to list identities */ + if (transctx->state == agent_NB_state_init) { + transctx->request = &c; + transctx->request_len = 1; + transctx->state = agent_NB_state_request_created; + } + + /* Make sure to be re-called as a result of EAGAIN. */ + if (*transctx->request != SSH2_AGENTC_REQUEST_IDENTITIES) + return _libssh2_error(agent->session, LIBSSH2_ERROR_BAD_USE, + "illegal agent request"); + + if (!agent->ops) + /* if no agent has been connected, bail out */ + return _libssh2_error(agent->session, LIBSSH2_ERROR_BAD_USE, + "agent not connected"); + + rc = agent->ops->transact(agent, transctx); + if (rc) { + goto error; + } + transctx->request = NULL; + + len = transctx->response_len; + s = transctx->response; + len--; + if (len < 0) { + rc = LIBSSH2_ERROR_AGENT_PROTOCOL; + goto error; + } + if (*s != SSH2_AGENT_IDENTITIES_ANSWER) { + rc = LIBSSH2_ERROR_AGENT_PROTOCOL; + goto error; + } + s++; + + /* Read the length of identities */ + len -= 4; + if (len < 0) { + rc = LIBSSH2_ERROR_AGENT_PROTOCOL; + goto error; + } + num_identities = _libssh2_ntohu32(s); + s += 4; + + while (num_identities--) { + struct agent_publickey *identity; + ssize_t comment_len; + + identity = LIBSSH2_ALLOC(agent->session, sizeof *identity); + if (!identity) { + rc = LIBSSH2_ERROR_ALLOC; + goto error; + } + + /* Read the length of the blob */ + len -= 4; + if (len < 0) { + rc = LIBSSH2_ERROR_AGENT_PROTOCOL; + goto error; + } + identity->external.blob_len = _libssh2_ntohu32(s); + s += 4; + + /* Read the blob */ + len -= identity->external.blob_len; + if (len < 0) { + rc = LIBSSH2_ERROR_AGENT_PROTOCOL; + goto error; + } + identity->external.blob = LIBSSH2_ALLOC(agent->session, + identity->external.blob_len); + if (!identity->external.blob) { + rc = LIBSSH2_ERROR_ALLOC; + goto error; + } + memcpy(identity->external.blob, s, identity->external.blob_len); + s += identity->external.blob_len; + + /* Read the length of the comment */ + len -= 4; + if (len < 0) { + rc = LIBSSH2_ERROR_AGENT_PROTOCOL; + goto error; + } + comment_len = _libssh2_ntohu32(s); + s += 4; + + /* Read the comment */ + len -= comment_len; + if (len < 0) { + rc = LIBSSH2_ERROR_AGENT_PROTOCOL; + goto error; + } + identity->external.comment = LIBSSH2_ALLOC(agent->session, + comment_len + 1); + if (!identity->external.comment) { + rc = LIBSSH2_ERROR_ALLOC; + goto error; + } + identity->external.comment[comment_len] = '\0'; + memcpy(identity->external.comment, s, comment_len); + s += comment_len; + + _libssh2_list_add(&agent->head, &identity->node); + } + error: + LIBSSH2_FREE(agent->session, transctx->response); + transctx->response = NULL; + + return _libssh2_error(agent->session, rc, + "agent list id failed"); +} + +static void +agent_free_identities(LIBSSH2_AGENT *agent) { + struct agent_publickey *node; + struct agent_publickey *next; + + for (node = _libssh2_list_first(&agent->head); node; node = next) { + next = _libssh2_list_next(&node->node); + LIBSSH2_FREE(agent->session, node->external.blob); + LIBSSH2_FREE(agent->session, node->external.comment); + LIBSSH2_FREE(agent->session, node); + } + _libssh2_list_init(&agent->head); +} + +#define AGENT_PUBLICKEY_MAGIC 0x3bdefed2 +/* + * agent_publickey_to_external() + * + * Copies data from the internal to the external representation struct. + * + */ +static struct libssh2_agent_publickey * +agent_publickey_to_external(struct agent_publickey *node) +{ + struct libssh2_agent_publickey *ext = &node->external; + + ext->magic = AGENT_PUBLICKEY_MAGIC; + ext->node = node; + + return ext; +} + +/* + * libssh2_agent_init + * + * Init an ssh-agent handle. Returns the pointer to the handle. + * + */ +LIBSSH2_API LIBSSH2_AGENT * +libssh2_agent_init(LIBSSH2_SESSION *session) +{ + LIBSSH2_AGENT *agent; + + agent = LIBSSH2_ALLOC(session, sizeof *agent); + if (!agent) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate space for agent connection"); + return NULL; + } + memset(agent, 0, sizeof *agent); + agent->session = session; + _libssh2_list_init(&agent->head); + + return agent; +} + +/* + * libssh2_agent_connect() + * + * Connect to an ssh-agent. + * + * Returns 0 if succeeded, or a negative value for error. + */ +LIBSSH2_API int +libssh2_agent_connect(LIBSSH2_AGENT *agent) +{ + int i, rc = -1; + for (i = 0; supported_backends[i].name; i++) { + agent->ops = supported_backends[i].ops; + rc = agent->ops->connect(agent); + if (!rc) + return 0; + } + return rc; +} + +/* + * libssh2_agent_list_identities() + * + * Request ssh-agent to list identities. + * + * Returns 0 if succeeded, or a negative value for error. + */ +LIBSSH2_API int +libssh2_agent_list_identities(LIBSSH2_AGENT *agent) +{ + memset(&agent->transctx, 0, sizeof agent->transctx); + /* Abondon the last fetched identities */ + agent_free_identities(agent); + return agent_list_identities(agent); +} + +/* + * libssh2_agent_get_identity() + * + * Traverse the internal list of public keys. Pass NULL to 'prev' to get + * the first one. Or pass a poiner to the previously returned one to get the + * next. + * + * Returns: + * 0 if a fine public key was stored in 'store' + * 1 if end of public keys + * [negative] on errors + */ +LIBSSH2_API int +libssh2_agent_get_identity(LIBSSH2_AGENT *agent, + struct libssh2_agent_publickey **ext, + struct libssh2_agent_publickey *oprev) +{ + struct agent_publickey *node; + if (oprev && oprev->node) { + /* we have a starting point */ + struct agent_publickey *prev = oprev->node; + + /* get the next node in the list */ + node = _libssh2_list_next(&prev->node); + } + else + node = _libssh2_list_first(&agent->head); + + if (!node) + /* no (more) node */ + return 1; + + *ext = agent_publickey_to_external(node); + + return 0; +} + +/* + * libssh2_agent_userauth() + * + * Do publickey user authentication with the help of ssh-agent. + * + * Returns 0 if succeeded, or a negative value for error. + */ +LIBSSH2_API int +libssh2_agent_userauth(LIBSSH2_AGENT *agent, + const char *username, + struct libssh2_agent_publickey *identity) +{ + void *abstract = agent; + int rc; + + if (agent->session->userauth_pblc_state == libssh2_NB_state_idle) { + memset(&agent->transctx, 0, sizeof agent->transctx); + agent->identity = identity->node; + } + + BLOCK_ADJUST(rc, agent->session, + _libssh2_userauth_publickey(agent->session, username, + strlen(username), + identity->blob, + identity->blob_len, + agent_sign, + &abstract)); + return rc; +} + +/* + * libssh2_agent_disconnect() + * + * Close a connection to an ssh-agent. + * + * Returns 0 if succeeded, or a negative value for error. + */ +LIBSSH2_API int +libssh2_agent_disconnect(LIBSSH2_AGENT *agent) +{ + if (agent->ops && agent->fd != LIBSSH2_INVALID_SOCKET) + return agent->ops->disconnect(agent); + return 0; +} + +/* + * libssh2_agent_free() + * + * Free an ssh-agent handle. This function also frees the internal + * collection of public keys. + */ +LIBSSH2_API void +libssh2_agent_free(LIBSSH2_AGENT *agent) { + /* Allow connection freeing when the socket has lost its connection */ + if (agent->fd != LIBSSH2_INVALID_SOCKET) { + libssh2_agent_disconnect(agent); + } + agent_free_identities(agent); + LIBSSH2_FREE(agent->session, agent); +} diff --git a/libssh2/src/channel.c b/libssh2/src/channel.c new file mode 100644 index 0000000..4f41e1f --- /dev/null +++ b/libssh2/src/channel.c @@ -0,0 +1,2571 @@ +/* Copyright (c) 2004-2007 Sara Golemon + * Copyright (c) 2005 Mikhail Gusarov + * Copyright (c) 2008-2011 by Daniel Stenberg + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include "libssh2_priv.h" +#ifdef HAVE_UNISTD_H +#include +#endif +#include +#ifdef HAVE_INTTYPES_H +#include +#endif +#include + +#include "channel.h" +#include "transport.h" +#include "packet.h" +#include "session.h" + +/* + * _libssh2_channel_nextid + * + * Determine the next channel ID we can use at our end + */ +uint32_t +_libssh2_channel_nextid(LIBSSH2_SESSION * session) +{ + uint32_t id = session->next_channel; + LIBSSH2_CHANNEL *channel; + + channel = _libssh2_list_first(&session->channels); + + while (channel) { + if (channel->local.id > id) { + id = channel->local.id; + } + channel = _libssh2_list_next(&channel->node); + } + + /* This is a shortcut to avoid waiting for close packets on channels we've + * forgotten about, This *could* be a problem if we request and close 4 + * billion or so channels in too rapid succession for the remote end to + * respond, but the worst case scenario is that some data meant for + * another channel Gets picked up by the new one.... Pretty unlikely all + * told... + */ + session->next_channel = id + 1; + _libssh2_debug(session, LIBSSH2_TRACE_CONN, "Allocated new channel ID#%lu", + id); + return id; +} + +/* + * _libssh2_channel_locate + * + * Locate a channel pointer by number + */ +LIBSSH2_CHANNEL * +_libssh2_channel_locate(LIBSSH2_SESSION *session, uint32_t channel_id) +{ + LIBSSH2_CHANNEL *channel; + LIBSSH2_LISTENER *l; + + for(channel = _libssh2_list_first(&session->channels); + channel; + channel = _libssh2_list_next(&channel->node)) { + if (channel->local.id == channel_id) + return channel; + } + + /* We didn't find the channel in the session, let's then check its + listeners since each listener may have its own set of pending channels + */ + for(l = _libssh2_list_first(&session->listeners); l; + l = _libssh2_list_next(&l->node)) { + for(channel = _libssh2_list_first(&l->queue); + channel; + channel = _libssh2_list_next(&channel->node)) { + if (channel->local.id == channel_id) + return channel; + } + } + + return NULL; +} + +/* + * _libssh2_channel_open + * + * Establish a generic session channel + */ +LIBSSH2_CHANNEL * +_libssh2_channel_open(LIBSSH2_SESSION * session, const char *channel_type, + uint32_t channel_type_len, + uint32_t window_size, + uint32_t packet_size, + const unsigned char *message, + size_t message_len) +{ + static const unsigned char reply_codes[3] = { + SSH_MSG_CHANNEL_OPEN_CONFIRMATION, + SSH_MSG_CHANNEL_OPEN_FAILURE, + 0 + }; + unsigned char *s; + int rc; + + if (session->open_state == libssh2_NB_state_idle) { + session->open_channel = NULL; + session->open_packet = NULL; + session->open_data = NULL; + /* 17 = packet_type(1) + channel_type_len(4) + sender_channel(4) + + * window_size(4) + packet_size(4) */ + session->open_packet_len = channel_type_len + 17; + session->open_local_channel = _libssh2_channel_nextid(session); + + /* Zero the whole thing out */ + memset(&session->open_packet_requirev_state, 0, + sizeof(session->open_packet_requirev_state)); + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Opening Channel - win %d pack %d", window_size, + packet_size); + session->open_channel = + LIBSSH2_ALLOC(session, sizeof(LIBSSH2_CHANNEL)); + if (!session->open_channel) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate space for channel data"); + return NULL; + } + memset(session->open_channel, 0, sizeof(LIBSSH2_CHANNEL)); + + session->open_channel->channel_type_len = channel_type_len; + session->open_channel->channel_type = + LIBSSH2_ALLOC(session, channel_type_len); + if (!session->open_channel->channel_type) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Failed allocating memory for channel type name"); + LIBSSH2_FREE(session, session->open_channel); + session->open_channel = NULL; + return NULL; + } + memcpy(session->open_channel->channel_type, channel_type, + channel_type_len); + + /* REMEMBER: local as in locally sourced */ + session->open_channel->local.id = session->open_local_channel; + session->open_channel->remote.window_size = window_size; + session->open_channel->remote.window_size_initial = window_size; + session->open_channel->remote.packet_size = packet_size; + session->open_channel->session = session; + + _libssh2_list_add(&session->channels, + &session->open_channel->node); + + s = session->open_packet = + LIBSSH2_ALLOC(session, session->open_packet_len); + if (!session->open_packet) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate temporary space for packet"); + goto channel_error; + } + *(s++) = SSH_MSG_CHANNEL_OPEN; + _libssh2_store_str(&s, channel_type, channel_type_len); + _libssh2_store_u32(&s, session->open_local_channel); + _libssh2_store_u32(&s, window_size); + _libssh2_store_u32(&s, packet_size); + + /* Do not copy the message */ + + session->open_state = libssh2_NB_state_created; + } + + if (session->open_state == libssh2_NB_state_created) { + rc = _libssh2_transport_send(session, + session->open_packet, + session->open_packet_len, + message, message_len); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, rc, + "Would block sending channel-open request"); + return NULL; + } + else if (rc) { + _libssh2_error(session, rc, + "Unable to send channel-open request"); + goto channel_error; + } + + session->open_state = libssh2_NB_state_sent; + } + + if (session->open_state == libssh2_NB_state_sent) { + rc = _libssh2_packet_requirev(session, reply_codes, + &session->open_data, + &session->open_data_len, 1, + session->open_packet + 5 + + channel_type_len, 4, + &session->open_packet_requirev_state); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, "Would block"); + return NULL; + } else if (rc) { + goto channel_error; + } + + if (session->open_data[0] == SSH_MSG_CHANNEL_OPEN_CONFIRMATION) { + session->open_channel->remote.id = + _libssh2_ntohu32(session->open_data + 5); + session->open_channel->local.window_size = + _libssh2_ntohu32(session->open_data + 9); + session->open_channel->local.window_size_initial = + _libssh2_ntohu32(session->open_data + 9); + session->open_channel->local.packet_size = + _libssh2_ntohu32(session->open_data + 13); + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Connection Established - ID: %lu/%lu win: %lu/%lu" + " pack: %lu/%lu", + session->open_channel->local.id, + session->open_channel->remote.id, + session->open_channel->local.window_size, + session->open_channel->remote.window_size, + session->open_channel->local.packet_size, + session->open_channel->remote.packet_size); + LIBSSH2_FREE(session, session->open_packet); + session->open_packet = NULL; + LIBSSH2_FREE(session, session->open_data); + session->open_data = NULL; + + session->open_state = libssh2_NB_state_idle; + return session->open_channel; + } + + if (session->open_data[0] == SSH_MSG_CHANNEL_OPEN_FAILURE) { + _libssh2_error(session, LIBSSH2_ERROR_CHANNEL_FAILURE, + "Channel open failure"); + } + } + + channel_error: + + if (session->open_data) { + LIBSSH2_FREE(session, session->open_data); + session->open_data = NULL; + } + if (session->open_packet) { + LIBSSH2_FREE(session, session->open_packet); + session->open_packet = NULL; + } + if (session->open_channel) { + unsigned char channel_id[4]; + LIBSSH2_FREE(session, session->open_channel->channel_type); + + _libssh2_list_remove(&session->open_channel->node); + + /* Clear out packets meant for this channel */ + _libssh2_htonu32(channel_id, session->open_channel->local.id); + while ((_libssh2_packet_ask(session, SSH_MSG_CHANNEL_DATA, + &session->open_data, + &session->open_data_len, 1, + channel_id, 4) >= 0) + || + (_libssh2_packet_ask(session, SSH_MSG_CHANNEL_EXTENDED_DATA, + &session->open_data, + &session->open_data_len, 1, + channel_id, 4) >= 0)) { + LIBSSH2_FREE(session, session->open_data); + session->open_data = NULL; + } + + LIBSSH2_FREE(session, session->open_channel); + session->open_channel = NULL; + } + + session->open_state = libssh2_NB_state_idle; + return NULL; +} + +/* + * libssh2_channel_open_ex + * + * Establish a generic session channel + */ +LIBSSH2_API LIBSSH2_CHANNEL * +libssh2_channel_open_ex(LIBSSH2_SESSION *session, const char *type, + unsigned int type_len, + unsigned int window_size, unsigned int packet_size, + const char *msg, unsigned int msg_len) +{ + LIBSSH2_CHANNEL *ptr; + + if(!session) + return NULL; + + BLOCK_ADJUST_ERRNO(ptr, session, + _libssh2_channel_open(session, type, type_len, + window_size, packet_size, + (unsigned char *)msg, + msg_len)); + return ptr; +} + +/* + * libssh2_channel_direct_tcpip_ex + * + * Tunnel TCP/IP connect through the SSH session to direct host/port + */ +static LIBSSH2_CHANNEL * +channel_direct_tcpip(LIBSSH2_SESSION * session, const char *host, + int port, const char *shost, int sport) +{ + LIBSSH2_CHANNEL *channel; + unsigned char *s; + + if (session->direct_state == libssh2_NB_state_idle) { + session->direct_host_len = strlen(host); + session->direct_shost_len = strlen(shost); + /* host_len(4) + port(4) + shost_len(4) + sport(4) */ + session->direct_message_len = + session->direct_host_len + session->direct_shost_len + 16; + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Requesting direct-tcpip session to from %s:%d to %s:%d", + shost, sport, host, port); + + s = session->direct_message = + LIBSSH2_ALLOC(session, session->direct_message_len); + if (!session->direct_message) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for direct-tcpip connection"); + return NULL; + } + _libssh2_store_str(&s, host, session->direct_host_len); + _libssh2_store_u32(&s, port); + _libssh2_store_str(&s, shost, session->direct_shost_len); + _libssh2_store_u32(&s, sport); + } + + channel = + _libssh2_channel_open(session, "direct-tcpip", + sizeof("direct-tcpip") - 1, + LIBSSH2_CHANNEL_WINDOW_DEFAULT, + LIBSSH2_CHANNEL_PACKET_DEFAULT, + session->direct_message, + session->direct_message_len); + + if (!channel && + libssh2_session_last_errno(session) == LIBSSH2_ERROR_EAGAIN) { + /* The error code is still set to LIBSSH2_ERROR_EAGAIN, set our state + to created to avoid re-creating the package on next invoke */ + session->direct_state = libssh2_NB_state_created; + return NULL; + } + /* by default we set (keep?) idle state... */ + session->direct_state = libssh2_NB_state_idle; + + LIBSSH2_FREE(session, session->direct_message); + session->direct_message = NULL; + + return channel; +} + +/* + * libssh2_channel_direct_tcpip_ex + * + * Tunnel TCP/IP connect through the SSH session to direct host/port + */ +LIBSSH2_API LIBSSH2_CHANNEL * +libssh2_channel_direct_tcpip_ex(LIBSSH2_SESSION *session, const char *host, + int port, const char *shost, int sport) +{ + LIBSSH2_CHANNEL *ptr; + + if(!session) + return NULL; + + BLOCK_ADJUST_ERRNO(ptr, session, + channel_direct_tcpip(session, host, port, shost, sport)); + return ptr; +} + +/* + * channel_forward_listen + * + * Bind a port on the remote host and listen for connections + */ +static LIBSSH2_LISTENER * +channel_forward_listen(LIBSSH2_SESSION * session, const char *host, + int port, int *bound_port, int queue_maxsize) +{ + unsigned char *s; + static const unsigned char reply_codes[3] = + { SSH_MSG_REQUEST_SUCCESS, SSH_MSG_REQUEST_FAILURE, 0 }; + int rc; + + if(!host) + host = "0.0.0.0"; + + if (session->fwdLstn_state == libssh2_NB_state_idle) { + session->fwdLstn_host_len = strlen(host); + /* 14 = packet_type(1) + request_len(4) + want_replay(1) + host_len(4) + + port(4) */ + session->fwdLstn_packet_len = + session->fwdLstn_host_len + (sizeof("tcpip-forward") - 1) + 14; + + /* Zero the whole thing out */ + memset(&session->fwdLstn_packet_requirev_state, 0, + sizeof(session->fwdLstn_packet_requirev_state)); + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Requesting tcpip-forward session for %s:%d", host, + port); + + s = session->fwdLstn_packet = + LIBSSH2_ALLOC(session, session->fwdLstn_packet_len); + if (!session->fwdLstn_packet) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memeory for setenv packet"); + return NULL; + } + + *(s++) = SSH_MSG_GLOBAL_REQUEST; + _libssh2_store_str(&s, "tcpip-forward", sizeof("tcpip-forward") - 1); + *(s++) = 0x01; /* want_reply */ + + _libssh2_store_str(&s, host, session->fwdLstn_host_len); + _libssh2_store_u32(&s, port); + + session->fwdLstn_state = libssh2_NB_state_created; + } + + if (session->fwdLstn_state == libssh2_NB_state_created) { + rc = _libssh2_transport_send(session, + session->fwdLstn_packet, + session->fwdLstn_packet_len, + NULL, 0); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block sending global-request packet for " + "forward listen request"); + return NULL; + } + else if (rc) { + _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send global-request packet for forward " + "listen request"); + LIBSSH2_FREE(session, session->fwdLstn_packet); + session->fwdLstn_packet = NULL; + session->fwdLstn_state = libssh2_NB_state_idle; + return NULL; + } + LIBSSH2_FREE(session, session->fwdLstn_packet); + session->fwdLstn_packet = NULL; + + session->fwdLstn_state = libssh2_NB_state_sent; + } + + if (session->fwdLstn_state == libssh2_NB_state_sent) { + unsigned char *data; + size_t data_len; + rc = _libssh2_packet_requirev(session, reply_codes, &data, &data_len, + 0, NULL, 0, + &session->fwdLstn_packet_requirev_state); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, "Would block"); + return NULL; + } else if (rc) { + _libssh2_error(session, LIBSSH2_ERROR_PROTO, "Unknown"); + session->fwdLstn_state = libssh2_NB_state_idle; + return NULL; + } + + if (data[0] == SSH_MSG_REQUEST_SUCCESS) { + LIBSSH2_LISTENER *listener; + + listener = LIBSSH2_ALLOC(session, sizeof(LIBSSH2_LISTENER)); + if (!listener) + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for listener queue"); + else { + memset(listener, 0, sizeof(LIBSSH2_LISTENER)); + listener->host = + LIBSSH2_ALLOC(session, session->fwdLstn_host_len + 1); + if (!listener->host) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for listener queue"); + LIBSSH2_FREE(session, listener); + listener = NULL; + } + else { + listener->session = session; + memcpy(listener->host, host ? host : "0.0.0.0", + session->fwdLstn_host_len); + listener->host[session->fwdLstn_host_len] = 0; + if (data_len >= 5 && !port) { + listener->port = _libssh2_ntohu32(data + 1); + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Dynamic tcpip-forward port allocated: %d", + listener->port); + } + else + listener->port = port; + + listener->queue_size = 0; + listener->queue_maxsize = queue_maxsize; + + /* append this to the parent's list of listeners */ + _libssh2_list_add(&session->listeners, &listener->node); + + if (bound_port) { + *bound_port = listener->port; + } + } + } + + LIBSSH2_FREE(session, data); + session->fwdLstn_state = libssh2_NB_state_idle; + return listener; + } + else if (data[0] == SSH_MSG_REQUEST_FAILURE) { + LIBSSH2_FREE(session, data); + _libssh2_error(session, LIBSSH2_ERROR_REQUEST_DENIED, + "Unable to complete request for forward-listen"); + session->fwdLstn_state = libssh2_NB_state_idle; + return NULL; + } + } + + session->fwdLstn_state = libssh2_NB_state_idle; + + return NULL; +} + +/* + * libssh2_channel_forward_listen_ex + * + * Bind a port on the remote host and listen for connections + */ +LIBSSH2_API LIBSSH2_LISTENER * +libssh2_channel_forward_listen_ex(LIBSSH2_SESSION *session, const char *host, + int port, int *bound_port, int queue_maxsize) +{ + LIBSSH2_LISTENER *ptr; + + if(!session) + return NULL; + + BLOCK_ADJUST_ERRNO(ptr, session, + channel_forward_listen(session, host, port, bound_port, + queue_maxsize)); + return ptr; +} + +/* + * _libssh2_channel_forward_cancel + * + * Stop listening on a remote port and free the listener + * Toss out any pending (un-accept()ed) connections + * + * Return 0 on success, LIBSSH2_ERROR_EAGAIN if would block, -1 on error + */ +int _libssh2_channel_forward_cancel(LIBSSH2_LISTENER *listener) +{ + LIBSSH2_SESSION *session = listener->session; + LIBSSH2_CHANNEL *queued; + unsigned char *packet, *s; + size_t host_len = strlen(listener->host); + /* 14 = packet_type(1) + request_len(4) + want_replay(1) + host_len(4) + + port(4) */ + size_t packet_len = + host_len + 14 + sizeof("cancel-tcpip-forward") - 1; + int rc; + + if (listener->chanFwdCncl_state == libssh2_NB_state_idle) { + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Cancelling tcpip-forward session for %s:%d", + listener->host, listener->port); + + s = packet = LIBSSH2_ALLOC(session, packet_len); + if (!packet) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memeory for setenv packet"); + return LIBSSH2_ERROR_ALLOC; + } + + *(s++) = SSH_MSG_GLOBAL_REQUEST; + _libssh2_store_str(&s, "cancel-tcpip-forward", + sizeof("cancel-tcpip-forward") - 1); + *(s++) = 0x00; /* want_reply */ + + _libssh2_store_str(&s, listener->host, host_len); + _libssh2_store_u32(&s, listener->port); + + listener->chanFwdCncl_state = libssh2_NB_state_created; + } else { + packet = listener->chanFwdCncl_data; + } + + if (listener->chanFwdCncl_state == libssh2_NB_state_created) { + rc = _libssh2_transport_send(session, packet, packet_len, NULL, 0); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, rc, + "Would block sending forward request"); + listener->chanFwdCncl_data = packet; + return rc; + } + else if (rc) { + _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send global-request packet for forward " + "listen request"); + LIBSSH2_FREE(session, packet); + listener->chanFwdCncl_state = libssh2_NB_state_idle; + return LIBSSH2_ERROR_SOCKET_SEND; + } + LIBSSH2_FREE(session, packet); + + listener->chanFwdCncl_state = libssh2_NB_state_sent; + } + + queued = _libssh2_list_first(&listener->queue); + while (queued) { + LIBSSH2_CHANNEL *next = _libssh2_list_next(&queued->node); + + rc = _libssh2_channel_free(queued); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } + queued = next; + } + LIBSSH2_FREE(session, listener->host); + + /* remove this entry from the parent's list of listeners */ + _libssh2_list_remove(&listener->node); + + LIBSSH2_FREE(session, listener); + + listener->chanFwdCncl_state = libssh2_NB_state_idle; + + return 0; +} + +/* + * libssh2_channel_forward_cancel + * + * Stop listening on a remote port and free the listener + * Toss out any pending (un-accept()ed) connections + * + * Return 0 on success, LIBSSH2_ERROR_EAGAIN if would block, -1 on error + */ +LIBSSH2_API int +libssh2_channel_forward_cancel(LIBSSH2_LISTENER *listener) +{ + int rc; + + if(!listener) + return LIBSSH2_ERROR_BAD_USE; + + BLOCK_ADJUST(rc, listener->session, + _libssh2_channel_forward_cancel(listener)); + return rc; +} + +/* + * channel_forward_accept + * + * Accept a connection + */ +static LIBSSH2_CHANNEL * +channel_forward_accept(LIBSSH2_LISTENER *listener) +{ + int rc; + + do { + rc = _libssh2_transport_read(listener->session); + } while (rc > 0); + + if (_libssh2_list_first(&listener->queue)) { + LIBSSH2_CHANNEL *channel = _libssh2_list_first(&listener->queue); + + /* detach channel from listener's queue */ + _libssh2_list_remove(&channel->node); + + listener->queue_size--; + + /* add channel to session's channel list */ + _libssh2_list_add(&channel->session->channels, &channel->node); + + return channel; + } + + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(listener->session, LIBSSH2_ERROR_EAGAIN, + "Would block waiting for packet"); + } + else + _libssh2_error(listener->session, LIBSSH2_ERROR_CHANNEL_UNKNOWN, + "Channel not found"); + return NULL; +} + +/* + * libssh2_channel_forward_accept + * + * Accept a connection + */ +LIBSSH2_API LIBSSH2_CHANNEL * +libssh2_channel_forward_accept(LIBSSH2_LISTENER *listener) +{ + LIBSSH2_CHANNEL *ptr; + + if(!listener) + return NULL; + + BLOCK_ADJUST_ERRNO(ptr, listener->session, + channel_forward_accept(listener)); + return ptr; + +} + +/* + * channel_setenv + * + * Set an environment variable prior to requesting a shell/program/subsystem + */ +static int channel_setenv(LIBSSH2_CHANNEL *channel, + const char *varname, unsigned int varname_len, + const char *value, unsigned int value_len) +{ + LIBSSH2_SESSION *session = channel->session; + unsigned char *s, *data; + static const unsigned char reply_codes[3] = + { SSH_MSG_CHANNEL_SUCCESS, SSH_MSG_CHANNEL_FAILURE, 0 }; + size_t data_len; + int rc; + + if (channel->setenv_state == libssh2_NB_state_idle) { + /* 21 = packet_type(1) + channel_id(4) + request_len(4) + + * request(3)"env" + want_reply(1) + varname_len(4) + value_len(4) */ + channel->setenv_packet_len = varname_len + value_len + 21; + + /* Zero the whole thing out */ + memset(&channel->setenv_packet_requirev_state, 0, + sizeof(channel->setenv_packet_requirev_state)); + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Setting remote environment variable: %s=%s on " + "channel %lu/%lu", + varname, value, channel->local.id, channel->remote.id); + + s = channel->setenv_packet = + LIBSSH2_ALLOC(session, channel->setenv_packet_len); + if (!channel->setenv_packet) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memeory " + "for setenv packet"); + } + + *(s++) = SSH_MSG_CHANNEL_REQUEST; + _libssh2_store_u32(&s, channel->remote.id); + _libssh2_store_str(&s, "env", sizeof("env") - 1); + *(s++) = 0x01; + _libssh2_store_str(&s, varname, varname_len); + _libssh2_store_str(&s, value, value_len); + + channel->setenv_state = libssh2_NB_state_created; + } + + if (channel->setenv_state == libssh2_NB_state_created) { + rc = _libssh2_transport_send(session, + channel->setenv_packet, + channel->setenv_packet_len, + NULL, 0); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, rc, + "Would block sending setenv request"); + return rc; + } else if (rc) { + LIBSSH2_FREE(session, channel->setenv_packet); + channel->setenv_packet = NULL; + channel->setenv_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send channel-request packet for " + "setenv request"); + } + LIBSSH2_FREE(session, channel->setenv_packet); + channel->setenv_packet = NULL; + + _libssh2_htonu32(channel->setenv_local_channel, channel->local.id); + + channel->setenv_state = libssh2_NB_state_sent; + } + + if (channel->setenv_state == libssh2_NB_state_sent) { + rc = _libssh2_packet_requirev(session, reply_codes, &data, &data_len, + 1, channel->setenv_local_channel, 4, + &channel-> + setenv_packet_requirev_state); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } + if (rc) { + channel->setenv_state = libssh2_NB_state_idle; + return rc; + } + + if (data[0] == SSH_MSG_CHANNEL_SUCCESS) { + LIBSSH2_FREE(session, data); + channel->setenv_state = libssh2_NB_state_idle; + return 0; + } + + LIBSSH2_FREE(session, data); + } + + channel->setenv_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED, + "Unable to complete request for channel-setenv"); +} + +/* + * libssh2_channel_setenv_ex + * + * Set an environment variable prior to requesting a shell/program/subsystem + */ +LIBSSH2_API int +libssh2_channel_setenv_ex(LIBSSH2_CHANNEL *channel, + const char *varname, unsigned int varname_len, + const char *value, unsigned int value_len) +{ + int rc; + + if(!channel) + return LIBSSH2_ERROR_BAD_USE; + + BLOCK_ADJUST(rc, channel->session, + channel_setenv(channel, varname, varname_len, + value, value_len)); + return rc; +} + +/* + * channel_request_pty + * Duh... Request a PTY + */ +static int channel_request_pty(LIBSSH2_CHANNEL *channel, + const char *term, unsigned int term_len, + const char *modes, unsigned int modes_len, + int width, int height, + int width_px, int height_px) +{ + LIBSSH2_SESSION *session = channel->session; + unsigned char *s; + static const unsigned char reply_codes[3] = + { SSH_MSG_CHANNEL_SUCCESS, SSH_MSG_CHANNEL_FAILURE, 0 }; + int rc; + + if (channel->reqPTY_state == libssh2_NB_state_idle) { + /* 41 = packet_type(1) + channel(4) + pty_req_len(4) + "pty_req"(7) + + * want_reply(1) + term_len(4) + width(4) + height(4) + width_px(4) + + * height_px(4) + modes_len(4) */ + if(term_len + modes_len > 256) { + return _libssh2_error(session, LIBSSH2_ERROR_INVAL, + "term + mode lengths too large"); + } + + channel->reqPTY_packet_len = term_len + modes_len + 41; + + /* Zero the whole thing out */ + memset(&channel->reqPTY_packet_requirev_state, 0, + sizeof(channel->reqPTY_packet_requirev_state)); + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Allocating tty on channel %lu/%lu", channel->local.id, + channel->remote.id); + + s = channel->reqPTY_packet; + + *(s++) = SSH_MSG_CHANNEL_REQUEST; + _libssh2_store_u32(&s, channel->remote.id); + _libssh2_store_str(&s, (char *)"pty-req", sizeof("pty-req") - 1); + + *(s++) = 0x01; + + _libssh2_store_str(&s, term, term_len); + _libssh2_store_u32(&s, width); + _libssh2_store_u32(&s, height); + _libssh2_store_u32(&s, width_px); + _libssh2_store_u32(&s, height_px); + _libssh2_store_str(&s, modes, modes_len); + + channel->reqPTY_state = libssh2_NB_state_created; + } + + if (channel->reqPTY_state == libssh2_NB_state_created) { + rc = _libssh2_transport_send(session, channel->reqPTY_packet, + channel->reqPTY_packet_len, + NULL, 0); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, rc, + "Would block sending pty request"); + return rc; + } else if (rc) { + channel->reqPTY_state = libssh2_NB_state_idle; + return _libssh2_error(session, rc, + "Unable to send pty-request packet"); + } + _libssh2_htonu32(channel->reqPTY_local_channel, channel->local.id); + + channel->reqPTY_state = libssh2_NB_state_sent; + } + + if (channel->reqPTY_state == libssh2_NB_state_sent) { + unsigned char *data; + size_t data_len; + unsigned char code; + rc = _libssh2_packet_requirev(session, reply_codes, &data, &data_len, + 1, channel->reqPTY_local_channel, 4, + &channel->reqPTY_packet_requirev_state); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } else if (rc) { + channel->reqPTY_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Failed to require the PTY package"); + } + + code = data[0]; + + LIBSSH2_FREE(session, data); + channel->reqPTY_state = libssh2_NB_state_idle; + + if (code == SSH_MSG_CHANNEL_SUCCESS) + return 0; + } + + return _libssh2_error(session, LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED, + "Unable to complete request for channel request-pty"); +} + +/* + * libssh2_channel_request_pty_ex + * Duh... Request a PTY + */ +LIBSSH2_API int +libssh2_channel_request_pty_ex(LIBSSH2_CHANNEL *channel, const char *term, + unsigned int term_len, const char *modes, + unsigned int modes_len, int width, int height, + int width_px, int height_px) +{ + int rc; + + if(!channel) + return LIBSSH2_ERROR_BAD_USE; + + BLOCK_ADJUST(rc, channel->session, + channel_request_pty(channel, term, term_len, modes, + modes_len, width, height, + width_px, height_px)); + return rc; +} + +static int +channel_request_pty_size(LIBSSH2_CHANNEL * channel, int width, + int height, int width_px, int height_px) +{ + LIBSSH2_SESSION *session = channel->session; + unsigned char *s; + int rc; + int retcode = LIBSSH2_ERROR_PROTO; + + if (channel->reqPTY_state == libssh2_NB_state_idle) { + channel->reqPTY_packet_len = 39; + + /* Zero the whole thing out */ + memset(&channel->reqPTY_packet_requirev_state, 0, + sizeof(channel->reqPTY_packet_requirev_state)); + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "changing tty size on channel %lu/%lu", + channel->local.id, + channel->remote.id); + + s = channel->reqPTY_packet; + + *(s++) = SSH_MSG_CHANNEL_REQUEST; + _libssh2_store_u32(&s, channel->remote.id); + _libssh2_store_str(&s, (char *)"window-change", + sizeof("window-change") - 1); + *(s++) = 0x00; /* Don't reply */ + _libssh2_store_u32(&s, width); + _libssh2_store_u32(&s, height); + _libssh2_store_u32(&s, width_px); + _libssh2_store_u32(&s, height_px); + + channel->reqPTY_state = libssh2_NB_state_created; + } + + if (channel->reqPTY_state == libssh2_NB_state_created) { + rc = _libssh2_transport_send(session, channel->reqPTY_packet, + channel->reqPTY_packet_len, + NULL, 0); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, rc, + "Would block sending window-change request"); + return rc; + } else if (rc) { + channel->reqPTY_state = libssh2_NB_state_idle; + return _libssh2_error(session, rc, + "Unable to send window-change packet"); + } + _libssh2_htonu32(channel->reqPTY_local_channel, channel->local.id); + retcode = LIBSSH2_ERROR_NONE; + } + + channel->reqPTY_state = libssh2_NB_state_idle; + return retcode; +} + +LIBSSH2_API int +libssh2_channel_request_pty_size_ex(LIBSSH2_CHANNEL *channel, int width, + int height, int width_px, int height_px) +{ + int rc; + + if(!channel) + return LIBSSH2_ERROR_BAD_USE; + + BLOCK_ADJUST(rc, channel->session, + channel_request_pty_size(channel, width, height, width_px, + height_px)); + return rc; +} + +/* Keep this an even number */ +#define LIBSSH2_X11_RANDOM_COOKIE_LEN 32 + +/* + * channel_x11_req + * Request X11 forwarding + */ +static int +channel_x11_req(LIBSSH2_CHANNEL *channel, int single_connection, + const char *auth_proto, const char *auth_cookie, + int screen_number) +{ + LIBSSH2_SESSION *session = channel->session; + unsigned char *s; + static const unsigned char reply_codes[3] = + { SSH_MSG_CHANNEL_SUCCESS, SSH_MSG_CHANNEL_FAILURE, 0 }; + size_t proto_len = + auth_proto ? strlen(auth_proto) : (sizeof("MIT-MAGIC-COOKIE-1") - 1); + size_t cookie_len = + auth_cookie ? strlen(auth_cookie) : LIBSSH2_X11_RANDOM_COOKIE_LEN; + int rc; + + if (channel->reqX11_state == libssh2_NB_state_idle) { + /* 30 = packet_type(1) + channel(4) + x11_req_len(4) + "x11-req"(7) + + * want_reply(1) + single_cnx(1) + proto_len(4) + cookie_len(4) + + * screen_num(4) */ + channel->reqX11_packet_len = proto_len + cookie_len + 30; + + /* Zero the whole thing out */ + memset(&channel->reqX11_packet_requirev_state, 0, + sizeof(channel->reqX11_packet_requirev_state)); + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Requesting x11-req for channel %lu/%lu: single=%d " + "proto=%s cookie=%s screen=%d", + channel->local.id, channel->remote.id, + single_connection, + auth_proto ? auth_proto : "MIT-MAGIC-COOKIE-1", + auth_cookie ? auth_cookie : "", screen_number); + + s = channel->reqX11_packet = + LIBSSH2_ALLOC(session, channel->reqX11_packet_len); + if (!channel->reqX11_packet) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for pty-request"); + } + + *(s++) = SSH_MSG_CHANNEL_REQUEST; + _libssh2_store_u32(&s, channel->remote.id); + _libssh2_store_str(&s, "x11-req", sizeof("x11-req") - 1); + + *(s++) = 0x01; /* want_reply */ + *(s++) = single_connection ? 0x01 : 0x00; + + _libssh2_store_str(&s, auth_proto?auth_proto:"MIT-MAGIC-COOKIE-1", + proto_len); + + _libssh2_store_u32(&s, cookie_len); + if (auth_cookie) { + memcpy(s, auth_cookie, cookie_len); + } else { + int i; + /* note: the extra +1 below is necessary since the sprintf() + loop will always write 3 bytes so the last one will write + the trailing zero at the LIBSSH2_X11_RANDOM_COOKIE_LEN/2 + border */ + unsigned char buffer[(LIBSSH2_X11_RANDOM_COOKIE_LEN / 2) +1]; + + _libssh2_random(buffer, LIBSSH2_X11_RANDOM_COOKIE_LEN / 2); + for(i = 0; i < (LIBSSH2_X11_RANDOM_COOKIE_LEN / 2); i++) { + sprintf((char *)&s[i*2], "%02X", buffer[i]); + } + } + s += cookie_len; + + _libssh2_store_u32(&s, screen_number); + channel->reqX11_state = libssh2_NB_state_created; + } + + if (channel->reqX11_state == libssh2_NB_state_created) { + rc = _libssh2_transport_send(session, channel->reqX11_packet, + channel->reqX11_packet_len, + NULL, 0); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, rc, + "Would block sending X11-req packet"); + return rc; + } + if (rc) { + LIBSSH2_FREE(session, channel->reqX11_packet); + channel->reqX11_packet = NULL; + channel->reqX11_state = libssh2_NB_state_idle; + return _libssh2_error(session, rc, + "Unable to send x11-req packet"); + } + LIBSSH2_FREE(session, channel->reqX11_packet); + channel->reqX11_packet = NULL; + + _libssh2_htonu32(channel->reqX11_local_channel, channel->local.id); + + channel->reqX11_state = libssh2_NB_state_sent; + } + + if (channel->reqX11_state == libssh2_NB_state_sent) { + size_t data_len; + unsigned char *data; + unsigned char code; + + rc = _libssh2_packet_requirev(session, reply_codes, &data, &data_len, + 1, channel->reqX11_local_channel, 4, + &channel->reqX11_packet_requirev_state); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } else if (rc) { + channel->reqX11_state = libssh2_NB_state_idle; + return _libssh2_error(session, rc, + "waiting for x11-req response packet"); + } + + code = data[0]; + LIBSSH2_FREE(session, data); + channel->reqX11_state = libssh2_NB_state_idle; + + if (code == SSH_MSG_CHANNEL_SUCCESS) + return 0; + } + + return _libssh2_error(session, LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED, + "Unable to complete request for channel x11-req"); +} + +/* + * libssh2_channel_x11_req_ex + * Request X11 forwarding + */ +LIBSSH2_API int +libssh2_channel_x11_req_ex(LIBSSH2_CHANNEL *channel, int single_connection, + const char *auth_proto, const char *auth_cookie, + int screen_number) +{ + int rc; + + if(!channel) + return LIBSSH2_ERROR_BAD_USE; + + BLOCK_ADJUST(rc, channel->session, + channel_x11_req(channel, single_connection, auth_proto, + auth_cookie, screen_number)); + return rc; +} + + +/* + * _libssh2_channel_process_startup + * + * Primitive for libssh2_channel_(shell|exec|subsystem) + */ +int +_libssh2_channel_process_startup(LIBSSH2_CHANNEL *channel, + const char *request, size_t request_len, + const char *message, size_t message_len) +{ + LIBSSH2_SESSION *session = channel->session; + unsigned char *s; + static const unsigned char reply_codes[3] = + { SSH_MSG_CHANNEL_SUCCESS, SSH_MSG_CHANNEL_FAILURE, 0 }; + int rc; + + if (channel->process_state == libssh2_NB_state_idle) { + /* 10 = packet_type(1) + channel(4) + request_len(4) + want_reply(1) */ + channel->process_packet_len = request_len + 10; + + /* Zero the whole thing out */ + memset(&channel->process_packet_requirev_state, 0, + sizeof(channel->process_packet_requirev_state)); + + if (message) + channel->process_packet_len += + 4; + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "starting request(%s) on channel %lu/%lu, message=%s", + request, channel->local.id, channel->remote.id, + message?message:""); + s = channel->process_packet = + LIBSSH2_ALLOC(session, channel->process_packet_len); + if (!channel->process_packet) + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory " + "for channel-process request"); + + *(s++) = SSH_MSG_CHANNEL_REQUEST; + _libssh2_store_u32(&s, channel->remote.id); + _libssh2_store_str(&s, request, request_len); + *(s++) = 0x01; + + if (message) + _libssh2_store_u32(&s, message_len); + + channel->process_state = libssh2_NB_state_created; + } + + if (channel->process_state == libssh2_NB_state_created) { + rc = _libssh2_transport_send(session, + channel->process_packet, + channel->process_packet_len, + (unsigned char *)message, message_len); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, rc, + "Would block sending channel request"); + return rc; + } + else if (rc) { + LIBSSH2_FREE(session, channel->process_packet); + channel->process_packet = NULL; + channel->process_state = libssh2_NB_state_idle; + return _libssh2_error(session, rc, + "Unable to send channel request"); + } + LIBSSH2_FREE(session, channel->process_packet); + channel->process_packet = NULL; + + _libssh2_htonu32(channel->process_local_channel, channel->local.id); + + channel->process_state = libssh2_NB_state_sent; + } + + if (channel->process_state == libssh2_NB_state_sent) { + unsigned char *data; + size_t data_len; + unsigned char code; + rc = _libssh2_packet_requirev(session, reply_codes, &data, &data_len, + 1, channel->process_local_channel, 4, + &channel->process_packet_requirev_state); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } else if (rc) { + channel->process_state = libssh2_NB_state_idle; + return _libssh2_error(session, rc, + "Failed waiting for channel success"); + } + + code = data[0]; + LIBSSH2_FREE(session, data); + channel->process_state = libssh2_NB_state_idle; + + if (code == SSH_MSG_CHANNEL_SUCCESS) + return 0; + } + + return _libssh2_error(session, LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED, + "Unable to complete request for " + "channel-process-startup"); +} + +/* + * libssh2_channel_process_startup + * + * Primitive for libssh2_channel_(shell|exec|subsystem) + */ +LIBSSH2_API int +libssh2_channel_process_startup(LIBSSH2_CHANNEL *channel, + const char *req, unsigned int req_len, + const char *msg, unsigned int msg_len) +{ + int rc; + + if(!channel) + return LIBSSH2_ERROR_BAD_USE; + + BLOCK_ADJUST(rc, channel->session, + _libssh2_channel_process_startup(channel, req, req_len, + msg, msg_len)); + return rc; +} + + +/* + * libssh2_channel_set_blocking + * + * Set a channel's BEHAVIOR blocking on or off. The socket will remain non- + * blocking. + */ +LIBSSH2_API void +libssh2_channel_set_blocking(LIBSSH2_CHANNEL * channel, int blocking) +{ + if(channel) + (void) _libssh2_session_set_blocking(channel->session, blocking); +} + +/* + * _libssh2_channel_flush + * + * Flush data from one (or all) stream + * Returns number of bytes flushed, or negative on failure + */ +int +_libssh2_channel_flush(LIBSSH2_CHANNEL *channel, int streamid) +{ + if (channel->flush_state == libssh2_NB_state_idle) { + LIBSSH2_PACKET *packet = + _libssh2_list_first(&channel->session->packets); + channel->flush_refund_bytes = 0; + channel->flush_flush_bytes = 0; + + while (packet) { + LIBSSH2_PACKET *next = _libssh2_list_next(&packet->node); + unsigned char packet_type = packet->data[0]; + + if (((packet_type == SSH_MSG_CHANNEL_DATA) + || (packet_type == SSH_MSG_CHANNEL_EXTENDED_DATA)) + && (_libssh2_ntohu32(packet->data + 1) == channel->local.id)) { + /* It's our channel at least */ + long packet_stream_id = + (packet_type == SSH_MSG_CHANNEL_DATA) ? 0 : + _libssh2_ntohu32(packet->data + 5); + if ((streamid == LIBSSH2_CHANNEL_FLUSH_ALL) + || ((packet_type == SSH_MSG_CHANNEL_EXTENDED_DATA) + && ((streamid == LIBSSH2_CHANNEL_FLUSH_EXTENDED_DATA) + || (streamid == packet_stream_id))) + || ((packet_type == SSH_MSG_CHANNEL_DATA) + && (streamid == 0))) { + int bytes_to_flush = packet->data_len - packet->data_head; + + _libssh2_debug(channel->session, LIBSSH2_TRACE_CONN, + "Flushing %d bytes of data from stream " + "%lu on channel %lu/%lu", + bytes_to_flush, packet_stream_id, + channel->local.id, channel->remote.id); + + /* It's one of the streams we wanted to flush */ + channel->flush_refund_bytes += packet->data_len - 13; + channel->flush_flush_bytes += bytes_to_flush; + + LIBSSH2_FREE(channel->session, packet->data); + + /* remove this packet from the parent's list */ + _libssh2_list_remove(&packet->node); + LIBSSH2_FREE(channel->session, packet); + } + } + packet = next; + } + + channel->flush_state = libssh2_NB_state_created; + } + + if (channel->flush_refund_bytes) { + int rc; + + rc = _libssh2_channel_receive_window_adjust(channel, + channel->flush_refund_bytes, + 1, NULL); + if (rc == LIBSSH2_ERROR_EAGAIN) + return rc; + } + + channel->flush_state = libssh2_NB_state_idle; + + return channel->flush_flush_bytes; +} + +/* + * libssh2_channel_flush_ex + * + * Flush data from one (or all) stream + * Returns number of bytes flushed, or negative on failure + */ +LIBSSH2_API int +libssh2_channel_flush_ex(LIBSSH2_CHANNEL *channel, int stream) +{ + int rc; + + if(!channel) + return LIBSSH2_ERROR_BAD_USE; + + BLOCK_ADJUST(rc, channel->session, + _libssh2_channel_flush(channel, stream)); + return rc; +} + +/* + * libssh2_channel_get_exit_status + * + * Return the channel's program exit status. Note that the actual protocol + * provides the full 32bit this function returns. We cannot abuse it to + * return error values in case of errors so we return a zero if channel is + * NULL. + */ +LIBSSH2_API int +libssh2_channel_get_exit_status(LIBSSH2_CHANNEL *channel) +{ + if(!channel) + return 0; + + return channel->exit_status; +} + +/* + * libssh2_channel_get_exit_signal + * + * Get exit signal (without leading "SIG"), error message, and language + * tag into newly allocated buffers of indicated length. Caller can + * use NULL pointers to indicate that the value should not be set. The + * *_len variables are set if they are non-NULL even if the + * corresponding string parameter is NULL. Returns LIBSSH2_ERROR_NONE + * on success, or an API error code. + */ +LIBSSH2_API int +libssh2_channel_get_exit_signal(LIBSSH2_CHANNEL *channel, + char **exitsignal, + size_t *exitsignal_len, + char **errmsg, + size_t *errmsg_len, + char **langtag, + size_t *langtag_len) +{ + size_t namelen = 0; + + if (channel) { + LIBSSH2_SESSION *session = channel->session; + + if (channel->exit_signal) { + namelen = strlen(channel->exit_signal); + if (exitsignal) { + *exitsignal = LIBSSH2_ALLOC(session, namelen + 1); + if (!*exitsignal) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for signal name"); + } + memcpy(*exitsignal, channel->exit_signal, namelen); + (*exitsignal)[namelen] = '\0'; + } + if (exitsignal_len) + *exitsignal_len = namelen; + } else { + if (exitsignal) + *exitsignal = NULL; + if (exitsignal_len) + *exitsignal_len = 0; + } + + /* TODO: set error message and language tag */ + + if (errmsg) + *errmsg = NULL; + + if (errmsg_len) + *errmsg_len = 0; + + if (langtag) + *langtag = NULL; + + if (langtag_len) + *langtag_len = 0; + } + + return LIBSSH2_ERROR_NONE; +} + +/* + * _libssh2_channel_receive_window_adjust + * + * Adjust the receive window for a channel by adjustment bytes. If the amount + * to be adjusted is less than LIBSSH2_CHANNEL_MINADJUST and force is 0 the + * adjustment amount will be queued for a later packet. + * + * Calls _libssh2_error() ! + */ +int +_libssh2_channel_receive_window_adjust(LIBSSH2_CHANNEL * channel, + uint32_t adjustment, + unsigned char force, + unsigned int *store) +{ + int rc; + + if (channel->adjust_state == libssh2_NB_state_idle) { + if (!force + && (adjustment + channel->adjust_queue < + LIBSSH2_CHANNEL_MINADJUST)) { + _libssh2_debug(channel->session, LIBSSH2_TRACE_CONN, + "Queueing %lu bytes for receive window adjustment " + "for channel %lu/%lu", + adjustment, channel->local.id, channel->remote.id); + channel->adjust_queue += adjustment; + if(store) + *store = channel->remote.window_size; + return 0; + } + + if (!adjustment && !channel->adjust_queue) { + if(store) + *store = channel->remote.window_size; + return 0; + } + + adjustment += channel->adjust_queue; + channel->adjust_queue = 0; + + /* Adjust the window based on the block we just freed */ + channel->adjust_adjust[0] = SSH_MSG_CHANNEL_WINDOW_ADJUST; + _libssh2_htonu32(&channel->adjust_adjust[1], channel->remote.id); + _libssh2_htonu32(&channel->adjust_adjust[5], adjustment); + _libssh2_debug(channel->session, LIBSSH2_TRACE_CONN, + "Adjusting window %lu bytes for data on " + "channel %lu/%lu", + adjustment, channel->local.id, channel->remote.id); + + channel->adjust_state = libssh2_NB_state_created; + } + + rc = _libssh2_transport_send(channel->session, channel->adjust_adjust, 9, + NULL, 0); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(channel->session, rc, + "Would block sending window adjust"); + return rc; + } + else if (rc) { + channel->adjust_queue = adjustment; + return _libssh2_error(channel->session, LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send transfer-window adjustment " + "packet, deferring"); + } + else { + channel->remote.window_size += adjustment; + } + + channel->adjust_state = libssh2_NB_state_idle; + + if(store) + *store = channel->remote.window_size; + return 0; +} + +/* + * libssh2_channel_receive_window_adjust + * + * DEPRECATED + * + * Adjust the receive window for a channel by adjustment bytes. If the amount + * to be adjusted is less than LIBSSH2_CHANNEL_MINADJUST and force is 0 the + * adjustment amount will be queued for a later packet. + * + * Returns the new size of the receive window (as understood by remote end). + * Note that it might return EAGAIN too which is highly stupid. + * + */ +LIBSSH2_API unsigned long +libssh2_channel_receive_window_adjust(LIBSSH2_CHANNEL *channel, + unsigned long adj, + unsigned char force) +{ + unsigned int window; + int rc; + + if(!channel) + return LIBSSH2_ERROR_BAD_USE; + + BLOCK_ADJUST(rc, channel->session, + _libssh2_channel_receive_window_adjust(channel, adj, + force, &window)); + + /* stupid - but this is how it was made to work before and this is just + kept for backwards compatibility */ + return rc?(unsigned long)rc:window; +} + +/* + * libssh2_channel_receive_window_adjust2 + * + * Adjust the receive window for a channel by adjustment bytes. If the amount + * to be adjusted is less than LIBSSH2_CHANNEL_MINADJUST and force is 0 the + * adjustment amount will be queued for a later packet. + * + * Stores the new size of the receive window in the data 'window' points to. + * + * Returns the "normal" error code: 0 for success, negative for failure. + */ +LIBSSH2_API int +libssh2_channel_receive_window_adjust2(LIBSSH2_CHANNEL *channel, + unsigned long adj, + unsigned char force, + unsigned int *window) +{ + int rc; + + if(!channel) + return LIBSSH2_ERROR_BAD_USE; + + BLOCK_ADJUST(rc, channel->session, + _libssh2_channel_receive_window_adjust(channel, adj, force, + window)); + return rc; +} + +int +_libssh2_channel_extended_data(LIBSSH2_CHANNEL *channel, int ignore_mode) +{ + if (channel->extData2_state == libssh2_NB_state_idle) { + _libssh2_debug(channel->session, LIBSSH2_TRACE_CONN, + "Setting channel %lu/%lu handle_extended_data" + " mode to %d", + channel->local.id, channel->remote.id, ignore_mode); + channel->remote.extended_data_ignore_mode = ignore_mode; + + channel->extData2_state = libssh2_NB_state_created; + } + + if (channel->extData2_state == libssh2_NB_state_idle) { + if (ignore_mode == LIBSSH2_CHANNEL_EXTENDED_DATA_IGNORE) { + int rc = + _libssh2_channel_flush(channel, + LIBSSH2_CHANNEL_FLUSH_EXTENDED_DATA); + if(LIBSSH2_ERROR_EAGAIN == rc) + return rc; + } + } + + channel->extData2_state = libssh2_NB_state_idle; + return 0; +} + +/* + * libssh2_channel_handle_extended_data2() + * + */ +LIBSSH2_API int +libssh2_channel_handle_extended_data2(LIBSSH2_CHANNEL *channel, + int mode) +{ + int rc; + + if(!channel) + return LIBSSH2_ERROR_BAD_USE; + + BLOCK_ADJUST(rc, channel->session, _libssh2_channel_extended_data(channel, + mode)); + return rc; +} + +/* + * libssh2_channel_handle_extended_data + * + * DEPRECATED DO NOTE USE! + * + * How should extended data look to the calling app? Keep it in separate + * channels[_read() _read_stdder()]? (NORMAL) Merge the extended data to the + * standard data? [everything via _read()]? (MERGE) Ignore it entirely [toss + * out packets as they come in]? (IGNORE) + */ +LIBSSH2_API void +libssh2_channel_handle_extended_data(LIBSSH2_CHANNEL *channel, + int ignore_mode) +{ + (void)libssh2_channel_handle_extended_data2(channel, ignore_mode); +} + + + +/* + * _libssh2_channel_read + * + * Read data from a channel + * + * It is important to not return 0 until the currently read channel is + * complete. If we read stuff from the wire but it was no payload data to fill + * in the buffer with, we MUST make sure to return LIBSSH2_ERROR_EAGAIN. + * + * The receive window must be maintained (enlarged) by the user of this + * function. + */ +ssize_t _libssh2_channel_read(LIBSSH2_CHANNEL *channel, int stream_id, + char *buf, size_t buflen) +{ + LIBSSH2_SESSION *session = channel->session; + int rc; + int bytes_read = 0; + int bytes_want; + int unlink_packet; + LIBSSH2_PACKET *read_packet; + LIBSSH2_PACKET *read_next; + + if (channel->read_state == libssh2_NB_state_idle) { + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "channel_read() wants %d bytes from channel %lu/%lu " + "stream #%d", + (int) buflen, channel->local.id, channel->remote.id, + stream_id); + channel->read_state = libssh2_NB_state_created; + } + + rc = 1; /* set to >0 to let the while loop start */ + + /* Process all pending incoming packets in all states in order to "even + out" the network readings. Tests prove that this way produces faster + transfers. */ + while (rc > 0) + rc = _libssh2_transport_read(session); + + if ((rc < 0) && (rc != LIBSSH2_ERROR_EAGAIN)) + return _libssh2_error(session, rc, "transport read"); + + read_packet = _libssh2_list_first(&session->packets); + while (read_packet && (bytes_read < (int) buflen)) { + /* previously this loop condition also checked for + !channel->remote.close but we cannot let it do this: + + We may have a series of packets to read that are still pending even + if a close has been received. Acknowledging the close too early + makes us flush buffers prematurely and loose data. + */ + + LIBSSH2_PACKET *readpkt = read_packet; + + /* In case packet gets destroyed during this iteration */ + read_next = _libssh2_list_next(&readpkt->node); + + channel->read_local_id = + _libssh2_ntohu32(readpkt->data + 1); + + /* + * Either we asked for a specific extended data stream + * (and data was available), + * or the standard stream (and data was available), + * or the standard stream with extended_data_merge + * enabled and data was available + */ + if ((stream_id + && (readpkt->data[0] == SSH_MSG_CHANNEL_EXTENDED_DATA) + && (channel->local.id == channel->read_local_id) + && (stream_id == (int) _libssh2_ntohu32(readpkt->data + 5))) + || (!stream_id && (readpkt->data[0] == SSH_MSG_CHANNEL_DATA) + && (channel->local.id == channel->read_local_id)) + || (!stream_id + && (readpkt->data[0] == SSH_MSG_CHANNEL_EXTENDED_DATA) + && (channel->local.id == channel->read_local_id) + && (channel->remote.extended_data_ignore_mode == + LIBSSH2_CHANNEL_EXTENDED_DATA_MERGE))) { + + /* figure out much more data we want to read */ + bytes_want = buflen - bytes_read; + unlink_packet = FALSE; + + if (bytes_want >= (int) (readpkt->data_len - readpkt->data_head)) { + /* we want more than this node keeps, so adjust the number and + delete this node after the copy */ + bytes_want = readpkt->data_len - readpkt->data_head; + unlink_packet = TRUE; + } + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "channel_read() got %d of data from %lu/%lu/%d%s", + bytes_want, channel->local.id, + channel->remote.id, stream_id, + unlink_packet?" [ul]":""); + + /* copy data from this struct to the target buffer */ + memcpy(&buf[bytes_read], + &readpkt->data[readpkt->data_head], bytes_want); + + /* advance pointer and counter */ + readpkt->data_head += bytes_want; + bytes_read += bytes_want; + + /* if drained, remove from list */ + if (unlink_packet) { + /* detach readpkt from session->packets list */ + _libssh2_list_remove(&readpkt->node); + + LIBSSH2_FREE(session, readpkt->data); + LIBSSH2_FREE(session, readpkt); + } + } + + /* check the next struct in the chain */ + read_packet = read_next; + } + + if (!bytes_read) { + channel->read_state = libssh2_NB_state_idle; + + /* If the channel is already at EOF or even closed, we need to signal + that back. We may have gotten that info while draining the incoming + transport layer until EAGAIN so we must not be fooled by that + return code. */ + if(channel->remote.eof || channel->remote.close) + return 0; + else if(rc != LIBSSH2_ERROR_EAGAIN) + return 0; + + /* if the transport layer said EAGAIN then we say so as well */ + return _libssh2_error(session, rc, "would block"); + } + else + /* make sure we remain in the created state to focus on emptying the + data we already have in the packet brigade before we try to read + more off the network again */ + channel->read_state = libssh2_NB_state_created; + + return bytes_read; +} + +/* + * libssh2_channel_read_ex + * + * Read data from a channel (blocking or non-blocking depending on set state) + * + * When this is done non-blocking, it is important to not return 0 until the + * currently read channel is complete. If we read stuff from the wire but it + * was no payload data to fill in the buffer with, we MUST make sure to return + * LIBSSH2_ERROR_EAGAIN. + * + * This function will first make sure there's a receive window enough to + * receive a full buffer's wort of contents. An application may choose to + * adjust the receive window more to increase transfer performance. + */ +LIBSSH2_API ssize_t +libssh2_channel_read_ex(LIBSSH2_CHANNEL *channel, int stream_id, char *buf, + size_t buflen) +{ + int rc; + unsigned long recv_window; + + if(!channel) + return LIBSSH2_ERROR_BAD_USE; + + recv_window = libssh2_channel_window_read_ex(channel, NULL, NULL); + + if(buflen > recv_window) { + BLOCK_ADJUST(rc, channel->session, + _libssh2_channel_receive_window_adjust(channel, buflen, + 1, NULL)); + } + + BLOCK_ADJUST(rc, channel->session, + _libssh2_channel_read(channel, stream_id, buf, buflen)); + return rc; +} + +/* + * _libssh2_channel_packet_data_len + * + * Return the size of the data block of the current packet, or 0 if there + * isn't a packet. + */ +size_t +_libssh2_channel_packet_data_len(LIBSSH2_CHANNEL * channel, int stream_id) +{ + LIBSSH2_SESSION *session = channel->session; + LIBSSH2_PACKET *read_packet; + uint32_t read_local_id; + + read_packet = _libssh2_list_first(&session->packets); + if (read_packet == NULL) + return 0; + + while (read_packet) { + read_local_id = _libssh2_ntohu32(read_packet->data + 1); + + /* + * Either we asked for a specific extended data stream + * (and data was available), + * or the standard stream (and data was available), + * or the standard stream with extended_data_merge + * enabled and data was available + */ + if ((stream_id + && (read_packet->data[0] == SSH_MSG_CHANNEL_EXTENDED_DATA) + && (channel->local.id == read_local_id) + && (stream_id == (int) _libssh2_ntohu32(read_packet->data + 5))) + || + (!stream_id + && (read_packet->data[0] == SSH_MSG_CHANNEL_DATA) + && (channel->local.id == read_local_id)) + || + (!stream_id + && (read_packet->data[0] == SSH_MSG_CHANNEL_EXTENDED_DATA) + && (channel->local.id == read_local_id) + && (channel->remote.extended_data_ignore_mode + == LIBSSH2_CHANNEL_EXTENDED_DATA_MERGE))) + { + return (read_packet->data_len - read_packet->data_head); + } + read_packet = _libssh2_list_next(&read_packet->node); + } + + return 0; +} + +/* + * _libssh2_channel_write + * + * Send data to a channel. Note that if this returns EAGAIN, the caller must + * call this function again with the SAME input arguments. + * + * Returns: number of bytes sent, or if it returns a negative number, that is + * the error code! + */ +ssize_t +_libssh2_channel_write(LIBSSH2_CHANNEL *channel, int stream_id, + const unsigned char *buf, size_t buflen) +{ + int rc = 0; + LIBSSH2_SESSION *session = channel->session; + ssize_t wrote = 0; /* counter for this specific this call */ + + /* In theory we could split larger buffers into several smaller packets + * but it turns out to be really hard and nasty to do while still offering + * the API/prototype. + * + * Instead we only deal with the first 32K in this call and for the parent + * function to call it again with the remainder! 32K is a conservative + * limit based on the text in RFC4253 section 6.1. + */ + if(buflen > 32700) + buflen = 32700; + + if (channel->write_state == libssh2_NB_state_idle) { + unsigned char *s = channel->write_packet; + + _libssh2_debug(channel->session, LIBSSH2_TRACE_CONN, + "Writing %d bytes on channel %lu/%lu, stream #%d", + (int) buflen, channel->local.id, channel->remote.id, + stream_id); + + if (channel->local.close) + return _libssh2_error(channel->session, + LIBSSH2_ERROR_CHANNEL_CLOSED, + "We've already closed this channel"); + else if (channel->local.eof) + return _libssh2_error(channel->session, + LIBSSH2_ERROR_CHANNEL_EOF_SENT, + "EOF has already been received, " + "data might be ignored"); + + /* drain the incoming flow first, mostly to make sure we get all + * pending window adjust packets */ + do + rc = _libssh2_transport_read(session); + while (rc > 0); + + if((rc < 0) && (rc != LIBSSH2_ERROR_EAGAIN)) + return rc; + + if(channel->local.window_size <= 0) + /* there's no room for data so we stop */ + return (rc==LIBSSH2_ERROR_EAGAIN?rc:0); + + channel->write_bufwrite = buflen; + + *(s++) = stream_id ? SSH_MSG_CHANNEL_EXTENDED_DATA : + SSH_MSG_CHANNEL_DATA; + _libssh2_store_u32(&s, channel->remote.id); + if (stream_id) + _libssh2_store_u32(&s, stream_id); + + /* Don't exceed the remote end's limits */ + /* REMEMBER local means local as the SOURCE of the data */ + if (channel->write_bufwrite > channel->local.window_size) { + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Splitting write block due to %lu byte " + "window_size on %lu/%lu/%d", + channel->local.window_size, channel->local.id, + channel->remote.id, stream_id); + channel->write_bufwrite = channel->local.window_size; + } + if (channel->write_bufwrite > channel->local.packet_size) { + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Splitting write block due to %lu byte " + "packet_size on %lu/%lu/%d", + channel->local.packet_size, channel->local.id, + channel->remote.id, stream_id); + channel->write_bufwrite = channel->local.packet_size; + } + /* store the size here only, the buffer is passed in as-is to + _libssh2_transport_send() */ + _libssh2_store_u32(&s, channel->write_bufwrite); + channel->write_packet_len = s - channel->write_packet; + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Sending %d bytes on channel %lu/%lu, stream_id=%d", + (int) channel->write_bufwrite, channel->local.id, + channel->remote.id, stream_id); + + channel->write_state = libssh2_NB_state_created; + } + + if (channel->write_state == libssh2_NB_state_created) { + rc = _libssh2_transport_send(session, channel->write_packet, + channel->write_packet_len, + buf, channel->write_bufwrite); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return _libssh2_error(session, rc, + "Unable to send channel data"); + } + else if (rc) { + channel->write_state = libssh2_NB_state_idle; + return _libssh2_error(session, rc, + "Unable to send channel data"); + } + /* Shrink local window size */ + channel->local.window_size -= channel->write_bufwrite; + + wrote += channel->write_bufwrite; + + /* Since _libssh2_transport_write() succeeded, we must return + now to allow the caller to provide the next chunk of data. + + We cannot move on to send the next piece of data that may + already have been provided in this same function call, as we + risk getting EAGAIN for that and we can't return information + both about sent data as well as EAGAIN. So, by returning short + now, the caller will call this function again with new data to + send */ + + channel->write_state = libssh2_NB_state_idle; + + return wrote; + } + + return LIBSSH2_ERROR_INVAL; /* reaching this point is really bad */ +} + +/* + * libssh2_channel_write_ex + * + * Send data to a channel + */ +LIBSSH2_API ssize_t +libssh2_channel_write_ex(LIBSSH2_CHANNEL *channel, int stream_id, + const char *buf, size_t buflen) +{ + ssize_t rc; + + if(!channel) + return LIBSSH2_ERROR_BAD_USE; + + BLOCK_ADJUST(rc, channel->session, + _libssh2_channel_write(channel, stream_id, + (unsigned char *)buf, buflen)); + return rc; +} + +/* + * channel_send_eof + * + * Send EOF on channel + */ +static int channel_send_eof(LIBSSH2_CHANNEL *channel) +{ + LIBSSH2_SESSION *session = channel->session; + unsigned char packet[5]; /* packet_type(1) + channelno(4) */ + int rc; + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, "Sending EOF on channel %lu/%lu", + channel->local.id, channel->remote.id); + packet[0] = SSH_MSG_CHANNEL_EOF; + _libssh2_htonu32(packet + 1, channel->remote.id); + rc = _libssh2_transport_send(session, packet, 5, NULL, 0); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, rc, + "Would block sending EOF"); + return rc; + } + else if (rc) { + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send EOF on channel"); + } + channel->local.eof = 1; + + return 0; +} + +/* + * libssh2_channel_send_eof + * + * Send EOF on channel + */ +LIBSSH2_API int +libssh2_channel_send_eof(LIBSSH2_CHANNEL *channel) +{ + int rc; + + if(!channel) + return LIBSSH2_ERROR_BAD_USE; + + BLOCK_ADJUST(rc, channel->session, channel_send_eof(channel)); + return rc; +} + +/* + * libssh2_channel_eof + * + * Read channel's eof status + */ +LIBSSH2_API int +libssh2_channel_eof(LIBSSH2_CHANNEL * channel) +{ + LIBSSH2_SESSION *session; + LIBSSH2_PACKET *packet; + + if(!channel) + return LIBSSH2_ERROR_BAD_USE; + + session = channel->session; + packet = _libssh2_list_first(&session->packets); + + while (packet) { + if (((packet->data[0] == SSH_MSG_CHANNEL_DATA) + || (packet->data[0] == SSH_MSG_CHANNEL_EXTENDED_DATA)) + && (channel->local.id == _libssh2_ntohu32(packet->data + 1))) { + /* There's data waiting to be read yet, mask the EOF status */ + return 0; + } + packet = _libssh2_list_next(&packet->node); + } + + return channel->remote.eof; +} + +/* + * channel_wait_eof + * + * Awaiting channel EOF + */ +static int channel_wait_eof(LIBSSH2_CHANNEL *channel) +{ + LIBSSH2_SESSION *session = channel->session; + int rc; + + if (channel->wait_eof_state == libssh2_NB_state_idle) { + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Awaiting close of channel %lu/%lu", channel->local.id, + channel->remote.id); + + channel->wait_eof_state = libssh2_NB_state_created; + } + + /* + * While channel is not eof, read more packets from the network. + * Either the EOF will be set or network timeout will occur. + */ + do { + if (channel->remote.eof) { + break; + } + rc = _libssh2_transport_read(session); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } + else if (rc < 0) { + channel->wait_eof_state = libssh2_NB_state_idle; + return _libssh2_error(session, rc, + "_libssh2_transport_read() bailed out!"); + } + } while (1); + + channel->wait_eof_state = libssh2_NB_state_idle; + + return 0; +} + +/* + * libssh2_channel_wait_eof + * + * Awaiting channel EOF + */ +LIBSSH2_API int +libssh2_channel_wait_eof(LIBSSH2_CHANNEL *channel) +{ + int rc; + + if(!channel) + return LIBSSH2_ERROR_BAD_USE; + + BLOCK_ADJUST(rc, channel->session, channel_wait_eof(channel)); + return rc; +} + +int _libssh2_channel_close(LIBSSH2_CHANNEL * channel) +{ + LIBSSH2_SESSION *session = channel->session; + int rc = 0; + int retcode; + + if (channel->local.close) { + /* Already closed, act like we sent another close, + * even though we didn't... shhhhhh */ + channel->close_state = libssh2_NB_state_idle; + return 0; + } + + if (!channel->local.eof) + if ((retcode = channel_send_eof(channel))) + return retcode; + + /* ignore if we have received a remote eof or not, as it is now too + late for us to wait for it. Continue closing! */ + + if (channel->close_state == libssh2_NB_state_idle) { + _libssh2_debug(session, LIBSSH2_TRACE_CONN, "Closing channel %lu/%lu", + channel->local.id, channel->remote.id); + + channel->close_packet[0] = SSH_MSG_CHANNEL_CLOSE; + _libssh2_htonu32(channel->close_packet + 1, channel->remote.id); + + channel->close_state = libssh2_NB_state_created; + } + + if (channel->close_state == libssh2_NB_state_created) { + retcode = _libssh2_transport_send(session, channel->close_packet, 5, + NULL, 0); + if (retcode == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, rc, + "Would block sending close-channel"); + return retcode; + } else if (retcode) { + channel->close_state = libssh2_NB_state_idle; + return _libssh2_error(session, retcode, + "Unable to send close-channel request"); + } + + channel->close_state = libssh2_NB_state_sent; + } + + if (channel->close_state == libssh2_NB_state_sent) { + /* We must wait for the remote SSH_MSG_CHANNEL_CLOSE message */ + + while (!channel->remote.close && !rc && + (session->socket_state != LIBSSH2_SOCKET_DISCONNECTED)) + rc = _libssh2_transport_read(session); + } + + if(rc != LIBSSH2_ERROR_EAGAIN) { + /* set the local close state first when we're perfectly confirmed to not + do any more EAGAINs */ + channel->local.close = 1; + + /* We call the callback last in this function to make it keep the local + data as long as EAGAIN is returned. */ + if (channel->close_cb) { + LIBSSH2_CHANNEL_CLOSE(session, channel); + } + + channel->close_state = libssh2_NB_state_idle; + } + + /* return 0 or an error */ + return rc>=0?0:rc; +} + +/* + * libssh2_channel_close + * + * Close a channel + */ +LIBSSH2_API int +libssh2_channel_close(LIBSSH2_CHANNEL *channel) +{ + int rc; + + if(!channel) + return LIBSSH2_ERROR_BAD_USE; + + BLOCK_ADJUST(rc, channel->session, _libssh2_channel_close(channel) ); + return rc; +} + +/* + * channel_wait_closed + * + * Awaiting channel close after EOF + */ +static int channel_wait_closed(LIBSSH2_CHANNEL *channel) +{ + LIBSSH2_SESSION *session = channel->session; + int rc; + + if (!libssh2_channel_eof(channel)) { + return _libssh2_error(session, LIBSSH2_ERROR_INVAL, + "libssh2_channel_wait_closed() invoked when " + "channel is not in EOF state"); + } + + if (channel->wait_closed_state == libssh2_NB_state_idle) { + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Awaiting close of channel %lu/%lu", channel->local.id, + channel->remote.id); + + channel->wait_closed_state = libssh2_NB_state_created; + } + + /* + * While channel is not closed, read more packets from the network. + * Either the channel will be closed or network timeout will occur. + */ + if (!channel->remote.close) { + do { + rc = _libssh2_transport_read(session); + if (channel->remote.close) + /* it is now closed, move on! */ + break; + } while (rc > 0); + if(rc < 0) + return rc; + } + + channel->wait_closed_state = libssh2_NB_state_idle; + + return 0; +} + +/* + * libssh2_channel_wait_closed + * + * Awaiting channel close after EOF + */ +LIBSSH2_API int +libssh2_channel_wait_closed(LIBSSH2_CHANNEL *channel) +{ + int rc; + + if(!channel) + return LIBSSH2_ERROR_BAD_USE; + + BLOCK_ADJUST(rc, channel->session, channel_wait_closed(channel)); + return rc; +} + +/* + * _libssh2_channel_free + * + * Make sure a channel is closed, then remove the channel from the session + * and free its resource(s) + * + * Returns 0 on success, negative on failure + */ +int _libssh2_channel_free(LIBSSH2_CHANNEL *channel) +{ + LIBSSH2_SESSION *session = channel->session; + unsigned char channel_id[4]; + unsigned char *data; + size_t data_len; + int rc; + + assert(session); + + if (channel->free_state == libssh2_NB_state_idle) { + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Freeing channel %lu/%lu resources", channel->local.id, + channel->remote.id); + + channel->free_state = libssh2_NB_state_created; + } + + /* Allow channel freeing even when the socket has lost its connection */ + if (!channel->local.close + && (session->socket_state == LIBSSH2_SOCKET_CONNECTED)) { + rc = _libssh2_channel_close(channel); + + if(rc == LIBSSH2_ERROR_EAGAIN) + return rc; + + /* ignore all other errors as they otherwise risk blocking the channel + free from happening */ + } + + channel->free_state = libssh2_NB_state_idle; + + if (channel->exit_signal) { + LIBSSH2_FREE(session, channel->exit_signal); + } + + /* + * channel->remote.close *might* not be set yet, Well... + * We've sent the close packet, what more do you want? + * Just let packet_add ignore it when it finally arrives + */ + + /* Clear out packets meant for this channel */ + _libssh2_htonu32(channel_id, channel->local.id); + while ((_libssh2_packet_ask(session, SSH_MSG_CHANNEL_DATA, &data, + &data_len, 1, channel_id, 4) >= 0) + || + (_libssh2_packet_ask(session, SSH_MSG_CHANNEL_EXTENDED_DATA, &data, + &data_len, 1, channel_id, 4) >= 0)) { + LIBSSH2_FREE(session, data); + } + + /* free "channel_type" */ + if (channel->channel_type) { + LIBSSH2_FREE(session, channel->channel_type); + } + + /* Unlink from channel list */ + _libssh2_list_remove(&channel->node); + + /* + * Make sure all memory used in the state variables are free + */ + if (channel->setenv_packet) { + LIBSSH2_FREE(session, channel->setenv_packet); + } + if (channel->reqX11_packet) { + LIBSSH2_FREE(session, channel->reqX11_packet); + } + if (channel->process_packet) { + LIBSSH2_FREE(session, channel->process_packet); + } + + LIBSSH2_FREE(session, channel); + + return 0; +} + +/* + * libssh2_channel_free + * + * Make sure a channel is closed, then remove the channel from the session + * and free its resource(s) + * + * Returns 0 on success, negative on failure + */ +LIBSSH2_API int +libssh2_channel_free(LIBSSH2_CHANNEL *channel) +{ + int rc; + + if(!channel) + return LIBSSH2_ERROR_BAD_USE; + + BLOCK_ADJUST(rc, channel->session, _libssh2_channel_free(channel)); + return rc; +} +/* + * libssh2_channel_window_read_ex + * + * Check the status of the read window. Returns the number of bytes which the + * remote end may send without overflowing the window limit read_avail (if + * passed) will be populated with the number of bytes actually available to be + * read window_size_initial (if passed) will be populated with the + * window_size_initial as defined by the channel_open request + */ +LIBSSH2_API unsigned long +libssh2_channel_window_read_ex(LIBSSH2_CHANNEL *channel, + unsigned long *read_avail, + unsigned long *window_size_initial) +{ + if(!channel) + return 0; /* no channel, no window! */ + + if (window_size_initial) { + *window_size_initial = channel->remote.window_size_initial; + } + + if (read_avail) { + size_t bytes_queued = 0; + LIBSSH2_PACKET *packet = + _libssh2_list_first(&channel->session->packets); + + while (packet) { + unsigned char packet_type = packet->data[0]; + + if (((packet_type == SSH_MSG_CHANNEL_DATA) + || (packet_type == SSH_MSG_CHANNEL_EXTENDED_DATA)) + && (_libssh2_ntohu32(packet->data + 1) == channel->local.id)) { + bytes_queued += packet->data_len - packet->data_head; + } + + packet = _libssh2_list_next(&packet->node); + } + + *read_avail = bytes_queued; + } + + return channel->remote.window_size; +} + +/* + * libssh2_channel_window_write_ex + * + * Check the status of the write window Returns the number of bytes which may + * be safely writen on the channel without blocking window_size_initial (if + * passed) will be populated with the size of the initial window as defined by + * the channel_open request + */ +LIBSSH2_API unsigned long +libssh2_channel_window_write_ex(LIBSSH2_CHANNEL *channel, + unsigned long *window_size_initial) +{ + if(!channel) + return 0; /* no channel, no window! */ + + if (window_size_initial) { + /* For locally initiated channels this is very often 0, so it's not + * *that* useful as information goes */ + *window_size_initial = channel->local.window_size_initial; + } + + return channel->local.window_size; +} diff --git a/libssh2/src/comp.c b/libssh2/src/comp.c new file mode 100644 index 0000000..4593ce4 --- /dev/null +++ b/libssh2/src/comp.c @@ -0,0 +1,376 @@ +/* Copyright (c) 2004-2007, Sara Golemon + * Copyright (c) 2010, Daniel Stenberg + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include "libssh2_priv.h" +#ifdef LIBSSH2_HAVE_ZLIB +# include +#endif + +#include "comp.h" + +/* ******** + * none * + ******** */ + +/* + * comp_method_none_comp + * + * Minimalist compression: Absolutely none + */ +static int +comp_method_none_comp(LIBSSH2_SESSION *session, + unsigned char *dest, + size_t *dest_len, + const unsigned char *src, + size_t src_len, + void **abstract) +{ + (void) session; + (void) abstract; + (void) dest; + (void) dest_len; + (void) src; + (void) src_len; + + return 0; +} + +/* + * comp_method_none_decomp + * + * Minimalist decompression: Absolutely none + */ +static int +comp_method_none_decomp(LIBSSH2_SESSION * session, + unsigned char **dest, + size_t *dest_len, + size_t payload_limit, + const unsigned char *src, + size_t src_len, void **abstract) +{ + (void) session; + (void) payload_limit; + (void) abstract; + *dest = (unsigned char *) src; + *dest_len = src_len; + return 0; +} + + + +static const LIBSSH2_COMP_METHOD comp_method_none = { + "none", + 0, /* not really compressing */ + 0, /* isn't used in userauth, go figure */ + NULL, + comp_method_none_comp, + comp_method_none_decomp, + NULL +}; + +#ifdef LIBSSH2_HAVE_ZLIB +/* ******** + * zlib * + ******** */ + +/* Memory management wrappers + * Yes, I realize we're doing a callback to a callback, + * Deal... + */ + +static voidpf +comp_method_zlib_alloc(voidpf opaque, uInt items, uInt size) +{ + LIBSSH2_SESSION *session = (LIBSSH2_SESSION *) opaque; + + return (voidpf) LIBSSH2_ALLOC(session, items * size); +} + +static void +comp_method_zlib_free(voidpf opaque, voidpf address) +{ + LIBSSH2_SESSION *session = (LIBSSH2_SESSION *) opaque; + + LIBSSH2_FREE(session, address); +} + + + +/* libssh2_comp_method_zlib_init + * All your bandwidth are belong to us (so save some) + */ +static int +comp_method_zlib_init(LIBSSH2_SESSION * session, int compr, + void **abstract) +{ + z_stream *strm; + int status; + + strm = LIBSSH2_ALLOC(session, sizeof(z_stream)); + if (!strm) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for " + "zlib compression/decompression"); + } + memset(strm, 0, sizeof(z_stream)); + + strm->opaque = (voidpf) session; + strm->zalloc = (alloc_func) comp_method_zlib_alloc; + strm->zfree = (free_func) comp_method_zlib_free; + if (compr) { + /* deflate */ + status = deflateInit(strm, Z_DEFAULT_COMPRESSION); + } else { + /* inflate */ + status = inflateInit(strm); + } + + if (status != Z_OK) { + LIBSSH2_FREE(session, strm); + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, + "unhandled zlib error %d", status); + return LIBSSH2_ERROR_COMPRESS; + } + *abstract = strm; + + return LIBSSH2_ERROR_NONE; +} + +/* + * libssh2_comp_method_zlib_comp + * + * Compresses source to destination. Without allocation. + */ +static int +comp_method_zlib_comp(LIBSSH2_SESSION *session, + unsigned char *dest, + + /* dest_len is a pointer to allow this function to + update it with the final actual size used */ + size_t *dest_len, + const unsigned char *src, + size_t src_len, + void **abstract) +{ + z_stream *strm = *abstract; + int out_maxlen = *dest_len; + int status; + + strm->next_in = (unsigned char *) src; + strm->avail_in = src_len; + strm->next_out = dest; + strm->avail_out = out_maxlen; + + status = deflate(strm, Z_PARTIAL_FLUSH); + + if (status != Z_OK) { + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, + "unhandled zlib compression error %d", status); + return _libssh2_error(session, LIBSSH2_ERROR_ZLIB, + "compression failure"); + } + + *dest_len = out_maxlen - strm->avail_out; + return 0; +} + +/* + * libssh2_comp_method_zlib_decomp + * + * Decompresses source to destination. Allocates the output memory. + */ +static int +comp_method_zlib_decomp(LIBSSH2_SESSION * session, + unsigned char **dest, + size_t *dest_len, + size_t payload_limit, + const unsigned char *src, + size_t src_len, void **abstract) +{ + z_stream *strm = *abstract; + /* A short-term alloc of a full data chunk is better than a series of + reallocs */ + char *out; + int out_maxlen = 8 * src_len; + int limiter = 0; + + /* If strm is null, then we have not yet been initialized. */ + if (strm == NULL) + return _libssh2_error(session, LIBSSH2_ERROR_COMPRESS, + "decompression unitilized");; + + /* In practice they never come smaller than this */ + if (out_maxlen < 25) + out_maxlen = 25; + + if (out_maxlen > (int) payload_limit) + out_maxlen = payload_limit; + + strm->next_in = (unsigned char *) src; + strm->avail_in = src_len; + strm->next_out = (unsigned char *) LIBSSH2_ALLOC(session, out_maxlen); + out = (char *) strm->next_out; + strm->avail_out = out_maxlen; + if (!strm->next_out) + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate decompression buffer"); + + /* Loop until it's all inflated or hit error */ + for (;;) { + int status, grow_size; + size_t out_ofs; + char *newout; + + status = inflate(strm, Z_PARTIAL_FLUSH); + + if (status == Z_OK) { + if (! strm->avail_in) { + /* status is OK and input all used so we're done */ + break; + } + } else if (status == Z_BUF_ERROR) { + /* This is OK, just drop through to grow the buffer */ + } else { + /* error state */ + LIBSSH2_FREE(session, out); + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, + "unhandled zlib error %d", status); + return _libssh2_error(session, LIBSSH2_ERROR_ZLIB, + "decompression failure"); + } + + /* If we get here we need to grow the output buffer and try again */ + out_ofs = out_maxlen - strm->avail_out; + if (strm->avail_in) { + grow_size = strm->avail_in * 8; + } else { + /* Not sure how much to grow by */ + grow_size = 32; + } + out_maxlen += grow_size; + + if ((out_maxlen > (int) payload_limit) && limiter++) { + LIBSSH2_FREE(session, out); + return _libssh2_error(session, LIBSSH2_ERROR_ZLIB, + "Excessive growth in decompression phase"); + } + + newout = LIBSSH2_REALLOC(session, out, out_maxlen); + if (!newout) { + LIBSSH2_FREE(session, out); + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to expand decompression buffer"); + } + out = newout; + strm->next_out = (unsigned char *) out + out_ofs; + strm->avail_out += grow_size; + } + + *dest = (unsigned char *) out; + *dest_len = out_maxlen - strm->avail_out; + + return 0; +} + + +/* libssh2_comp_method_zlib_dtor + * All done, no more compression for you + */ +static int +comp_method_zlib_dtor(LIBSSH2_SESSION *session, int compr, void **abstract) +{ + z_stream *strm = *abstract; + + if (strm) { + if (compr) + deflateEnd(strm); + else + inflateEnd(strm); + LIBSSH2_FREE(session, strm); + } + + *abstract = NULL; + return 0; +} + +static const LIBSSH2_COMP_METHOD comp_method_zlib = { + "zlib", + 1, /* yes, this compresses */ + 1, /* do compression during userauth */ + comp_method_zlib_init, + comp_method_zlib_comp, + comp_method_zlib_decomp, + comp_method_zlib_dtor, +}; + +static const LIBSSH2_COMP_METHOD comp_method_zlib_openssh = { + "zlib@openssh.com", + 1, /* yes, this compresses */ + 0, /* don't use compression during userauth */ + comp_method_zlib_init, + comp_method_zlib_comp, + comp_method_zlib_decomp, + comp_method_zlib_dtor, +}; +#endif /* LIBSSH2_HAVE_ZLIB */ + +/* If compression is enabled by the API, then this array is used which then + may allow compression if zlib is available at build time */ +static const LIBSSH2_COMP_METHOD *comp_methods[] = { +#ifdef LIBSSH2_HAVE_ZLIB + &comp_method_zlib, + &comp_method_zlib_openssh, +#endif /* LIBSSH2_HAVE_ZLIB */ + &comp_method_none, + NULL +}; + +/* If compression is disabled by the API, then this array is used */ +static const LIBSSH2_COMP_METHOD *no_comp_methods[] = { + &comp_method_none, + NULL +}; + +const LIBSSH2_COMP_METHOD ** +_libssh2_comp_methods(LIBSSH2_SESSION *session) +{ + if(session->flag.compress) + return comp_methods; + else + return no_comp_methods; +} diff --git a/libssh2/src/crypt.c b/libssh2/src/crypt.c new file mode 100644 index 0000000..931ae8b --- /dev/null +++ b/libssh2/src/crypt.c @@ -0,0 +1,336 @@ +/* Copyright (c) 2009, 2010 Simon Josefsson + * Copyright (c) 2004-2007, Sara Golemon + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include "libssh2_priv.h" + +#ifdef LIBSSH2_CRYPT_NONE + +/* crypt_none_crypt + * Minimalist cipher: VERY secure *wink* + */ +static int +crypt_none_crypt(LIBSSH2_SESSION * session, unsigned char *buf, + void **abstract) +{ + /* Do nothing to the data! */ + return 0; +} + +static const LIBSSH2_CRYPT_METHOD libssh2_crypt_method_none = { + "none", + 8, /* blocksize (SSH2 defines minimum blocksize as 8) */ + 0, /* iv_len */ + 0, /* secret_len */ + 0, /* flags */ + NULL, + crypt_none_crypt, + NULL +}; +#endif /* LIBSSH2_CRYPT_NONE */ + +struct crypt_ctx +{ + int encrypt; + _libssh2_cipher_type(algo); + _libssh2_cipher_ctx h; +}; + +static int +crypt_init(LIBSSH2_SESSION * session, + const LIBSSH2_CRYPT_METHOD * method, + unsigned char *iv, int *free_iv, + unsigned char *secret, int *free_secret, + int encrypt, void **abstract) +{ + struct crypt_ctx *ctx = LIBSSH2_ALLOC(session, + sizeof(struct crypt_ctx)); + if (!ctx) + return LIBSSH2_ERROR_ALLOC; + + ctx->encrypt = encrypt; + ctx->algo = method->algo; + if (_libssh2_cipher_init(&ctx->h, ctx->algo, iv, secret, encrypt)) { + LIBSSH2_FREE(session, ctx); + return -1; + } + *abstract = ctx; + *free_iv = 1; + *free_secret = 1; + return 0; +} + +static int +crypt_encrypt(LIBSSH2_SESSION * session, unsigned char *block, + size_t blocksize, void **abstract) +{ + struct crypt_ctx *cctx = *(struct crypt_ctx **) abstract; + (void) session; + return _libssh2_cipher_crypt(&cctx->h, cctx->algo, cctx->encrypt, block, + blocksize); +} + +static int +crypt_dtor(LIBSSH2_SESSION * session, void **abstract) +{ + struct crypt_ctx **cctx = (struct crypt_ctx **) abstract; + if (cctx && *cctx) { + _libssh2_cipher_dtor(&(*cctx)->h); + LIBSSH2_FREE(session, *cctx); + *abstract = NULL; + } + return 0; +} + +#if LIBSSH2_AES_CTR +static const LIBSSH2_CRYPT_METHOD libssh2_crypt_method_aes128_ctr = { + "aes128-ctr", + 16, /* blocksize */ + 16, /* initial value length */ + 16, /* secret length -- 16*8 == 128bit */ + 0, /* flags */ + &crypt_init, + &crypt_encrypt, + &crypt_dtor, + _libssh2_cipher_aes128ctr +}; + +static const LIBSSH2_CRYPT_METHOD libssh2_crypt_method_aes192_ctr = { + "aes192-ctr", + 16, /* blocksize */ + 16, /* initial value length */ + 24, /* secret length -- 24*8 == 192bit */ + 0, /* flags */ + &crypt_init, + &crypt_encrypt, + &crypt_dtor, + _libssh2_cipher_aes192ctr +}; + +static const LIBSSH2_CRYPT_METHOD libssh2_crypt_method_aes256_ctr = { + "aes256-ctr", + 16, /* blocksize */ + 16, /* initial value length */ + 32, /* secret length -- 32*8 == 256bit */ + 0, /* flags */ + &crypt_init, + &crypt_encrypt, + &crypt_dtor, + _libssh2_cipher_aes256ctr +}; +#endif + +#if LIBSSH2_AES +static const LIBSSH2_CRYPT_METHOD libssh2_crypt_method_aes128_cbc = { + "aes128-cbc", + 16, /* blocksize */ + 16, /* initial value length */ + 16, /* secret length -- 16*8 == 128bit */ + 0, /* flags */ + &crypt_init, + &crypt_encrypt, + &crypt_dtor, + _libssh2_cipher_aes128 +}; + +static const LIBSSH2_CRYPT_METHOD libssh2_crypt_method_aes192_cbc = { + "aes192-cbc", + 16, /* blocksize */ + 16, /* initial value length */ + 24, /* secret length -- 24*8 == 192bit */ + 0, /* flags */ + &crypt_init, + &crypt_encrypt, + &crypt_dtor, + _libssh2_cipher_aes192 +}; + +static const LIBSSH2_CRYPT_METHOD libssh2_crypt_method_aes256_cbc = { + "aes256-cbc", + 16, /* blocksize */ + 16, /* initial value length */ + 32, /* secret length -- 32*8 == 256bit */ + 0, /* flags */ + &crypt_init, + &crypt_encrypt, + &crypt_dtor, + _libssh2_cipher_aes256 +}; + +/* rijndael-cbc@lysator.liu.se == aes256-cbc */ +static const LIBSSH2_CRYPT_METHOD + libssh2_crypt_method_rijndael_cbc_lysator_liu_se = { + "rijndael-cbc@lysator.liu.se", + 16, /* blocksize */ + 16, /* initial value length */ + 32, /* secret length -- 32*8 == 256bit */ + 0, /* flags */ + &crypt_init, + &crypt_encrypt, + &crypt_dtor, + _libssh2_cipher_aes256 +}; +#endif /* LIBSSH2_AES */ + +#if LIBSSH2_BLOWFISH +static const LIBSSH2_CRYPT_METHOD libssh2_crypt_method_blowfish_cbc = { + "blowfish-cbc", + 8, /* blocksize */ + 8, /* initial value length */ + 16, /* secret length */ + 0, /* flags */ + &crypt_init, + &crypt_encrypt, + &crypt_dtor, + _libssh2_cipher_blowfish +}; +#endif /* LIBSSH2_BLOWFISH */ + +#if LIBSSH2_RC4 +static const LIBSSH2_CRYPT_METHOD libssh2_crypt_method_arcfour = { + "arcfour", + 8, /* blocksize */ + 8, /* initial value length */ + 16, /* secret length */ + 0, /* flags */ + &crypt_init, + &crypt_encrypt, + &crypt_dtor, + _libssh2_cipher_arcfour +}; + +static int +crypt_init_arcfour128(LIBSSH2_SESSION * session, + const LIBSSH2_CRYPT_METHOD * method, + unsigned char *iv, int *free_iv, + unsigned char *secret, int *free_secret, + int encrypt, void **abstract) +{ + int rc; + + rc = crypt_init (session, method, iv, free_iv, secret, free_secret, + encrypt, abstract); + if (rc == 0) { + struct crypt_ctx *cctx = *(struct crypt_ctx **) abstract; + unsigned char block[8]; + size_t discard = 1536; + for (; discard; discard -= 8) + _libssh2_cipher_crypt(&cctx->h, cctx->algo, cctx->encrypt, block, + method->blocksize); + } + + return rc; +} + +static const LIBSSH2_CRYPT_METHOD libssh2_crypt_method_arcfour128 = { + "arcfour128", + 8, /* blocksize */ + 8, /* initial value length */ + 16, /* secret length */ + 0, /* flags */ + &crypt_init_arcfour128, + &crypt_encrypt, + &crypt_dtor, + _libssh2_cipher_arcfour +}; +#endif /* LIBSSH2_RC4 */ + +#if LIBSSH2_CAST +static const LIBSSH2_CRYPT_METHOD libssh2_crypt_method_cast128_cbc = { + "cast128-cbc", + 8, /* blocksize */ + 8, /* initial value length */ + 16, /* secret length */ + 0, /* flags */ + &crypt_init, + &crypt_encrypt, + &crypt_dtor, + _libssh2_cipher_cast5 +}; +#endif /* LIBSSH2_CAST */ + +#if LIBSSH2_3DES +static const LIBSSH2_CRYPT_METHOD libssh2_crypt_method_3des_cbc = { + "3des-cbc", + 8, /* blocksize */ + 8, /* initial value length */ + 24, /* secret length */ + 0, /* flags */ + &crypt_init, + &crypt_encrypt, + &crypt_dtor, + _libssh2_cipher_3des +}; +#endif + +static const LIBSSH2_CRYPT_METHOD *_libssh2_crypt_methods[] = { +#if LIBSSH2_AES_CTR + &libssh2_crypt_method_aes128_ctr, + &libssh2_crypt_method_aes192_ctr, + &libssh2_crypt_method_aes256_ctr, +#endif /* LIBSSH2_AES */ +#if LIBSSH2_AES + &libssh2_crypt_method_aes256_cbc, + &libssh2_crypt_method_rijndael_cbc_lysator_liu_se, /* == aes256-cbc */ + &libssh2_crypt_method_aes192_cbc, + &libssh2_crypt_method_aes128_cbc, +#endif /* LIBSSH2_AES */ +#if LIBSSH2_BLOWFISH + &libssh2_crypt_method_blowfish_cbc, +#endif /* LIBSSH2_BLOWFISH */ +#if LIBSSH2_RC4 + &libssh2_crypt_method_arcfour128, + &libssh2_crypt_method_arcfour, +#endif /* LIBSSH2_RC4 */ +#if LIBSSH2_CAST + &libssh2_crypt_method_cast128_cbc, +#endif /* LIBSSH2_CAST */ +#if LIBSSH2_3DES + &libssh2_crypt_method_3des_cbc, +#endif /* LIBSSH2_DES */ +#ifdef LIBSSH2_CRYPT_NONE + &libssh2_crypt_method_none, +#endif + NULL +}; + +/* Expose to kex.c */ +const LIBSSH2_CRYPT_METHOD ** +libssh2_crypt_methods(void) +{ + return _libssh2_crypt_methods; +} diff --git a/libssh2/src/global.c b/libssh2/src/global.c new file mode 100644 index 0000000..dc45e70 --- /dev/null +++ b/libssh2/src/global.c @@ -0,0 +1,78 @@ +/* Copyright (c) 2010 Lars Nordin + * Copyright (C) 2010 Simon Josefsson + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include "libssh2_priv.h" + +static int _libssh2_initialized = 0; +static int _libssh2_init_flags = 0; + +LIBSSH2_API int +libssh2_init(int flags) +{ + if (_libssh2_initialized == 0 && !(flags & LIBSSH2_INIT_NO_CRYPTO)) { + libssh2_crypto_init(); + _libssh2_init_aes_ctr(); + } + + _libssh2_initialized++; + _libssh2_init_flags |= flags; + + return 0; +} + +LIBSSH2_API void +libssh2_exit(void) +{ + if (_libssh2_initialized == 0) + return; + + _libssh2_initialized--; + + if (!(_libssh2_init_flags & LIBSSH2_INIT_NO_CRYPTO)) { + libssh2_crypto_exit(); + } + + return; +} + +void +_libssh2_init_if_needed(void) +{ + if (_libssh2_initialized == 0) + (void)libssh2_init (0); +} diff --git a/libssh2/src/hostkey.c b/libssh2/src/hostkey.c new file mode 100644 index 0000000..753563d --- /dev/null +++ b/libssh2/src/hostkey.c @@ -0,0 +1,487 @@ +/* Copyright (c) 2004-2006, Sara Golemon + * Copyright (c) 2009 by Daniel Stenberg + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include "libssh2_priv.h" +#include "misc.h" + +/* Needed for struct iovec on some platforms */ +#ifdef HAVE_SYS_UIO_H +#include +#endif + +#if LIBSSH2_RSA +/* *********** + * ssh-rsa * + *********** */ + +static int hostkey_method_ssh_rsa_dtor(LIBSSH2_SESSION * session, + void **abstract); + +/* + * hostkey_method_ssh_rsa_init + * + * Initialize the server hostkey working area with e/n pair + */ +static int +hostkey_method_ssh_rsa_init(LIBSSH2_SESSION * session, + const unsigned char *hostkey_data, + size_t hostkey_data_len, + void **abstract) +{ + libssh2_rsa_ctx *rsactx; + const unsigned char *s, *e, *n; + unsigned long len, e_len, n_len; + + (void) hostkey_data_len; + + if (*abstract) { + hostkey_method_ssh_rsa_dtor(session, abstract); + *abstract = NULL; + } + + s = hostkey_data; + len = _libssh2_ntohu32(s); + s += 4; + + if (len != 7 || strncmp((char *) s, "ssh-rsa", 7) != 0) { + return -1; + } + s += 7; + + e_len = _libssh2_ntohu32(s); + s += 4; + + e = s; + s += e_len; + n_len = _libssh2_ntohu32(s); + s += 4; + n = s; + + if (_libssh2_rsa_new(&rsactx, e, e_len, n, n_len, NULL, 0, + NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0)) + return -1; + + *abstract = rsactx; + + return 0; +} + +/* + * hostkey_method_ssh_rsa_initPEM + * + * Load a Private Key from a PEM file + */ +static int +hostkey_method_ssh_rsa_initPEM(LIBSSH2_SESSION * session, + const char *privkeyfile, + unsigned const char *passphrase, + void **abstract) +{ + libssh2_rsa_ctx *rsactx; + int ret; + + if (*abstract) { + hostkey_method_ssh_rsa_dtor(session, abstract); + *abstract = NULL; + } + + ret = _libssh2_rsa_new_private(&rsactx, session, privkeyfile, passphrase); + if (ret) { + return -1; + } + + *abstract = rsactx; + + return 0; +} + +/* + * hostkey_method_ssh_rsa_sign + * + * Verify signature created by remote + */ +static int +hostkey_method_ssh_rsa_sig_verify(LIBSSH2_SESSION * session, + const unsigned char *sig, + size_t sig_len, + const unsigned char *m, + size_t m_len, void **abstract) +{ + libssh2_rsa_ctx *rsactx = (libssh2_rsa_ctx *) (*abstract); + (void) session; + + /* Skip past keyname_len(4) + keyname(7){"ssh-rsa"} + signature_len(4) */ + sig += 15; + sig_len -= 15; + return _libssh2_rsa_sha1_verify(rsactx, sig, sig_len, m, m_len); +} + +/* + * hostkey_method_ssh_rsa_signv + * + * Construct a signature from an array of vectors + */ +static int +hostkey_method_ssh_rsa_signv(LIBSSH2_SESSION * session, + unsigned char **signature, + size_t *signature_len, + int veccount, + const struct iovec datavec[], + void **abstract) +{ + libssh2_rsa_ctx *rsactx = (libssh2_rsa_ctx *) (*abstract); + int ret; + int i; + unsigned char hash[SHA_DIGEST_LENGTH]; + libssh2_sha1_ctx ctx; + + libssh2_sha1_init(&ctx); + for(i = 0; i < veccount; i++) { + libssh2_sha1_update(ctx, datavec[i].iov_base, datavec[i].iov_len); + } + libssh2_sha1_final(ctx, hash); + + ret = _libssh2_rsa_sha1_sign(session, rsactx, hash, SHA_DIGEST_LENGTH, + signature, signature_len); + if (ret) { + return -1; + } + + return 0; +} + +/* + * hostkey_method_ssh_rsa_dtor + * + * Shutdown the hostkey + */ +static int +hostkey_method_ssh_rsa_dtor(LIBSSH2_SESSION * session, void **abstract) +{ + libssh2_rsa_ctx *rsactx = (libssh2_rsa_ctx *) (*abstract); + (void) session; + + _libssh2_rsa_free(rsactx); + + *abstract = NULL; + + return 0; +} + +static const LIBSSH2_HOSTKEY_METHOD hostkey_method_ssh_rsa = { + "ssh-rsa", + MD5_DIGEST_LENGTH, + hostkey_method_ssh_rsa_init, + hostkey_method_ssh_rsa_initPEM, + hostkey_method_ssh_rsa_sig_verify, + hostkey_method_ssh_rsa_signv, + NULL, /* encrypt */ + hostkey_method_ssh_rsa_dtor, +}; +#endif /* LIBSSH2_RSA */ + +#if LIBSSH2_DSA +/* *********** + * ssh-dss * + *********** */ + +static int hostkey_method_ssh_dss_dtor(LIBSSH2_SESSION * session, + void **abstract); + +/* + * hostkey_method_ssh_dss_init + * + * Initialize the server hostkey working area with p/q/g/y set + */ +static int +hostkey_method_ssh_dss_init(LIBSSH2_SESSION * session, + const unsigned char *hostkey_data, + size_t hostkey_data_len, + void **abstract) +{ + libssh2_dsa_ctx *dsactx; + const unsigned char *p, *q, *g, *y, *s; + unsigned long p_len, q_len, g_len, y_len, len; + (void) hostkey_data_len; + + if (*abstract) { + hostkey_method_ssh_dss_dtor(session, abstract); + *abstract = NULL; + } + + s = hostkey_data; + len = _libssh2_ntohu32(s); + s += 4; + if (len != 7 || strncmp((char *) s, "ssh-dss", 7) != 0) { + return -1; + } + s += 7; + + p_len = _libssh2_ntohu32(s); + s += 4; + p = s; + s += p_len; + q_len = _libssh2_ntohu32(s); + s += 4; + q = s; + s += q_len; + g_len = _libssh2_ntohu32(s); + s += 4; + g = s; + s += g_len; + y_len = _libssh2_ntohu32(s); + s += 4; + y = s; + /* s += y_len; */ + + _libssh2_dsa_new(&dsactx, p, p_len, q, q_len, g, g_len, y, y_len, NULL, 0); + + *abstract = dsactx; + + return 0; +} + +/* + * hostkey_method_ssh_dss_initPEM + * + * Load a Private Key from a PEM file + */ +static int +hostkey_method_ssh_dss_initPEM(LIBSSH2_SESSION * session, + const char *privkeyfile, + unsigned const char *passphrase, + void **abstract) +{ + libssh2_dsa_ctx *dsactx; + int ret; + + if (*abstract) { + hostkey_method_ssh_dss_dtor(session, abstract); + *abstract = NULL; + } + + ret = _libssh2_dsa_new_private(&dsactx, session, privkeyfile, passphrase); + if (ret) { + return -1; + } + + *abstract = dsactx; + + return 0; +} + +/* + * libssh2_hostkey_method_ssh_dss_sign + * + * Verify signature created by remote + */ +static int +hostkey_method_ssh_dss_sig_verify(LIBSSH2_SESSION * session, + const unsigned char *sig, + size_t sig_len, + const unsigned char *m, + size_t m_len, void **abstract) +{ + libssh2_dsa_ctx *dsactx = (libssh2_dsa_ctx *) (*abstract); + + /* Skip past keyname_len(4) + keyname(7){"ssh-dss"} + signature_len(4) */ + sig += 15; + sig_len -= 15; + if (sig_len != 40) { + return _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Invalid DSS signature length"); + } + return _libssh2_dsa_sha1_verify(dsactx, sig, m, m_len); +} + +/* + * hostkey_method_ssh_dss_signv + * + * Construct a signature from an array of vectors + */ +static int +hostkey_method_ssh_dss_signv(LIBSSH2_SESSION * session, + unsigned char **signature, + size_t *signature_len, + int veccount, + const struct iovec datavec[], + void **abstract) +{ + libssh2_dsa_ctx *dsactx = (libssh2_dsa_ctx *) (*abstract); + unsigned char hash[SHA_DIGEST_LENGTH]; + libssh2_sha1_ctx ctx; + int i; + + *signature = LIBSSH2_ALLOC(session, 2 * SHA_DIGEST_LENGTH); + if (!*signature) { + return -1; + } + + *signature_len = 2 * SHA_DIGEST_LENGTH; + memset(*signature, 0, 2 * SHA_DIGEST_LENGTH); + + libssh2_sha1_init(&ctx); + for(i = 0; i < veccount; i++) { + libssh2_sha1_update(ctx, datavec[i].iov_base, datavec[i].iov_len); + } + libssh2_sha1_final(ctx, hash); + + if (_libssh2_dsa_sha1_sign(dsactx, hash, SHA_DIGEST_LENGTH, *signature)) { + LIBSSH2_FREE(session, *signature); + return -1; + } + + return 0; +} + +/* + * libssh2_hostkey_method_ssh_dss_dtor + * + * Shutdown the hostkey method + */ +static int +hostkey_method_ssh_dss_dtor(LIBSSH2_SESSION * session, void **abstract) +{ + libssh2_dsa_ctx *dsactx = (libssh2_dsa_ctx *) (*abstract); + (void) session; + + _libssh2_dsa_free(dsactx); + + *abstract = NULL; + + return 0; +} + +static const LIBSSH2_HOSTKEY_METHOD hostkey_method_ssh_dss = { + "ssh-dss", + MD5_DIGEST_LENGTH, + hostkey_method_ssh_dss_init, + hostkey_method_ssh_dss_initPEM, + hostkey_method_ssh_dss_sig_verify, + hostkey_method_ssh_dss_signv, + NULL, /* encrypt */ + hostkey_method_ssh_dss_dtor, +}; +#endif /* LIBSSH2_DSA */ + +static const LIBSSH2_HOSTKEY_METHOD *hostkey_methods[] = { +#if LIBSSH2_RSA + &hostkey_method_ssh_rsa, +#endif /* LIBSSH2_RSA */ +#if LIBSSH2_DSA + &hostkey_method_ssh_dss, +#endif /* LIBSSH2_DSA */ + NULL +}; + +const LIBSSH2_HOSTKEY_METHOD ** +libssh2_hostkey_methods(void) +{ + return hostkey_methods; +} + +/* + * libssh2_hostkey_hash + * + * Returns hash signature + * Returned buffer should NOT be freed + * Length of buffer is determined by hash type + * i.e. MD5 == 16, SHA1 == 20 + */ +LIBSSH2_API const char * +libssh2_hostkey_hash(LIBSSH2_SESSION * session, int hash_type) +{ + switch (hash_type) { +#if LIBSSH2_MD5 + case LIBSSH2_HOSTKEY_HASH_MD5: + return (session->server_hostkey_md5_valid) + ? (char *) session->server_hostkey_md5 + : NULL; + break; +#endif /* LIBSSH2_MD5 */ + case LIBSSH2_HOSTKEY_HASH_SHA1: + return (char *) session->server_hostkey_sha1; + break; + default: + return NULL; + } +} + +static int hostkey_type(const unsigned char *hostkey, size_t len) +{ + const unsigned char rsa[] = { + 0, 0, 0, 0x07, 's', 's', 'h', '-', 'r', 's', 'a' + }; + const unsigned char dss[] = { + 0, 0, 0, 0x07, 's', 's', 'h', '-', 'd', 's', 's' + }; + + if (len < 11) + return LIBSSH2_HOSTKEY_TYPE_UNKNOWN; + + if (!memcmp(rsa, hostkey, 11)) + return LIBSSH2_HOSTKEY_TYPE_RSA; + + if (!memcmp(dss, hostkey, 11)) + return LIBSSH2_HOSTKEY_TYPE_DSS; + + return LIBSSH2_HOSTKEY_TYPE_UNKNOWN; +} + +/* + * libssh2_session_hostkey() + * + * Returns the server key and length. + * + */ +LIBSSH2_API const char * +libssh2_session_hostkey(LIBSSH2_SESSION *session, size_t *len, int *type) +{ + if(session->server_hostkey_len) { + if(len) + *len = session->server_hostkey_len; + if (type) + *type = hostkey_type(session->server_hostkey, + session->server_hostkey_len); + return (char *) session->server_hostkey; + } + if(len) + *len = 0; + return NULL; +} + diff --git a/libssh2/src/keepalive.c b/libssh2/src/keepalive.c new file mode 100644 index 0000000..260206a --- /dev/null +++ b/libssh2/src/keepalive.c @@ -0,0 +1,98 @@ +/* Copyright (C) 2010 Simon Josefsson + * Author: Simon Josefsson + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + */ + +#include "libssh2_priv.h" +#include "transport.h" /* _libssh2_transport_write */ + +/* Keep-alive stuff. */ + +LIBSSH2_API void +libssh2_keepalive_config (LIBSSH2_SESSION *session, + int want_reply, + unsigned interval) +{ + if (interval == 1) + session->keepalive_interval = 2; + else + session->keepalive_interval = interval; + session->keepalive_want_reply = want_reply ? 1 : 0; +} + +LIBSSH2_API int +libssh2_keepalive_send (LIBSSH2_SESSION *session, + int *seconds_to_next) +{ + time_t now; + + if (!session->keepalive_interval) { + if (seconds_to_next) + *seconds_to_next = 0; + return 0; + } + + now = time (NULL); + + if (session->keepalive_last_sent + session->keepalive_interval <= now) { + /* Format is + "SSH_MSG_GLOBAL_REQUEST || 4-byte len || str || want-reply". */ + unsigned char keepalive_data[] + = "\x50\x00\x00\x00\x15keepalive@libssh2.orgW"; + size_t len = sizeof (keepalive_data) - 1; + int rc; + + keepalive_data[len - 1] = session->keepalive_want_reply; + + rc = _libssh2_transport_send(session, keepalive_data, len, NULL, 0); + /* Silently ignore PACKET_EAGAIN here: if the write buffer is + already full, sending another keepalive is not useful. */ + if (rc && rc != LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send keepalive message"); + return rc; + } + + session->keepalive_last_sent = now; + if (seconds_to_next) + *seconds_to_next = session->keepalive_interval; + } else if (seconds_to_next) { + *seconds_to_next = (int) session->keepalive_last_sent + + session->keepalive_interval - now; + } + + return 0; +} diff --git a/libssh2/src/kex.c b/libssh2/src/kex.c new file mode 100644 index 0000000..07e717f --- /dev/null +++ b/libssh2/src/kex.c @@ -0,0 +1,2013 @@ +/* Copyright (c) 2004-2007, Sara Golemon + * Copyright (c) 2010, Daniel Stenberg + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include "libssh2_priv.h" + +#include "transport.h" +#include "comp.h" +#include "mac.h" + +/* TODO: Switch this to an inline and handle alloc() failures */ +/* Helper macro called from kex_method_diffie_hellman_group1_sha1_key_exchange */ +#define LIBSSH2_KEX_METHOD_DIFFIE_HELLMAN_SHA1_HASH(value, reqlen, version) \ + { \ + libssh2_sha1_ctx hash; \ + unsigned long len = 0; \ + if (!(value)) { \ + value = LIBSSH2_ALLOC(session, reqlen + SHA_DIGEST_LENGTH); \ + } \ + if (value) \ + while (len < (unsigned long)reqlen) { \ + libssh2_sha1_init(&hash); \ + libssh2_sha1_update(hash, exchange_state->k_value, \ + exchange_state->k_value_len); \ + libssh2_sha1_update(hash, exchange_state->h_sig_comp, \ + SHA_DIGEST_LENGTH); \ + if (len > 0) { \ + libssh2_sha1_update(hash, value, len); \ + } else { \ + libssh2_sha1_update(hash, (version), 1); \ + libssh2_sha1_update(hash, session->session_id, \ + session->session_id_len); \ + } \ + libssh2_sha1_final(hash, (value) + len); \ + len += SHA_DIGEST_LENGTH; \ + } \ + } + +/* + * diffie_hellman_sha1 + * + * Diffie Hellman Key Exchange, Group Agnostic + */ +static int diffie_hellman_sha1(LIBSSH2_SESSION *session, + _libssh2_bn *g, + _libssh2_bn *p, + int group_order, + unsigned char packet_type_init, + unsigned char packet_type_reply, + unsigned char *midhash, + unsigned long midhash_len, + kmdhgGPsha1kex_state_t *exchange_state) +{ + int ret = 0; + int rc; + + if (exchange_state->state == libssh2_NB_state_idle) { + /* Setup initial values */ + exchange_state->e_packet = NULL; + exchange_state->s_packet = NULL; + exchange_state->k_value = NULL; + exchange_state->ctx = _libssh2_bn_ctx_new(); + exchange_state->x = _libssh2_bn_init(); /* Random from client */ + exchange_state->e = _libssh2_bn_init(); /* g^x mod p */ + exchange_state->f = _libssh2_bn_init(); /* g^(Random from server) mod p */ + exchange_state->k = _libssh2_bn_init(); /* The shared secret: f^x mod p */ + + /* Zero the whole thing out */ + memset(&exchange_state->req_state, 0, sizeof(packet_require_state_t)); + + /* Generate x and e */ + _libssh2_bn_rand(exchange_state->x, group_order, 0, -1); + _libssh2_bn_mod_exp(exchange_state->e, g, exchange_state->x, p, + exchange_state->ctx); + + /* Send KEX init */ + /* packet_type(1) + String Length(4) + leading 0(1) */ + exchange_state->e_packet_len = + _libssh2_bn_bytes(exchange_state->e) + 6; + if (_libssh2_bn_bits(exchange_state->e) % 8) { + /* Leading 00 not needed */ + exchange_state->e_packet_len--; + } + + exchange_state->e_packet = + LIBSSH2_ALLOC(session, exchange_state->e_packet_len); + if (!exchange_state->e_packet) { + ret = _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Out of memory error"); + goto clean_exit; + } + exchange_state->e_packet[0] = packet_type_init; + _libssh2_htonu32(exchange_state->e_packet + 1, + exchange_state->e_packet_len - 5); + if (_libssh2_bn_bits(exchange_state->e) % 8) { + _libssh2_bn_to_bin(exchange_state->e, + exchange_state->e_packet + 5); + } else { + exchange_state->e_packet[5] = 0; + _libssh2_bn_to_bin(exchange_state->e, + exchange_state->e_packet + 6); + } + + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Sending KEX packet %d", + (int) packet_type_init); + exchange_state->state = libssh2_NB_state_created; + } + + if (exchange_state->state == libssh2_NB_state_created) { + rc = _libssh2_transport_send(session, exchange_state->e_packet, + exchange_state->e_packet_len, + NULL, 0); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } else if (rc) { + ret = _libssh2_error(session, rc, + "Unable to send KEX init message"); + goto clean_exit; + } + exchange_state->state = libssh2_NB_state_sent; + } + + if (exchange_state->state == libssh2_NB_state_sent) { + if (session->burn_optimistic_kexinit) { + /* The first KEX packet to come along will be the guess initially + * sent by the server. That guess turned out to be wrong so we + * need to silently ignore it */ + int burn_type; + + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Waiting for badly guessed KEX packet (to be ignored)"); + burn_type = + _libssh2_packet_burn(session, &exchange_state->burn_state); + if (burn_type == LIBSSH2_ERROR_EAGAIN) { + return burn_type; + } else if (burn_type <= 0) { + /* Failed to receive a packet */ + ret = burn_type; + goto clean_exit; + } + session->burn_optimistic_kexinit = 0; + + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Burnt packet of type: %02x", + (unsigned int) burn_type); + } + + exchange_state->state = libssh2_NB_state_sent1; + } + + if (exchange_state->state == libssh2_NB_state_sent1) { + /* Wait for KEX reply */ + rc = _libssh2_packet_require(session, packet_type_reply, + &exchange_state->s_packet, + &exchange_state->s_packet_len, 0, NULL, + 0, &exchange_state->req_state); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } + if (rc) { + ret = _libssh2_error(session, LIBSSH2_ERROR_TIMEOUT, + "Timed out waiting for KEX reply"); + goto clean_exit; + } + + /* Parse KEXDH_REPLY */ + exchange_state->s = exchange_state->s_packet + 1; + + session->server_hostkey_len = _libssh2_ntohu32(exchange_state->s); + exchange_state->s += 4; + session->server_hostkey = + LIBSSH2_ALLOC(session, session->server_hostkey_len); + if (!session->server_hostkey) { + ret = _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for a copy " + "of the host key"); + goto clean_exit; + } + memcpy(session->server_hostkey, exchange_state->s, + session->server_hostkey_len); + exchange_state->s += session->server_hostkey_len; + +#if LIBSSH2_MD5 + { + libssh2_md5_ctx fingerprint_ctx; + + if (libssh2_md5_init(&fingerprint_ctx)) { + libssh2_md5_update(fingerprint_ctx, session->server_hostkey, + session->server_hostkey_len); + libssh2_md5_final(fingerprint_ctx, session->server_hostkey_md5); + session->server_hostkey_md5_valid = TRUE; + } + else { + session->server_hostkey_md5_valid = FALSE; + } + } +#ifdef LIBSSH2DEBUG + { + char fingerprint[50], *fprint = fingerprint; + int i; + for(i = 0; i < 16; i++, fprint += 3) { + snprintf(fprint, 4, "%02x:", session->server_hostkey_md5[i]); + } + *(--fprint) = '\0'; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Server's MD5 Fingerprint: %s", fingerprint); + } +#endif /* LIBSSH2DEBUG */ +#endif /* ! LIBSSH2_MD5 */ + + { + libssh2_sha1_ctx fingerprint_ctx; + + libssh2_sha1_init(&fingerprint_ctx); + libssh2_sha1_update(fingerprint_ctx, session->server_hostkey, + session->server_hostkey_len); + libssh2_sha1_final(fingerprint_ctx, session->server_hostkey_sha1); + } +#ifdef LIBSSH2DEBUG + { + char fingerprint[64], *fprint = fingerprint; + int i; + + for(i = 0; i < 20; i++, fprint += 3) { + snprintf(fprint, 4, "%02x:", session->server_hostkey_sha1[i]); + } + *(--fprint) = '\0'; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Server's SHA1 Fingerprint: %s", fingerprint); + } +#endif /* LIBSSH2DEBUG */ + + if (session->hostkey->init(session, session->server_hostkey, + session->server_hostkey_len, + &session->server_hostkey_abstract)) { + ret = _libssh2_error(session, LIBSSH2_ERROR_HOSTKEY_INIT, + "Unable to initialize hostkey importer"); + goto clean_exit; + } + + exchange_state->f_value_len = _libssh2_ntohu32(exchange_state->s); + exchange_state->s += 4; + exchange_state->f_value = exchange_state->s; + exchange_state->s += exchange_state->f_value_len; + _libssh2_bn_from_bin(exchange_state->f, exchange_state->f_value_len, + exchange_state->f_value); + + exchange_state->h_sig_len = _libssh2_ntohu32(exchange_state->s); + exchange_state->s += 4; + exchange_state->h_sig = exchange_state->s; + + /* Compute the shared secret */ + _libssh2_bn_mod_exp(exchange_state->k, exchange_state->f, + exchange_state->x, p, exchange_state->ctx); + exchange_state->k_value_len = _libssh2_bn_bytes(exchange_state->k) + 5; + if (_libssh2_bn_bits(exchange_state->k) % 8) { + /* don't need leading 00 */ + exchange_state->k_value_len--; + } + exchange_state->k_value = + LIBSSH2_ALLOC(session, exchange_state->k_value_len); + if (!exchange_state->k_value) { + ret = _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate buffer for K"); + goto clean_exit; + } + _libssh2_htonu32(exchange_state->k_value, + exchange_state->k_value_len - 4); + if (_libssh2_bn_bits(exchange_state->k) % 8) { + _libssh2_bn_to_bin(exchange_state->k, exchange_state->k_value + 4); + } else { + exchange_state->k_value[4] = 0; + _libssh2_bn_to_bin(exchange_state->k, exchange_state->k_value + 5); + } + + libssh2_sha1_init(&exchange_state->exchange_hash); + if (session->local.banner) { + _libssh2_htonu32(exchange_state->h_sig_comp, + strlen((char *) session->local.banner) - 2); + libssh2_sha1_update(exchange_state->exchange_hash, + exchange_state->h_sig_comp, 4); + libssh2_sha1_update(exchange_state->exchange_hash, + (char *) session->local.banner, + strlen((char *) session->local.banner) - 2); + } else { + _libssh2_htonu32(exchange_state->h_sig_comp, + sizeof(LIBSSH2_SSH_DEFAULT_BANNER) - 1); + libssh2_sha1_update(exchange_state->exchange_hash, + exchange_state->h_sig_comp, 4); + libssh2_sha1_update(exchange_state->exchange_hash, + LIBSSH2_SSH_DEFAULT_BANNER, + sizeof(LIBSSH2_SSH_DEFAULT_BANNER) - 1); + } + + _libssh2_htonu32(exchange_state->h_sig_comp, + strlen((char *) session->remote.banner)); + libssh2_sha1_update(exchange_state->exchange_hash, + exchange_state->h_sig_comp, 4); + libssh2_sha1_update(exchange_state->exchange_hash, + session->remote.banner, + strlen((char *) session->remote.banner)); + + _libssh2_htonu32(exchange_state->h_sig_comp, + session->local.kexinit_len); + libssh2_sha1_update(exchange_state->exchange_hash, + exchange_state->h_sig_comp, 4); + libssh2_sha1_update(exchange_state->exchange_hash, + session->local.kexinit, + session->local.kexinit_len); + + _libssh2_htonu32(exchange_state->h_sig_comp, + session->remote.kexinit_len); + libssh2_sha1_update(exchange_state->exchange_hash, + exchange_state->h_sig_comp, 4); + libssh2_sha1_update(exchange_state->exchange_hash, + session->remote.kexinit, + session->remote.kexinit_len); + + _libssh2_htonu32(exchange_state->h_sig_comp, + session->server_hostkey_len); + libssh2_sha1_update(exchange_state->exchange_hash, + exchange_state->h_sig_comp, 4); + libssh2_sha1_update(exchange_state->exchange_hash, + session->server_hostkey, + session->server_hostkey_len); + + if (packet_type_init == SSH_MSG_KEX_DH_GEX_INIT) { + /* diffie-hellman-group-exchange hashes additional fields */ +#ifdef LIBSSH2_DH_GEX_NEW + _libssh2_htonu32(exchange_state->h_sig_comp, + LIBSSH2_DH_GEX_MINGROUP); + _libssh2_htonu32(exchange_state->h_sig_comp + 4, + LIBSSH2_DH_GEX_OPTGROUP); + _libssh2_htonu32(exchange_state->h_sig_comp + 8, + LIBSSH2_DH_GEX_MAXGROUP); + libssh2_sha1_update(exchange_state->exchange_hash, + exchange_state->h_sig_comp, 12); +#else + _libssh2_htonu32(exchange_state->h_sig_comp, + LIBSSH2_DH_GEX_OPTGROUP); + libssh2_sha1_update(exchange_state->exchange_hash, + exchange_state->h_sig_comp, 4); +#endif + } + + if (midhash) { + libssh2_sha1_update(exchange_state->exchange_hash, midhash, + midhash_len); + } + + libssh2_sha1_update(exchange_state->exchange_hash, + exchange_state->e_packet + 1, + exchange_state->e_packet_len - 1); + + _libssh2_htonu32(exchange_state->h_sig_comp, + exchange_state->f_value_len); + libssh2_sha1_update(exchange_state->exchange_hash, + exchange_state->h_sig_comp, 4); + libssh2_sha1_update(exchange_state->exchange_hash, + exchange_state->f_value, + exchange_state->f_value_len); + + libssh2_sha1_update(exchange_state->exchange_hash, + exchange_state->k_value, + exchange_state->k_value_len); + + libssh2_sha1_final(exchange_state->exchange_hash, + exchange_state->h_sig_comp); + + if (session->hostkey-> + sig_verify(session, exchange_state->h_sig, + exchange_state->h_sig_len, exchange_state->h_sig_comp, + 20, &session->server_hostkey_abstract)) { + ret = _libssh2_error(session, LIBSSH2_ERROR_HOSTKEY_SIGN, + "Unable to verify hostkey signature"); + goto clean_exit; + } + + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Sending NEWKEYS message"); + exchange_state->c = SSH_MSG_NEWKEYS; + + exchange_state->state = libssh2_NB_state_sent2; + } + + if (exchange_state->state == libssh2_NB_state_sent2) { + rc = _libssh2_transport_send(session, &exchange_state->c, 1, NULL, 0); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } else if (rc) { + ret = _libssh2_error(session, rc, "Unable to send NEWKEYS message"); + goto clean_exit; + } + + exchange_state->state = libssh2_NB_state_sent3; + } + + if (exchange_state->state == libssh2_NB_state_sent3) { + rc = _libssh2_packet_require(session, SSH_MSG_NEWKEYS, + &exchange_state->tmp, + &exchange_state->tmp_len, 0, NULL, 0, + &exchange_state->req_state); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } else if (rc) { + ret = _libssh2_error(session, rc, "Timed out waiting for NEWKEYS"); + goto clean_exit; + } + /* The first key exchange has been performed, + switch to active crypt/comp/mac mode */ + session->state |= LIBSSH2_STATE_NEWKEYS; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Received NEWKEYS message"); + + /* This will actually end up being just packet_type(1) + for this packet type anyway */ + LIBSSH2_FREE(session, exchange_state->tmp); + + if (!session->session_id) { + session->session_id = LIBSSH2_ALLOC(session, SHA_DIGEST_LENGTH); + if (!session->session_id) { + ret = _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate buffer for SHA digest"); + goto clean_exit; + } + memcpy(session->session_id, exchange_state->h_sig_comp, + SHA_DIGEST_LENGTH); + session->session_id_len = SHA_DIGEST_LENGTH; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "session_id calculated"); + } + + /* Cleanup any existing cipher */ + if (session->local.crypt->dtor) { + session->local.crypt->dtor(session, + &session->local.crypt_abstract); + } + + /* Calculate IV/Secret/Key for each direction */ + if (session->local.crypt->init) { + unsigned char *iv = NULL, *secret = NULL; + int free_iv = 0, free_secret = 0; + + LIBSSH2_KEX_METHOD_DIFFIE_HELLMAN_SHA1_HASH(iv, + session->local.crypt-> + iv_len, "A"); + if (!iv) { + ret = -1; + goto clean_exit; + } + LIBSSH2_KEX_METHOD_DIFFIE_HELLMAN_SHA1_HASH(secret, + session->local.crypt-> + secret_len, "C"); + if (!secret) { + LIBSSH2_FREE(session, iv); + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + if (session->local.crypt-> + init(session, session->local.crypt, iv, &free_iv, secret, + &free_secret, 1, &session->local.crypt_abstract)) { + LIBSSH2_FREE(session, iv); + LIBSSH2_FREE(session, secret); + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + + if (free_iv) { + memset(iv, 0, session->local.crypt->iv_len); + LIBSSH2_FREE(session, iv); + } + + if (free_secret) { + memset(secret, 0, session->local.crypt->secret_len); + LIBSSH2_FREE(session, secret); + } + } + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Client to Server IV and Key calculated"); + + if (session->remote.crypt->dtor) { + /* Cleanup any existing cipher */ + session->remote.crypt->dtor(session, + &session->remote.crypt_abstract); + } + + if (session->remote.crypt->init) { + unsigned char *iv = NULL, *secret = NULL; + int free_iv = 0, free_secret = 0; + + LIBSSH2_KEX_METHOD_DIFFIE_HELLMAN_SHA1_HASH(iv, + session->remote.crypt-> + iv_len, "B"); + if (!iv) { + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + LIBSSH2_KEX_METHOD_DIFFIE_HELLMAN_SHA1_HASH(secret, + session->remote.crypt-> + secret_len, "D"); + if (!secret) { + LIBSSH2_FREE(session, iv); + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + if (session->remote.crypt-> + init(session, session->remote.crypt, iv, &free_iv, secret, + &free_secret, 0, &session->remote.crypt_abstract)) { + LIBSSH2_FREE(session, iv); + LIBSSH2_FREE(session, secret); + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + + if (free_iv) { + memset(iv, 0, session->remote.crypt->iv_len); + LIBSSH2_FREE(session, iv); + } + + if (free_secret) { + memset(secret, 0, session->remote.crypt->secret_len); + LIBSSH2_FREE(session, secret); + } + } + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Server to Client IV and Key calculated"); + + if (session->local.mac->dtor) { + session->local.mac->dtor(session, &session->local.mac_abstract); + } + + if (session->local.mac->init) { + unsigned char *key = NULL; + int free_key = 0; + + LIBSSH2_KEX_METHOD_DIFFIE_HELLMAN_SHA1_HASH(key, + session->local.mac-> + key_len, "E"); + if (!key) { + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + session->local.mac->init(session, key, &free_key, + &session->local.mac_abstract); + + if (free_key) { + memset(key, 0, session->local.mac->key_len); + LIBSSH2_FREE(session, key); + } + } + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Client to Server HMAC Key calculated"); + + if (session->remote.mac->dtor) { + session->remote.mac->dtor(session, &session->remote.mac_abstract); + } + + if (session->remote.mac->init) { + unsigned char *key = NULL; + int free_key = 0; + + LIBSSH2_KEX_METHOD_DIFFIE_HELLMAN_SHA1_HASH(key, + session->remote.mac-> + key_len, "F"); + if (!key) { + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + session->remote.mac->init(session, key, &free_key, + &session->remote.mac_abstract); + + if (free_key) { + memset(key, 0, session->remote.mac->key_len); + LIBSSH2_FREE(session, key); + } + } + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Server to Client HMAC Key calculated"); + + /* Initialize compression for each direction */ + + /* Cleanup any existing compression */ + if (session->local.comp && session->local.comp->dtor) { + session->local.comp->dtor(session, 1, + &session->local.comp_abstract); + } + + if (session->local.comp && session->local.comp->init) { + if (session->local.comp->init(session, 1, + &session->local.comp_abstract)) { + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + } + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Client to Server compression initialized"); + + if (session->remote.comp && session->remote.comp->dtor) { + session->remote.comp->dtor(session, 0, + &session->remote.comp_abstract); + } + + if (session->remote.comp && session->remote.comp->init) { + if (session->remote.comp->init(session, 0, + &session->remote.comp_abstract)) { + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + } + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Server to Client compression initialized"); + + } + + clean_exit: + _libssh2_bn_free(exchange_state->x); + exchange_state->x = NULL; + _libssh2_bn_free(exchange_state->e); + exchange_state->e = NULL; + _libssh2_bn_free(exchange_state->f); + exchange_state->f = NULL; + _libssh2_bn_free(exchange_state->k); + exchange_state->k = NULL; + _libssh2_bn_ctx_free(exchange_state->ctx); + exchange_state->ctx = NULL; + + if (exchange_state->e_packet) { + LIBSSH2_FREE(session, exchange_state->e_packet); + exchange_state->e_packet = NULL; + } + + if (exchange_state->s_packet) { + LIBSSH2_FREE(session, exchange_state->s_packet); + exchange_state->s_packet = NULL; + } + + if (exchange_state->k_value) { + LIBSSH2_FREE(session, exchange_state->k_value); + exchange_state->k_value = NULL; + } + + exchange_state->state = libssh2_NB_state_idle; + + return ret; +} + + + +/* kex_method_diffie_hellman_group1_sha1_key_exchange + * Diffie-Hellman Group1 (Actually Group2) Key Exchange using SHA1 + */ +static int +kex_method_diffie_hellman_group1_sha1_key_exchange(LIBSSH2_SESSION *session, + key_exchange_state_low_t + * key_state) +{ + static const unsigned char p_value[128] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xC9, 0x0F, 0xDA, 0xA2, 0x21, 0x68, 0xC2, 0x34, + 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, + 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, + 0x02, 0x0B, 0xBE, 0xA6, 0x3B, 0x13, 0x9B, 0x22, + 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, + 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, + 0x30, 0x2B, 0x0A, 0x6D, 0xF2, 0x5F, 0x14, 0x37, + 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, + 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, + 0xF4, 0x4C, 0x42, 0xE9, 0xA6, 0x37, 0xED, 0x6B, + 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, + 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, + 0xAE, 0x9F, 0x24, 0x11, 0x7C, 0x4B, 0x1F, 0xE6, + 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE6, 0x53, 0x81, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF + }; + + int ret; + + if (key_state->state == libssh2_NB_state_idle) { + /* g == 2 */ + key_state->p = _libssh2_bn_init(); /* SSH2 defined value (p_value) */ + key_state->g = _libssh2_bn_init(); /* SSH2 defined value (2) */ + + /* Initialize P and G */ + _libssh2_bn_set_word(key_state->g, 2); + _libssh2_bn_from_bin(key_state->p, 128, p_value); + + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Initiating Diffie-Hellman Group1 Key Exchange"); + + key_state->state = libssh2_NB_state_created; + } + ret = diffie_hellman_sha1(session, key_state->g, key_state->p, 128, + SSH_MSG_KEXDH_INIT, SSH_MSG_KEXDH_REPLY, + NULL, 0, &key_state->exchange_state); + if (ret == LIBSSH2_ERROR_EAGAIN) { + return ret; + } + + _libssh2_bn_free(key_state->p); + key_state->p = NULL; + _libssh2_bn_free(key_state->g); + key_state->g = NULL; + key_state->state = libssh2_NB_state_idle; + + return ret; +} + + + +/* kex_method_diffie_hellman_group14_sha1_key_exchange + * Diffie-Hellman Group14 Key Exchange using SHA1 + */ +static int +kex_method_diffie_hellman_group14_sha1_key_exchange(LIBSSH2_SESSION *session, + key_exchange_state_low_t + * key_state) +{ + static const unsigned char p_value[256] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xC9, 0x0F, 0xDA, 0xA2, 0x21, 0x68, 0xC2, 0x34, + 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, + 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, + 0x02, 0x0B, 0xBE, 0xA6, 0x3B, 0x13, 0x9B, 0x22, + 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, + 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, + 0x30, 0x2B, 0x0A, 0x6D, 0xF2, 0x5F, 0x14, 0x37, + 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, + 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, + 0xF4, 0x4C, 0x42, 0xE9, 0xA6, 0x37, 0xED, 0x6B, + 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, + 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, + 0xAE, 0x9F, 0x24, 0x11, 0x7C, 0x4B, 0x1F, 0xE6, + 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE4, 0x5B, 0x3D, + 0xC2, 0x00, 0x7C, 0xB8, 0xA1, 0x63, 0xBF, 0x05, + 0x98, 0xDA, 0x48, 0x36, 0x1C, 0x55, 0xD3, 0x9A, + 0x69, 0x16, 0x3F, 0xA8, 0xFD, 0x24, 0xCF, 0x5F, + 0x83, 0x65, 0x5D, 0x23, 0xDC, 0xA3, 0xAD, 0x96, + 0x1C, 0x62, 0xF3, 0x56, 0x20, 0x85, 0x52, 0xBB, + 0x9E, 0xD5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6D, + 0x67, 0x0C, 0x35, 0x4E, 0x4A, 0xBC, 0x98, 0x04, + 0xF1, 0x74, 0x6C, 0x08, 0xCA, 0x18, 0x21, 0x7C, + 0x32, 0x90, 0x5E, 0x46, 0x2E, 0x36, 0xCE, 0x3B, + 0xE3, 0x9E, 0x77, 0x2C, 0x18, 0x0E, 0x86, 0x03, + 0x9B, 0x27, 0x83, 0xA2, 0xEC, 0x07, 0xA2, 0x8F, + 0xB5, 0xC5, 0x5D, 0xF0, 0x6F, 0x4C, 0x52, 0xC9, + 0xDE, 0x2B, 0xCB, 0xF6, 0x95, 0x58, 0x17, 0x18, + 0x39, 0x95, 0x49, 0x7C, 0xEA, 0x95, 0x6A, 0xE5, + 0x15, 0xD2, 0x26, 0x18, 0x98, 0xFA, 0x05, 0x10, + 0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAC, 0xAA, 0x68, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF + }; + int ret; + + if (key_state->state == libssh2_NB_state_idle) { + key_state->p = _libssh2_bn_init(); /* SSH2 defined value (p_value) */ + key_state->g = _libssh2_bn_init(); /* SSH2 defined value (2) */ + + /* g == 2 */ + /* Initialize P and G */ + _libssh2_bn_set_word(key_state->g, 2); + _libssh2_bn_from_bin(key_state->p, 256, p_value); + + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Initiating Diffie-Hellman Group14 Key Exchange"); + + key_state->state = libssh2_NB_state_created; + } + ret = diffie_hellman_sha1(session, key_state->g, key_state->p, + 256, SSH_MSG_KEXDH_INIT, SSH_MSG_KEXDH_REPLY, + NULL, 0, &key_state->exchange_state); + if (ret == LIBSSH2_ERROR_EAGAIN) { + return ret; + } + + key_state->state = libssh2_NB_state_idle; + _libssh2_bn_free(key_state->p); + key_state->p = NULL; + _libssh2_bn_free(key_state->g); + key_state->g = NULL; + + return ret; +} + + + +/* kex_method_diffie_hellman_group_exchange_sha1_key_exchange + * Diffie-Hellman Group Exchange Key Exchange using SHA1 + * Negotiates random(ish) group for secret derivation + */ +static int +kex_method_diffie_hellman_group_exchange_sha1_key_exchange +(LIBSSH2_SESSION * session, key_exchange_state_low_t * key_state) +{ + unsigned long p_len, g_len; + int ret = 0; + int rc; + + if (key_state->state == libssh2_NB_state_idle) { + key_state->p = _libssh2_bn_init(); + key_state->g = _libssh2_bn_init(); + /* Ask for a P and G pair */ +#ifdef LIBSSH2_DH_GEX_NEW + key_state->request[0] = SSH_MSG_KEX_DH_GEX_REQUEST; + _libssh2_htonu32(key_state->request + 1, LIBSSH2_DH_GEX_MINGROUP); + _libssh2_htonu32(key_state->request + 5, LIBSSH2_DH_GEX_OPTGROUP); + _libssh2_htonu32(key_state->request + 9, LIBSSH2_DH_GEX_MAXGROUP); + key_state->request_len = 13; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Initiating Diffie-Hellman Group-Exchange (New Method)"); +#else + key_state->request[0] = SSH_MSG_KEX_DH_GEX_REQUEST_OLD; + _libssh2_htonu32(key_state->request + 1, LIBSSH2_DH_GEX_OPTGROUP); + key_state->request_len = 5; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Initiating Diffie-Hellman Group-Exchange (Old Method)"); +#endif + + key_state->state = libssh2_NB_state_created; + } + + if (key_state->state == libssh2_NB_state_created) { + rc = _libssh2_transport_send(session, key_state->request, + key_state->request_len, NULL, 0); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } else if (rc) { + ret = _libssh2_error(session, rc, + "Unable to send Group Exchange Request"); + goto dh_gex_clean_exit; + } + + key_state->state = libssh2_NB_state_sent; + } + + if (key_state->state == libssh2_NB_state_sent) { + rc = _libssh2_packet_require(session, SSH_MSG_KEX_DH_GEX_GROUP, + &key_state->data, &key_state->data_len, + 0, NULL, 0, &key_state->req_state); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } else if (rc) { + ret = _libssh2_error(session, rc, + "Timeout waiting for GEX_GROUP reply"); + goto dh_gex_clean_exit; + } + + key_state->state = libssh2_NB_state_sent1; + } + + if (key_state->state == libssh2_NB_state_sent1) { + unsigned char *s = key_state->data + 1; + p_len = _libssh2_ntohu32(s); + s += 4; + _libssh2_bn_from_bin(key_state->p, p_len, s); + s += p_len; + + g_len = _libssh2_ntohu32(s); + s += 4; + _libssh2_bn_from_bin(key_state->g, g_len, s); + + ret = diffie_hellman_sha1(session, key_state->g, key_state->p, p_len, + SSH_MSG_KEX_DH_GEX_INIT, + SSH_MSG_KEX_DH_GEX_REPLY, + key_state->data + 1, + key_state->data_len - 1, + &key_state->exchange_state); + if (ret == LIBSSH2_ERROR_EAGAIN) { + return ret; + } + + LIBSSH2_FREE(session, key_state->data); + } + + dh_gex_clean_exit: + key_state->state = libssh2_NB_state_idle; + _libssh2_bn_free(key_state->g); + key_state->g = NULL; + _libssh2_bn_free(key_state->p); + key_state->p = NULL; + + return ret; +} + + + +#define LIBSSH2_KEX_METHOD_FLAG_REQ_ENC_HOSTKEY 0x0001 +#define LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY 0x0002 + +static const LIBSSH2_KEX_METHOD kex_method_diffie_helman_group1_sha1 = { + "diffie-hellman-group1-sha1", + kex_method_diffie_hellman_group1_sha1_key_exchange, + LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY, +}; + +static const LIBSSH2_KEX_METHOD kex_method_diffie_helman_group14_sha1 = { + "diffie-hellman-group14-sha1", + kex_method_diffie_hellman_group14_sha1_key_exchange, + LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY, +}; + +static const LIBSSH2_KEX_METHOD +kex_method_diffie_helman_group_exchange_sha1 = { + "diffie-hellman-group-exchange-sha1", + kex_method_diffie_hellman_group_exchange_sha1_key_exchange, + LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY, +}; + +static const LIBSSH2_KEX_METHOD *libssh2_kex_methods[] = { + &kex_method_diffie_helman_group14_sha1, + &kex_method_diffie_helman_group_exchange_sha1, + &kex_method_diffie_helman_group1_sha1, + NULL +}; + +typedef struct _LIBSSH2_COMMON_METHOD +{ + const char *name; +} LIBSSH2_COMMON_METHOD; + +/* kex_method_strlen + * Calculate the length of a particular method list's resulting string + * Includes SUM(strlen() of each individual method plus 1 (for coma)) - 1 (because the last coma isn't used) + * Another sign of bad coding practices gone mad. Pretend you don't see this. + */ +static size_t +kex_method_strlen(LIBSSH2_COMMON_METHOD ** method) +{ + size_t len = 0; + + if (!method || !*method) { + return 0; + } + + while (*method && (*method)->name) { + len += strlen((*method)->name) + 1; + method++; + } + + return len - 1; +} + + + +/* kex_method_list + * Generate formatted preference list in buf + */ +static size_t +kex_method_list(unsigned char *buf, size_t list_strlen, + LIBSSH2_COMMON_METHOD ** method) +{ + _libssh2_htonu32(buf, list_strlen); + buf += 4; + + if (!method || !*method) { + return 4; + } + + while (*method && (*method)->name) { + int mlen = strlen((*method)->name); + memcpy(buf, (*method)->name, mlen); + buf += mlen; + *(buf++) = ','; + method++; + } + + return list_strlen + 4; +} + + + +#define LIBSSH2_METHOD_PREFS_LEN(prefvar, defaultvar) \ + ((prefvar) ? strlen(prefvar) : \ + kex_method_strlen((LIBSSH2_COMMON_METHOD**)(defaultvar))) + +#define LIBSSH2_METHOD_PREFS_STR(buf, prefvarlen, prefvar, defaultvar) \ + if (prefvar) { \ + _libssh2_htonu32((buf), (prefvarlen)); \ + buf += 4; \ + memcpy((buf), (prefvar), (prefvarlen)); \ + buf += (prefvarlen); \ + } else { \ + buf += kex_method_list((buf), (prefvarlen), \ + (LIBSSH2_COMMON_METHOD**)(defaultvar)); \ + } + +/* kexinit + * Send SSH_MSG_KEXINIT packet + */ +static int kexinit(LIBSSH2_SESSION * session) +{ + /* 62 = packet_type(1) + cookie(16) + first_packet_follows(1) + + reserved(4) + length longs(40) */ + size_t data_len = 62; + size_t kex_len, hostkey_len = 0; + size_t crypt_cs_len, crypt_sc_len; + size_t comp_cs_len, comp_sc_len; + size_t mac_cs_len, mac_sc_len; + size_t lang_cs_len, lang_sc_len; + unsigned char *data, *s; + int rc; + + if (session->kexinit_state == libssh2_NB_state_idle) { + kex_len = + LIBSSH2_METHOD_PREFS_LEN(session->kex_prefs, libssh2_kex_methods); + hostkey_len = + LIBSSH2_METHOD_PREFS_LEN(session->hostkey_prefs, + libssh2_hostkey_methods()); + crypt_cs_len = + LIBSSH2_METHOD_PREFS_LEN(session->local.crypt_prefs, + libssh2_crypt_methods()); + crypt_sc_len = + LIBSSH2_METHOD_PREFS_LEN(session->remote.crypt_prefs, + libssh2_crypt_methods()); + mac_cs_len = + LIBSSH2_METHOD_PREFS_LEN(session->local.mac_prefs, + _libssh2_mac_methods()); + mac_sc_len = + LIBSSH2_METHOD_PREFS_LEN(session->remote.mac_prefs, + _libssh2_mac_methods()); + comp_cs_len = + LIBSSH2_METHOD_PREFS_LEN(session->local.comp_prefs, + _libssh2_comp_methods(session)); + comp_sc_len = + LIBSSH2_METHOD_PREFS_LEN(session->remote.comp_prefs, + _libssh2_comp_methods(session)); + lang_cs_len = + LIBSSH2_METHOD_PREFS_LEN(session->local.lang_prefs, NULL); + lang_sc_len = + LIBSSH2_METHOD_PREFS_LEN(session->remote.lang_prefs, NULL); + + data_len += kex_len + hostkey_len + crypt_cs_len + crypt_sc_len + + comp_cs_len + comp_sc_len + mac_cs_len + mac_sc_len + + lang_cs_len + lang_sc_len; + + s = data = LIBSSH2_ALLOC(session, data_len); + if (!data) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory"); + } + + *(s++) = SSH_MSG_KEXINIT; + + _libssh2_random(s, 16); + s += 16; + + /* Ennumerating through these lists twice is probably (certainly?) + inefficient from a CPU standpoint, but it saves multiple + malloc/realloc calls */ + LIBSSH2_METHOD_PREFS_STR(s, kex_len, session->kex_prefs, + libssh2_kex_methods); + LIBSSH2_METHOD_PREFS_STR(s, hostkey_len, session->hostkey_prefs, + libssh2_hostkey_methods()); + LIBSSH2_METHOD_PREFS_STR(s, crypt_cs_len, session->local.crypt_prefs, + libssh2_crypt_methods()); + LIBSSH2_METHOD_PREFS_STR(s, crypt_sc_len, session->remote.crypt_prefs, + libssh2_crypt_methods()); + LIBSSH2_METHOD_PREFS_STR(s, mac_cs_len, session->local.mac_prefs, + _libssh2_mac_methods()); + LIBSSH2_METHOD_PREFS_STR(s, mac_sc_len, session->remote.mac_prefs, + _libssh2_mac_methods()); + LIBSSH2_METHOD_PREFS_STR(s, comp_cs_len, session->local.comp_prefs, + _libssh2_comp_methods(session)); + LIBSSH2_METHOD_PREFS_STR(s, comp_sc_len, session->remote.comp_prefs, + _libssh2_comp_methods(session)); + LIBSSH2_METHOD_PREFS_STR(s, lang_cs_len, session->local.lang_prefs, + NULL); + LIBSSH2_METHOD_PREFS_STR(s, lang_sc_len, session->remote.lang_prefs, + NULL); + + /* No optimistic KEX packet follows */ + /* Deal with optimistic packets + * session->flags |= KEXINIT_OPTIMISTIC + * session->flags |= KEXINIT_METHODSMATCH + */ + *(s++) = 0; + + /* Reserved == 0 */ + _libssh2_htonu32(s, 0); + +#ifdef LIBSSH2DEBUG + { + /* Funnily enough, they'll all "appear" to be '\0' terminated */ + unsigned char *p = data + 21; /* type(1) + cookie(16) + len(4) */ + + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Sent KEX: %s", p); + p += kex_len + 4; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Sent HOSTKEY: %s", p); + p += hostkey_len + 4; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Sent CRYPT_CS: %s", p); + p += crypt_cs_len + 4; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Sent CRYPT_SC: %s", p); + p += crypt_sc_len + 4; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Sent MAC_CS: %s", p); + p += mac_cs_len + 4; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Sent MAC_SC: %s", p); + p += mac_sc_len + 4; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Sent COMP_CS: %s", p); + p += comp_cs_len + 4; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Sent COMP_SC: %s", p); + p += comp_sc_len + 4; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Sent LANG_CS: %s", p); + p += lang_cs_len + 4; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Sent LANG_SC: %s", p); + p += lang_sc_len + 4; + } +#endif /* LIBSSH2DEBUG */ + + session->kexinit_state = libssh2_NB_state_created; + } else { + data = session->kexinit_data; + data_len = session->kexinit_data_len; + /* zap the variables to ensure there is NOT a double free later */ + session->kexinit_data = NULL; + session->kexinit_data_len = 0; + } + + rc = _libssh2_transport_send(session, data, data_len, NULL, 0); + if (rc == LIBSSH2_ERROR_EAGAIN) { + session->kexinit_data = data; + session->kexinit_data_len = data_len; + return rc; + } + else if (rc) { + LIBSSH2_FREE(session, data); + session->kexinit_state = libssh2_NB_state_idle; + return _libssh2_error(session, rc, + "Unable to send KEXINIT packet to remote host"); + + } + + if (session->local.kexinit) { + LIBSSH2_FREE(session, session->local.kexinit); + } + + session->local.kexinit = data; + session->local.kexinit_len = data_len; + + session->kexinit_state = libssh2_NB_state_idle; + + return 0; +} + +/* kex_agree_instr + * Kex specific variant of strstr() + * Needle must be preceed by BOL or ',', and followed by ',' or EOL + */ +static unsigned char * +kex_agree_instr(unsigned char *haystack, unsigned long haystack_len, + const unsigned char *needle, unsigned long needle_len) +{ + unsigned char *s; + + /* Haystack too short to bother trying */ + if (haystack_len < needle_len) { + return NULL; + } + + /* Needle at start of haystack */ + if ((strncmp((char *) haystack, (char *) needle, needle_len) == 0) && + (needle_len == haystack_len || haystack[needle_len] == ',')) { + return haystack; + } + + s = haystack; + /* Search until we run out of comas or we run out of haystack, + whichever comes first */ + while ((s = (unsigned char *) strchr((char *) s, ',')) + && ((haystack_len - (s - haystack)) > needle_len)) { + s++; + /* Needle at X position */ + if ((strncmp((char *) s, (char *) needle, needle_len) == 0) && + (((s - haystack) + needle_len) == haystack_len + || s[needle_len] == ',')) { + return s; + } + } + + return NULL; +} + + + +/* kex_get_method_by_name + */ +static const LIBSSH2_COMMON_METHOD * +kex_get_method_by_name(const char *name, size_t name_len, + const LIBSSH2_COMMON_METHOD ** methodlist) +{ + while (*methodlist) { + if ((strlen((*methodlist)->name) == name_len) && + (strncmp((*methodlist)->name, name, name_len) == 0)) { + return *methodlist; + } + methodlist++; + } + return NULL; +} + + + +/* kex_agree_hostkey + * Agree on a Hostkey which works with this kex + */ +static int kex_agree_hostkey(LIBSSH2_SESSION * session, + unsigned long kex_flags, + unsigned char *hostkey, unsigned long hostkey_len) +{ + const LIBSSH2_HOSTKEY_METHOD **hostkeyp = libssh2_hostkey_methods(); + unsigned char *s; + + if (session->hostkey_prefs) { + s = (unsigned char *) session->hostkey_prefs; + + while (s && *s) { + unsigned char *p = (unsigned char *) strchr((char *) s, ','); + size_t method_len = (p ? (size_t)(p - s) : strlen((char *) s)); + if (kex_agree_instr(hostkey, hostkey_len, s, method_len)) { + const LIBSSH2_HOSTKEY_METHOD *method = + (const LIBSSH2_HOSTKEY_METHOD *) + kex_get_method_by_name((char *) s, method_len, + (const LIBSSH2_COMMON_METHOD **) + hostkeyp); + + if (!method) { + /* Invalid method -- Should never be reached */ + return -1; + } + + /* So far so good, but does it suit our purposes? (Encrypting + vs Signing) */ + if (((kex_flags & LIBSSH2_KEX_METHOD_FLAG_REQ_ENC_HOSTKEY) == + 0) || (method->encrypt)) { + /* Either this hostkey can do encryption or this kex just + doesn't require it */ + if (((kex_flags & LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY) + == 0) || (method->sig_verify)) { + /* Either this hostkey can do signing or this kex just + doesn't require it */ + session->hostkey = method; + return 0; + } + } + } + + s = p ? p + 1 : NULL; + } + return -1; + } + + while (hostkeyp && (*hostkeyp) && (*hostkeyp)->name) { + s = kex_agree_instr(hostkey, hostkey_len, + (unsigned char *) (*hostkeyp)->name, + strlen((*hostkeyp)->name)); + if (s) { + /* So far so good, but does it suit our purposes? (Encrypting vs + Signing) */ + if (((kex_flags & LIBSSH2_KEX_METHOD_FLAG_REQ_ENC_HOSTKEY) == 0) || + ((*hostkeyp)->encrypt)) { + /* Either this hostkey can do encryption or this kex just + doesn't require it */ + if (((kex_flags & LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY) == + 0) || ((*hostkeyp)->sig_verify)) { + /* Either this hostkey can do signing or this kex just + doesn't require it */ + session->hostkey = *hostkeyp; + return 0; + } + } + } + hostkeyp++; + } + + return -1; +} + + + +/* kex_agree_kex_hostkey + * Agree on a Key Exchange method and a hostkey encoding type + */ +static int kex_agree_kex_hostkey(LIBSSH2_SESSION * session, unsigned char *kex, + unsigned long kex_len, unsigned char *hostkey, + unsigned long hostkey_len) +{ + const LIBSSH2_KEX_METHOD **kexp = libssh2_kex_methods; + unsigned char *s; + + if (session->kex_prefs) { + s = (unsigned char *) session->kex_prefs; + + while (s && *s) { + unsigned char *q, *p = (unsigned char *) strchr((char *) s, ','); + size_t method_len = (p ? (size_t)(p - s) : strlen((char *) s)); + if ((q = kex_agree_instr(kex, kex_len, s, method_len))) { + const LIBSSH2_KEX_METHOD *method = (const LIBSSH2_KEX_METHOD *) + kex_get_method_by_name((char *) s, method_len, + (const LIBSSH2_COMMON_METHOD **) + kexp); + + if (!method) { + /* Invalid method -- Should never be reached */ + return -1; + } + + /* We've agreed on a key exchange method, + * Can we agree on a hostkey that works with this kex? + */ + if (kex_agree_hostkey(session, method->flags, hostkey, + hostkey_len) == 0) { + session->kex = method; + if (session->burn_optimistic_kexinit && (kex == q)) { + /* Server sent an optimistic packet, + * and client agrees with preference + * cancel burning the first KEX_INIT packet that comes in */ + session->burn_optimistic_kexinit = 0; + } + return 0; + } + } + + s = p ? p + 1 : NULL; + } + return -1; + } + + while (*kexp && (*kexp)->name) { + s = kex_agree_instr(kex, kex_len, + (unsigned char *) (*kexp)->name, + strlen((*kexp)->name)); + if (s) { + /* We've agreed on a key exchange method, + * Can we agree on a hostkey that works with this kex? + */ + if (kex_agree_hostkey(session, (*kexp)->flags, hostkey, + hostkey_len) == 0) { + session->kex = *kexp; + if (session->burn_optimistic_kexinit && (kex == s)) { + /* Server sent an optimistic packet, + * and client agrees with preference + * cancel burning the first KEX_INIT packet that comes in */ + session->burn_optimistic_kexinit = 0; + } + return 0; + } + } + kexp++; + } + return -1; +} + + + +/* kex_agree_crypt + * Agree on a cipher algo + */ +static int kex_agree_crypt(LIBSSH2_SESSION * session, + libssh2_endpoint_data *endpoint, + unsigned char *crypt, + unsigned long crypt_len) +{ + const LIBSSH2_CRYPT_METHOD **cryptp = libssh2_crypt_methods(); + unsigned char *s; + + (void) session; + + if (endpoint->crypt_prefs) { + s = (unsigned char *) endpoint->crypt_prefs; + + while (s && *s) { + unsigned char *p = (unsigned char *) strchr((char *) s, ','); + size_t method_len = (p ? (size_t)(p - s) : strlen((char *) s)); + + if (kex_agree_instr(crypt, crypt_len, s, method_len)) { + const LIBSSH2_CRYPT_METHOD *method = + (const LIBSSH2_CRYPT_METHOD *) + kex_get_method_by_name((char *) s, method_len, + (const LIBSSH2_COMMON_METHOD **) + cryptp); + + if (!method) { + /* Invalid method -- Should never be reached */ + return -1; + } + + endpoint->crypt = method; + return 0; + } + + s = p ? p + 1 : NULL; + } + return -1; + } + + while (*cryptp && (*cryptp)->name) { + s = kex_agree_instr(crypt, crypt_len, + (unsigned char *) (*cryptp)->name, + strlen((*cryptp)->name)); + if (s) { + endpoint->crypt = *cryptp; + return 0; + } + cryptp++; + } + + return -1; +} + + + +/* kex_agree_mac + * Agree on a message authentication hash + */ +static int kex_agree_mac(LIBSSH2_SESSION * session, + libssh2_endpoint_data * endpoint, unsigned char *mac, + unsigned long mac_len) +{ + const LIBSSH2_MAC_METHOD **macp = _libssh2_mac_methods(); + unsigned char *s; + (void) session; + + if (endpoint->mac_prefs) { + s = (unsigned char *) endpoint->mac_prefs; + + while (s && *s) { + unsigned char *p = (unsigned char *) strchr((char *) s, ','); + size_t method_len = (p ? (size_t)(p - s) : strlen((char *) s)); + + if (kex_agree_instr(mac, mac_len, s, method_len)) { + const LIBSSH2_MAC_METHOD *method = (const LIBSSH2_MAC_METHOD *) + kex_get_method_by_name((char *) s, method_len, + (const LIBSSH2_COMMON_METHOD **) + macp); + + if (!method) { + /* Invalid method -- Should never be reached */ + return -1; + } + + endpoint->mac = method; + return 0; + } + + s = p ? p + 1 : NULL; + } + return -1; + } + + while (*macp && (*macp)->name) { + s = kex_agree_instr(mac, mac_len, (unsigned char *) (*macp)->name, + strlen((*macp)->name)); + if (s) { + endpoint->mac = *macp; + return 0; + } + macp++; + } + + return -1; +} + + + +/* kex_agree_comp + * Agree on a compression scheme + */ +static int kex_agree_comp(LIBSSH2_SESSION *session, + libssh2_endpoint_data *endpoint, unsigned char *comp, + unsigned long comp_len) +{ + const LIBSSH2_COMP_METHOD **compp = _libssh2_comp_methods(session); + unsigned char *s; + (void) session; + + if (endpoint->comp_prefs) { + s = (unsigned char *) endpoint->comp_prefs; + + while (s && *s) { + unsigned char *p = (unsigned char *) strchr((char *) s, ','); + size_t method_len = (p ? (size_t)(p - s) : strlen((char *) s)); + + if (kex_agree_instr(comp, comp_len, s, method_len)) { + const LIBSSH2_COMP_METHOD *method = + (const LIBSSH2_COMP_METHOD *) + kex_get_method_by_name((char *) s, method_len, + (const LIBSSH2_COMMON_METHOD **) + compp); + + if (!method) { + /* Invalid method -- Should never be reached */ + return -1; + } + + endpoint->comp = method; + return 0; + } + + s = p ? p + 1 : NULL; + } + return -1; + } + + while (*compp && (*compp)->name) { + s = kex_agree_instr(comp, comp_len, (unsigned char *) (*compp)->name, + strlen((*compp)->name)); + if (s) { + endpoint->comp = *compp; + return 0; + } + compp++; + } + + return -1; +} + + + +/* TODO: When in server mode we need to turn this logic on its head + * The Client gets to make the final call on "agreed methods" + */ + +/* kex_agree_methods + * Decide which specific method to use of the methods offered by each party + */ +static int kex_agree_methods(LIBSSH2_SESSION * session, unsigned char *data, + unsigned data_len) +{ + unsigned char *kex, *hostkey, *crypt_cs, *crypt_sc, *comp_cs, *comp_sc, + *mac_cs, *mac_sc; + size_t kex_len, hostkey_len, crypt_cs_len, crypt_sc_len, comp_cs_len; + size_t comp_sc_len, mac_cs_len, mac_sc_len; + unsigned char *s = data; + + /* Skip packet_type, we know it already */ + s++; + + /* Skip cookie, don't worry, it's preserved in the kexinit field */ + s += 16; + + /* Locate each string */ + kex_len = _libssh2_ntohu32(s); + kex = s + 4; + s += 4 + kex_len; + hostkey_len = _libssh2_ntohu32(s); + hostkey = s + 4; + s += 4 + hostkey_len; + crypt_cs_len = _libssh2_ntohu32(s); + crypt_cs = s + 4; + s += 4 + crypt_cs_len; + crypt_sc_len = _libssh2_ntohu32(s); + crypt_sc = s + 4; + s += 4 + crypt_sc_len; + mac_cs_len = _libssh2_ntohu32(s); + mac_cs = s + 4; + s += 4 + mac_cs_len; + mac_sc_len = _libssh2_ntohu32(s); + mac_sc = s + 4; + s += 4 + mac_sc_len; + comp_cs_len = _libssh2_ntohu32(s); + comp_cs = s + 4; + s += 4 + comp_cs_len; + comp_sc_len = _libssh2_ntohu32(s); + comp_sc = s + 4; +#if 0 + s += 4 + comp_sc_len; + lang_cs_len = _libssh2_ntohu32(s); + lang_cs = s + 4; + s += 4 + lang_cs_len; + lang_sc_len = _libssh2_ntohu32(s); + lang_sc = s + 4; + s += 4 + lang_sc_len; +#endif + /* If the server sent an optimistic packet, assume that it guessed wrong. + * If the guess is determined to be right (by kex_agree_kex_hostkey) + * This flag will be reset to zero so that it's not ignored */ + session->burn_optimistic_kexinit = *(s++); + /* Next uint32 in packet is all zeros (reserved) */ + + if (data_len < (unsigned) (s - data)) + return -1; /* short packet */ + + if (kex_agree_kex_hostkey(session, kex, kex_len, hostkey, hostkey_len)) { + return -1; + } + + if (kex_agree_crypt(session, &session->local, crypt_cs, crypt_cs_len) + || kex_agree_crypt(session, &session->remote, crypt_sc, crypt_sc_len)) { + return -1; + } + + if (kex_agree_mac(session, &session->local, mac_cs, mac_cs_len) || + kex_agree_mac(session, &session->remote, mac_sc, mac_sc_len)) { + return -1; + } + + if (kex_agree_comp(session, &session->local, comp_cs, comp_cs_len) || + kex_agree_comp(session, &session->remote, comp_sc, comp_sc_len)) { + return -1; + } + +#if 0 + if (libssh2_kex_agree_lang(session, &session->local, lang_cs, lang_cs_len) + || libssh2_kex_agree_lang(session, &session->remote, lang_sc, + lang_sc_len)) { + return -1; + } +#endif + + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Agreed on KEX method: %s", + session->kex->name); + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Agreed on HOSTKEY method: %s", + session->hostkey->name); + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Agreed on CRYPT_CS method: %s", + session->local.crypt->name); + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Agreed on CRYPT_SC method: %s", + session->remote.crypt->name); + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Agreed on MAC_CS method: %s", + session->local.mac->name); + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Agreed on MAC_SC method: %s", + session->remote.mac->name); + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Agreed on COMP_CS method: %s", + session->local.comp->name); + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Agreed on COMP_SC method: %s", + session->remote.comp->name); + + return 0; +} + + + +/* _libssh2_kex_exchange + * Exchange keys + * Returns 0 on success, non-zero on failure + * + * Returns some errors without _libssh2_error() + */ +int +_libssh2_kex_exchange(LIBSSH2_SESSION * session, int reexchange, + key_exchange_state_t * key_state) +{ + int rc = 0; + int retcode; + + session->state |= LIBSSH2_STATE_KEX_ACTIVE; + + if (key_state->state == libssh2_NB_state_idle) { + /* Prevent loop in packet_add() */ + session->state |= LIBSSH2_STATE_EXCHANGING_KEYS; + + if (reexchange) { + session->kex = NULL; + + if (session->hostkey && session->hostkey->dtor) { + session->hostkey->dtor(session, + &session->server_hostkey_abstract); + } + session->hostkey = NULL; + } + + key_state->state = libssh2_NB_state_created; + } + + if (!session->kex || !session->hostkey) { + if (key_state->state == libssh2_NB_state_created) { + /* Preserve in case of failure */ + key_state->oldlocal = session->local.kexinit; + key_state->oldlocal_len = session->local.kexinit_len; + + session->local.kexinit = NULL; + + key_state->state = libssh2_NB_state_sent; + } + + if (key_state->state == libssh2_NB_state_sent) { + retcode = kexinit(session); + if (retcode == LIBSSH2_ERROR_EAGAIN) { + session->state &= ~LIBSSH2_STATE_KEX_ACTIVE; + return retcode; + } else if (retcode) { + session->local.kexinit = key_state->oldlocal; + session->local.kexinit_len = key_state->oldlocal_len; + key_state->state = libssh2_NB_state_idle; + session->state &= ~LIBSSH2_STATE_KEX_ACTIVE; + session->state &= ~LIBSSH2_STATE_EXCHANGING_KEYS; + return -1; + } + + key_state->state = libssh2_NB_state_sent1; + } + + if (key_state->state == libssh2_NB_state_sent1) { + retcode = + _libssh2_packet_require(session, SSH_MSG_KEXINIT, + &key_state->data, + &key_state->data_len, 0, NULL, 0, + &key_state->req_state); + if (retcode == LIBSSH2_ERROR_EAGAIN) { + session->state &= ~LIBSSH2_STATE_KEX_ACTIVE; + return retcode; + } + else if (retcode) { + if (session->local.kexinit) { + LIBSSH2_FREE(session, session->local.kexinit); + } + session->local.kexinit = key_state->oldlocal; + session->local.kexinit_len = key_state->oldlocal_len; + key_state->state = libssh2_NB_state_idle; + session->state &= ~LIBSSH2_STATE_KEX_ACTIVE; + session->state &= ~LIBSSH2_STATE_EXCHANGING_KEYS; + return -1; + } + + if (session->remote.kexinit) { + LIBSSH2_FREE(session, session->remote.kexinit); + } + session->remote.kexinit = key_state->data; + session->remote.kexinit_len = key_state->data_len; + + if (kex_agree_methods(session, key_state->data, + key_state->data_len)) + rc = LIBSSH2_ERROR_KEX_FAILURE; + + key_state->state = libssh2_NB_state_sent2; + } + } else { + key_state->state = libssh2_NB_state_sent2; + } + + if (rc == 0) { + if (key_state->state == libssh2_NB_state_sent2) { + retcode = session->kex->exchange_keys(session, + &key_state->key_state_low); + if (retcode == LIBSSH2_ERROR_EAGAIN) { + session->state &= ~LIBSSH2_STATE_KEX_ACTIVE; + return retcode; + } else if (retcode) { + rc = _libssh2_error(session, LIBSSH2_ERROR_KEY_EXCHANGE_FAILURE, + "Unrecoverable error exchanging keys"); + } + } + } + + /* Done with kexinit buffers */ + if (session->local.kexinit) { + LIBSSH2_FREE(session, session->local.kexinit); + session->local.kexinit = NULL; + } + if (session->remote.kexinit) { + LIBSSH2_FREE(session, session->remote.kexinit); + session->remote.kexinit = NULL; + } + + session->state &= ~LIBSSH2_STATE_KEX_ACTIVE; + session->state &= ~LIBSSH2_STATE_EXCHANGING_KEYS; + + key_state->state = libssh2_NB_state_idle; + + return rc; +} + + + +/* libssh2_session_method_pref + * Set preferred method + */ +LIBSSH2_API int +libssh2_session_method_pref(LIBSSH2_SESSION * session, int method_type, + const char *prefs) +{ + char **prefvar, *s, *newprefs; + int prefs_len = strlen(prefs); + const LIBSSH2_COMMON_METHOD **mlist; + + switch (method_type) { + case LIBSSH2_METHOD_KEX: + prefvar = &session->kex_prefs; + mlist = (const LIBSSH2_COMMON_METHOD **) libssh2_kex_methods; + break; + + case LIBSSH2_METHOD_HOSTKEY: + prefvar = &session->hostkey_prefs; + mlist = (const LIBSSH2_COMMON_METHOD **) libssh2_hostkey_methods(); + break; + + case LIBSSH2_METHOD_CRYPT_CS: + prefvar = &session->local.crypt_prefs; + mlist = (const LIBSSH2_COMMON_METHOD **) libssh2_crypt_methods(); + break; + + case LIBSSH2_METHOD_CRYPT_SC: + prefvar = &session->remote.crypt_prefs; + mlist = (const LIBSSH2_COMMON_METHOD **) libssh2_crypt_methods(); + break; + + case LIBSSH2_METHOD_MAC_CS: + prefvar = &session->local.mac_prefs; + mlist = (const LIBSSH2_COMMON_METHOD **) _libssh2_mac_methods(); + break; + + case LIBSSH2_METHOD_MAC_SC: + prefvar = &session->remote.mac_prefs; + mlist = (const LIBSSH2_COMMON_METHOD **) _libssh2_mac_methods(); + break; + + case LIBSSH2_METHOD_COMP_CS: + prefvar = &session->local.comp_prefs; + mlist = (const LIBSSH2_COMMON_METHOD **) + _libssh2_comp_methods(session); + break; + + case LIBSSH2_METHOD_COMP_SC: + prefvar = &session->remote.comp_prefs; + mlist = (const LIBSSH2_COMMON_METHOD **) + _libssh2_comp_methods(session); + break; + + case LIBSSH2_METHOD_LANG_CS: + prefvar = &session->local.lang_prefs; + mlist = NULL; + break; + + case LIBSSH2_METHOD_LANG_SC: + prefvar = &session->remote.lang_prefs; + mlist = NULL; + break; + + default: + return _libssh2_error(session, LIBSSH2_ERROR_INVAL, + "Invalid parameter specified for method_type"); + } + + s = newprefs = LIBSSH2_ALLOC(session, prefs_len + 1); + if (!newprefs) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Error allocated space for method preferences"); + } + memcpy(s, prefs, prefs_len + 1); + + while (s && *s) { + char *p = strchr(s, ','); + int method_len = p ? (p - s) : (int) strlen(s); + + if (!kex_get_method_by_name(s, method_len, mlist)) { + /* Strip out unsupported method */ + if (p) { + memcpy(s, p + 1, strlen(s) - method_len); + } else { + if (s > newprefs) { + *(--s) = '\0'; + } else { + *s = '\0'; + } + } + } + + s = p ? (p + 1) : NULL; + } + + if (strlen(newprefs) == 0) { + LIBSSH2_FREE(session, newprefs); + return _libssh2_error(session, LIBSSH2_ERROR_METHOD_NOT_SUPPORTED, + "The requested method(s) are not currently " + "supported"); + } + + if (*prefvar) { + LIBSSH2_FREE(session, *prefvar); + } + *prefvar = newprefs; + + return 0; +} + +/* + * libssh2_session_supported_algs() + * returns a number of returned algorithms (a positive number) on success, + * a negative number on failure + */ + +LIBSSH2_API int libssh2_session_supported_algs(LIBSSH2_SESSION* session, + int method_type, + const char*** algs) +{ + unsigned int i; + unsigned int j; + unsigned int ialg; + const LIBSSH2_COMMON_METHOD **mlist; + + /* to prevent coredumps due to dereferencing of NULL */ + if (NULL == algs) + return _libssh2_error(session, LIBSSH2_ERROR_BAD_USE, + "algs must not be NULL"); + + switch (method_type) { + case LIBSSH2_METHOD_KEX: + mlist = (const LIBSSH2_COMMON_METHOD **) libssh2_kex_methods; + break; + + case LIBSSH2_METHOD_HOSTKEY: + mlist = (const LIBSSH2_COMMON_METHOD **) libssh2_hostkey_methods(); + break; + + case LIBSSH2_METHOD_CRYPT_CS: + case LIBSSH2_METHOD_CRYPT_SC: + mlist = (const LIBSSH2_COMMON_METHOD **) libssh2_crypt_methods(); + break; + + case LIBSSH2_METHOD_MAC_CS: + case LIBSSH2_METHOD_MAC_SC: + mlist = (const LIBSSH2_COMMON_METHOD **) _libssh2_mac_methods(); + break; + + case LIBSSH2_METHOD_COMP_CS: + case LIBSSH2_METHOD_COMP_SC: + mlist = (const LIBSSH2_COMMON_METHOD **) _libssh2_comp_methods(session); + break; + + default: + return _libssh2_error(session, LIBSSH2_ERROR_METHOD_NOT_SUPPORTED, + "Unknown method type"); + } /* switch */ + + /* weird situation */ + if (NULL==mlist) + return _libssh2_error(session, LIBSSH2_ERROR_INVAL, + "No algorithm found"); + + /* + mlist is looped through twice. The first time to find the number od + supported algorithms (needed to allocate the proper size of array) and + the second time to actually copy the pointers. Typically this function + will not be called often (typically at the beginning of a session) and + the number of algorithms (i.e. niumber of iterations in one loop) will + not be high (typically it will not exceed 20) for quite a long time. + + So double looping really shouldn't be an issue and it is definitely a + better solution than reallocation several times. + */ + + /* count the number of supported algorithms */ + for ( i=0, ialg=0; NULL!=mlist[i]; i++) { + /* do not count fields with NULL name */ + if (mlist[i]->name) + ialg++; + } + + /* weird situation, no algorithm found */ + if (0==ialg) + return _libssh2_error(session, LIBSSH2_ERROR_INVAL, + "No algorithm found"); + + /* allocate buffer */ + *algs = (const char**) LIBSSH2_ALLOC(session, ialg*sizeof(const char*)); + if ( NULL==*algs ) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Memory allocation failed"); + } + /* Past this point *algs must be deallocated in case of an error!! */ + + /* copy non-NULL pointers only */ + for ( i=0, j=0; NULL!=mlist[i] && jname ){ + /* maybe a weird situation but if it occurs, do not include NULL + pointers */ + continue; + } + + /* note that [] has higher priority than * (dereferencing) */ + (*algs)[j++] = mlist[i]->name; + } + + /* correct number of pointers copied? (test the code above) */ + if ( j!=ialg ) { + /* deallocate buffer */ + LIBSSH2_FREE(session, (void *)*algs); + *algs = NULL; + + return _libssh2_error(session, LIBSSH2_ERROR_BAD_USE, + "Internal error"); + } + + return ialg; +} diff --git a/libssh2/src/knownhost.c b/libssh2/src/knownhost.c new file mode 100644 index 0000000..1087bc2 --- /dev/null +++ b/libssh2/src/knownhost.c @@ -0,0 +1,1149 @@ +/* + * Copyright (c) 2009-2011 by Daniel Stenberg + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include "libssh2_priv.h" +#include "misc.h" + +struct known_host { + struct list_node node; + char *name; /* points to the name or the hash (allocated) */ + size_t name_len; /* needed for hashed data */ + int port; /* if non-zero, a specific port this key is for on this + host */ + int typemask; /* plain, sha1, custom, ... */ + char *salt; /* points to binary salt (allocated) */ + size_t salt_len; /* size of salt */ + char *key; /* the (allocated) associated key. This is kept base64 + encoded in memory. */ + char *comment; /* the (allocated) optional comment text, may be NULL */ + + /* this is the struct we expose externally */ + struct libssh2_knownhost external; +}; + +struct _LIBSSH2_KNOWNHOSTS +{ + LIBSSH2_SESSION *session; /* the session this "belongs to" */ + struct list_head head; +}; + +static void free_host(LIBSSH2_SESSION *session, struct known_host *entry) +{ + if(entry) { + if(entry->comment) + LIBSSH2_FREE(session, entry->comment); + if(entry->key) + LIBSSH2_FREE(session, entry->key); + if(entry->salt) + LIBSSH2_FREE(session, entry->salt); + if(entry->name) + LIBSSH2_FREE(session, entry->name); + LIBSSH2_FREE(session, entry); + } +} + +/* + * libssh2_knownhost_init + * + * Init a collection of known hosts. Returns the pointer to a collection. + * + */ +LIBSSH2_API LIBSSH2_KNOWNHOSTS * +libssh2_knownhost_init(LIBSSH2_SESSION *session) +{ + LIBSSH2_KNOWNHOSTS *knh = + LIBSSH2_ALLOC(session, sizeof(struct _LIBSSH2_KNOWNHOSTS)); + + if(!knh) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for known-hosts " + "collection"); + return NULL; + } + + knh->session = session; + + _libssh2_list_init(&knh->head); + + return knh; +} + +#define KNOWNHOST_MAGIC 0xdeadcafe +/* + * knownhost_to_external() + * + * Copies data from the internal to the external representation struct. + * + */ +static struct libssh2_knownhost *knownhost_to_external(struct known_host *node) +{ + struct libssh2_knownhost *ext = &node->external; + + ext->magic = KNOWNHOST_MAGIC; + ext->node = node; + ext->name = ((node->typemask & LIBSSH2_KNOWNHOST_TYPE_MASK) == + LIBSSH2_KNOWNHOST_TYPE_PLAIN)? node->name:NULL; + ext->key = node->key; + ext->typemask = node->typemask; + + return ext; +} + +static int +knownhost_add(LIBSSH2_KNOWNHOSTS *hosts, + const char *host, const char *salt, + const char *key, size_t keylen, + const char *comment, size_t commentlen, + int typemask, struct libssh2_knownhost **store) +{ + struct known_host *entry; + size_t hostlen = strlen(host); + int rc; + char *ptr; + unsigned int ptrlen; + + /* make sure we have a key type set */ + if(!(typemask & LIBSSH2_KNOWNHOST_KEY_MASK)) + return _libssh2_error(hosts->session, LIBSSH2_ERROR_INVAL, + "No key type set"); + + if(!(entry = LIBSSH2_ALLOC(hosts->session, sizeof(struct known_host)))) + return _libssh2_error(hosts->session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for known host " + "entry"); + + memset(entry, 0, sizeof(struct known_host)); + + entry->typemask = typemask; + + switch(entry->typemask & LIBSSH2_KNOWNHOST_TYPE_MASK) { + case LIBSSH2_KNOWNHOST_TYPE_PLAIN: + case LIBSSH2_KNOWNHOST_TYPE_CUSTOM: + entry->name = LIBSSH2_ALLOC(hosts->session, hostlen+1); + if(!entry->name) { + rc = _libssh2_error(hosts->session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for host name"); + goto error; + } + memcpy(entry->name, host, hostlen+1); + break; + case LIBSSH2_KNOWNHOST_TYPE_SHA1: + rc = libssh2_base64_decode(hosts->session, &ptr, &ptrlen, + host, hostlen); + if(rc) + goto error; + entry->name = ptr; + entry->name_len = ptrlen; + + rc = libssh2_base64_decode(hosts->session, &ptr, &ptrlen, + salt, strlen(salt)); + if(rc) + goto error; + entry->salt = ptr; + entry->salt_len = ptrlen; + break; + default: + rc = _libssh2_error(hosts->session, LIBSSH2_ERROR_METHOD_NOT_SUPPORTED, + "Unknown host name type"); + goto error; + } + + if(typemask & LIBSSH2_KNOWNHOST_KEYENC_BASE64) { + /* the provided key is base64 encoded already */ + if(!keylen) + keylen = strlen(key); + entry->key = LIBSSH2_ALLOC(hosts->session, keylen+1); + if(!entry->key) { + rc = _libssh2_error(hosts->session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for key"); + goto error; + } + memcpy(entry->key, key, keylen+1); + entry->key[keylen]=0; /* force a terminating zero trailer */ + } + else { + /* key is raw, we base64 encode it and store it as such */ + size_t nlen = _libssh2_base64_encode(hosts->session, key, keylen, + &ptr); + if(!nlen) { + rc = _libssh2_error(hosts->session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for " + "base64-encoded key"); + goto error; + } + + entry->key = ptr; + } + + if (comment) { + entry->comment = LIBSSH2_ALLOC(hosts->session, commentlen+1); + if(!entry->comment) { + rc = _libssh2_error(hosts->session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for comment"); + goto error; + } + memcpy(entry->comment, comment, commentlen+1); + entry->comment[commentlen]=0; /* force a terminating zero trailer */ + } + else { + entry->comment = NULL; + } + + /* add this new host to the big list of known hosts */ + _libssh2_list_add(&hosts->head, &entry->node); + + if(store) + *store = knownhost_to_external(entry); + + return LIBSSH2_ERROR_NONE; + error: + free_host(hosts->session, entry); + return rc; +} + +/* + * libssh2_knownhost_add + * + * Add a host and its associated key to the collection of known hosts. + * + * The 'type' argument specifies on what format the given host and keys are: + * + * plain - ascii "hostname.domain.tld" + * sha1 - SHA1( ) base64-encoded! + * custom - another hash + * + * If 'sha1' is selected as type, the salt must be provided to the salt + * argument. This too base64 encoded. + * + * The SHA-1 hash is what OpenSSH can be told to use in known_hosts files. If + * a custom type is used, salt is ignored and you must provide the host + * pre-hashed when checking for it in the libssh2_knownhost_check() function. + * + * The keylen parameter may be omitted (zero) if the key is provided as a + * NULL-terminated base64-encoded string. + */ + +LIBSSH2_API int +libssh2_knownhost_add(LIBSSH2_KNOWNHOSTS *hosts, + const char *host, const char *salt, + const char *key, size_t keylen, + int typemask, struct libssh2_knownhost **store) +{ + return knownhost_add(hosts, host, salt, key, keylen, NULL, 0, typemask, + store); +} + + +/* + * libssh2_knownhost_addc + * + * Add a host and its associated key to the collection of known hosts. + * + * Takes a comment argument that may be NULL. A NULL comment indicates + * there is no comment and the entry will end directly after the key + * when written out to a file. An empty string "" comment will indicate an + * empty comment which will cause a single space to be written after the key. + * + * The 'type' argument specifies on what format the given host and keys are: + * + * plain - ascii "hostname.domain.tld" + * sha1 - SHA1( ) base64-encoded! + * custom - another hash + * + * If 'sha1' is selected as type, the salt must be provided to the salt + * argument. This too base64 encoded. + * + * The SHA-1 hash is what OpenSSH can be told to use in known_hosts files. If + * a custom type is used, salt is ignored and you must provide the host + * pre-hashed when checking for it in the libssh2_knownhost_check() function. + * + * The keylen parameter may be omitted (zero) if the key is provided as a + * NULL-terminated base64-encoded string. + */ + +LIBSSH2_API int +libssh2_knownhost_addc(LIBSSH2_KNOWNHOSTS *hosts, + const char *host, const char *salt, + const char *key, size_t keylen, + const char *comment, size_t commentlen, + int typemask, struct libssh2_knownhost **store) +{ + return knownhost_add(hosts, host, salt, key, keylen, comment, commentlen, + typemask, store); +} + +/* + * knownhost_check + * + * Check a host and its associated key against the collection of known hosts. + * + * The typemask is the type/format of the given host name and key + * + * plain - ascii "hostname.domain.tld" + * sha1 - NOT SUPPORTED AS INPUT + * custom - prehashed base64 encoded. Note that this cannot use any salts. + * + * Returns: + * + * LIBSSH2_KNOWNHOST_CHECK_FAILURE + * LIBSSH2_KNOWNHOST_CHECK_NOTFOUND + * LIBSSH2_KNOWNHOST_CHECK_MATCH + * LIBSSH2_KNOWNHOST_CHECK_MISMATCH + */ +static int +knownhost_check(LIBSSH2_KNOWNHOSTS *hosts, + const char *hostp, int port, + const char *key, size_t keylen, + int typemask, + struct libssh2_knownhost **ext) +{ + struct known_host *node; + struct known_host *badkey = NULL; + int type = typemask & LIBSSH2_KNOWNHOST_TYPE_MASK; + char *keyalloc = NULL; + int rc = LIBSSH2_KNOWNHOST_CHECK_NOTFOUND; + char hostbuff[270]; /* most host names can't be longer than like 256 */ + const char *host; + int numcheck; /* number of host combos to check */ + int match = 0; + + if(type == LIBSSH2_KNOWNHOST_TYPE_SHA1) + /* we can't work with a sha1 as given input */ + return LIBSSH2_KNOWNHOST_CHECK_MISMATCH; + + if(!(typemask & LIBSSH2_KNOWNHOST_KEYENC_BASE64)) { + /* we got a raw key input, convert it to base64 for the checks below */ + size_t nlen = _libssh2_base64_encode(hosts->session, key, keylen, + &keyalloc); + if(!nlen) { + _libssh2_error(hosts->session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for base64-encoded " + "key"); + return LIBSSH2_KNOWNHOST_CHECK_FAILURE; + } + + /* make the key point to this */ + key = keyalloc; + } + + /* if a port number is given, check for a '[host]:port' first before the + plain 'host' */ + if(port >= 0) { + snprintf(hostbuff, sizeof(hostbuff), "[%s]:%d", hostp, port); + host = hostbuff; + numcheck = 2; /* check both combos, start with this */ + } + else { + host = hostp; + numcheck = 1; /* only check this host version */ + } + + do { + node = _libssh2_list_first(&hosts->head); + while (node) { + switch(node->typemask & LIBSSH2_KNOWNHOST_TYPE_MASK) { + case LIBSSH2_KNOWNHOST_TYPE_PLAIN: + if(type == LIBSSH2_KNOWNHOST_TYPE_PLAIN) + match = !strcmp(host, node->name); + break; + case LIBSSH2_KNOWNHOST_TYPE_CUSTOM: + if(type == LIBSSH2_KNOWNHOST_TYPE_CUSTOM) + match = !strcmp(host, node->name); + break; + case LIBSSH2_KNOWNHOST_TYPE_SHA1: + if(type == LIBSSH2_KNOWNHOST_TYPE_PLAIN) { + /* when we have the sha1 version stored, we can use a + plain input to produce a hash to compare with the + stored hash. + */ + libssh2_hmac_ctx ctx; + unsigned char hash[SHA_DIGEST_LENGTH]; + + if(SHA_DIGEST_LENGTH != node->name_len) { + /* the name hash length must be the sha1 size or + we can't match it */ + break; + } + libssh2_hmac_sha1_init(&ctx, node->salt, node->salt_len); + libssh2_hmac_update(ctx, (unsigned char *)host, + strlen(host)); + libssh2_hmac_final(ctx, hash); + libssh2_hmac_cleanup(&ctx); + + if(!memcmp(hash, node->name, SHA_DIGEST_LENGTH)) + /* this is a node we're interested in */ + match = 1; + } + break; + default: /* unsupported type */ + break; + } + if(match) { + /* host name match, now compare the keys */ + if(!strcmp(key, node->key)) { + /* they match! */ + if (ext) + *ext = knownhost_to_external(node); + badkey = NULL; + rc = LIBSSH2_KNOWNHOST_CHECK_MATCH; + break; + } + else { + /* remember the first node that had a host match but a + failed key match since we continue our search from + here */ + if(!badkey) + badkey = node; + match = 0; /* don't count this as a match anymore */ + } + } + node= _libssh2_list_next(&node->node); + } + host = hostp; + } while(!match && --numcheck); + + if(badkey) { + /* key mismatch */ + if (ext) + *ext = knownhost_to_external(badkey); + rc = LIBSSH2_KNOWNHOST_CHECK_MISMATCH; + } + + if(keyalloc) + LIBSSH2_FREE(hosts->session, keyalloc); + + return rc; +} + +/* + * libssh2_knownhost_check + * + * Check a host and its associated key against the collection of known hosts. + * + * The typemask is the type/format of the given host name and key + * + * plain - ascii "hostname.domain.tld" + * sha1 - NOT SUPPORTED AS INPUT + * custom - prehashed base64 encoded. Note that this cannot use any salts. + * + * Returns: + * + * LIBSSH2_KNOWNHOST_CHECK_FAILURE + * LIBSSH2_KNOWNHOST_CHECK_NOTFOUND + * LIBSSH2_KNOWNHOST_CHECK_MATCH + * LIBSSH2_KNOWNHOST_CHECK_MISMATCH + */ +LIBSSH2_API int +libssh2_knownhost_check(LIBSSH2_KNOWNHOSTS *hosts, + const char *hostp, const char *key, size_t keylen, + int typemask, + struct libssh2_knownhost **ext) +{ + return knownhost_check(hosts, hostp, -1, key, keylen, + typemask, ext); +} + +/* + * libssh2_knownhost_checkp + * + * Check a host+port and its associated key against the collection of known + * hosts. + * + * Note that if 'port' is specified as greater than zero, the check function + * will be able to check for a dedicated key for this particular host+port + * combo, and if 'port' is negative it only checks for the generic host key. + * + * The typemask is the type/format of the given host name and key + * + * plain - ascii "hostname.domain.tld" + * sha1 - NOT SUPPORTED AS INPUT + * custom - prehashed base64 encoded. Note that this cannot use any salts. + * + * Returns: + * + * LIBSSH2_KNOWNHOST_CHECK_FAILURE + * LIBSSH2_KNOWNHOST_CHECK_NOTFOUND + * LIBSSH2_KNOWNHOST_CHECK_MATCH + * LIBSSH2_KNOWNHOST_CHECK_MISMATCH + */ +LIBSSH2_API int +libssh2_knownhost_checkp(LIBSSH2_KNOWNHOSTS *hosts, + const char *hostp, int port, + const char *key, size_t keylen, + int typemask, + struct libssh2_knownhost **ext) +{ + return knownhost_check(hosts, hostp, port, key, keylen, + typemask, ext); +} + + +/* + * libssh2_knownhost_del + * + * Remove a host from the collection of known hosts. + * + */ +LIBSSH2_API int +libssh2_knownhost_del(LIBSSH2_KNOWNHOSTS *hosts, + struct libssh2_knownhost *entry) +{ + struct known_host *node; + + /* check that this was retrieved the right way or get out */ + if(!entry || (entry->magic != KNOWNHOST_MAGIC)) + return _libssh2_error(hosts->session, LIBSSH2_ERROR_INVAL, + "Invalid host information"); + + /* get the internal node pointer */ + node = entry->node; + + /* unlink from the list of all hosts */ + _libssh2_list_remove(&node->node); + + /* clear the struct now since the memory in which it is allocated is + about to be freed! */ + memset(entry, 0, sizeof(struct libssh2_knownhost)); + + /* free all resources */ + free_host(hosts->session, node); + + return 0; +} + +/* + * libssh2_knownhost_free + * + * Free an entire collection of known hosts. + * + */ +LIBSSH2_API void +libssh2_knownhost_free(LIBSSH2_KNOWNHOSTS *hosts) +{ + struct known_host *node; + struct known_host *next; + + for(node = _libssh2_list_first(&hosts->head); node; node = next) { + next = _libssh2_list_next(&node->node); + free_host(hosts->session, node); + } + LIBSSH2_FREE(hosts->session, hosts); +} + + +/* old style plain text: [name]([,][name])* + + for the sake of simplicity, we add them as separate hosts with the same + key +*/ +static int oldstyle_hostline(LIBSSH2_KNOWNHOSTS *hosts, + const char *host, size_t hostlen, + const char *key, size_t keylen, int key_type, + const char *comment, size_t commentlen) +{ + int rc = 0; + size_t namelen = 0; + const char *name = host + hostlen; + + if(hostlen < 1) + return _libssh2_error(hosts->session, + LIBSSH2_ERROR_METHOD_NOT_SUPPORTED, + "Failed to parse known_hosts line " + "(no host names)"); + + while(name > host) { + --name; + ++namelen; + + /* when we get the the start or see a comma coming up, add the host + name to the collection */ + if((name == host) || (*(name-1) == ',')) { + + char hostbuf[256]; + + /* make sure we don't overflow the buffer */ + if(namelen >= sizeof(hostbuf)-1) + return _libssh2_error(hosts->session, + LIBSSH2_ERROR_METHOD_NOT_SUPPORTED, + "Failed to parse known_hosts line " + "(unexpected length)"); + + /* copy host name to the temp buffer and zero terminate */ + memcpy(hostbuf, name, namelen); + hostbuf[namelen]=0; + + rc = knownhost_add(hosts, hostbuf, NULL, key, keylen, + comment, commentlen, + key_type | LIBSSH2_KNOWNHOST_TYPE_PLAIN | + LIBSSH2_KNOWNHOST_KEYENC_BASE64, NULL); + if(rc) + return rc; + + if(name > host) { + namelen = 0; + --name; /* skip comma */ + } + } + } + + return rc; +} + +/* |1|[salt]|[hash] */ +static int hashed_hostline(LIBSSH2_KNOWNHOSTS *hosts, + const char *host, size_t hostlen, + const char *key, size_t keylen, int key_type, + const char *comment, size_t commentlen) +{ + const char *p; + char saltbuf[32]; + char hostbuf[256]; + + const char *salt = &host[3]; /* skip the magic marker */ + hostlen -= 3; /* deduct the marker */ + + /* this is where the salt starts, find the end of it */ + for(p = salt; *p && (*p != '|'); p++) + ; + + if(*p=='|') { + const char *hash = NULL; + size_t saltlen = p - salt; + if(saltlen >= (sizeof(saltbuf)-1)) /* weird length */ + return _libssh2_error(hosts->session, + LIBSSH2_ERROR_METHOD_NOT_SUPPORTED, + "Failed to parse known_hosts line " + "(unexpectedly long salt)"); + + memcpy(saltbuf, salt, saltlen); + saltbuf[saltlen] = 0; /* zero terminate */ + salt = saltbuf; /* point to the stack based buffer */ + + hash = p+1; /* the host hash is after the separator */ + + /* now make the host point to the hash */ + host = hash; + hostlen -= saltlen+1; /* deduct the salt and separator */ + + /* check that the lengths seem sensible */ + if(hostlen >= sizeof(hostbuf)-1) + return _libssh2_error(hosts->session, + LIBSSH2_ERROR_METHOD_NOT_SUPPORTED, + "Failed to parse known_hosts line " + "(unexpected length)"); + + memcpy(hostbuf, host, hostlen); + hostbuf[hostlen]=0; + + return knownhost_add(hosts, hostbuf, salt, key, keylen, comment, + commentlen, + key_type | LIBSSH2_KNOWNHOST_TYPE_SHA1 | + LIBSSH2_KNOWNHOST_KEYENC_BASE64, NULL); + } + else + return 0; /* XXX: This should be an error, shouldn't it? */ +} + +/* + * hostline() + * + * Parse a single known_host line pre-split into host and key. + * + * The key part may include an optional comment which will be parsed here + * for ssh-rsa and ssh-dsa keys. Comments in other key types aren't handled. + * + * The function assumes new-lines have already been removed from the arguments. + */ +static int hostline(LIBSSH2_KNOWNHOSTS *hosts, + const char *host, size_t hostlen, + const char *key, size_t keylen) +{ + const char *comment = NULL; + size_t commentlen = 0; + int key_type; + + /* make some checks that the lengths seem sensible */ + if(keylen < 20) + return _libssh2_error(hosts->session, + LIBSSH2_ERROR_METHOD_NOT_SUPPORTED, + "Failed to parse known_hosts line " + "(key too short)"); + + switch(key[0]) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + key_type = LIBSSH2_KNOWNHOST_KEY_RSA1; + + /* Note that the old-style keys (RSA1) aren't truly base64, but we + * claim it is for now since we can get away with strcmp()ing the + * entire anything anyway! We need to check and fix these to make them + * work properly. + */ + break; + + case 's': /* ssh-dss or ssh-rsa */ + if(!strncmp(key, "ssh-dss", 7)) + key_type = LIBSSH2_KNOWNHOST_KEY_SSHDSS; + else if(!strncmp(key, "ssh-rsa", 7)) + key_type = LIBSSH2_KNOWNHOST_KEY_SSHRSA; + else + /* unknown key type */ + return _libssh2_error(hosts->session, + LIBSSH2_ERROR_METHOD_NOT_SUPPORTED, + "Unknown key type"); + + key += 7; + keylen -= 7; + + /* skip whitespaces */ + while((*key ==' ') || (*key == '\t')) { + key++; + keylen--; + } + + comment = key; + commentlen = keylen; + + /* move over key */ + while(commentlen && *comment && + (*comment != ' ') && (*comment != '\t')) { + comment++; + commentlen--; + } + + /* reduce key by comment length */ + keylen -= commentlen; + + /* Distinguish empty comment (a space) from no comment (no space) */ + if (commentlen == 0) + comment = NULL; + + /* skip whitespaces */ + while(commentlen && *comment && + ((*comment ==' ') || (*comment == '\t'))) { + comment++; + commentlen--; + } + break; + + default: /* unknown key format */ + return _libssh2_error(hosts->session, + LIBSSH2_ERROR_METHOD_NOT_SUPPORTED, + "Unknown key format"); + } + + /* Figure out host format */ + if((hostlen >2) && memcmp(host, "|1|", 3)) { + /* old style plain text: [name]([,][name])* + + for the sake of simplicity, we add them as separate hosts with the + same key + */ + return oldstyle_hostline(hosts, host, hostlen, key, keylen, key_type, + comment, commentlen); + } + else { + /* |1|[salt]|[hash] */ + return hashed_hostline(hosts, host, hostlen, key, keylen, key_type, + comment, commentlen); + } +} + +/* + * libssh2_knownhost_readline() + * + * Pass in a line of a file of 'type'. + * + * LIBSSH2_KNOWNHOST_FILE_OPENSSH is the only supported type. + * + * OpenSSH line format: + * + * + * + * Where the two parts can be created like: + * + * can be either + * or + * + * consists of + * [name] optionally followed by [,name] one or more times + * + * consists of + * |1||hash + * + * can be one of: + * [RSA bits] [e] [n as a decimal number] + * 'ssh-dss' [base64-encoded-key] + * 'ssh-rsa' [base64-encoded-key] + * + */ +LIBSSH2_API int +libssh2_knownhost_readline(LIBSSH2_KNOWNHOSTS *hosts, + const char *line, size_t len, int type) +{ + const char *cp; + const char *hostp; + const char *keyp; + size_t hostlen; + size_t keylen; + int rc; + + if(type != LIBSSH2_KNOWNHOST_FILE_OPENSSH) + return _libssh2_error(hosts->session, + LIBSSH2_ERROR_METHOD_NOT_SUPPORTED, + "Unsupported type of known-host information " + "store"); + + cp = line; + + /* skip leading whitespaces */ + while(len && ((*cp==' ') || (*cp == '\t'))) { + cp++; + len--; + } + + if(!len || !*cp || (*cp == '#') || (*cp == '\n')) + /* comment or empty line */ + return LIBSSH2_ERROR_NONE; + + /* the host part starts here */ + hostp = cp; + + /* move over the host to the separator */ + while(len && *cp && (*cp!=' ') && (*cp != '\t')) { + cp++; + len--; + } + + hostlen = cp - hostp; + + /* the key starts after the whitespaces */ + while(len && *cp && ((*cp==' ') || (*cp == '\t'))) { + cp++; + len--; + } + + if(!*cp || !len) /* illegal line */ + return _libssh2_error(hosts->session, + LIBSSH2_ERROR_METHOD_NOT_SUPPORTED, + "Failed to parse known_hosts line"); + + keyp = cp; /* the key starts here */ + keylen = len; + + /* check if the line (key) ends with a newline and if so kill it */ + while(len && *cp && (*cp != '\n')) { + cp++; + len--; + } + + /* zero terminate where the newline is */ + if(*cp == '\n') + keylen--; /* don't include this in the count */ + + /* deal with this one host+key line */ + rc = hostline(hosts, hostp, hostlen, keyp, keylen); + if(rc) + return rc; /* failed */ + + return LIBSSH2_ERROR_NONE; /* success */ +} + +/* + * libssh2_knownhost_readfile + * + * Read hosts+key pairs from a given file. + * + * Returns a negative value for error or number of successfully added hosts. + * + */ + +LIBSSH2_API int +libssh2_knownhost_readfile(LIBSSH2_KNOWNHOSTS *hosts, + const char *filename, int type) +{ + FILE *file; + int num = 0; + char buf[2048]; + + if(type != LIBSSH2_KNOWNHOST_FILE_OPENSSH) + return _libssh2_error(hosts->session, + LIBSSH2_ERROR_METHOD_NOT_SUPPORTED, + "Unsupported type of known-host information " + "store"); + + file = fopen(filename, "r"); + if(file) { + while(fgets(buf, sizeof(buf), file)) { + if(libssh2_knownhost_readline(hosts, buf, strlen(buf), type)) { + num = _libssh2_error(hosts->session, LIBSSH2_ERROR_KNOWN_HOSTS, + "Failed to parse known hosts file"); + break; + } + num++; + } + fclose(file); + } + else + return _libssh2_error(hosts->session, LIBSSH2_ERROR_FILE, + "Failed to open file"); + + return num; +} + +/* + * knownhost_writeline() + * + * Ask libssh2 to convert a known host to an output line for storage. + * + * Note that this function returns LIBSSH2_ERROR_BUFFER_TOO_SMALL if the given + * output buffer is too small to hold the desired output. The 'outlen' field + * will then contain the size libssh2 wanted to store, which then is the + * smallest sufficient buffer it would require. + * + */ +static int +knownhost_writeline(LIBSSH2_KNOWNHOSTS *hosts, + struct known_host *node, + char *buf, size_t buflen, + size_t *outlen, int type) +{ + int rc = LIBSSH2_ERROR_NONE; + int tindex; + const char *keytypes[4]={ + "", /* not used */ + "", /* this type has no name in the file */ + " ssh-rsa", + " ssh-dss" + }; + const char *keytype; + size_t nlen; + size_t commentlen = 0; + + /* we only support this single file type for now, bail out on all other + attempts */ + if(type != LIBSSH2_KNOWNHOST_FILE_OPENSSH) + return _libssh2_error(hosts->session, + LIBSSH2_ERROR_METHOD_NOT_SUPPORTED, + "Unsupported type of known-host information " + "store"); + + tindex = (node->typemask & LIBSSH2_KNOWNHOST_KEY_MASK) >> + LIBSSH2_KNOWNHOST_KEY_SHIFT; + + /* set the string used in the file */ + keytype = keytypes[tindex]; + + /* calculate extra space needed for comment */ + if(node->comment) + commentlen = strlen(node->comment) + 1; + + if((node->typemask & LIBSSH2_KNOWNHOST_TYPE_MASK) == + LIBSSH2_KNOWNHOST_TYPE_SHA1) { + char *namealloc; + char *saltalloc; + nlen = _libssh2_base64_encode(hosts->session, node->name, + node->name_len, &namealloc); + if(!nlen) + return _libssh2_error(hosts->session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for " + "base64-encoded host name"); + + nlen = _libssh2_base64_encode(hosts->session, + node->salt, node->salt_len, + &saltalloc); + if(!nlen) { + free(namealloc); + return _libssh2_error(hosts->session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for " + "base64-encoded salt"); + } + + nlen = strlen(saltalloc) + strlen(namealloc) + strlen(keytype) + + strlen(node->key) + commentlen + 7; + /* |1| + | + ' ' + \n + \0 = 7 */ + + if(nlen <= buflen) + if(node->comment) + snprintf(buf, buflen, "|1|%s|%s%s %s %s\n", saltalloc, namealloc, + keytype, node->key, node->comment); + else + snprintf(buf, buflen, "|1|%s|%s%s %s\n", saltalloc, namealloc, + keytype, node->key); + else + rc = _libssh2_error(hosts->session, LIBSSH2_ERROR_BUFFER_TOO_SMALL, + "Known-host write buffer too small"); + + free(namealloc); + free(saltalloc); + } + else { + nlen = strlen(node->name) + strlen(keytype) + strlen(node->key) + + commentlen + 3; + /* ' ' + '\n' + \0 = 3 */ + if(nlen <= buflen) + /* these types have the plain name */ + if(node->comment) + snprintf(buf, buflen, "%s%s %s %s\n", node->name, keytype, node->key, + node->comment); + else + snprintf(buf, buflen, "%s%s %s\n", node->name, keytype, node->key); + else + rc = _libssh2_error(hosts->session, LIBSSH2_ERROR_BUFFER_TOO_SMALL, + "Known-host write buffer too small"); + } + + /* we report the full length of the data with the trailing zero excluded */ + *outlen = nlen-1; + + return rc; +} + +/* + * libssh2_knownhost_writeline() + * + * Ask libssh2 to convert a known host to an output line for storage. + * + * Note that this function returns LIBSSH2_ERROR_BUFFER_TOO_SMALL if the given + * output buffer is too small to hold the desired output. + */ +LIBSSH2_API int +libssh2_knownhost_writeline(LIBSSH2_KNOWNHOSTS *hosts, + struct libssh2_knownhost *known, + char *buffer, size_t buflen, + size_t *outlen, /* the amount of written data */ + int type) +{ + struct known_host *node; + + if(known->magic != KNOWNHOST_MAGIC) + return _libssh2_error(hosts->session, LIBSSH2_ERROR_INVAL, + "Invalid host information"); + + node = known->node; + + return knownhost_writeline(hosts, node, buffer, buflen, outlen, type); +} + +/* + * libssh2_knownhost_writefile() + * + * Write hosts+key pairs to the given file. + */ +LIBSSH2_API int +libssh2_knownhost_writefile(LIBSSH2_KNOWNHOSTS *hosts, + const char *filename, int type) +{ + struct known_host *node; + FILE *file; + int rc = LIBSSH2_ERROR_NONE; + char buffer[2048]; + + /* we only support this single file type for now, bail out on all other + attempts */ + if(type != LIBSSH2_KNOWNHOST_FILE_OPENSSH) + return _libssh2_error(hosts->session, + LIBSSH2_ERROR_METHOD_NOT_SUPPORTED, + "Unsupported type of known-host information " + "store"); + + file = fopen(filename, "w"); + if(!file) + return _libssh2_error(hosts->session, LIBSSH2_ERROR_FILE, + "Failed to open file"); + + for(node = _libssh2_list_first(&hosts->head); + node; + node= _libssh2_list_next(&node->node) ) { + size_t wrote; + size_t nwrote; + rc = knownhost_writeline(hosts, node, buffer, sizeof(buffer), &wrote, + type); + if(rc) + break; + + nwrote = fwrite(buffer, 1, wrote, file); + if(nwrote != wrote) { + /* failed to write the whole thing, bail out */ + rc = _libssh2_error(hosts->session, LIBSSH2_ERROR_FILE, + "Write failed"); + break; + } + } + fclose(file); + + return rc; +} + + +/* + * libssh2_knownhost_get() + * + * Traverse the internal list of known hosts. Pass NULL to 'prev' to get + * the first one. + * + * Returns: + * 0 if a fine host was stored in 'store' + * 1 if end of hosts + * [negative] on errors + */ +LIBSSH2_API int +libssh2_knownhost_get(LIBSSH2_KNOWNHOSTS *hosts, + struct libssh2_knownhost **ext, + struct libssh2_knownhost *oprev) +{ + struct known_host *node; + if(oprev && oprev->node) { + /* we have a starting point */ + struct known_host *prev = oprev->node; + + /* get the next node in the list */ + node = _libssh2_list_next(&prev->node); + + } + else + node = _libssh2_list_first(&hosts->head); + + if(!node) + /* no (more) node */ + return 1; + + *ext = knownhost_to_external(node); + + return 0; +} diff --git a/libssh2/src/libgcrypt.c b/libssh2/src/libgcrypt.c new file mode 100644 index 0000000..29770c7 --- /dev/null +++ b/libssh2/src/libgcrypt.c @@ -0,0 +1,587 @@ +/* Copyright (C) 2008, 2009, Simon Josefsson + * Copyright (C) 2006, 2007, The Written Word, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include "libssh2_priv.h" + +#ifdef LIBSSH2_LIBGCRYPT /* compile only if we build with libgcrypt */ + +#include + +int +_libssh2_rsa_new(libssh2_rsa_ctx ** rsa, + const unsigned char *edata, + unsigned long elen, + const unsigned char *ndata, + unsigned long nlen, + const unsigned char *ddata, + unsigned long dlen, + const unsigned char *pdata, + unsigned long plen, + const unsigned char *qdata, + unsigned long qlen, + const unsigned char *e1data, + unsigned long e1len, + const unsigned char *e2data, + unsigned long e2len, + const unsigned char *coeffdata, unsigned long coefflen) +{ + int rc; + (void) e1data; + (void) e1len; + (void) e2data; + (void) e2len; + + if (ddata) { + rc = gcry_sexp_build + (rsa, NULL, + "(private-key(rsa(n%b)(e%b)(d%b)(q%b)(p%b)(u%b)))", + nlen, ndata, elen, edata, dlen, ddata, plen, pdata, + qlen, qdata, coefflen, coeffdata); + } else { + rc = gcry_sexp_build(rsa, NULL, "(public-key(rsa(n%b)(e%b)))", + nlen, ndata, elen, edata); + } + if (rc) { + *rsa = NULL; + return -1; + } + + return 0; +} + +int +_libssh2_rsa_sha1_verify(libssh2_rsa_ctx * rsa, + const unsigned char *sig, + unsigned long sig_len, + const unsigned char *m, unsigned long m_len) +{ + unsigned char hash[SHA_DIGEST_LENGTH]; + gcry_sexp_t s_sig, s_hash; + int rc = -1; + + libssh2_sha1(m, m_len, hash); + + rc = gcry_sexp_build(&s_hash, NULL, + "(data (flags pkcs1) (hash sha1 %b))", + SHA_DIGEST_LENGTH, hash); + if (rc != 0) { + return -1; + } + + rc = gcry_sexp_build(&s_sig, NULL, "(sig-val(rsa(s %b)))", sig_len, sig); + if (rc != 0) { + gcry_sexp_release(s_hash); + return -1; + } + + rc = gcry_pk_verify(s_sig, s_hash, rsa); + gcry_sexp_release(s_sig); + gcry_sexp_release(s_hash); + + return (rc == 0) ? 0 : -1; +} + +int +_libssh2_dsa_new(libssh2_dsa_ctx ** dsactx, + const unsigned char *p, + unsigned long p_len, + const unsigned char *q, + unsigned long q_len, + const unsigned char *g, + unsigned long g_len, + const unsigned char *y, + unsigned long y_len, + const unsigned char *x, unsigned long x_len) +{ + int rc; + + if (x_len) { + rc = gcry_sexp_build + (dsactx, NULL, + "(private-key(dsa(p%b)(q%b)(g%b)(y%b)(x%b)))", + p_len, p, q_len, q, g_len, g, y_len, y, x_len, x); + } else { + rc = gcry_sexp_build(dsactx, NULL, + "(public-key(dsa(p%b)(q%b)(g%b)(y%b)))", + p_len, p, q_len, q, g_len, g, y_len, y); + } + + if (rc) { + *dsactx = NULL; + return -1; + } + + return 0; +} + +int +_libssh2_rsa_new_private(libssh2_rsa_ctx ** rsa, + LIBSSH2_SESSION * session, + const char *filename, unsigned const char *passphrase) +{ + FILE *fp; + unsigned char *data, *save_data; + unsigned int datalen; + int ret; + unsigned char *n, *e, *d, *p, *q, *e1, *e2, *coeff; + unsigned int nlen, elen, dlen, plen, qlen, e1len, e2len, coefflen; + + (void) passphrase; + + fp = fopen(filename, "r"); + if (!fp) { + return -1; + } + + ret = _libssh2_pem_parse(session, + "-----BEGIN RSA PRIVATE KEY-----", + "-----END RSA PRIVATE KEY-----", + fp, &data, &datalen); + fclose(fp); + if (ret) { + return -1; + } + + save_data = data; + + if (_libssh2_pem_decode_sequence(&data, &datalen)) { + ret = -1; + goto fail; + } +/* First read Version field (should be 0). */ + ret = _libssh2_pem_decode_integer(&data, &datalen, &n, &nlen); + if (ret != 0 || (nlen != 1 && *n != '\0')) { + ret = -1; + goto fail; + } + + ret = _libssh2_pem_decode_integer(&data, &datalen, &n, &nlen); + if (ret != 0) { + ret = -1; + goto fail; + } + + ret = _libssh2_pem_decode_integer(&data, &datalen, &e, &elen); + if (ret != 0) { + ret = -1; + goto fail; + } + + ret = _libssh2_pem_decode_integer(&data, &datalen, &d, &dlen); + if (ret != 0) { + ret = -1; + goto fail; + } + + ret = _libssh2_pem_decode_integer(&data, &datalen, &p, &plen); + if (ret != 0) { + ret = -1; + goto fail; + } + + ret = _libssh2_pem_decode_integer(&data, &datalen, &q, &qlen); + if (ret != 0) { + ret = -1; + goto fail; + } + + ret = _libssh2_pem_decode_integer(&data, &datalen, &e1, &e1len); + if (ret != 0) { + ret = -1; + goto fail; + } + + ret = _libssh2_pem_decode_integer(&data, &datalen, &e2, &e2len); + if (ret != 0) { + ret = -1; + goto fail; + } + + ret = _libssh2_pem_decode_integer(&data, &datalen, &coeff, &coefflen); + if (ret != 0) { + ret = -1; + goto fail; + } + + if (_libssh2_rsa_new(rsa, e, elen, n, nlen, d, dlen, p, plen, + q, qlen, e1, e1len, e2, e2len, coeff, coefflen)) { + ret = -1; + goto fail; + } + + ret = 0; + + fail: + LIBSSH2_FREE(session, save_data); + return ret; +} + +int +_libssh2_dsa_new_private(libssh2_dsa_ctx ** dsa, + LIBSSH2_SESSION * session, + const char *filename, unsigned const char *passphrase) +{ + FILE *fp; + unsigned char *data, *save_data; + unsigned int datalen; + int ret; + unsigned char *p, *q, *g, *y, *x; + unsigned int plen, qlen, glen, ylen, xlen; + + (void) passphrase; + + fp = fopen(filename, "r"); + if (!fp) { + return -1; + } + + ret = _libssh2_pem_parse(session, + "-----BEGIN DSA PRIVATE KEY-----", + "-----END DSA PRIVATE KEY-----", + fp, &data, &datalen); + fclose(fp); + if (ret) { + return -1; + } + + save_data = data; + + if (_libssh2_pem_decode_sequence(&data, &datalen)) { + ret = -1; + goto fail; + } + +/* First read Version field (should be 0). */ + ret = _libssh2_pem_decode_integer(&data, &datalen, &p, &plen); + if (ret != 0 || (plen != 1 && *p != '\0')) { + ret = -1; + goto fail; + } + + ret = _libssh2_pem_decode_integer(&data, &datalen, &p, &plen); + if (ret != 0) { + ret = -1; + goto fail; + } + + ret = _libssh2_pem_decode_integer(&data, &datalen, &q, &qlen); + if (ret != 0) { + ret = -1; + goto fail; + } + + ret = _libssh2_pem_decode_integer(&data, &datalen, &g, &glen); + if (ret != 0) { + ret = -1; + goto fail; + } + + ret = _libssh2_pem_decode_integer(&data, &datalen, &y, &ylen); + if (ret != 0) { + ret = -1; + goto fail; + } + + ret = _libssh2_pem_decode_integer(&data, &datalen, &x, &xlen); + if (ret != 0) { + ret = -1; + goto fail; + } + + if (datalen != 0) { + ret = -1; + goto fail; + } + + if (_libssh2_dsa_new(dsa, p, plen, q, qlen, g, glen, y, ylen, x, xlen)) { + ret = -1; + goto fail; + } + + ret = 0; + + fail: + LIBSSH2_FREE(session, save_data); + return ret; +} + +int +_libssh2_rsa_sha1_sign(LIBSSH2_SESSION * session, + libssh2_dsa_ctx * rsactx, + const unsigned char *hash, + size_t hash_len, + unsigned char **signature, size_t *signature_len) +{ + gcry_sexp_t sig_sexp; + gcry_sexp_t data; + int rc; + const char *tmp; + size_t size; + + if (hash_len != SHA_DIGEST_LENGTH) { + return -1; + } + + if (gcry_sexp_build(&data, NULL, + "(data (flags pkcs1) (hash sha1 %b))", + hash_len, hash)) { + return -1; + } + + rc = gcry_pk_sign(&sig_sexp, data, rsactx); + + gcry_sexp_release(data); + + if (rc != 0) { + return -1; + } + + data = gcry_sexp_find_token(sig_sexp, "s", 0); + if (!data) { + return -1; + } + + tmp = gcry_sexp_nth_data(data, 1, &size); + if (!tmp) { + return -1; + } + + if (tmp[0] == '\0') { + tmp++; + size--; + } + + *signature = LIBSSH2_ALLOC(session, size); + memcpy(*signature, tmp, size); + *signature_len = size; + + return rc; +} + +int +_libssh2_dsa_sha1_sign(libssh2_dsa_ctx * dsactx, + const unsigned char *hash, + unsigned long hash_len, unsigned char *sig) +{ + unsigned char zhash[SHA_DIGEST_LENGTH + 1]; + gcry_sexp_t sig_sexp; + gcry_sexp_t data; + int ret; + const char *tmp; + size_t size; + + if (hash_len != SHA_DIGEST_LENGTH) { + return -1; + } + + memcpy(zhash + 1, hash, hash_len); + zhash[0] = 0; + + if (gcry_sexp_build(&data, NULL, "(data (value %b))", hash_len + 1, zhash)) { + return -1; + } + + ret = gcry_pk_sign(&sig_sexp, data, dsactx); + + gcry_sexp_release(data); + + if (ret != 0) { + return -1; + } + + memset(sig, 0, 40); + +/* Extract R. */ + + data = gcry_sexp_find_token(sig_sexp, "r", 0); + if (!data) + goto err; + + tmp = gcry_sexp_nth_data(data, 1, &size); + if (!tmp) + goto err; + + if (tmp[0] == '\0') { + tmp++; + size--; + } + + if (size < 1 || size > 20) + goto err; + + memcpy(sig + (20 - size), tmp, size); + + gcry_sexp_release(data); + +/* Extract S. */ + + data = gcry_sexp_find_token(sig_sexp, "s", 0); + if (!data) + goto err; + + tmp = gcry_sexp_nth_data(data, 1, &size); + if (!tmp) + goto err; + + if (tmp[0] == '\0') { + tmp++; + size--; + } + + if (size < 1 || size > 20) + goto err; + + memcpy(sig + 20 + (20 - size), tmp, size); + goto out; + + err: + ret = -1; + + out: + if (sig_sexp) { + gcry_sexp_release(sig_sexp); + } + if (data) { + gcry_sexp_release(data); + } + return ret; +} + +int +_libssh2_dsa_sha1_verify(libssh2_dsa_ctx * dsactx, + const unsigned char *sig, + const unsigned char *m, unsigned long m_len) +{ + unsigned char hash[SHA_DIGEST_LENGTH + 1]; + gcry_sexp_t s_sig, s_hash; + int rc = -1; + + libssh2_sha1(m, m_len, hash + 1); + hash[0] = 0; + + if (gcry_sexp_build(&s_hash, NULL, "(data(flags raw)(value %b))", + SHA_DIGEST_LENGTH + 1, hash)) { + return -1; + } + + if (gcry_sexp_build(&s_sig, NULL, "(sig-val(dsa(r %b)(s %b)))", + 20, sig, 20, sig + 20)) { + gcry_sexp_release(s_hash); + return -1; + } + + rc = gcry_pk_verify(s_sig, s_hash, dsactx); + gcry_sexp_release(s_sig); + gcry_sexp_release(s_hash); + + return (rc == 0) ? 0 : -1; +} + +int +_libssh2_cipher_init(_libssh2_cipher_ctx * h, + _libssh2_cipher_type(algo), + unsigned char *iv, unsigned char *secret, int encrypt) +{ + int ret; + int cipher = _libssh2_gcry_cipher (algo); + int mode = _libssh2_gcry_mode (algo); + int keylen = gcry_cipher_get_algo_keylen(cipher); + + (void) encrypt; + + ret = gcry_cipher_open(h, cipher, mode, 0); + if (ret) { + return -1; + } + + ret = gcry_cipher_setkey(*h, secret, keylen); + if (ret) { + gcry_cipher_close(*h); + return -1; + } + + if (mode != GCRY_CIPHER_MODE_STREAM) { + int blklen = gcry_cipher_get_algo_blklen(cipher); + if (mode == GCRY_CIPHER_MODE_CTR) + ret = gcry_cipher_setctr(*h, iv, blklen); + else + ret = gcry_cipher_setiv(*h, iv, blklen); + if (ret) { + gcry_cipher_close(*h); + return -1; + } + } + + return 0; +} + +int +_libssh2_cipher_crypt(_libssh2_cipher_ctx * ctx, + _libssh2_cipher_type(algo), + int encrypt, unsigned char *block, size_t blklen) +{ + int cipher = _libssh2_gcry_cipher (algo); + int ret; + + if (encrypt) { + ret = gcry_cipher_encrypt(*ctx, block, blklen, block, blklen); + } else { + ret = gcry_cipher_decrypt(*ctx, block, blklen, block, blklen); + } + return ret; +} + +int +_libssh2_pub_priv_keyfile(LIBSSH2_SESSION *session, + unsigned char **method, + size_t *method_len, + unsigned char **pubkeydata, + size_t *pubkeydata_len, + const char *privatekey, + const char *passphrase) +{ + return _libssh2_error(session, LIBSSH2_ERROR_FILE, + "Unable to extract public key from private key file: " + "Method unimplemented in libgcrypt backend"); +} + +void _libssh2_init_aes_ctr(void) +{ + /* no implementation */ +} +#endif /* LIBSSH2_LIBGCRYPT */ diff --git a/libssh2/src/mac.c b/libssh2/src/mac.c new file mode 100644 index 0000000..76894fc --- /dev/null +++ b/libssh2/src/mac.c @@ -0,0 +1,314 @@ +/* Copyright (c) 2004-2007, Sara Golemon + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include "libssh2_priv.h" +#include "mac.h" + +#ifdef LIBSSH2_MAC_NONE +/* mac_none_MAC + * Minimalist MAC: No MAC + */ +static int +mac_none_MAC(LIBSSH2_SESSION * session, unsigned char *buf, + uint32_t seqno, const unsigned char *packet, + uint32_t packet_len, const unsigned char *addtl, + uint32_t addtl_len, void **abstract) +{ + return 0; +} + + + + +static LIBSSH2_MAC_METHOD mac_method_none = { + "none", + 0, + 0, + NULL, + mac_none_MAC, + NULL +}; +#endif /* LIBSSH2_MAC_NONE */ + +/* mac_method_common_init + * Initialize simple mac methods + */ +static int +mac_method_common_init(LIBSSH2_SESSION * session, unsigned char *key, + int *free_key, void **abstract) +{ + *abstract = key; + *free_key = 0; + (void) session; + + return 0; +} + + + +/* mac_method_common_dtor + * Cleanup simple mac methods + */ +static int +mac_method_common_dtor(LIBSSH2_SESSION * session, void **abstract) +{ + if (*abstract) { + LIBSSH2_FREE(session, *abstract); + } + *abstract = NULL; + + return 0; +} + + + +/* mac_method_hmac_sha1_hash + * Calculate hash using full sha1 value + */ +static int +mac_method_hmac_sha1_hash(LIBSSH2_SESSION * session, + unsigned char *buf, uint32_t seqno, + const unsigned char *packet, + uint32_t packet_len, + const unsigned char *addtl, + uint32_t addtl_len, void **abstract) +{ + libssh2_hmac_ctx ctx; + unsigned char seqno_buf[4]; + (void) session; + + _libssh2_htonu32(seqno_buf, seqno); + + libssh2_hmac_sha1_init(&ctx, *abstract, 20); + libssh2_hmac_update(ctx, seqno_buf, 4); + libssh2_hmac_update(ctx, packet, packet_len); + if (addtl && addtl_len) { + libssh2_hmac_update(ctx, addtl, addtl_len); + } + libssh2_hmac_final(ctx, buf); + libssh2_hmac_cleanup(&ctx); + + return 0; +} + + + +static const LIBSSH2_MAC_METHOD mac_method_hmac_sha1 = { + "hmac-sha1", + 20, + 20, + mac_method_common_init, + mac_method_hmac_sha1_hash, + mac_method_common_dtor, +}; + +/* mac_method_hmac_sha1_96_hash + * Calculate hash using first 96 bits of sha1 value + */ +static int +mac_method_hmac_sha1_96_hash(LIBSSH2_SESSION * session, + unsigned char *buf, uint32_t seqno, + const unsigned char *packet, + uint32_t packet_len, + const unsigned char *addtl, + uint32_t addtl_len, void **abstract) +{ + unsigned char temp[SHA_DIGEST_LENGTH]; + + mac_method_hmac_sha1_hash(session, temp, seqno, packet, packet_len, + addtl, addtl_len, abstract); + memcpy(buf, (char *) temp, 96 / 8); + + return 0; +} + + + +static const LIBSSH2_MAC_METHOD mac_method_hmac_sha1_96 = { + "hmac-sha1-96", + 12, + 20, + mac_method_common_init, + mac_method_hmac_sha1_96_hash, + mac_method_common_dtor, +}; + +#if LIBSSH2_MD5 +/* mac_method_hmac_md5_hash + * Calculate hash using full md5 value + */ +static int +mac_method_hmac_md5_hash(LIBSSH2_SESSION * session, unsigned char *buf, + uint32_t seqno, + const unsigned char *packet, + uint32_t packet_len, + const unsigned char *addtl, + uint32_t addtl_len, void **abstract) +{ + libssh2_hmac_ctx ctx; + unsigned char seqno_buf[4]; + (void) session; + + _libssh2_htonu32(seqno_buf, seqno); + + libssh2_hmac_md5_init(&ctx, *abstract, 16); + libssh2_hmac_update(ctx, seqno_buf, 4); + libssh2_hmac_update(ctx, packet, packet_len); + if (addtl && addtl_len) { + libssh2_hmac_update(ctx, addtl, addtl_len); + } + libssh2_hmac_final(ctx, buf); + libssh2_hmac_cleanup(&ctx); + + return 0; +} + + + +static const LIBSSH2_MAC_METHOD mac_method_hmac_md5 = { + "hmac-md5", + 16, + 16, + mac_method_common_init, + mac_method_hmac_md5_hash, + mac_method_common_dtor, +}; + +/* mac_method_hmac_md5_96_hash + * Calculate hash using first 96 bits of md5 value + */ +static int +mac_method_hmac_md5_96_hash(LIBSSH2_SESSION * session, + unsigned char *buf, uint32_t seqno, + const unsigned char *packet, + uint32_t packet_len, + const unsigned char *addtl, + uint32_t addtl_len, void **abstract) +{ + unsigned char temp[MD5_DIGEST_LENGTH]; + mac_method_hmac_md5_hash(session, temp, seqno, packet, packet_len, + addtl, addtl_len, abstract); + memcpy(buf, (char *) temp, 96 / 8); + return 0; +} + + + +static const LIBSSH2_MAC_METHOD mac_method_hmac_md5_96 = { + "hmac-md5-96", + 12, + 16, + mac_method_common_init, + mac_method_hmac_md5_96_hash, + mac_method_common_dtor, +}; +#endif /* LIBSSH2_MD5 */ + +#if LIBSSH2_HMAC_RIPEMD +/* mac_method_hmac_ripemd160_hash + * Calculate hash using ripemd160 value + */ +static int +mac_method_hmac_ripemd160_hash(LIBSSH2_SESSION * session, + unsigned char *buf, uint32_t seqno, + const unsigned char *packet, + uint32_t packet_len, + const unsigned char *addtl, + uint32_t addtl_len, + void **abstract) +{ + libssh2_hmac_ctx ctx; + unsigned char seqno_buf[4]; + (void) session; + + _libssh2_htonu32(seqno_buf, seqno); + + libssh2_hmac_ripemd160_init(&ctx, *abstract, 20); + libssh2_hmac_update(ctx, seqno_buf, 4); + libssh2_hmac_update(ctx, packet, packet_len); + if (addtl && addtl_len) { + libssh2_hmac_update(ctx, addtl, addtl_len); + } + libssh2_hmac_final(ctx, buf); + libssh2_hmac_cleanup(&ctx); + + return 0; +} + + + +static const LIBSSH2_MAC_METHOD mac_method_hmac_ripemd160 = { + "hmac-ripemd160", + 20, + 20, + mac_method_common_init, + mac_method_hmac_ripemd160_hash, + mac_method_common_dtor, +}; + +static const LIBSSH2_MAC_METHOD mac_method_hmac_ripemd160_openssh_com = { + "hmac-ripemd160@openssh.com", + 20, + 20, + mac_method_common_init, + mac_method_hmac_ripemd160_hash, + mac_method_common_dtor, +}; +#endif /* LIBSSH2_HMAC_RIPEMD */ + +static const LIBSSH2_MAC_METHOD *mac_methods[] = { + &mac_method_hmac_sha1, + &mac_method_hmac_sha1_96, +#if LIBSSH2_MD5 + &mac_method_hmac_md5, + &mac_method_hmac_md5_96, +#endif +#if LIBSSH2_HMAC_RIPEMD + &mac_method_hmac_ripemd160, + &mac_method_hmac_ripemd160_openssh_com, +#endif /* LIBSSH2_HMAC_RIPEMD */ +#ifdef LIBSSH2_MAC_NONE + &mac_method_none, +#endif /* LIBSSH2_MAC_NONE */ + NULL +}; + +const LIBSSH2_MAC_METHOD ** +_libssh2_mac_methods(void) +{ + return mac_methods; +} diff --git a/libssh2/src/misc.c b/libssh2/src/misc.c new file mode 100644 index 0000000..a9f423a --- /dev/null +++ b/libssh2/src/misc.c @@ -0,0 +1,612 @@ +/* Copyright (c) 2004-2007 Sara Golemon + * Copyright (c) 2009-2010 by Daniel Stenberg + * Copyright (c) 2010 Simon Josefsson + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include "libssh2_priv.h" +#include "misc.h" + +#ifdef HAVE_UNISTD_H +#include +#endif + +#ifdef HAVE_SYS_TIME_H +#include +#endif + +#include +#include + +int _libssh2_error(LIBSSH2_SESSION* session, int errcode, const char* errmsg) +{ + session->err_msg = errmsg; + session->err_code = errcode; +#ifdef LIBSSH2DEBUG + if((errcode == LIBSSH2_ERROR_EAGAIN) && !session->api_block_mode) + /* if this is EAGAIN and we're in non-blocking mode, don't generate + a debug output for this */ + return errcode; + _libssh2_debug(session, LIBSSH2_TRACE_ERROR, "%d - %s", session->err_code, + session->err_msg); +#endif + + return errcode; +} + +#ifdef WIN32 +static int wsa2errno(void) +{ + switch (WSAGetLastError()) { + case WSAEWOULDBLOCK: + return EAGAIN; + + case WSAENOTSOCK: + return EBADF; + + case WSAEINTR: + return EINTR; + + default: + /* It is most important to ensure errno does not stay at EAGAIN + * when a different error occurs so just set errno to a generic + * error */ + return EIO; + } +} +#endif + +/* _libssh2_recv + * + * Replacement for the standard recv, return -errno on failure. + */ +ssize_t +_libssh2_recv(libssh2_socket_t sock, void *buffer, size_t length, int flags, void **abstract) +{ + ssize_t rc = recv(sock, buffer, length, flags); +#ifdef WIN32 + if (rc < 0 ) + return -wsa2errno(); +#elif defined(__VMS) + if (rc < 0 ){ + if ( errno == EWOULDBLOCK ) + return -EAGAIN; + else + return -errno; + } +#else + if (rc < 0 ){ + /* Sometimes the first recv() function call sets errno to ENOENT on + Solaris and HP-UX */ + if ( errno == ENOENT ) + return -EAGAIN; + else + return -errno; + } +#endif + return rc; +} + +/* _libssh2_send + * + * Replacement for the standard send, return -errno on failure. + */ +ssize_t +_libssh2_send(libssh2_socket_t sock, const void *buffer, size_t length, + int flags, void **abstract) +{ + ssize_t rc = send(sock, buffer, length, flags); +#ifdef WIN32 + if (rc < 0 ) + return -wsa2errno(); +#elif defined(__VMS) + if (rc < 0 ) { + if ( errno == EWOULDBLOCK ) + return -EAGAIN; + else + return -errno; + } +#else + if (rc < 0 ) + return -errno; +#endif + return rc; +} + +/* libssh2_ntohu32 + */ +unsigned int +_libssh2_ntohu32(const unsigned char *buf) +{ + return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; +} + + +/* _libssh2_ntohu64 + */ +libssh2_uint64_t +_libssh2_ntohu64(const unsigned char *buf) +{ + unsigned long msl, lsl; + + msl = ((libssh2_uint64_t)buf[0] << 24) | ((libssh2_uint64_t)buf[1] << 16) + | ((libssh2_uint64_t)buf[2] << 8) | (libssh2_uint64_t)buf[3]; + lsl = ((libssh2_uint64_t)buf[4] << 24) | ((libssh2_uint64_t)buf[5] << 16) + | ((libssh2_uint64_t)buf[6] << 8) | (libssh2_uint64_t)buf[7]; + + return ((libssh2_uint64_t)msl <<32) | lsl; +} + +/* _libssh2_htonu32 + */ +void +_libssh2_htonu32(unsigned char *buf, uint32_t value) +{ + buf[0] = (value >> 24) & 0xFF; + buf[1] = (value >> 16) & 0xFF; + buf[2] = (value >> 8) & 0xFF; + buf[3] = value & 0xFF; +} + +/* _libssh2_store_u32 + */ +void _libssh2_store_u32(unsigned char **buf, uint32_t value) +{ + _libssh2_htonu32(*buf, value); + *buf += sizeof(uint32_t); +} + +/* _libssh2_store_str + */ +void _libssh2_store_str(unsigned char **buf, const char *str, size_t len) +{ + _libssh2_store_u32(buf, (uint32_t)len); + if(len) { + memcpy(*buf, str, len); + *buf += len; + } +} + +/* Base64 Conversion */ + +static const char base64_table[] = +{ + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', '\0' +}; + +static const char base64_pad = '='; + +static const short base64_reverse_table[256] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 +}; + +/* libssh2_base64_decode + * + * Decode a base64 chunk and store it into a newly alloc'd buffer + */ +LIBSSH2_API int +libssh2_base64_decode(LIBSSH2_SESSION *session, char **data, + unsigned int *datalen, const char *src, + unsigned int src_len) +{ + unsigned char *s, *d; + short v; + int i = 0, len = 0; + + *data = LIBSSH2_ALLOC(session, (3 * src_len / 4) + 1); + d = (unsigned char *) *data; + if (!d) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for base64 decoding"); + } + + for(s = (unsigned char *) src; ((char *) s) < (src + src_len); s++) { + if ((v = base64_reverse_table[*s]) < 0) + continue; + switch (i % 4) { + case 0: + d[len] = v << 2; + break; + case 1: + d[len++] |= v >> 4; + d[len] = v << 4; + break; + case 2: + d[len++] |= v >> 2; + d[len] = v << 6; + break; + case 3: + d[len++] |= v; + break; + } + i++; + } + if ((i % 4) == 1) { + /* Invalid -- We have a byte which belongs exclusively to a partial + octet */ + LIBSSH2_FREE(session, *data); + return _libssh2_error(session, LIBSSH2_ERROR_INVAL, "Invalid base64"); + } + + *datalen = len; + return 0; +} + +/* ---- Base64 Encoding/Decoding Table --- */ +static const char table64[]= + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +/* + * _libssh2_base64_encode() + * + * Returns the length of the newly created base64 string. The third argument + * is a pointer to an allocated area holding the base64 data. If something + * went wrong, 0 is returned. + * + */ +size_t _libssh2_base64_encode(LIBSSH2_SESSION *session, + const char *inp, size_t insize, char **outptr) +{ + unsigned char ibuf[3]; + unsigned char obuf[4]; + int i; + int inputparts; + char *output; + char *base64data; + const char *indata = inp; + + *outptr = NULL; /* set to NULL in case of failure before we reach the end */ + + if(0 == insize) + insize = strlen(indata); + + base64data = output = LIBSSH2_ALLOC(session, insize*4/3+4); + if(NULL == output) + return 0; + + while(insize > 0) { + for (i = inputparts = 0; i < 3; i++) { + if(insize > 0) { + inputparts++; + ibuf[i] = *indata; + indata++; + insize--; + } + else + ibuf[i] = 0; + } + + obuf[0] = (unsigned char) ((ibuf[0] & 0xFC) >> 2); + obuf[1] = (unsigned char) (((ibuf[0] & 0x03) << 4) | \ + ((ibuf[1] & 0xF0) >> 4)); + obuf[2] = (unsigned char) (((ibuf[1] & 0x0F) << 2) | \ + ((ibuf[2] & 0xC0) >> 6)); + obuf[3] = (unsigned char) (ibuf[2] & 0x3F); + + switch(inputparts) { + case 1: /* only one byte read */ + snprintf(output, 5, "%c%c==", + table64[obuf[0]], + table64[obuf[1]]); + break; + case 2: /* two bytes read */ + snprintf(output, 5, "%c%c%c=", + table64[obuf[0]], + table64[obuf[1]], + table64[obuf[2]]); + break; + default: + snprintf(output, 5, "%c%c%c%c", + table64[obuf[0]], + table64[obuf[1]], + table64[obuf[2]], + table64[obuf[3]] ); + break; + } + output += 4; + } + *output=0; + *outptr = base64data; /* make it return the actual data memory */ + + return strlen(base64data); /* return the length of the new data */ +} +/* ---- End of Base64 Encoding ---- */ + +LIBSSH2_API void +libssh2_free(LIBSSH2_SESSION *session, void *ptr) +{ + LIBSSH2_FREE(session, ptr); +} + +#ifdef LIBSSH2DEBUG +LIBSSH2_API int +libssh2_trace(LIBSSH2_SESSION * session, int bitmask) +{ + session->showmask = bitmask; + return 0; +} + +LIBSSH2_API int +libssh2_trace_sethandler(LIBSSH2_SESSION *session, void* handler_context, + libssh2_trace_handler_func callback) +{ + session->tracehandler = callback; + session->tracehandler_context = handler_context; + return 0; +} + +void +_libssh2_debug(LIBSSH2_SESSION * session, int context, const char *format, ...) +{ + char buffer[1536]; + int len, msglen, buflen = sizeof(buffer); + va_list vargs; + struct timeval now; + static int firstsec; + static const char *const contexts[] = { + "Unknown", + "Transport", + "Key Ex", + "Userauth", + "Conn", + "SCP", + "SFTP", + "Failure Event", + "Publickey", + "Socket", + }; + const char* contexttext = contexts[0]; + unsigned int contextindex; + + if (!(session->showmask & context)) { + /* no such output asked for */ + return; + } + + /* Find the first matching context string for this message */ + for (contextindex = 0; contextindex < ARRAY_SIZE(contexts); + contextindex++) { + if ((context & (1 << contextindex)) != 0) { + contexttext = contexts[contextindex]; + break; + } + } + + _libssh2_gettimeofday(&now, NULL); + if(!firstsec) { + firstsec = now.tv_sec; + } + now.tv_sec -= firstsec; + + len = snprintf(buffer, buflen, "[libssh2] %d.%06d %s: ", + (int)now.tv_sec, (int)now.tv_usec, contexttext); + + if (len >= buflen) + msglen = buflen - 1; + else { + buflen -= len; + msglen = len; + va_start(vargs, format); + len = vsnprintf(buffer + msglen, buflen, format, vargs); + va_end(vargs); + msglen += len < buflen ? len : buflen - 1; + } + + if (session->tracehandler) + (session->tracehandler)(session, session->tracehandler_context, buffer, + msglen); + else + fprintf(stderr, "%s\n", buffer); +} + +#else +LIBSSH2_API int +libssh2_trace(LIBSSH2_SESSION * session, int bitmask) +{ + (void) session; + (void) bitmask; + return 0; +} + +LIBSSH2_API int +libssh2_trace_sethandler(LIBSSH2_SESSION *session, void* handler_context, + libssh2_trace_handler_func callback) +{ + (void) session; + (void) handler_context; + (void) callback; + return 0; +} +#endif + +/* init the list head */ +void _libssh2_list_init(struct list_head *head) +{ + head->first = head->last = NULL; +} + +/* add a node to the list */ +void _libssh2_list_add(struct list_head *head, + struct list_node *entry) +{ + /* store a pointer to the head */ + entry->head = head; + + /* we add this entry at the "top" so it has no next */ + entry->next = NULL; + + /* make our prev point to what the head thinks is last */ + entry->prev = head->last; + + /* and make head's last be us now */ + head->last = entry; + + /* make sure our 'prev' node points to us next */ + if(entry->prev) + entry->prev->next = entry; + else + head->first = entry; +} + +/* return the "first" node in the list this head points to */ +void *_libssh2_list_first(struct list_head *head) +{ + return head->first; +} + +/* return the next node in the list */ +void *_libssh2_list_next(struct list_node *node) +{ + return node->next; +} + +/* return the prev node in the list */ +void *_libssh2_list_prev(struct list_node *node) +{ + return node->prev; +} + +/* remove this node from the list */ +void _libssh2_list_remove(struct list_node *entry) +{ + if(entry->prev) + entry->prev->next = entry->next; + else + entry->head->first = entry->next; + + if(entry->next) + entry->next->prev = entry->prev; + else + entry->head->last = entry->prev; +} + +#if 0 +/* insert a node before the given 'after' entry */ +void _libssh2_list_insert(struct list_node *after, /* insert before this */ + struct list_node *entry) +{ + /* 'after' is next to 'entry' */ + bentry->next = after; + + /* entry's prev is then made to be the prev after current has */ + entry->prev = after->prev; + + /* the node that is now before 'entry' was previously before 'after' + and must be made to point to 'entry' correctly */ + if(entry->prev) + entry->prev->next = entry; + else + /* there was no node before this, so we make sure we point the head + pointer to this node */ + after->head->first = entry; + + /* after's prev entry points back to entry */ + after->prev = entry; + + /* after's next entry is still the same as before */ + + /* entry's head is the same as after's */ + entry->head = after->head; +} + +#endif + +/* this define is defined in misc.h for the correct platforms */ +#ifdef LIBSSH2_GETTIMEOFDAY_WIN32 +/* + * gettimeofday + * Implementation according to: + * The Open Group Base Specifications Issue 6 + * IEEE Std 1003.1, 2004 Edition + */ + +/* + * THIS SOFTWARE IS NOT COPYRIGHTED + * + * This source code is offered for use in the public domain. You may + * use, modify or distribute it freely. + * + * This code is distributed in the hope that it will be useful but + * WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESS OR IMPLIED ARE HEREBY + * DISCLAIMED. This includes but is not limited to warranties of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * Contributed by: + * Danny Smith + */ + +/* Offset between 1/1/1601 and 1/1/1970 in 100 nanosec units */ +#define _W32_FT_OFFSET (116444736000000000) + +int __cdecl _libssh2_gettimeofday(struct timeval *tp, void *tzp) + { + union { + unsigned __int64 ns100; /*time since 1 Jan 1601 in 100ns units */ + FILETIME ft; + } _now; + + if(tp) + { + GetSystemTimeAsFileTime (&_now.ft); + tp->tv_usec=(long)((_now.ns100 / 10) % 1000000 ); + tp->tv_sec= (long)((_now.ns100 - _W32_FT_OFFSET) / 10000000); + } + /* Always return 0 as per Open Group Base Specifications Issue 6. + Do not set errno on error. */ + return 0; +} + + +#endif diff --git a/libssh2/src/openssl.c b/libssh2/src/openssl.c new file mode 100644 index 0000000..29c8f47 --- /dev/null +++ b/libssh2/src/openssl.c @@ -0,0 +1,804 @@ +/* Copyright (C) 2009, 2010 Simon Josefsson + * Copyright (C) 2006, 2007 The Written Word, Inc. All rights reserved. + * Copyright (c) 2004-2006, Sara Golemon + * + * Author: Simon Josefsson + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include "libssh2_priv.h" + +#ifndef LIBSSH2_LIBGCRYPT /* compile only if we build with OpenSSL */ + +#include + +#ifndef EVP_MAX_BLOCK_LENGTH +#define EVP_MAX_BLOCK_LENGTH 32 +#endif + +int +_libssh2_rsa_new(libssh2_rsa_ctx ** rsa, + const unsigned char *edata, + unsigned long elen, + const unsigned char *ndata, + unsigned long nlen, + const unsigned char *ddata, + unsigned long dlen, + const unsigned char *pdata, + unsigned long plen, + const unsigned char *qdata, + unsigned long qlen, + const unsigned char *e1data, + unsigned long e1len, + const unsigned char *e2data, + unsigned long e2len, + const unsigned char *coeffdata, unsigned long coefflen) +{ + *rsa = RSA_new(); + + (*rsa)->e = BN_new(); + BN_bin2bn(edata, elen, (*rsa)->e); + + (*rsa)->n = BN_new(); + BN_bin2bn(ndata, nlen, (*rsa)->n); + + if (ddata) { + (*rsa)->d = BN_new(); + BN_bin2bn(ddata, dlen, (*rsa)->d); + + (*rsa)->p = BN_new(); + BN_bin2bn(pdata, plen, (*rsa)->p); + + (*rsa)->q = BN_new(); + BN_bin2bn(qdata, qlen, (*rsa)->q); + + (*rsa)->dmp1 = BN_new(); + BN_bin2bn(e1data, e1len, (*rsa)->dmp1); + + (*rsa)->dmq1 = BN_new(); + BN_bin2bn(e2data, e2len, (*rsa)->dmq1); + + (*rsa)->iqmp = BN_new(); + BN_bin2bn(coeffdata, coefflen, (*rsa)->iqmp); + } + return 0; +} + +int +_libssh2_rsa_sha1_verify(libssh2_rsa_ctx * rsactx, + const unsigned char *sig, + unsigned long sig_len, + const unsigned char *m, unsigned long m_len) +{ + unsigned char hash[SHA_DIGEST_LENGTH]; + int ret; + + libssh2_sha1(m, m_len, hash); + ret = RSA_verify(NID_sha1, hash, SHA_DIGEST_LENGTH, + (unsigned char *) sig, sig_len, rsactx); + return (ret == 1) ? 0 : -1; +} + +#if LIBSSH2_DSA +int +_libssh2_dsa_new(libssh2_dsa_ctx ** dsactx, + const unsigned char *p, + unsigned long p_len, + const unsigned char *q, + unsigned long q_len, + const unsigned char *g, + unsigned long g_len, + const unsigned char *y, + unsigned long y_len, + const unsigned char *x, unsigned long x_len) +{ + *dsactx = DSA_new(); + + (*dsactx)->p = BN_new(); + BN_bin2bn(p, p_len, (*dsactx)->p); + + (*dsactx)->q = BN_new(); + BN_bin2bn(q, q_len, (*dsactx)->q); + + (*dsactx)->g = BN_new(); + BN_bin2bn(g, g_len, (*dsactx)->g); + + (*dsactx)->pub_key = BN_new(); + BN_bin2bn(y, y_len, (*dsactx)->pub_key); + + if (x_len) { + (*dsactx)->priv_key = BN_new(); + BN_bin2bn(x, x_len, (*dsactx)->priv_key); + } + + return 0; +} + +int +_libssh2_dsa_sha1_verify(libssh2_dsa_ctx * dsactx, + const unsigned char *sig, + const unsigned char *m, unsigned long m_len) +{ + unsigned char hash[SHA_DIGEST_LENGTH]; + DSA_SIG dsasig; + int ret; + + dsasig.r = BN_new(); + BN_bin2bn(sig, 20, dsasig.r); + dsasig.s = BN_new(); + BN_bin2bn(sig + 20, 20, dsasig.s); + + libssh2_sha1(m, m_len, hash); + ret = DSA_do_verify(hash, SHA_DIGEST_LENGTH, &dsasig, dsactx); + BN_clear_free(dsasig.s); + BN_clear_free(dsasig.r); + + return (ret == 1) ? 0 : -1; +} +#endif /* LIBSSH_DSA */ + +int +_libssh2_cipher_init(_libssh2_cipher_ctx * h, + _libssh2_cipher_type(algo), + unsigned char *iv, unsigned char *secret, int encrypt) +{ + EVP_CIPHER_CTX_init(h); + return !EVP_CipherInit(h, algo(), secret, iv, encrypt); +} + +int +_libssh2_cipher_crypt(_libssh2_cipher_ctx * ctx, + _libssh2_cipher_type(algo), + int encrypt, unsigned char *block, size_t blocksize) +{ + unsigned char buf[EVP_MAX_BLOCK_LENGTH]; + int ret; + (void) algo; + (void) encrypt; + + ret = EVP_Cipher(ctx, buf, block, blocksize); + if (ret == 1) { + memcpy(block, buf, blocksize); + } + return ret == 1 ? 0 : 1; +} + +#if LIBSSH2_AES_CTR + +#include +#include + +typedef struct +{ + AES_KEY key; + EVP_CIPHER_CTX *aes_ctx; + unsigned char ctr[AES_BLOCK_SIZE]; +} aes_ctr_ctx; + +static int +aes_ctr_init(EVP_CIPHER_CTX *ctx, const unsigned char *key, + const unsigned char *iv, int enc) /* init key */ +{ + /* + * variable "c" is leaked from this scope, but is later freed + * in aes_ctr_cleanup + */ + aes_ctr_ctx *c; + const EVP_CIPHER *aes_cipher; + (void) enc; + + switch (ctx->key_len) { + case 16: + aes_cipher = EVP_aes_128_ecb(); + break; + case 24: + aes_cipher = EVP_aes_192_ecb(); + break; + case 32: + aes_cipher = EVP_aes_256_ecb(); + break; + default: + return 0; + } + + c = malloc(sizeof(*c)); + if (c == NULL) + return 0; + + c->aes_ctx = malloc(sizeof(EVP_CIPHER_CTX)); + if (c->aes_ctx == NULL) { + free(c); + return 0; + } + + if (EVP_EncryptInit(c->aes_ctx, aes_cipher, key, NULL) != 1) { + free(c->aes_ctx); + free(c); + return 0; + } + + EVP_CIPHER_CTX_set_padding(c->aes_ctx, 0); + + memcpy(c->ctr, iv, AES_BLOCK_SIZE); + + EVP_CIPHER_CTX_set_app_data(ctx, c); + + return 1; +} + +static int +aes_ctr_do_cipher(EVP_CIPHER_CTX *ctx, unsigned char *out, + const unsigned char *in, + size_t inl) /* encrypt/decrypt data */ +{ + aes_ctr_ctx *c = EVP_CIPHER_CTX_get_app_data(ctx); + unsigned char b1[AES_BLOCK_SIZE]; + size_t i = 0; + int outlen = 0; + + if (inl != 16) /* libssh2 only ever encrypt one block */ + return 0; + + if (c == NULL) { + return 0; + } + +/* + To encrypt a packet P=P1||P2||...||Pn (where P1, P2, ..., Pn are each + blocks of length L), the encryptor first encrypts with + to obtain a block B1. The block B1 is then XORed with P1 to generate + the ciphertext block C1. The counter X is then incremented +*/ + + if (EVP_EncryptUpdate(c->aes_ctx, b1, &outlen, c->ctr, AES_BLOCK_SIZE) != 1) { + return 0; + } + + for (i = 0; i < 16; i++) + *out++ = *in++ ^ b1[i]; + + i = 15; + while (c->ctr[i]++ == 0xFF) { + if (i == 0) + break; + i--; + } + + return 1; +} + +static int +aes_ctr_cleanup(EVP_CIPHER_CTX *ctx) /* cleanup ctx */ +{ + aes_ctr_ctx *c = EVP_CIPHER_CTX_get_app_data(ctx); + + if (c == NULL) { + return 1; + } + + if (c->aes_ctx != NULL) { + _libssh2_cipher_dtor(c->aes_ctx); + free(c->aes_ctx); + } + + free(c); + + return 1; +} + +static const EVP_CIPHER * +make_ctr_evp (size_t keylen, EVP_CIPHER *aes_ctr_cipher) +{ + aes_ctr_cipher->block_size = 16; + aes_ctr_cipher->key_len = keylen; + aes_ctr_cipher->iv_len = 16; + aes_ctr_cipher->init = aes_ctr_init; + aes_ctr_cipher->do_cipher = aes_ctr_do_cipher; + aes_ctr_cipher->cleanup = aes_ctr_cleanup; + + return aes_ctr_cipher; +} + +const EVP_CIPHER * +_libssh2_EVP_aes_128_ctr(void) +{ + static EVP_CIPHER aes_ctr_cipher; + return !aes_ctr_cipher.key_len? + make_ctr_evp (16, &aes_ctr_cipher) : &aes_ctr_cipher; +} + +const EVP_CIPHER * +_libssh2_EVP_aes_192_ctr(void) +{ + static EVP_CIPHER aes_ctr_cipher; + return !aes_ctr_cipher.key_len? + make_ctr_evp (24, &aes_ctr_cipher) : &aes_ctr_cipher; +} + +const EVP_CIPHER * +_libssh2_EVP_aes_256_ctr(void) +{ + static EVP_CIPHER aes_ctr_cipher; + return !aes_ctr_cipher.key_len? + make_ctr_evp (32, &aes_ctr_cipher) : &aes_ctr_cipher; +} + +void _libssh2_init_aes_ctr(void) +{ + _libssh2_EVP_aes_128_ctr(); + _libssh2_EVP_aes_192_ctr(); + _libssh2_EVP_aes_256_ctr(); +} + +#else +void _libssh2_init_aes_ctr(void) {} +#endif /* LIBSSH2_AES_CTR */ + +/* TODO: Optionally call a passphrase callback specified by the + * calling program + */ +static int +passphrase_cb(char *buf, int size, int rwflag, char *passphrase) +{ + int passphrase_len = strlen(passphrase); + (void) rwflag; + + if (passphrase_len > (size - 1)) { + passphrase_len = size - 1; + } + memcpy(buf, passphrase, passphrase_len); + buf[passphrase_len] = '\0'; + + return passphrase_len; +} + +typedef void * (*pem_read_bio_func)(BIO *, void **, pem_password_cb *, + void * u); + +static int +read_private_key_from_file(void ** key_ctx, + pem_read_bio_func read_private_key, + const char * filename, + unsigned const char *passphrase) +{ + BIO * bp; + + *key_ctx = NULL; + + bp = BIO_new_file(filename, "r"); + if (!bp) { + return -1; + } + + *key_ctx = read_private_key(bp, NULL, (pem_password_cb *) passphrase_cb, + (void *) passphrase); + + BIO_free(bp); + return (*key_ctx) ? 0 : -1; +} + +int +_libssh2_rsa_new_private(libssh2_rsa_ctx ** rsa, + LIBSSH2_SESSION * session, + const char *filename, unsigned const char *passphrase) +{ + pem_read_bio_func read_rsa = + (pem_read_bio_func) &PEM_read_bio_RSAPrivateKey; + (void) session; + + _libssh2_init_if_needed (); + + return read_private_key_from_file((void **) rsa, read_rsa, + filename, passphrase); +} + +#if LIBSSH2_DSA +int +_libssh2_dsa_new_private(libssh2_dsa_ctx ** dsa, + LIBSSH2_SESSION * session, + const char *filename, unsigned const char *passphrase) +{ + pem_read_bio_func read_dsa = + (pem_read_bio_func) &PEM_read_bio_DSAPrivateKey; + (void) session; + + _libssh2_init_if_needed (); + + return read_private_key_from_file((void **) dsa, read_dsa, + filename, passphrase); +} +#endif /* LIBSSH_DSA */ + +int +_libssh2_rsa_sha1_sign(LIBSSH2_SESSION * session, + libssh2_rsa_ctx * rsactx, + const unsigned char *hash, + size_t hash_len, + unsigned char **signature, size_t *signature_len) +{ + int ret; + unsigned char *sig; + unsigned int sig_len; + + sig_len = RSA_size(rsactx); + sig = LIBSSH2_ALLOC(session, sig_len); + + if (!sig) { + return -1; + } + + ret = RSA_sign(NID_sha1, hash, hash_len, sig, &sig_len, rsactx); + + if (!ret) { + LIBSSH2_FREE(session, sig); + return -1; + } + + *signature = sig; + *signature_len = sig_len; + + return 0; +} + +#if LIBSSH2_DSA +int +_libssh2_dsa_sha1_sign(libssh2_dsa_ctx * dsactx, + const unsigned char *hash, + unsigned long hash_len, unsigned char *signature) +{ + DSA_SIG *sig; + int r_len, s_len; + (void) hash_len; + + sig = DSA_do_sign(hash, SHA_DIGEST_LENGTH, dsactx); + if (!sig) { + return -1; + } + + r_len = BN_num_bytes(sig->r); + if (r_len < 1 || r_len > 20) { + DSA_SIG_free(sig); + return -1; + } + s_len = BN_num_bytes(sig->s); + if (s_len < 1 || s_len > 20) { + DSA_SIG_free(sig); + return -1; + } + + memset(signature, 0, 40); + + BN_bn2bin(sig->r, signature + (20 - r_len)); + BN_bn2bin(sig->s, signature + 20 + (20 - s_len)); + + DSA_SIG_free(sig); + + return 0; +} +#endif /* LIBSSH_DSA */ + +void +libssh2_sha1(const unsigned char *message, unsigned long len, + unsigned char *out) +{ + EVP_MD_CTX ctx; + + EVP_DigestInit(&ctx, EVP_get_digestbyname("sha1")); + EVP_DigestUpdate(&ctx, message, len); + EVP_DigestFinal(&ctx, out, NULL); +} + +void +libssh2_md5(const unsigned char *message, unsigned long len, + unsigned char *out) +{ + EVP_MD_CTX ctx; + + EVP_DigestInit(&ctx, EVP_get_digestbyname("md5")); + EVP_DigestUpdate(&ctx, message, len); + EVP_DigestFinal(&ctx, out, NULL); +} + +static unsigned char * +write_bn(unsigned char *buf, const BIGNUM *bn, int bn_bytes) +{ + unsigned char *p = buf; + + /* Left space for bn size which will be written below. */ + p += 4; + + *p = 0; + BN_bn2bin(bn, p + 1); + if (!(*(p + 1) & 0x80)) { + memmove(p, p + 1, --bn_bytes); + } + _libssh2_htonu32(p - 4, bn_bytes); /* Post write bn size. */ + + return p + bn_bytes; +} + +static unsigned char * +gen_publickey_from_rsa(LIBSSH2_SESSION *session, RSA *rsa, + size_t *key_len) +{ + int e_bytes, n_bytes; + unsigned long len; + unsigned char* key; + unsigned char* p; + + e_bytes = BN_num_bytes(rsa->e) + 1; + n_bytes = BN_num_bytes(rsa->n) + 1; + + /* Key form is "ssh-rsa" + e + n. */ + len = 4 + 7 + 4 + e_bytes + 4 + n_bytes; + + key = LIBSSH2_ALLOC(session, len); + if (key == NULL) { + return NULL; + } + + /* Process key encoding. */ + p = key; + + _libssh2_htonu32(p, 7); /* Key type. */ + p += 4; + memcpy(p, "ssh-rsa", 7); + p += 7; + + p = write_bn(p, rsa->e, e_bytes); + p = write_bn(p, rsa->n, n_bytes); + + *key_len = (size_t)(p - key); + return key; +} + +static unsigned char * +gen_publickey_from_dsa(LIBSSH2_SESSION* session, DSA *dsa, + size_t *key_len) +{ + int p_bytes, q_bytes, g_bytes, k_bytes; + unsigned long len; + unsigned char* key; + unsigned char* p; + + p_bytes = BN_num_bytes(dsa->p) + 1; + q_bytes = BN_num_bytes(dsa->q) + 1; + g_bytes = BN_num_bytes(dsa->g) + 1; + k_bytes = BN_num_bytes(dsa->pub_key) + 1; + + /* Key form is "ssh-dss" + p + q + g + pub_key. */ + len = 4 + 7 + 4 + p_bytes + 4 + q_bytes + 4 + g_bytes + 4 + k_bytes; + + key = LIBSSH2_ALLOC(session, len); + if (key == NULL) { + return NULL; + } + + /* Process key encoding. */ + p = key; + + _libssh2_htonu32(p, 7); /* Key type. */ + p += 4; + memcpy(p, "ssh-dss", 7); + p += 7; + + p = write_bn(p, dsa->p, p_bytes); + p = write_bn(p, dsa->q, q_bytes); + p = write_bn(p, dsa->g, g_bytes); + p = write_bn(p, dsa->pub_key, k_bytes); + + *key_len = (size_t)(p - key); + return key; +} + +static int +gen_publickey_from_rsa_evp(LIBSSH2_SESSION *session, + unsigned char **method, + size_t *method_len, + unsigned char **pubkeydata, + size_t *pubkeydata_len, + EVP_PKEY *pk) +{ + RSA* rsa = NULL; + unsigned char* key; + unsigned char* method_buf = NULL; + size_t key_len; + + _libssh2_debug(session, + LIBSSH2_TRACE_AUTH, + "Computing public key from RSA private key envelop"); + + rsa = EVP_PKEY_get1_RSA(pk); + if (rsa == NULL) { + /* Assume memory allocation error... what else could it be ? */ + goto __alloc_error; + } + + method_buf = LIBSSH2_ALLOC(session, 7); /* ssh-rsa. */ + if (method_buf == NULL) { + goto __alloc_error; + } + + key = gen_publickey_from_rsa(session, rsa, &key_len); + if (key == NULL) { + goto __alloc_error; + } + RSA_free(rsa); + + memcpy(method_buf, "ssh-rsa", 7); + *method = method_buf; + *method_len = 7; + *pubkeydata = key; + *pubkeydata_len = key_len; + return 0; + + __alloc_error: + if (rsa != NULL) { + RSA_free(rsa); + } + if (method_buf != NULL) { + LIBSSH2_FREE(session, method_buf); + } + + return _libssh2_error(session, + LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for private key data"); +} + +static int +gen_publickey_from_dsa_evp(LIBSSH2_SESSION *session, + unsigned char **method, + size_t *method_len, + unsigned char **pubkeydata, + size_t *pubkeydata_len, + EVP_PKEY *pk) +{ + DSA* dsa = NULL; + unsigned char* key; + unsigned char* method_buf = NULL; + size_t key_len; + + _libssh2_debug(session, + LIBSSH2_TRACE_AUTH, + "Computing public key from DSA private key envelop"); + + dsa = EVP_PKEY_get1_DSA(pk); + if (dsa == NULL) { + /* Assume memory allocation error... what else could it be ? */ + goto __alloc_error; + } + + method_buf = LIBSSH2_ALLOC(session, 7); /* ssh-dss. */ + if (method_buf == NULL) { + goto __alloc_error; + } + + key = gen_publickey_from_dsa(session, dsa, &key_len); + if (key == NULL) { + goto __alloc_error; + } + DSA_free(dsa); + + memcpy(method_buf, "ssh-dss", 7); + *method = method_buf; + *method_len = 7; + *pubkeydata = key; + *pubkeydata_len = key_len; + return 0; + + __alloc_error: + if (dsa != NULL) { + DSA_free(dsa); + } + if (method_buf != NULL) { + LIBSSH2_FREE(session, method_buf); + } + + return _libssh2_error(session, + LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for private key data"); +} + +int +_libssh2_pub_priv_keyfile(LIBSSH2_SESSION *session, + unsigned char **method, + size_t *method_len, + unsigned char **pubkeydata, + size_t *pubkeydata_len, + const char *privatekey, + const char *passphrase) +{ + int st; + BIO* bp; + EVP_PKEY* pk; + + _libssh2_debug(session, + LIBSSH2_TRACE_AUTH, + "Computing public key from private key file: %s", + privatekey); + + bp = BIO_new_file(privatekey, "r"); + if (bp == NULL) { + return _libssh2_error(session, + LIBSSH2_ERROR_FILE, + "Unable to extract public key from private key " + "file: Unable to open private key file"); + } + if (!EVP_get_cipherbyname("des")) { + /* If this cipher isn't loaded it's a pretty good indication that none + * are. I have *NO DOUBT* that there's a better way to deal with this + * ($#&%#$(%$#( Someone buy me an OpenSSL manual and I'll read up on + * it. + */ + OpenSSL_add_all_ciphers(); + } + BIO_reset(bp); + pk = PEM_read_bio_PrivateKey(bp, NULL, NULL, (void*)passphrase); + BIO_free(bp); + + if (pk == NULL) { + return _libssh2_error(session, + LIBSSH2_ERROR_FILE, + "Unable to extract public key " + "from private key file: " + "Wrong passphrase or invalid/unrecognized " + "private key file format"); + } + + switch (pk->type) { + case EVP_PKEY_RSA : + st = gen_publickey_from_rsa_evp( + session, method, method_len, pubkeydata, pubkeydata_len, pk); + break; + + case EVP_PKEY_DSA : + st = gen_publickey_from_dsa_evp( + session, method, method_len, pubkeydata, pubkeydata_len, pk); + break; + + default : + st = _libssh2_error(session, + LIBSSH2_ERROR_FILE, + "Unable to extract public key " + "from private key file: " + "Unsupported private key file format"); + break; + } + + EVP_PKEY_free(pk); + return st; +} + +#endif /* !LIBSSH2_LIBGCRYPT */ diff --git a/libssh2/src/packet.c b/libssh2/src/packet.c new file mode 100644 index 0000000..bfbd56a --- /dev/null +++ b/libssh2/src/packet.c @@ -0,0 +1,1243 @@ +/* Copyright (c) 2004-2007, Sara Golemon + * Copyright (c) 2005,2006 Mikhail Gusarov + * Copyright (c) 2009-2010 by Daniel Stenberg + * Copyright (c) 2010 Simon Josefsson + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include "libssh2_priv.h" +#include +#include + +#ifdef HAVE_UNISTD_H +#include +#endif + +#ifdef HAVE_SYS_TIME_H +#include +#endif + +#ifdef HAVE_INTTYPES_H +#include +#endif + +/* Needed for struct iovec on some platforms */ +#ifdef HAVE_SYS_UIO_H +#include +#endif + +#include + +#include "transport.h" +#include "channel.h" +#include "packet.h" + +/* + * libssh2_packet_queue_listener + * + * Queue a connection request for a listener + */ +static inline int +packet_queue_listener(LIBSSH2_SESSION * session, unsigned char *data, + unsigned long datalen, + packet_queue_listener_state_t *listen_state) +{ + /* + * Look for a matching listener + */ + /* 17 = packet_type(1) + channel(4) + reason(4) + descr(4) + lang(4) */ + unsigned long packet_len = 17 + (sizeof(FwdNotReq) - 1); + unsigned char *p; + LIBSSH2_LISTENER *listn = _libssh2_list_first(&session->listeners); + char failure_code = SSH_OPEN_ADMINISTRATIVELY_PROHIBITED; + int rc; + + (void) datalen; + + if (listen_state->state == libssh2_NB_state_idle) { + unsigned char *s = data + (sizeof("forwarded-tcpip") - 1) + 5; + listen_state->sender_channel = _libssh2_ntohu32(s); + s += 4; + + listen_state->initial_window_size = _libssh2_ntohu32(s); + s += 4; + listen_state->packet_size = _libssh2_ntohu32(s); + s += 4; + + listen_state->host_len = _libssh2_ntohu32(s); + s += 4; + listen_state->host = s; + s += listen_state->host_len; + listen_state->port = _libssh2_ntohu32(s); + s += 4; + + listen_state->shost_len = _libssh2_ntohu32(s); + s += 4; + listen_state->shost = s; + s += listen_state->shost_len; + listen_state->sport = _libssh2_ntohu32(s); + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Remote received connection from %s:%ld to %s:%ld", + listen_state->shost, listen_state->sport, + listen_state->host, listen_state->port); + + listen_state->state = libssh2_NB_state_allocated; + } + + if (listen_state->state != libssh2_NB_state_sent) { + while (listn) { + if ((listn->port == (int) listen_state->port) && + (strlen(listn->host) == listen_state->host_len) && + (memcmp (listn->host, listen_state->host, + listen_state->host_len) == 0)) { + /* This is our listener */ + LIBSSH2_CHANNEL *channel = NULL; + listen_state->channel = NULL; + + if (listen_state->state == libssh2_NB_state_allocated) { + if (listn->queue_maxsize && + (listn->queue_maxsize <= listn->queue_size)) { + /* Queue is full */ + failure_code = SSH_OPEN_RESOURCE_SHORTAGE; + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Listener queue full, ignoring"); + listen_state->state = libssh2_NB_state_sent; + break; + } + + channel = LIBSSH2_ALLOC(session, sizeof(LIBSSH2_CHANNEL)); + if (!channel) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate a channel for " + "new connection"); + failure_code = SSH_OPEN_RESOURCE_SHORTAGE; + listen_state->state = libssh2_NB_state_sent; + break; + } + listen_state->channel = channel; + + memset(channel, 0, sizeof(LIBSSH2_CHANNEL)); + + channel->session = session; + channel->channel_type_len = sizeof("forwarded-tcpip") - 1; + channel->channel_type = LIBSSH2_ALLOC(session, + channel-> + channel_type_len + + 1); + if (!channel->channel_type) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate a channel for new" + " connection"); + LIBSSH2_FREE(session, channel); + failure_code = SSH_OPEN_RESOURCE_SHORTAGE; + listen_state->state = libssh2_NB_state_sent; + break; + } + memcpy(channel->channel_type, "forwarded-tcpip", + channel->channel_type_len + 1); + + channel->remote.id = listen_state->sender_channel; + channel->remote.window_size_initial = + LIBSSH2_CHANNEL_WINDOW_DEFAULT; + channel->remote.window_size = + LIBSSH2_CHANNEL_WINDOW_DEFAULT; + channel->remote.packet_size = + LIBSSH2_CHANNEL_PACKET_DEFAULT; + + channel->local.id = _libssh2_channel_nextid(session); + channel->local.window_size_initial = + listen_state->initial_window_size; + channel->local.window_size = + listen_state->initial_window_size; + channel->local.packet_size = listen_state->packet_size; + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Connection queued: channel %lu/%lu " + "win %lu/%lu packet %lu/%lu", + channel->local.id, channel->remote.id, + channel->local.window_size, + channel->remote.window_size, + channel->local.packet_size, + channel->remote.packet_size); + + p = listen_state->packet; + *(p++) = SSH_MSG_CHANNEL_OPEN_CONFIRMATION; + _libssh2_store_u32(&p, channel->remote.id); + _libssh2_store_u32(&p, channel->local.id); + _libssh2_store_u32(&p, + channel->remote.window_size_initial); + _libssh2_store_u32(&p, channel->remote.packet_size); + + listen_state->state = libssh2_NB_state_created; + } + + if (listen_state->state == libssh2_NB_state_created) { + rc = _libssh2_transport_send(session, listen_state->packet, + 17, NULL, 0); + if (rc == LIBSSH2_ERROR_EAGAIN) + return rc; + else if (rc) { + listen_state->state = libssh2_NB_state_idle; + return _libssh2_error(session, rc, + "Unable to send channel " + "open confirmation"); + } + + /* Link the channel into the end of the queue list */ + _libssh2_list_add(&listn->queue, + &listen_state->channel->node); + listn->queue_size++; + + listen_state->state = libssh2_NB_state_idle; + return 0; + } + } + + listn = _libssh2_list_next(&listn->node); + } + + listen_state->state = libssh2_NB_state_sent; + } + + /* We're not listening to you */ + p = listen_state->packet; + *(p++) = SSH_MSG_CHANNEL_OPEN_FAILURE; + _libssh2_store_u32(&p, listen_state->sender_channel); + _libssh2_store_u32(&p, failure_code); + _libssh2_store_str(&p, FwdNotReq, sizeof(FwdNotReq) - 1); + _libssh2_htonu32(p, 0); + + rc = _libssh2_transport_send(session, listen_state->packet, + packet_len, NULL, 0); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } else if (rc) { + listen_state->state = libssh2_NB_state_idle; + return _libssh2_error(session, rc, "Unable to send open failure"); + + } + listen_state->state = libssh2_NB_state_idle; + return 0; +} + +/* + * packet_x11_open + * + * Accept a forwarded X11 connection + */ +static inline int +packet_x11_open(LIBSSH2_SESSION * session, unsigned char *data, + unsigned long datalen, + packet_x11_open_state_t *x11open_state) +{ + int failure_code = SSH_OPEN_CONNECT_FAILED; + /* 17 = packet_type(1) + channel(4) + reason(4) + descr(4) + lang(4) */ + unsigned long packet_len = 17 + (sizeof(X11FwdUnAvil) - 1); + unsigned char *p; + LIBSSH2_CHANNEL *channel = x11open_state->channel; + int rc; + + (void) datalen; + + if (x11open_state->state == libssh2_NB_state_idle) { + unsigned char *s = data + (sizeof("x11") - 1) + 5; + x11open_state->sender_channel = _libssh2_ntohu32(s); + s += 4; + x11open_state->initial_window_size = _libssh2_ntohu32(s); + s += 4; + x11open_state->packet_size = _libssh2_ntohu32(s); + s += 4; + x11open_state->shost_len = _libssh2_ntohu32(s); + s += 4; + x11open_state->shost = s; + s += x11open_state->shost_len; + x11open_state->sport = _libssh2_ntohu32(s); + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "X11 Connection Received from %s:%ld on channel %lu", + x11open_state->shost, x11open_state->sport, + x11open_state->sender_channel); + + x11open_state->state = libssh2_NB_state_allocated; + } + + if (session->x11) { + if (x11open_state->state == libssh2_NB_state_allocated) { + channel = LIBSSH2_ALLOC(session, sizeof(LIBSSH2_CHANNEL)); + if (!channel) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "allocate a channel for new connection"); + failure_code = SSH_OPEN_RESOURCE_SHORTAGE; + goto x11_exit; + } + memset(channel, 0, sizeof(LIBSSH2_CHANNEL)); + + channel->session = session; + channel->channel_type_len = sizeof("x11") - 1; + channel->channel_type = LIBSSH2_ALLOC(session, + channel->channel_type_len + + 1); + if (!channel->channel_type) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "allocate a channel for new connection"); + LIBSSH2_FREE(session, channel); + failure_code = SSH_OPEN_RESOURCE_SHORTAGE; + goto x11_exit; + } + memcpy(channel->channel_type, "x11", + channel->channel_type_len + 1); + + channel->remote.id = x11open_state->sender_channel; + channel->remote.window_size_initial = + LIBSSH2_CHANNEL_WINDOW_DEFAULT; + channel->remote.window_size = LIBSSH2_CHANNEL_WINDOW_DEFAULT; + channel->remote.packet_size = LIBSSH2_CHANNEL_PACKET_DEFAULT; + + channel->local.id = _libssh2_channel_nextid(session); + channel->local.window_size_initial = + x11open_state->initial_window_size; + channel->local.window_size = x11open_state->initial_window_size; + channel->local.packet_size = x11open_state->packet_size; + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "X11 Connection established: channel %lu/%lu " + "win %lu/%lu packet %lu/%lu", + channel->local.id, channel->remote.id, + channel->local.window_size, + channel->remote.window_size, + channel->local.packet_size, + channel->remote.packet_size); + p = x11open_state->packet; + *(p++) = SSH_MSG_CHANNEL_OPEN_CONFIRMATION; + _libssh2_store_u32(&p, channel->remote.id); + _libssh2_store_u32(&p, channel->local.id); + _libssh2_store_u32(&p, channel->remote.window_size_initial); + _libssh2_store_u32(&p, channel->remote.packet_size); + + x11open_state->state = libssh2_NB_state_created; + } + + if (x11open_state->state == libssh2_NB_state_created) { + rc = _libssh2_transport_send(session, x11open_state->packet, 17, + NULL, 0); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } else if (rc) { + x11open_state->state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send channel open " + "confirmation"); + } + + /* Link the channel into the session */ + _libssh2_list_add(&session->channels, &channel->node); + + /* + * Pass control to the callback, they may turn right around and + * free the channel, or actually use it + */ + LIBSSH2_X11_OPEN(channel, (char *)x11open_state->shost, + x11open_state->sport); + + x11open_state->state = libssh2_NB_state_idle; + return 0; + } + } + else + failure_code = SSH_OPEN_RESOURCE_SHORTAGE; + /* fall-trough */ + x11_exit: + p = x11open_state->packet; + *(p++) = SSH_MSG_CHANNEL_OPEN_FAILURE; + _libssh2_store_u32(&p, x11open_state->sender_channel); + _libssh2_store_u32(&p, failure_code); + _libssh2_store_str(&p, X11FwdUnAvil, sizeof(X11FwdUnAvil) - 1); + _libssh2_htonu32(p, 0); + + rc = _libssh2_transport_send(session, x11open_state->packet, packet_len, + NULL, 0); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } else if (rc) { + x11open_state->state = libssh2_NB_state_idle; + return _libssh2_error(session, rc, "Unable to send open failure"); + } + x11open_state->state = libssh2_NB_state_idle; + return 0; +} + +/* + * _libssh2_packet_add + * + * Create a new packet and attach it to the brigade. Called from the transport + * layer when it has received a packet. + * + * The input pointer 'data' is pointing to allocated data that this function + * is asked to deal with so on failure OR success, it must be freed fine. + * + * This function will always be called with 'datalen' greater than zero. + */ +int +_libssh2_packet_add(LIBSSH2_SESSION * session, unsigned char *data, + size_t datalen, int macstate) +{ + int rc = 0; + char *message=NULL; + char *language=NULL; + size_t message_len=0; + size_t language_len=0; + LIBSSH2_CHANNEL *channelp = NULL; + size_t data_head = 0; + unsigned char msg = data[0]; + + switch(session->packAdd_state) { + case libssh2_NB_state_idle: + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, + "Packet type %d received, length=%d", + (int) msg, (int) datalen); + + if ((macstate == LIBSSH2_MAC_INVALID) && + (!session->macerror || + LIBSSH2_MACERROR(session, (char *) data, datalen))) { + /* Bad MAC input, but no callback set or non-zero return from the + callback */ + + LIBSSH2_FREE(session, data); + return _libssh2_error(session, LIBSSH2_ERROR_INVALID_MAC, + "Invalid MAC received"); + } + session->packAdd_state = libssh2_NB_state_allocated; + break; + case libssh2_NB_state_jump1: + goto libssh2_packet_add_jump_point1; + case libssh2_NB_state_jump2: + goto libssh2_packet_add_jump_point2; + case libssh2_NB_state_jump3: + goto libssh2_packet_add_jump_point3; + case libssh2_NB_state_jump4: + goto libssh2_packet_add_jump_point4; + case libssh2_NB_state_jump5: + goto libssh2_packet_add_jump_point5; + default: /* nothing to do */ + break; + } + + if (session->packAdd_state == libssh2_NB_state_allocated) { + /* A couple exceptions to the packet adding rule: */ + switch (msg) { + + /* + byte SSH_MSG_DISCONNECT + uint32 reason code + string description in ISO-10646 UTF-8 encoding [RFC3629] + string language tag [RFC3066] + */ + + case SSH_MSG_DISCONNECT: + if(datalen >= 5) { + size_t reason = _libssh2_ntohu32(data + 1); + + if(datalen >= 9) { + message_len = _libssh2_ntohu32(data + 5); + + if(message_len < datalen-13) { + /* 9 = packet_type(1) + reason(4) + message_len(4) */ + message = (char *) data + 9; + + language_len = _libssh2_ntohu32(data + 9 + message_len); + language = (char *) data + 9 + message_len + 4; + + if(language_len > (datalen-13-message_len)) { + /* bad input, clear info */ + language = message = NULL; + language_len = message_len = 0; + } + } + else + /* bad size, clear it */ + message_len=0; + } + if (session->ssh_msg_disconnect) { + LIBSSH2_DISCONNECT(session, reason, message, + message_len, language, language_len); + } + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, + "Disconnect(%d): %s(%s)", reason, + message, language); + } + + LIBSSH2_FREE(session, data); + session->socket_state = LIBSSH2_SOCKET_DISCONNECTED; + session->packAdd_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_DISCONNECT, + "socket disconnect"); + /* + byte SSH_MSG_IGNORE + string data + */ + + case SSH_MSG_IGNORE: + if (datalen >= 2) { + if (session->ssh_msg_ignore) { + LIBSSH2_IGNORE(session, (char *) data + 1, datalen - 1); + } + } else if (session->ssh_msg_ignore) { + LIBSSH2_IGNORE(session, "", 0); + } + LIBSSH2_FREE(session, data); + session->packAdd_state = libssh2_NB_state_idle; + return 0; + + /* + byte SSH_MSG_DEBUG + boolean always_display + string message in ISO-10646 UTF-8 encoding [RFC3629] + string language tag [RFC3066] + */ + + case SSH_MSG_DEBUG: + if(datalen >= 2) { + int always_display= data[1]; + + if(datalen >= 6) { + message_len = _libssh2_ntohu32(data + 2); + + if(message_len <= (datalen - 10)) { + /* 6 = packet_type(1) + display(1) + message_len(4) */ + message = (char *) data + 6; + language_len = _libssh2_ntohu32(data + 6 + message_len); + + if(language_len <= (datalen - 10 - message_len)) + language = (char *) data + 10 + message_len; + } + } + + if (session->ssh_msg_debug) { + LIBSSH2_DEBUG(session, always_display, message, + message_len, language, language_len); + } + } + /* + * _libssh2_debug will actually truncate this for us so + * that it's not an inordinate about of data + */ + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, + "Debug Packet: %s", message); + LIBSSH2_FREE(session, data); + session->packAdd_state = libssh2_NB_state_idle; + return 0; + + /* + byte SSH_MSG_GLOBAL_REQUEST + string request name in US-ASCII only + boolean want reply + .... request-specific data follows + */ + + case SSH_MSG_GLOBAL_REQUEST: + if(datalen >= 5) { + uint32_t len =0; + unsigned char want_reply=0; + len = _libssh2_ntohu32(data + 1); + if(datalen >= (6 + len)) { + want_reply = data[5 + len]; + _libssh2_debug(session, + LIBSSH2_TRACE_CONN, + "Received global request type %.*s (wr %X)", + len, data + 5, want_reply); + } + + + if (want_reply) { + unsigned char packet = SSH_MSG_REQUEST_FAILURE; + libssh2_packet_add_jump_point5: + session->packAdd_state = libssh2_NB_state_jump5; + rc = _libssh2_transport_send(session, &packet, 1, NULL, 0); + if (rc == LIBSSH2_ERROR_EAGAIN) + return rc; + } + } + LIBSSH2_FREE(session, data); + session->packAdd_state = libssh2_NB_state_idle; + return 0; + + /* + byte SSH_MSG_CHANNEL_EXTENDED_DATA + uint32 recipient channel + uint32 data_type_code + string data + */ + + case SSH_MSG_CHANNEL_EXTENDED_DATA: + /* streamid(4) */ + data_head += 4; + + /* fall-through */ + + /* + byte SSH_MSG_CHANNEL_DATA + uint32 recipient channel + string data + */ + + case SSH_MSG_CHANNEL_DATA: + /* packet_type(1) + channelno(4) + datalen(4) */ + data_head += 9; + + if(datalen >= data_head) + channelp = + _libssh2_channel_locate(session, + _libssh2_ntohu32(data + 1)); + + if (!channelp) { + _libssh2_error(session, LIBSSH2_ERROR_CHANNEL_UNKNOWN, + "Packet received for unknown channel"); + LIBSSH2_FREE(session, data); + session->packAdd_state = libssh2_NB_state_idle; + return 0; + } +#ifdef LIBSSH2DEBUG + { + uint32_t stream_id = 0; + if (msg == SSH_MSG_CHANNEL_EXTENDED_DATA) + stream_id = _libssh2_ntohu32(data + 5); + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "%d bytes packet_add() for %lu/%lu/%lu", + (int) (datalen - data_head), + channelp->local.id, + channelp->remote.id, + stream_id); + } +#endif + if ((channelp->remote.extended_data_ignore_mode == + LIBSSH2_CHANNEL_EXTENDED_DATA_IGNORE) && + (msg == SSH_MSG_CHANNEL_EXTENDED_DATA)) { + /* Pretend we didn't receive this */ + LIBSSH2_FREE(session, data); + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Ignoring extended data and refunding %d bytes", + (int) (datalen - 13)); + session->packAdd_channelp = channelp; + + /* Adjust the window based on the block we just freed */ + libssh2_packet_add_jump_point1: + session->packAdd_state = libssh2_NB_state_jump1; + rc = _libssh2_channel_receive_window_adjust(session-> + packAdd_channelp, + datalen - 13, + 1, NULL); + if (rc == LIBSSH2_ERROR_EAGAIN) + return rc; + + session->packAdd_state = libssh2_NB_state_idle; + return 0; + } + + /* + * REMEMBER! remote means remote as source of data, + * NOT remote window! + */ + if (channelp->remote.packet_size < (datalen - data_head)) { + /* + * Spec says we MAY ignore bytes sent beyond + * packet_size + */ + _libssh2_error(session, + LIBSSH2_ERROR_CHANNEL_PACKET_EXCEEDED, + "Packet contains more data than we offered" + " to receive, truncating"); + datalen = channelp->remote.packet_size + data_head; + } + if (channelp->remote.window_size <= 0) { + /* + * Spec says we MAY ignore bytes sent beyond + * window_size + */ + _libssh2_error(session, + LIBSSH2_ERROR_CHANNEL_WINDOW_EXCEEDED, + "The current receive window is full," + " data ignored"); + LIBSSH2_FREE(session, data); + session->packAdd_state = libssh2_NB_state_idle; + return 0; + } + /* Reset EOF status */ + channelp->remote.eof = 0; + + if ((datalen - data_head) > channelp->remote.window_size) { + _libssh2_error(session, + LIBSSH2_ERROR_CHANNEL_WINDOW_EXCEEDED, + "Remote sent more data than current " + "window allows, truncating"); + datalen = channelp->remote.window_size + data_head; + channelp->remote.window_size = 0; + } + else + /* Now that we've received it, shrink our window */ + channelp->remote.window_size -= datalen - data_head; + + break; + + /* + byte SSH_MSG_CHANNEL_EOF + uint32 recipient channel + */ + + case SSH_MSG_CHANNEL_EOF: + if(datalen >= 5) + channelp = + _libssh2_channel_locate(session, + _libssh2_ntohu32(data + 1)); + if (!channelp) + /* We may have freed already, just quietly ignore this... */ + ; + else { + _libssh2_debug(session, + LIBSSH2_TRACE_CONN, + "EOF received for channel %lu/%lu", + channelp->local.id, + channelp->remote.id); + channelp->remote.eof = 1; + } + LIBSSH2_FREE(session, data); + session->packAdd_state = libssh2_NB_state_idle; + return 0; + + /* + byte SSH_MSG_CHANNEL_REQUEST + uint32 recipient channel + string request type in US-ASCII characters only + boolean want reply + .... type-specific data follows + */ + + case SSH_MSG_CHANNEL_REQUEST: + if(datalen >= 9) { + uint32_t channel = _libssh2_ntohu32(data + 1); + uint32_t len = _libssh2_ntohu32(data + 5); + unsigned char want_reply = 1; + + if(len < (datalen - 10)) + want_reply = data[9 + len]; + + _libssh2_debug(session, + LIBSSH2_TRACE_CONN, + "Channel %d received request type %.*s (wr %X)", + channel, len, data + 9, want_reply); + + if (len == sizeof("exit-status") - 1 + && !memcmp("exit-status", data + 9, + sizeof("exit-status") - 1)) { + + /* we've got "exit-status" packet. Set the session value */ + if(datalen >= 20) + channelp = + _libssh2_channel_locate(session, channel); + + if (channelp) { + channelp->exit_status = + _libssh2_ntohu32(data + 9 + sizeof("exit-status")); + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Exit status %lu received for " + "channel %lu/%lu", + channelp->exit_status, + channelp->local.id, + channelp->remote.id); + } + + } + else if (len == sizeof("exit-signal") - 1 + && !memcmp("exit-signal", data + 9, + sizeof("exit-signal") - 1)) { + /* command terminated due to signal */ + if(datalen >= 20) + channelp = _libssh2_channel_locate(session, channel); + + if (channelp) { + /* set signal name (without SIG prefix) */ + uint32_t namelen = + _libssh2_ntohu32(data + 9 + sizeof("exit-signal")); + channelp->exit_signal = + LIBSSH2_ALLOC(session, namelen + 1); + if (!channelp->exit_signal) + rc = _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "memory for signal name"); + else { + memcpy(channelp->exit_signal, + data + 13 + sizeof("exit_signal"), namelen); + channelp->exit_signal[namelen] = '\0'; + /* TODO: save error message and language tag */ + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Exit signal %s received for " + "channel %lu/%lu", + channelp->exit_signal, + channelp->local.id, + channelp->remote.id); + } + } + } + + + if (want_reply) { + unsigned char packet[5]; + libssh2_packet_add_jump_point4: + session->packAdd_state = libssh2_NB_state_jump4; + packet[0] = SSH_MSG_CHANNEL_FAILURE; + memcpy(&packet[1], data+1, 4); + rc = _libssh2_transport_send(session, packet, 5, NULL, 0); + if (rc == LIBSSH2_ERROR_EAGAIN) + return rc; + } + } + LIBSSH2_FREE(session, data); + session->packAdd_state = libssh2_NB_state_idle; + return rc; + + /* + byte SSH_MSG_CHANNEL_CLOSE + uint32 recipient channel + */ + + case SSH_MSG_CHANNEL_CLOSE: + if(datalen >= 5) + channelp = + _libssh2_channel_locate(session, + _libssh2_ntohu32(data + 1)); + if (!channelp) { + /* We may have freed already, just quietly ignore this... */ + LIBSSH2_FREE(session, data); + session->packAdd_state = libssh2_NB_state_idle; + return 0; + } + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Close received for channel %lu/%lu", + channelp->local.id, + channelp->remote.id); + + channelp->remote.close = 1; + channelp->remote.eof = 1; + + LIBSSH2_FREE(session, data); + session->packAdd_state = libssh2_NB_state_idle; + return 0; + + /* + byte SSH_MSG_CHANNEL_OPEN + string "session" + uint32 sender channel + uint32 initial window size + uint32 maximum packet size + */ + + case SSH_MSG_CHANNEL_OPEN: + if(datalen < 17) + ; + else if ((datalen >= (sizeof("forwarded-tcpip") + 4)) && + ((sizeof("forwarded-tcpip") - 1) == + _libssh2_ntohu32(data + 1)) + && + (memcmp(data + 5, "forwarded-tcpip", + sizeof("forwarded-tcpip") - 1) == 0)) { + + /* init the state struct */ + memset(&session->packAdd_Qlstn_state, 0, + sizeof(session->packAdd_Qlstn_state)); + + libssh2_packet_add_jump_point2: + session->packAdd_state = libssh2_NB_state_jump2; + rc = packet_queue_listener(session, data, datalen, + &session->packAdd_Qlstn_state); + } + else if ((datalen >= (sizeof("x11") + 4)) && + ((sizeof("x11") - 1) == _libssh2_ntohu32(data + 1)) && + (memcmp(data + 5, "x11", sizeof("x11") - 1) == 0)) { + + /* init the state struct */ + memset(&session->packAdd_x11open_state, 0, + sizeof(session->packAdd_x11open_state)); + + libssh2_packet_add_jump_point3: + session->packAdd_state = libssh2_NB_state_jump3; + rc = packet_x11_open(session, data, datalen, + &session->packAdd_x11open_state); + } + if (rc == LIBSSH2_ERROR_EAGAIN) + return rc; + + LIBSSH2_FREE(session, data); + session->packAdd_state = libssh2_NB_state_idle; + return rc; + + /* + byte SSH_MSG_CHANNEL_WINDOW_ADJUST + uint32 recipient channel + uint32 bytes to add + */ + case SSH_MSG_CHANNEL_WINDOW_ADJUST: + if(datalen < 9) + ; + else { + uint32_t bytestoadd = _libssh2_ntohu32(data + 5); + channelp = + _libssh2_channel_locate(session, + _libssh2_ntohu32(data + 1)); + if(channelp) { + channelp->local.window_size += bytestoadd; + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Window adjust for channel %lu/%lu, " + "adding %lu bytes, new window_size=%lu", + channelp->local.id, + channelp->remote.id, + bytestoadd, + channelp->local.window_size); + } + } + LIBSSH2_FREE(session, data); + session->packAdd_state = libssh2_NB_state_idle; + return 0; + default: + break; + } + + session->packAdd_state = libssh2_NB_state_sent; + } + + if (session->packAdd_state == libssh2_NB_state_sent) { + LIBSSH2_PACKET *packetp = + LIBSSH2_ALLOC(session, sizeof(LIBSSH2_PACKET)); + if (!packetp) { + _libssh2_debug(session, LIBSSH2_ERROR_ALLOC, + "memory for packet"); + session->packAdd_state = libssh2_NB_state_idle; + return LIBSSH2_ERROR_ALLOC; + } + packetp->data = data; + packetp->data_len = datalen; + packetp->data_head = data_head; + + _libssh2_list_add(&session->packets, &packetp->node); + + session->packAdd_state = libssh2_NB_state_sent1; + } + + if ((msg == SSH_MSG_KEXINIT && + !(session->state & LIBSSH2_STATE_EXCHANGING_KEYS)) || + (session->packAdd_state == libssh2_NB_state_sent2)) { + if (session->packAdd_state == libssh2_NB_state_sent1) { + /* + * Remote wants new keys + * Well, it's already in the brigade, + * let's just call back into ourselves + */ + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, "Renegotiating Keys"); + + session->packAdd_state = libssh2_NB_state_sent2; + } + + /* + * The KEXINIT message has been added to the queue. The packAdd and + * readPack states need to be reset because _libssh2_kex_exchange + * (eventually) calls upon _libssh2_transport_read to read the rest of + * the key exchange conversation. + */ + session->readPack_state = libssh2_NB_state_idle; + session->packet.total_num = 0; + session->packAdd_state = libssh2_NB_state_idle; + session->fullpacket_state = libssh2_NB_state_idle; + + memset(&session->startup_key_state, 0, sizeof(key_exchange_state_t)); + + /* + * If there was a key reexchange failure, let's just hope we didn't + * send NEWKEYS yet, otherwise remote will drop us like a rock + */ + rc = _libssh2_kex_exchange(session, 1, &session->startup_key_state); + if (rc == LIBSSH2_ERROR_EAGAIN) + return rc; + } + + session->packAdd_state = libssh2_NB_state_idle; + return 0; +} + +/* + * _libssh2_packet_ask + * + * Scan the brigade for a matching packet type, optionally poll the socket for + * a packet first + */ +int +_libssh2_packet_ask(LIBSSH2_SESSION * session, unsigned char packet_type, + unsigned char **data, size_t *data_len, + int match_ofs, const unsigned char *match_buf, + size_t match_len) +{ + LIBSSH2_PACKET *packet = _libssh2_list_first(&session->packets); + + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, + "Looking for packet of type: %d", (int) packet_type); + + while (packet) { + if (packet->data[0] == packet_type + && (packet->data_len >= (match_ofs + match_len)) + && (!match_buf || + (memcmp(packet->data + match_ofs, match_buf, + match_len) == 0))) { + *data = packet->data; + *data_len = packet->data_len; + + /* unlink struct from session->packets */ + _libssh2_list_remove(&packet->node); + + LIBSSH2_FREE(session, packet); + + return 0; + } + packet = _libssh2_list_next(&packet->node); + } + return -1; +} + +/* + * libssh2_packet_askv + * + * Scan for any of a list of packet types in the brigade, optionally poll the + * socket for a packet first + */ +int +_libssh2_packet_askv(LIBSSH2_SESSION * session, + const unsigned char *packet_types, + unsigned char **data, size_t *data_len, + int match_ofs, + const unsigned char *match_buf, + size_t match_len) +{ + int i, packet_types_len = strlen((char *) packet_types); + + for(i = 0; i < packet_types_len; i++) { + if (0 == _libssh2_packet_ask(session, packet_types[i], data, + data_len, match_ofs, + match_buf, match_len)) { + return 0; + } + } + + return -1; +} + +/* + * _libssh2_packet_require + * + * Loops _libssh2_transport_read() until the packet requested is available + * SSH_DISCONNECT or a SOCKET_DISCONNECTED will cause a bailout + * + * Returns negative on error + * Returns 0 when it has taken care of the requested packet. + */ +int +_libssh2_packet_require(LIBSSH2_SESSION * session, unsigned char packet_type, + unsigned char **data, size_t *data_len, + int match_ofs, + const unsigned char *match_buf, + size_t match_len, + packet_require_state_t *state) +{ + if (state->start == 0) { + if (_libssh2_packet_ask(session, packet_type, data, data_len, + match_ofs, match_buf, + match_len) == 0) { + /* A packet was available in the packet brigade */ + return 0; + } + + state->start = time(NULL); + } + + while (session->socket_state == LIBSSH2_SOCKET_CONNECTED) { + int ret = _libssh2_transport_read(session); + if (ret == LIBSSH2_ERROR_EAGAIN) + return ret; + else if (ret < 0) { + state->start = 0; + /* an error which is not just because of blocking */ + return ret; + } else if (ret == packet_type) { + /* Be lazy, let packet_ask pull it out of the brigade */ + ret = _libssh2_packet_ask(session, packet_type, data, data_len, + match_ofs, match_buf, match_len); + state->start = 0; + return ret; + } else if (ret == 0) { + /* nothing available, wait until data arrives or we time out */ + long left = LIBSSH2_READ_TIMEOUT - (long)(time(NULL) - + state->start); + + if (left <= 0) { + state->start = 0; + return LIBSSH2_ERROR_TIMEOUT; + } + return -1; /* no packet available yet */ + } + } + + /* Only reached if the socket died */ + return LIBSSH2_ERROR_SOCKET_DISCONNECT; +} + +/* + * _libssh2_packet_burn + * + * Loops _libssh2_transport_read() until any packet is available and promptly + * discards it. + * Used during KEX exchange to discard badly guessed KEX_INIT packets + */ +int +_libssh2_packet_burn(LIBSSH2_SESSION * session, + libssh2_nonblocking_states * state) +{ + unsigned char *data; + size_t data_len; + unsigned char all_packets[255]; + int i; + int ret; + + if (*state == libssh2_NB_state_idle) { + for(i = 1; i < 256; i++) { + all_packets[i - 1] = i; + } + + if (_libssh2_packet_askv(session, all_packets, &data, &data_len, 0, + NULL, 0) == 0) { + i = data[0]; + /* A packet was available in the packet brigade, burn it */ + LIBSSH2_FREE(session, data); + return i; + } + + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, + "Blocking until packet becomes available to burn"); + *state = libssh2_NB_state_created; + } + + while (session->socket_state == LIBSSH2_SOCKET_CONNECTED) { + ret = _libssh2_transport_read(session); + if (ret == LIBSSH2_ERROR_EAGAIN) { + return ret; + } else if (ret < 0) { + *state = libssh2_NB_state_idle; + return ret; + } else if (ret == 0) { + /* FIXME: this might busyloop */ + continue; + } + + /* Be lazy, let packet_ask pull it out of the brigade */ + if (0 == + _libssh2_packet_ask(session, ret, &data, &data_len, 0, NULL, 0)) { + /* Smoke 'em if you got 'em */ + LIBSSH2_FREE(session, data); + *state = libssh2_NB_state_idle; + return ret; + } + } + + /* Only reached if the socket died */ + return LIBSSH2_ERROR_SOCKET_DISCONNECT; +} + +/* + * _libssh2_packet_requirev + * + * Loops _libssh2_transport_read() until one of a list of packet types + * requested is available. SSH_DISCONNECT or a SOCKET_DISCONNECTED will cause + * a bailout. packet_types is a null terminated list of packet_type numbers + */ + +int +_libssh2_packet_requirev(LIBSSH2_SESSION *session, + const unsigned char *packet_types, + unsigned char **data, size_t *data_len, + int match_ofs, + const unsigned char *match_buf, size_t match_len, + packet_requirev_state_t * state) +{ + if (_libssh2_packet_askv(session, packet_types, data, data_len, match_ofs, + match_buf, match_len) == 0) { + /* One of the packets listed was available in the packet brigade */ + state->start = 0; + return 0; + } + + if (state->start == 0) { + state->start = time(NULL); + } + + while (session->socket_state != LIBSSH2_SOCKET_DISCONNECTED) { + int ret = _libssh2_transport_read(session); + if ((ret < 0) && (ret != LIBSSH2_ERROR_EAGAIN)) { + state->start = 0; + return ret; + } + if (ret <= 0) { + long left = LIBSSH2_READ_TIMEOUT - + (long)(time(NULL) - state->start); + + if (left <= 0) { + state->start = 0; + return LIBSSH2_ERROR_TIMEOUT; + } + else if (ret == LIBSSH2_ERROR_EAGAIN) { + return ret; + } + } + + if (strchr((char *) packet_types, ret)) { + /* Be lazy, let packet_ask pull it out of the brigade */ + return _libssh2_packet_askv(session, packet_types, data, + data_len, match_ofs, match_buf, + match_len); + } + } + + /* Only reached if the socket died */ + state->start = 0; + return LIBSSH2_ERROR_SOCKET_DISCONNECT; +} + diff --git a/libssh2/src/pem.c b/libssh2/src/pem.c new file mode 100644 index 0000000..5749bc8 --- /dev/null +++ b/libssh2/src/pem.c @@ -0,0 +1,213 @@ +/* Copyright (C) 2007 The Written Word, Inc. + * Copyright (C) 2008, Simon Josefsson + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include "libssh2_priv.h" + +#ifdef LIBSSH2_LIBGCRYPT /* compile only if we build with libgcrypt */ + +static int +readline(char *line, int line_size, FILE * fp) +{ + if (!fgets(line, line_size, fp)) { + return -1; + } + if (*line && line[strlen(line) - 1] == '\n') { + line[strlen(line) - 1] = '\0'; + } + if (*line && line[strlen(line) - 1] == '\r') { + line[strlen(line) - 1] = '\0'; + } + return 0; +} + +#define LINE_SIZE 128 + +int +_libssh2_pem_parse(LIBSSH2_SESSION * session, + const char *headerbegin, + const char *headerend, + FILE * fp, unsigned char **data, unsigned int *datalen) +{ + char line[LINE_SIZE]; + char *b64data = NULL; + unsigned int b64datalen = 0; + int ret; + + do { + if (readline(line, LINE_SIZE, fp)) { + return -1; + } + } + while (strcmp(line, headerbegin) != 0); + + *line = '\0'; + + do { + if (*line) { + char *tmp; + size_t linelen; + + linelen = strlen(line); + tmp = LIBSSH2_REALLOC(session, b64data, b64datalen + linelen); + if (!tmp) { + ret = -1; + goto out; + } + memcpy(tmp + b64datalen, line, linelen); + b64data = tmp; + b64datalen += linelen; + } + + if (readline(line, LINE_SIZE, fp)) { + ret = -1; + goto out; + } + } while (strcmp(line, headerend) != 0); + + if (libssh2_base64_decode(session, (char**) data, datalen, + b64data, b64datalen)) { + ret = -1; + goto out; + } + + ret = 0; + out: + if (b64data) { + LIBSSH2_FREE(session, b64data); + } + return ret; +} + +static int +read_asn1_length(const unsigned char *data, + unsigned int datalen, unsigned int *len) +{ + unsigned int lenlen; + int nextpos; + + if (datalen < 1) { + return -1; + } + *len = data[0]; + + if (*len >= 0x80) { + lenlen = *len & 0x7F; + *len = data[1]; + if (1 + lenlen > datalen) { + return -1; + } + if (lenlen > 1) { + *len <<= 8; + *len |= data[2]; + } + } else { + lenlen = 0; + } + + nextpos = 1 + lenlen; + if (lenlen > 2 || 1 + lenlen + *len > datalen) { + return -1; + } + + return nextpos; +} + +int +_libssh2_pem_decode_sequence(unsigned char **data, unsigned int *datalen) +{ + unsigned int len; + int lenlen; + + if (*datalen < 1) { + return -1; + } + + if ((*data)[0] != '\x30') { + return -1; + } + + (*data)++; + (*datalen)--; + + lenlen = read_asn1_length(*data, *datalen, &len); + if (lenlen < 0 || lenlen + len != *datalen) { + return -1; + } + + *data += lenlen; + *datalen -= lenlen; + + return 0; +} + +int +_libssh2_pem_decode_integer(unsigned char **data, unsigned int *datalen, + unsigned char **i, unsigned int *ilen) +{ + unsigned int len; + int lenlen; + + if (*datalen < 1) { + return -1; + } + + if ((*data)[0] != '\x02') { + return -1; + } + + (*data)++; + (*datalen)--; + + lenlen = read_asn1_length(*data, *datalen, &len); + if (lenlen < 0 || lenlen + len > *datalen) { + return -1; + } + + *data += lenlen; + *datalen -= lenlen; + + *i = *data; + *ilen = len; + + *data += len; + *datalen -= len; + + return 0; +} + +#endif /* LIBSSH2_LIBGCRYPT */ diff --git a/libssh2/src/publickey.c b/libssh2/src/publickey.c new file mode 100644 index 0000000..282fffe --- /dev/null +++ b/libssh2/src/publickey.c @@ -0,0 +1,1058 @@ +/* Copyright (c) 2004-2007, Sara Golemon + * Copyright (c) 2010-2012 by Daniel Stenberg + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include "libssh2_priv.h" +#include "libssh2_publickey.h" +#include "channel.h" +#include "session.h" + +#define LIBSSH2_PUBLICKEY_VERSION 2 + +/* Numericised response codes -- Not IETF, just local representation */ +#define LIBSSH2_PUBLICKEY_RESPONSE_STATUS 0 +#define LIBSSH2_PUBLICKEY_RESPONSE_VERSION 1 +#define LIBSSH2_PUBLICKEY_RESPONSE_PUBLICKEY 2 + +typedef struct _LIBSSH2_PUBLICKEY_CODE_LIST +{ + int code; + const char *name; + int name_len; +} LIBSSH2_PUBLICKEY_CODE_LIST; + +static const LIBSSH2_PUBLICKEY_CODE_LIST publickey_response_codes[] = +{ + {LIBSSH2_PUBLICKEY_RESPONSE_STATUS, "status", sizeof("status") - 1}, + {LIBSSH2_PUBLICKEY_RESPONSE_VERSION, "version", sizeof("version") - 1}, + {LIBSSH2_PUBLICKEY_RESPONSE_PUBLICKEY, "publickey", + sizeof("publickey") - 1} , + {0, NULL, 0} +}; + +/* PUBLICKEY status codes -- IETF defined */ +#define LIBSSH2_PUBLICKEY_SUCCESS 0 +#define LIBSSH2_PUBLICKEY_ACCESS_DENIED 1 +#define LIBSSH2_PUBLICKEY_STORAGE_EXCEEDED 2 +#define LIBSSH2_PUBLICKEY_VERSION_NOT_SUPPORTED 3 +#define LIBSSH2_PUBLICKEY_KEY_NOT_FOUND 4 +#define LIBSSH2_PUBLICKEY_KEY_NOT_SUPPORTED 5 +#define LIBSSH2_PUBLICKEY_KEY_ALREADY_PRESENT 6 +#define LIBSSH2_PUBLICKEY_GENERAL_FAILURE 7 +#define LIBSSH2_PUBLICKEY_REQUEST_NOT_SUPPORTED 8 + +#define LIBSSH2_PUBLICKEY_STATUS_CODE_MAX 8 + +static const LIBSSH2_PUBLICKEY_CODE_LIST publickey_status_codes[] = { + {LIBSSH2_PUBLICKEY_SUCCESS, "success", sizeof("success") - 1} , + {LIBSSH2_PUBLICKEY_ACCESS_DENIED, "access denied", + sizeof("access denied") - 1}, + {LIBSSH2_PUBLICKEY_STORAGE_EXCEEDED, "storage exceeded", + sizeof("storage exceeded") - 1} , + {LIBSSH2_PUBLICKEY_VERSION_NOT_SUPPORTED, "version not supported", + sizeof("version not supported") - 1} , + {LIBSSH2_PUBLICKEY_KEY_NOT_FOUND, "key not found", + sizeof("key not found") - 1}, + {LIBSSH2_PUBLICKEY_KEY_NOT_SUPPORTED, "key not supported", + sizeof("key not supported") - 1}, + {LIBSSH2_PUBLICKEY_KEY_ALREADY_PRESENT, "key already present", + sizeof("key already present") - 1}, + {LIBSSH2_PUBLICKEY_GENERAL_FAILURE, "general failure", + sizeof("general failure") - 1}, + {LIBSSH2_PUBLICKEY_REQUEST_NOT_SUPPORTED, "request not supported", + sizeof("request not supported") - 1}, + {0, NULL, 0} +}; + +/* + * publickey_status_error + * + * Format an error message from a status code + */ +static void +publickey_status_error(const LIBSSH2_PUBLICKEY *pkey, + LIBSSH2_SESSION *session, int status) +{ + const char *msg; + + /* GENERAL_FAILURE got remapped between version 1 and 2 */ + if (status == 6 && pkey && pkey->version == 1) { + status = 7; + } + + if (status < 0 || status > LIBSSH2_PUBLICKEY_STATUS_CODE_MAX) { + msg = "unknown"; + } else { + msg = publickey_status_codes[status].name; + } + + _libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_PROTOCOL, msg); +} + +/* + * publickey_packet_receive + * + * Read a packet from the subsystem + */ +static int +publickey_packet_receive(LIBSSH2_PUBLICKEY * pkey, + unsigned char **data, size_t *data_len) +{ + LIBSSH2_CHANNEL *channel = pkey->channel; + LIBSSH2_SESSION *session = channel->session; + unsigned char buffer[4]; + int rc; + + if (pkey->receive_state == libssh2_NB_state_idle) { + rc = _libssh2_channel_read(channel, 0, (char *) buffer, 4); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } else if (rc != 4) { + return _libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_PROTOCOL, + "Invalid response from publickey subsystem"); + } + + pkey->receive_packet_len = _libssh2_ntohu32(buffer); + pkey->receive_packet = + LIBSSH2_ALLOC(session, pkey->receive_packet_len); + if (!pkey->receive_packet) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate publickey response " + "buffer"); + } + + pkey->receive_state = libssh2_NB_state_sent; + } + + if (pkey->receive_state == libssh2_NB_state_sent) { + rc = _libssh2_channel_read(channel, 0, (char *) pkey->receive_packet, + pkey->receive_packet_len); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } else if (rc != (int)pkey->receive_packet_len) { + LIBSSH2_FREE(session, pkey->receive_packet); + pkey->receive_packet = NULL; + pkey->receive_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, + "Timeout waiting for publickey subsystem " + "response packet"); + } + + *data = pkey->receive_packet; + *data_len = pkey->receive_packet_len; + } + + pkey->receive_state = libssh2_NB_state_idle; + + return 0; +} + +/* publickey_response_id + * + * Translate a string response name to a numeric code + * Data will be incremented by 4 + response_len on success only + */ +static int +publickey_response_id(unsigned char **pdata, size_t data_len) +{ + size_t response_len; + unsigned char *data = *pdata; + const LIBSSH2_PUBLICKEY_CODE_LIST *codes = publickey_response_codes; + + if (data_len < 4) { + /* Malformed response */ + return -1; + } + response_len = _libssh2_ntohu32(data); + data += 4; + data_len -= 4; + if (data_len < response_len) { + /* Malformed response */ + return -1; + } + + while (codes->name) { + if ((unsigned long)codes->name_len == response_len && + strncmp(codes->name, (char *) data, response_len) == 0) { + *pdata = data + response_len; + return codes->code; + } + codes++; + } + + return -1; +} + +/* publickey_response_success + * + * Generic helper routine to wait for success response and nothing else + */ +static int +publickey_response_success(LIBSSH2_PUBLICKEY * pkey) +{ + LIBSSH2_SESSION *session = pkey->channel->session; + unsigned char *data, *s; + size_t data_len; + int response; + + while (1) { + int rc = publickey_packet_receive(pkey, &data, &data_len); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } else if (rc) { + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, + "Timeout waiting for response from " + "publickey subsystem"); + } + + s = data; + response = publickey_response_id(&s, data_len); + + switch (response) { + case LIBSSH2_PUBLICKEY_RESPONSE_STATUS: + /* Error, or processing complete */ + { + unsigned long status = _libssh2_ntohu32(s); + + LIBSSH2_FREE(session, data); + + if (status == LIBSSH2_PUBLICKEY_SUCCESS) + return 0; + + publickey_status_error(pkey, session, status); + return -1; + } + default: + LIBSSH2_FREE(session, data); + if (response < 0) { + return _libssh2_error(session, + LIBSSH2_ERROR_PUBLICKEY_PROTOCOL, + "Invalid publickey subsystem response"); + } + /* Unknown/Unexpected */ + _libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_PROTOCOL, + "Unexpected publickey subsystem response"); + data = NULL; + } + } + /* never reached, but include `return` to silence compiler warnings */ + return -1; +} + +/* ***************** + * Publickey API * + ***************** */ + +/* + * publickey_init + * + * Startup the publickey subsystem + */ +static LIBSSH2_PUBLICKEY *publickey_init(LIBSSH2_SESSION *session) +{ + int response; + int rc; + + if (session->pkeyInit_state == libssh2_NB_state_idle) { + session->pkeyInit_data = NULL; + session->pkeyInit_pkey = NULL; + session->pkeyInit_channel = NULL; + + _libssh2_debug(session, LIBSSH2_TRACE_PUBLICKEY, + "Initializing publickey subsystem"); + + session->pkeyInit_state = libssh2_NB_state_allocated; + } + + if (session->pkeyInit_state == libssh2_NB_state_allocated) { + + session->pkeyInit_channel = + _libssh2_channel_open(session, "session", + sizeof("session") - 1, + LIBSSH2_CHANNEL_WINDOW_DEFAULT, + LIBSSH2_CHANNEL_PACKET_DEFAULT, NULL, + 0); + if (!session->pkeyInit_channel) { + if (libssh2_session_last_errno(session) == LIBSSH2_ERROR_EAGAIN) + /* The error state is already set, so leave it */ + return NULL; + _libssh2_error(session, LIBSSH2_ERROR_CHANNEL_FAILURE, + "Unable to startup channel"); + goto err_exit; + } + + session->pkeyInit_state = libssh2_NB_state_sent; + } + + if (session->pkeyInit_state == libssh2_NB_state_sent) { + rc = _libssh2_channel_process_startup(session->pkeyInit_channel, + "subsystem", + sizeof("subsystem") - 1, + "publickey", + sizeof("publickey") - 1); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block starting publickey subsystem"); + return NULL; + } else if (rc) { + _libssh2_error(session, LIBSSH2_ERROR_CHANNEL_FAILURE, + "Unable to request publickey subsystem"); + goto err_exit; + } + + session->pkeyInit_state = libssh2_NB_state_sent1; + } + + if (session->pkeyInit_state == libssh2_NB_state_sent1) { + unsigned char *s; + rc = _libssh2_channel_extended_data(session->pkeyInit_channel, + LIBSSH2_CHANNEL_EXTENDED_DATA_IGNORE); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block starting publickey subsystem"); + return NULL; + } + + session->pkeyInit_pkey = + LIBSSH2_ALLOC(session, sizeof(LIBSSH2_PUBLICKEY)); + if (!session->pkeyInit_pkey) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate a new publickey structure"); + goto err_exit; + } + memset(session->pkeyInit_pkey, 0, sizeof(LIBSSH2_PUBLICKEY)); + session->pkeyInit_pkey->channel = session->pkeyInit_channel; + session->pkeyInit_pkey->version = 0; + + s = session->pkeyInit_buffer; + _libssh2_htonu32(s, 4 + (sizeof("version") - 1) + 4); + s += 4; + _libssh2_htonu32(s, sizeof("version") - 1); + s += 4; + memcpy(s, "version", sizeof("version") - 1); + s += sizeof("version") - 1; + _libssh2_htonu32(s, LIBSSH2_PUBLICKEY_VERSION); + + session->pkeyInit_buffer_sent = 0; + + _libssh2_debug(session, LIBSSH2_TRACE_PUBLICKEY, + "Sending publickey advertising version %d support", + (int) LIBSSH2_PUBLICKEY_VERSION); + + session->pkeyInit_state = libssh2_NB_state_sent2; + } + + if (session->pkeyInit_state == libssh2_NB_state_sent2) { + rc = _libssh2_channel_write(session->pkeyInit_channel, 0, + session->pkeyInit_buffer, + 19 - session->pkeyInit_buffer_sent); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block sending publickey version packet"); + return NULL; + } else if (rc < 0) { + _libssh2_error(session, rc, + "Unable to send publickey version packet"); + goto err_exit; + } + session->pkeyInit_buffer_sent += rc; + if(session->pkeyInit_buffer_sent < 19) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Need to be called again to complete this"); + return NULL; + } + + session->pkeyInit_state = libssh2_NB_state_sent3; + } + + if (session->pkeyInit_state == libssh2_NB_state_sent3) { + while (1) { + unsigned char *s; + rc = publickey_packet_receive(session->pkeyInit_pkey, + &session->pkeyInit_data, + &session->pkeyInit_data_len); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block waiting for response from " + "publickey subsystem"); + return NULL; + } else if (rc) { + _libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, + "Timeout waiting for response from " + "publickey subsystem"); + goto err_exit; + } + + s = session->pkeyInit_data; + if ((response = + publickey_response_id(&s, session->pkeyInit_data_len)) < 0) { + _libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_PROTOCOL, + "Invalid publickey subsystem response code"); + goto err_exit; + } + + switch (response) { + case LIBSSH2_PUBLICKEY_RESPONSE_STATUS: + /* Error */ + { + unsigned long status, descr_len, lang_len; + + status = _libssh2_ntohu32(s); + s += 4; + descr_len = _libssh2_ntohu32(s); + s += 4; + /* description starts here */ + s += descr_len; + lang_len = _libssh2_ntohu32(s); + s += 4; + /* lang starts here */ + s += lang_len; + + if (s > + session->pkeyInit_data + session->pkeyInit_data_len) { + _libssh2_error(session, + LIBSSH2_ERROR_PUBLICKEY_PROTOCOL, + "Malformed publickey subsystem packet"); + goto err_exit; + } + + publickey_status_error(NULL, session, status); + + goto err_exit; + } + + case LIBSSH2_PUBLICKEY_RESPONSE_VERSION: + /* What we want */ + session->pkeyInit_pkey->version = _libssh2_ntohu32(s); + if (session->pkeyInit_pkey->version > + LIBSSH2_PUBLICKEY_VERSION) { + _libssh2_debug(session, LIBSSH2_TRACE_PUBLICKEY, + "Truncate remote publickey version from %lu", + session->pkeyInit_pkey->version); + session->pkeyInit_pkey->version = + LIBSSH2_PUBLICKEY_VERSION; + } + _libssh2_debug(session, LIBSSH2_TRACE_PUBLICKEY, + "Enabling publickey subsystem version %lu", + session->pkeyInit_pkey->version); + LIBSSH2_FREE(session, session->pkeyInit_data); + session->pkeyInit_data = NULL; + session->pkeyInit_state = libssh2_NB_state_idle; + return session->pkeyInit_pkey; + + default: + /* Unknown/Unexpected */ + _libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_PROTOCOL, + "Unexpected publickey subsystem response, " + "ignoring"); + LIBSSH2_FREE(session, session->pkeyInit_data); + session->pkeyInit_data = NULL; + } + } + } + + /* Never reached except by direct goto */ + err_exit: + session->pkeyInit_state = libssh2_NB_state_sent4; + if (session->pkeyInit_channel) { + rc = _libssh2_channel_close(session->pkeyInit_channel); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block closing channel"); + return NULL; + } + } + if (session->pkeyInit_pkey) { + LIBSSH2_FREE(session, session->pkeyInit_pkey); + session->pkeyInit_pkey = NULL; + } + if (session->pkeyInit_data) { + LIBSSH2_FREE(session, session->pkeyInit_data); + session->pkeyInit_data = NULL; + } + session->pkeyInit_state = libssh2_NB_state_idle; + return NULL; +} + +/* + * libssh2_publickey_init + * + * Startup the publickey subsystem + */ +LIBSSH2_API LIBSSH2_PUBLICKEY * +libssh2_publickey_init(LIBSSH2_SESSION *session) +{ + LIBSSH2_PUBLICKEY *ptr; + + BLOCK_ADJUST_ERRNO(ptr, session, + publickey_init(session)); + return ptr; +} + + + +/* + * libssh2_publickey_add_ex + * + * Add a new public key entry + */ +LIBSSH2_API int +libssh2_publickey_add_ex(LIBSSH2_PUBLICKEY *pkey, const unsigned char *name, + unsigned long name_len, const unsigned char *blob, + unsigned long blob_len, char overwrite, + unsigned long num_attrs, + const libssh2_publickey_attribute attrs[]) +{ + LIBSSH2_CHANNEL *channel; + LIBSSH2_SESSION *session; + /* 19 = packet_len(4) + add_len(4) + "add"(3) + name_len(4) + {name} + blob_len(4) + {blob} */ + unsigned long i, packet_len = 19 + name_len + blob_len; + unsigned char *comment = NULL; + unsigned long comment_len = 0; + int rc; + + if(!pkey) + return LIBSSH2_ERROR_BAD_USE; + + channel = pkey->channel; + session = channel->session; + + if (pkey->add_state == libssh2_NB_state_idle) { + pkey->add_packet = NULL; + + _libssh2_debug(session, LIBSSH2_TRACE_PUBLICKEY, "Adding %s publickey", + name); + + if (pkey->version == 1) { + for(i = 0; i < num_attrs; i++) { + /* Search for a comment attribute */ + if (attrs[i].name_len == (sizeof("comment") - 1) && + strncmp(attrs[i].name, "comment", + sizeof("comment") - 1) == 0) { + comment = (unsigned char *) attrs[i].value; + comment_len = attrs[i].value_len; + break; + } + } + packet_len += 4 + comment_len; + } else { + packet_len += 5; /* overwrite(1) + attribute_count(4) */ + for(i = 0; i < num_attrs; i++) { + packet_len += 9 + attrs[i].name_len + attrs[i].value_len; + /* name_len(4) + value_len(4) + mandatory(1) */ + } + } + + pkey->add_packet = LIBSSH2_ALLOC(session, packet_len); + if (!pkey->add_packet) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for " + "publickey \"add\" packet"); + } + + pkey->add_s = pkey->add_packet; + _libssh2_htonu32(pkey->add_s, packet_len - 4); + pkey->add_s += 4; + _libssh2_htonu32(pkey->add_s, sizeof("add") - 1); + pkey->add_s += 4; + memcpy(pkey->add_s, "add", sizeof("add") - 1); + pkey->add_s += sizeof("add") - 1; + if (pkey->version == 1) { + _libssh2_htonu32(pkey->add_s, comment_len); + pkey->add_s += 4; + if (comment) { + memcpy(pkey->add_s, comment, comment_len); + pkey->add_s += comment_len; + } + + _libssh2_htonu32(pkey->add_s, name_len); + pkey->add_s += 4; + memcpy(pkey->add_s, name, name_len); + pkey->add_s += name_len; + _libssh2_htonu32(pkey->add_s, blob_len); + pkey->add_s += 4; + memcpy(pkey->add_s, blob, blob_len); + pkey->add_s += blob_len; + } else { + /* Version == 2 */ + + _libssh2_htonu32(pkey->add_s, name_len); + pkey->add_s += 4; + memcpy(pkey->add_s, name, name_len); + pkey->add_s += name_len; + _libssh2_htonu32(pkey->add_s, blob_len); + pkey->add_s += 4; + memcpy(pkey->add_s, blob, blob_len); + pkey->add_s += blob_len; + *(pkey->add_s++) = overwrite ? 0x01 : 0; + _libssh2_htonu32(pkey->add_s, num_attrs); + pkey->add_s += 4; + for(i = 0; i < num_attrs; i++) { + _libssh2_htonu32(pkey->add_s, attrs[i].name_len); + pkey->add_s += 4; + memcpy(pkey->add_s, attrs[i].name, attrs[i].name_len); + pkey->add_s += attrs[i].name_len; + _libssh2_htonu32(pkey->add_s, attrs[i].value_len); + pkey->add_s += 4; + memcpy(pkey->add_s, attrs[i].value, attrs[i].value_len); + pkey->add_s += attrs[i].value_len; + *(pkey->add_s++) = attrs[i].mandatory ? 0x01 : 0; + } + } + + _libssh2_debug(session, LIBSSH2_TRACE_PUBLICKEY, + "Sending publickey \"add\" packet: " + "type=%s blob_len=%ld num_attrs=%ld", + name, blob_len, num_attrs); + + pkey->add_state = libssh2_NB_state_created; + } + + if (pkey->add_state == libssh2_NB_state_created) { + rc = _libssh2_channel_write(channel, 0, pkey->add_packet, + (pkey->add_s - pkey->add_packet)); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } else if ((pkey->add_s - pkey->add_packet) != rc) { + LIBSSH2_FREE(session, pkey->add_packet); + pkey->add_packet = NULL; + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send publickey add packet"); + } + LIBSSH2_FREE(session, pkey->add_packet); + pkey->add_packet = NULL; + + pkey->add_state = libssh2_NB_state_sent; + } + + rc = publickey_response_success(pkey); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } + + pkey->add_state = libssh2_NB_state_idle; + + return rc; +} + +/* libssh2_publickey_remove_ex + * Remove an existing publickey so that authentication can no longer be + * performed using it + */ +LIBSSH2_API int +libssh2_publickey_remove_ex(LIBSSH2_PUBLICKEY * pkey, + const unsigned char *name, unsigned long name_len, + const unsigned char *blob, unsigned long blob_len) +{ + LIBSSH2_CHANNEL *channel; + LIBSSH2_SESSION *session; + /* 22 = packet_len(4) + remove_len(4) + "remove"(6) + name_len(4) + {name} + + blob_len(4) + {blob} */ + unsigned long packet_len = 22 + name_len + blob_len; + int rc; + + if(!pkey) + return LIBSSH2_ERROR_BAD_USE; + + channel = pkey->channel; + session = channel->session; + + if (pkey->remove_state == libssh2_NB_state_idle) { + pkey->remove_packet = NULL; + + pkey->remove_packet = LIBSSH2_ALLOC(session, packet_len); + if (!pkey->remove_packet) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for " + "publickey \"remove\" packet"); + } + + pkey->remove_s = pkey->remove_packet; + _libssh2_htonu32(pkey->remove_s, packet_len - 4); + pkey->remove_s += 4; + _libssh2_htonu32(pkey->remove_s, sizeof("remove") - 1); + pkey->remove_s += 4; + memcpy(pkey->remove_s, "remove", sizeof("remove") - 1); + pkey->remove_s += sizeof("remove") - 1; + _libssh2_htonu32(pkey->remove_s, name_len); + pkey->remove_s += 4; + memcpy(pkey->remove_s, name, name_len); + pkey->remove_s += name_len; + _libssh2_htonu32(pkey->remove_s, blob_len); + pkey->remove_s += 4; + memcpy(pkey->remove_s, blob, blob_len); + pkey->remove_s += blob_len; + + _libssh2_debug(session, LIBSSH2_TRACE_PUBLICKEY, + "Sending publickey \"remove\" packet: " + "type=%s blob_len=%ld", + name, blob_len); + + pkey->remove_state = libssh2_NB_state_created; + } + + if (pkey->remove_state == libssh2_NB_state_created) { + rc = _libssh2_channel_write(channel, 0, pkey->remove_packet, + (pkey->remove_s - pkey->remove_packet)); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } else if ((pkey->remove_s - pkey->remove_packet) != rc) { + LIBSSH2_FREE(session, pkey->remove_packet); + pkey->remove_packet = NULL; + pkey->remove_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send publickey remove packet"); + } + LIBSSH2_FREE(session, pkey->remove_packet); + pkey->remove_packet = NULL; + + pkey->remove_state = libssh2_NB_state_sent; + } + + rc = publickey_response_success(pkey); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } + + pkey->remove_state = libssh2_NB_state_idle; + + return rc; +} + +/* libssh2_publickey_list_fetch + * Fetch a list of supported public key from a server + */ +LIBSSH2_API int +libssh2_publickey_list_fetch(LIBSSH2_PUBLICKEY * pkey, unsigned long *num_keys, + libssh2_publickey_list ** pkey_list) +{ + LIBSSH2_CHANNEL *channel; + LIBSSH2_SESSION *session; + libssh2_publickey_list *list = NULL; + unsigned long buffer_len = 12, keys = 0, max_keys = 0, i; + /* 12 = packet_len(4) + list_len(4) + "list"(4) */ + int response; + int rc; + + if(!pkey) + return LIBSSH2_ERROR_BAD_USE; + + channel = pkey->channel; + session = channel->session; + + if (pkey->listFetch_state == libssh2_NB_state_idle) { + pkey->listFetch_data = NULL; + + pkey->listFetch_s = pkey->listFetch_buffer; + _libssh2_htonu32(pkey->listFetch_s, buffer_len - 4); + pkey->listFetch_s += 4; + _libssh2_htonu32(pkey->listFetch_s, sizeof("list") - 1); + pkey->listFetch_s += 4; + memcpy(pkey->listFetch_s, "list", sizeof("list") - 1); + pkey->listFetch_s += sizeof("list") - 1; + + _libssh2_debug(session, LIBSSH2_TRACE_PUBLICKEY, + "Sending publickey \"list\" packet"); + + pkey->listFetch_state = libssh2_NB_state_created; + } + + if (pkey->listFetch_state == libssh2_NB_state_created) { + rc = _libssh2_channel_write(channel, 0, + pkey->listFetch_buffer, + (pkey->listFetch_s - + pkey->listFetch_buffer)); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } else if ((pkey->listFetch_s - pkey->listFetch_buffer) != rc) { + pkey->listFetch_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send publickey list packet"); + } + + pkey->listFetch_state = libssh2_NB_state_sent; + } + + while (1) { + rc = publickey_packet_receive(pkey, &pkey->listFetch_data, + &pkey->listFetch_data_len); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } else if (rc) { + _libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, + "Timeout waiting for response from " + "publickey subsystem"); + goto err_exit; + } + + pkey->listFetch_s = pkey->listFetch_data; + if ((response = + publickey_response_id(&pkey->listFetch_s, + pkey->listFetch_data_len)) < 0) { + _libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_PROTOCOL, + "Invalid publickey subsystem response code"); + goto err_exit; + } + + switch (response) { + case LIBSSH2_PUBLICKEY_RESPONSE_STATUS: + /* Error, or processing complete */ + { + unsigned long status, descr_len, lang_len; + + status = _libssh2_ntohu32(pkey->listFetch_s); + pkey->listFetch_s += 4; + descr_len = _libssh2_ntohu32(pkey->listFetch_s); + pkey->listFetch_s += 4; + /* description starts at pkey->listFetch_s */ + pkey->listFetch_s += descr_len; + lang_len = _libssh2_ntohu32(pkey->listFetch_s); + pkey->listFetch_s += 4; + /* lang starts at pkey->listFetch_s */ + pkey->listFetch_s += lang_len; + + if (pkey->listFetch_s > + pkey->listFetch_data + pkey->listFetch_data_len) { + _libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_PROTOCOL, + "Malformed publickey subsystem packet"); + goto err_exit; + } + + if (status == LIBSSH2_PUBLICKEY_SUCCESS) { + LIBSSH2_FREE(session, pkey->listFetch_data); + pkey->listFetch_data = NULL; + *pkey_list = list; + *num_keys = keys; + pkey->listFetch_state = libssh2_NB_state_idle; + return 0; + } + + publickey_status_error(pkey, session, status); + goto err_exit; + } + case LIBSSH2_PUBLICKEY_RESPONSE_PUBLICKEY: + /* What we want */ + if (keys >= max_keys) { + libssh2_publickey_list *newlist; + /* Grow the key list if necessary */ + max_keys += 8; + newlist = + LIBSSH2_REALLOC(session, list, + (max_keys + + 1) * sizeof(libssh2_publickey_list)); + if (!newlist) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for " + "publickey list"); + goto err_exit; + } + list = newlist; + } + if (pkey->version == 1) { + unsigned long comment_len; + + comment_len = _libssh2_ntohu32(pkey->listFetch_s); + pkey->listFetch_s += 4; + if (comment_len) { + list[keys].num_attrs = 1; + list[keys].attrs = + LIBSSH2_ALLOC(session, + sizeof(libssh2_publickey_attribute)); + if (!list[keys].attrs) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for " + "publickey attributes"); + goto err_exit; + } + list[keys].attrs[0].name = "comment"; + list[keys].attrs[0].name_len = sizeof("comment") - 1; + list[keys].attrs[0].value = (char *) pkey->listFetch_s; + list[keys].attrs[0].value_len = comment_len; + list[keys].attrs[0].mandatory = 0; + + pkey->listFetch_s += comment_len; + } else { + list[keys].num_attrs = 0; + list[keys].attrs = NULL; + } + list[keys].name_len = _libssh2_ntohu32(pkey->listFetch_s); + pkey->listFetch_s += 4; + list[keys].name = pkey->listFetch_s; + pkey->listFetch_s += list[keys].name_len; + list[keys].blob_len = _libssh2_ntohu32(pkey->listFetch_s); + pkey->listFetch_s += 4; + list[keys].blob = pkey->listFetch_s; + pkey->listFetch_s += list[keys].blob_len; + } else { + /* Version == 2 */ + list[keys].name_len = _libssh2_ntohu32(pkey->listFetch_s); + pkey->listFetch_s += 4; + list[keys].name = pkey->listFetch_s; + pkey->listFetch_s += list[keys].name_len; + list[keys].blob_len = _libssh2_ntohu32(pkey->listFetch_s); + pkey->listFetch_s += 4; + list[keys].blob = pkey->listFetch_s; + pkey->listFetch_s += list[keys].blob_len; + list[keys].num_attrs = _libssh2_ntohu32(pkey->listFetch_s); + pkey->listFetch_s += 4; + if (list[keys].num_attrs) { + list[keys].attrs = + LIBSSH2_ALLOC(session, + list[keys].num_attrs * + sizeof(libssh2_publickey_attribute)); + if (!list[keys].attrs) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for " + "publickey attributes"); + goto err_exit; + } + for(i = 0; i < list[keys].num_attrs; i++) { + list[keys].attrs[i].name_len = + _libssh2_ntohu32(pkey->listFetch_s); + pkey->listFetch_s += 4; + list[keys].attrs[i].name = (char *) pkey->listFetch_s; + pkey->listFetch_s += list[keys].attrs[i].name_len; + list[keys].attrs[i].value_len = + _libssh2_ntohu32(pkey->listFetch_s); + pkey->listFetch_s += 4; + list[keys].attrs[i].value = (char *) pkey->listFetch_s; + pkey->listFetch_s += list[keys].attrs[i].value_len; + + /* actually an ignored value */ + list[keys].attrs[i].mandatory = 0; + } + } else { + list[keys].attrs = NULL; + } + } + /* To be FREEd in libssh2_publickey_list_free() */ + list[keys].packet = pkey->listFetch_data; + keys++; + + list[keys].packet = NULL; /* Terminate the list */ + pkey->listFetch_data = NULL; + break; + default: + /* Unknown/Unexpected */ + _libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_PROTOCOL, + "Unexpected publickey subsystem response"); + LIBSSH2_FREE(session, pkey->listFetch_data); + pkey->listFetch_data = NULL; + } + } + + /* Only reached via explicit goto */ + err_exit: + if (pkey->listFetch_data) { + LIBSSH2_FREE(session, pkey->listFetch_data); + pkey->listFetch_data = NULL; + } + if (list) { + libssh2_publickey_list_free(pkey, list); + } + pkey->listFetch_state = libssh2_NB_state_idle; + return -1; +} + +/* libssh2_publickey_list_free + * Free a previously fetched list of public keys + */ +LIBSSH2_API void +libssh2_publickey_list_free(LIBSSH2_PUBLICKEY * pkey, + libssh2_publickey_list * pkey_list) +{ + LIBSSH2_SESSION *session; + libssh2_publickey_list *p = pkey_list; + + if(!pkey || !p) + return; + + session = pkey->channel->session; + + while (p->packet) { + if (p->attrs) { + LIBSSH2_FREE(session, p->attrs); + } + LIBSSH2_FREE(session, p->packet); + p++; + } + + LIBSSH2_FREE(session, pkey_list); +} + +/* libssh2_publickey_shutdown + * Shutdown the publickey subsystem + */ +LIBSSH2_API int +libssh2_publickey_shutdown(LIBSSH2_PUBLICKEY *pkey) +{ + LIBSSH2_SESSION *session; + int rc; + + if(!pkey) + return LIBSSH2_ERROR_BAD_USE; + + session = pkey->channel->session; + + /* + * Make sure all memory used in the state variables are free + */ + if (pkey->receive_packet) { + LIBSSH2_FREE(session, pkey->receive_packet); + pkey->receive_packet = NULL; + } + if (pkey->add_packet) { + LIBSSH2_FREE(session, pkey->add_packet); + pkey->add_packet = NULL; + } + if (pkey->remove_packet) { + LIBSSH2_FREE(session, pkey->remove_packet); + pkey->remove_packet = NULL; + } + if (pkey->listFetch_data) { + LIBSSH2_FREE(session, pkey->listFetch_data); + pkey->listFetch_data = NULL; + } + + rc = _libssh2_channel_free(pkey->channel); + if (rc == LIBSSH2_ERROR_EAGAIN) + return rc; + + LIBSSH2_FREE(session, pkey); + return 0; +} diff --git a/libssh2/src/scp.c b/libssh2/src/scp.c new file mode 100644 index 0000000..63d181e --- /dev/null +++ b/libssh2/src/scp.c @@ -0,0 +1,1085 @@ +/* Copyright (c) 2009-2010 by Daniel Stenberg + * Copyright (c) 2004-2008, Sara Golemon + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include "libssh2_priv.h" +#include +#include + +#include "channel.h" +#include "session.h" + + +/* Max. length of a quoted string after libssh2_shell_quotearg() processing */ +#define _libssh2_shell_quotedsize(s) (3 * strlen(s) + 2) + +/* + This function quotes a string in a way suitable to be used with a + shell, e.g. the file name + one two + becomes + 'one two' + + The resulting output string is crafted in a way that makes it usable + with the two most common shell types: Bourne Shell derived shells + (sh, ksh, ksh93, bash, zsh) and C-Shell derivates (csh, tcsh). + + The following special cases are handled: + o If the string contains an apostrophy itself, the apostrophy + character is written in quotation marks, e.g. "'". + The shell cannot handle the syntax 'doesn\'t', so we close the + current argument word, add the apostrophe in quotation marks "", + and open a new argument word instead (_ indicate the input + string characters): + _____ _ _ + 'doesn' "'" 't' + + Sequences of apostrophes are combined in one pair of quotation marks: + a'''b + becomes + _ ___ _ + 'a'"'''"'b' + + o If the string contains an exclamation mark (!), the C-Shell + interprets it as an event number. Using \! (not within quotation + marks or single quotation marks) is a mechanism understood by + both Bourne Shell and C-Shell. + + If a quotation was already started, the argument word is closed + first: + a!b + + become + _ _ _ + 'a'\!'b' + + The result buffer must be large enough for the expanded result. A + bad case regarding expansion is alternating characters and + apostrophes: + + a'b'c'd' (length 8) gets converted to + 'a'"'"'b'"'"'c'"'"'d'"'" (length 24) + + This is the worst case. + + Maximum length of the result: + 1 + 6 * (length(input) + 1) / 2) + 1 + + => 3 * length(input) + 2 + + Explanation: + o leading apostrophe + o one character / apostrophe pair (two characters) can get + represented as 6 characters: a' -> a'"'"' + o String terminator (+1) + + A result buffer three times the size of the input buffer + 2 + characters should be safe. + + References: + o csh-compatible quotation (special handling for '!' etc.), see + http://www.grymoire.com/Unix/Csh.html#toc-uh-10 + + Return value: + Length of the resulting string (not counting the terminating '\0'), + or 0 in case of errors, e.g. result buffer too small + + Note: this function could possible be used elsewhere within libssh2, but + until then it is kept static and in this source file. +*/ + +static unsigned +shell_quotearg(const char *path, unsigned char *buf, + unsigned bufsize) +{ + const char *src; + unsigned char *dst, *endp; + + /* + * Processing States: + * UQSTRING: unquoted string: ... -- used for quoting exclamation + * marks. This is the initial state + * SQSTRING: single-qouted-string: '... -- any character may follow + * QSTRING: quoted string: "... -- only apostrophes may follow + */ + enum { UQSTRING, SQSTRING, QSTRING } state = UQSTRING; + + endp = &buf[bufsize]; + src = path; + dst = buf; + while (*src && dst < endp - 1) { + + switch (*src) { + /* + * Special handling for apostrophe. + * An apostrophe is always written in quotation marks, e.g. + * ' -> "'". + */ + + case '\'': + switch (state) { + case UQSTRING: /* Unquoted string */ + if (dst+1 >= endp) + return 0; + *dst++ = '"'; + break; + case QSTRING: /* Continue quoted string */ + break; + case SQSTRING: /* Close single quoted string */ + if (dst+2 >= endp) + return 0; + *dst++ = '\''; + *dst++ = '"'; + break; + default: + break; + } + state = QSTRING; + break; + + /* + * Special handling for exclamation marks. CSH interprets + * exclamation marks even when quoted with apostrophes. We convert + * it to the plain string \!, because both Bourne Shell and CSH + * interpret that as a verbatim exclamation mark. + */ + + case '!': + switch (state) { + case UQSTRING: + if (dst+1 >= endp) + return 0; + *dst++ = '\\'; + break; + case QSTRING: + if (dst+2 >= endp) + return 0; + *dst++ = '"'; /* Closing quotation mark */ + *dst++ = '\\'; + break; + case SQSTRING: /* Close single quoted string */ + if (dst+2 >= endp) + return 0; + *dst++ = '\''; + *dst++ = '\\'; + break; + default: + break; + } + state = UQSTRING; + break; + + /* + * Ordinary character: prefer single-quoted string + */ + + default: + switch (state) { + case UQSTRING: + if (dst+1 >= endp) + return 0; + *dst++ = '\''; + break; + case QSTRING: + if (dst+2 >= endp) + return 0; + *dst++ = '"'; /* Closing quotation mark */ + *dst++ = '\''; + break; + case SQSTRING: /* Continue single quoted string */ + break; + default: + break; + } + state = SQSTRING; /* Start single-quoted string */ + break; + } + + if (dst+1 >= endp) + return 0; + *dst++ = *src++; + } + + switch (state) { + case UQSTRING: + break; + case QSTRING: /* Close quoted string */ + if (dst+1 >= endp) + return 0; + *dst++ = '"'; + break; + case SQSTRING: /* Close single quoted string */ + if (dst+1 >= endp) + return 0; + *dst++ = '\''; + break; + default: + break; + } + + if (dst+1 >= endp) + return 0; + *dst = '\0'; + + /* The result cannot be larger than 3 * strlen(path) + 2 */ + /* assert((dst - buf) <= (3 * (src - path) + 2)); */ + + return dst - buf; +} + +/* + * scp_recv + * + * Open a channel and request a remote file via SCP + * + */ +static LIBSSH2_CHANNEL * +scp_recv(LIBSSH2_SESSION * session, const char *path, struct stat * sb) +{ + int cmd_len; + int rc; + int tmp_err_code; + const char *tmp_err_msg; + + if (session->scpRecv_state == libssh2_NB_state_idle) { + session->scpRecv_mode = 0; + session->scpRecv_size = 0; + session->scpRecv_mtime = 0; + session->scpRecv_atime = 0; + + session->scpRecv_command_len = + _libssh2_shell_quotedsize(path) + sizeof("scp -f ") + (sb?1:0); + + session->scpRecv_command = + LIBSSH2_ALLOC(session, session->scpRecv_command_len); + + if (!session->scpRecv_command) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate a command buffer for " + "SCP session"); + return NULL; + } + + snprintf((char *)session->scpRecv_command, + session->scpRecv_command_len, "scp -%sf ", sb?"p":""); + + cmd_len = strlen((char *)session->scpRecv_command); + + (void) shell_quotearg(path, + &session->scpRecv_command[cmd_len], + session->scpRecv_command_len - cmd_len); + + + _libssh2_debug(session, LIBSSH2_TRACE_SCP, + "Opening channel for SCP receive"); + + session->scpRecv_state = libssh2_NB_state_created; + } + + if (session->scpRecv_state == libssh2_NB_state_created) { + /* Allocate a channel */ + session->scpRecv_channel = + _libssh2_channel_open(session, "session", + sizeof("session") - 1, + LIBSSH2_CHANNEL_WINDOW_DEFAULT, + LIBSSH2_CHANNEL_PACKET_DEFAULT, NULL, + 0); + if (!session->scpRecv_channel) { + if (libssh2_session_last_errno(session) != + LIBSSH2_ERROR_EAGAIN) { + LIBSSH2_FREE(session, session->scpRecv_command); + session->scpRecv_command = NULL; + session->scpRecv_state = libssh2_NB_state_idle; + } + else { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block starting up channel"); + } + return NULL; + } + + session->scpRecv_state = libssh2_NB_state_sent; + } + + if (session->scpRecv_state == libssh2_NB_state_sent) { + /* Request SCP for the desired file */ + rc = _libssh2_channel_process_startup(session->scpRecv_channel, "exec", + sizeof("exec") - 1, + (char *) session->scpRecv_command, + session->scpRecv_command_len); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block requesting SCP startup"); + return NULL; + } else if (rc) { + LIBSSH2_FREE(session, session->scpRecv_command); + session->scpRecv_command = NULL; + goto scp_recv_error; + } + LIBSSH2_FREE(session, session->scpRecv_command); + session->scpRecv_command = NULL; + + _libssh2_debug(session, LIBSSH2_TRACE_SCP, "Sending initial wakeup"); + /* SCP ACK */ + session->scpRecv_response[0] = '\0'; + + session->scpRecv_state = libssh2_NB_state_sent1; + } + + if (session->scpRecv_state == libssh2_NB_state_sent1) { + rc = _libssh2_channel_write(session->scpRecv_channel, 0, + session->scpRecv_response, 1); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block sending initial wakeup"); + return NULL; + } else if (rc != 1) { + goto scp_recv_error; + } + + /* Parse SCP response */ + session->scpRecv_response_len = 0; + + session->scpRecv_state = libssh2_NB_state_sent2; + } + + if ((session->scpRecv_state == libssh2_NB_state_sent2) + || (session->scpRecv_state == libssh2_NB_state_sent3)) { + while (sb && (session->scpRecv_response_len < + LIBSSH2_SCP_RESPONSE_BUFLEN)) { + unsigned char *s, *p; + + if (session->scpRecv_state == libssh2_NB_state_sent2) { + rc = _libssh2_channel_read(session->scpRecv_channel, 0, + (char *) session-> + scpRecv_response + + session->scpRecv_response_len, 1); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block waiting for SCP response"); + return NULL; + } + else if (rc < 0) { + /* error, give up */ + _libssh2_error(session, rc, "Failed reading SCP response"); + goto scp_recv_error; + } + else if(rc == 0) + goto scp_recv_empty_channel; + + session->scpRecv_response_len++; + + if (session->scpRecv_response[0] != 'T') { + size_t err_len; + char *err_msg; + + /* there can be + 01 for warnings + 02 for errors + + The following string MUST be newline terminated + */ + err_len = + _libssh2_channel_packet_data_len(session-> + scpRecv_channel, 0); + err_msg = LIBSSH2_ALLOC(session, err_len + 1); + if (!err_msg) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Failed to get memory "); + goto scp_recv_error; + } + + /* Read the remote error message */ + (void)_libssh2_channel_read(session->scpRecv_channel, 0, + err_msg, err_len); + /* If it failed for any reason, we ignore it anyway. */ + + /* zero terminate the error */ + err_msg[err_len]=0; + + _libssh2_debug(session, LIBSSH2_TRACE_SCP, + "got %02x %s", session->scpRecv_response[0], + err_msg); + + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "Failed to recv file"); + + LIBSSH2_FREE(session, err_msg); + goto scp_recv_error; + } + + if ((session->scpRecv_response_len > 1) && + ((session-> + scpRecv_response[session->scpRecv_response_len - 1] < + '0') + || (session-> + scpRecv_response[session->scpRecv_response_len - 1] > + '9')) + && (session-> + scpRecv_response[session->scpRecv_response_len - 1] != + ' ') + && (session-> + scpRecv_response[session->scpRecv_response_len - 1] != + '\r') + && (session-> + scpRecv_response[session->scpRecv_response_len - 1] != + '\n')) { + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "Invalid data in SCP response"); + goto scp_recv_error; + } + + if ((session->scpRecv_response_len < 9) + || (session-> + scpRecv_response[session->scpRecv_response_len - 1] != + '\n')) { + if (session->scpRecv_response_len == + LIBSSH2_SCP_RESPONSE_BUFLEN) { + /* You had your chance */ + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "Unterminated response from SCP server"); + goto scp_recv_error; + } + /* Way too short to be an SCP response, or not done yet, + short circuit */ + continue; + } + + /* We're guaranteed not to go under response_len == 0 by the + logic above */ + while ((session-> + scpRecv_response[session->scpRecv_response_len - 1] == + '\r') + || (session-> + scpRecv_response[session->scpRecv_response_len - + 1] == '\n')) + session->scpRecv_response_len--; + session->scpRecv_response[session->scpRecv_response_len] = + '\0'; + + if (session->scpRecv_response_len < 8) { + /* EOL came too soon */ + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "Invalid response from SCP server, " + "too short" ); + goto scp_recv_error; + } + + s = session->scpRecv_response + 1; + + p = (unsigned char *) strchr((char *) s, ' '); + if (!p || ((p - s) <= 0)) { + /* No spaces or space in the wrong spot */ + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "Invalid response from SCP server, " + "malformed mtime"); + goto scp_recv_error; + } + + *(p++) = '\0'; + /* Make sure we don't get fooled by leftover values */ + session->scpRecv_mtime = strtol((char *) s, NULL, 10); + + s = (unsigned char *) strchr((char *) p, ' '); + if (!s || ((s - p) <= 0)) { + /* No spaces or space in the wrong spot */ + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "Invalid response from SCP server, malformed mtime.usec"); + goto scp_recv_error; + } + + /* Ignore mtime.usec */ + s++; + p = (unsigned char *) strchr((char *) s, ' '); + if (!p || ((p - s) <= 0)) { + /* No spaces or space in the wrong spot */ + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "Invalid response from SCP server, too short or malformed"); + goto scp_recv_error; + } + + *p = '\0'; + /* Make sure we don't get fooled by leftover values */ + session->scpRecv_atime = strtol((char *) s, NULL, 10); + + /* SCP ACK */ + session->scpRecv_response[0] = '\0'; + + session->scpRecv_state = libssh2_NB_state_sent3; + } + + if (session->scpRecv_state == libssh2_NB_state_sent3) { + rc = _libssh2_channel_write(session->scpRecv_channel, 0, + session->scpRecv_response, 1); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block waiting to send SCP ACK"); + return NULL; + } else if (rc != 1) { + goto scp_recv_error; + } + + _libssh2_debug(session, LIBSSH2_TRACE_SCP, + "mtime = %ld, atime = %ld", + session->scpRecv_mtime, session->scpRecv_atime); + + /* We *should* check that atime.usec is valid, but why let + that stop use? */ + break; + } + } + + session->scpRecv_state = libssh2_NB_state_sent4; + } + + if (session->scpRecv_state == libssh2_NB_state_sent4) { + session->scpRecv_response_len = 0; + + session->scpRecv_state = libssh2_NB_state_sent5; + } + + if ((session->scpRecv_state == libssh2_NB_state_sent5) + || (session->scpRecv_state == libssh2_NB_state_sent6)) { + while (session->scpRecv_response_len < LIBSSH2_SCP_RESPONSE_BUFLEN) { + char *s, *p, *e = NULL; + + if (session->scpRecv_state == libssh2_NB_state_sent5) { + rc = _libssh2_channel_read(session->scpRecv_channel, 0, + (char *) session-> + scpRecv_response + + session->scpRecv_response_len, 1); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block waiting for SCP response"); + return NULL; + } + else if (rc < 0) { + /* error, bail out*/ + _libssh2_error(session, rc, "Failed reading SCP response"); + goto scp_recv_error; + } + else if(rc == 0) + goto scp_recv_empty_channel; + + session->scpRecv_response_len++; + + if (session->scpRecv_response[0] != 'C') { + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "Invalid response from SCP server"); + goto scp_recv_error; + } + + if ((session->scpRecv_response_len > 1) && + (session-> + scpRecv_response[session->scpRecv_response_len - 1] != + '\r') + && (session-> + scpRecv_response[session->scpRecv_response_len - 1] != + '\n') + && + (session-> + scpRecv_response[session->scpRecv_response_len - 1] + < 32)) { + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "Invalid data in SCP response"); + goto scp_recv_error; + } + + if ((session->scpRecv_response_len < 7) + || (session-> + scpRecv_response[session->scpRecv_response_len - 1] != + '\n')) { + if (session->scpRecv_response_len == + LIBSSH2_SCP_RESPONSE_BUFLEN) { + /* You had your chance */ + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "Unterminated response from SCP server"); + goto scp_recv_error; + } + /* Way too short to be an SCP response, or not done yet, + short circuit */ + continue; + } + + /* We're guaranteed not to go under response_len == 0 by the + logic above */ + while ((session-> + scpRecv_response[session->scpRecv_response_len - 1] == + '\r') + || (session-> + scpRecv_response[session->scpRecv_response_len - + 1] == '\n')) { + session->scpRecv_response_len--; + } + session->scpRecv_response[session->scpRecv_response_len] = + '\0'; + + if (session->scpRecv_response_len < 6) { + /* EOL came too soon */ + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "Invalid response from SCP server, too short"); + goto scp_recv_error; + } + + s = (char *) session->scpRecv_response + 1; + + p = strchr(s, ' '); + if (!p || ((p - s) <= 0)) { + /* No spaces or space in the wrong spot */ + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "Invalid response from SCP server, malformed mode"); + goto scp_recv_error; + } + + *(p++) = '\0'; + /* Make sure we don't get fooled by leftover values */ + + session->scpRecv_mode = strtol(s, &e, 8); + if (e && *e) { + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "Invalid response from SCP server, invalid mode"); + goto scp_recv_error; + } + + s = strchr(p, ' '); + if (!s || ((s - p) <= 0)) { + /* No spaces or space in the wrong spot */ + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "Invalid response from SCP server, too short or malformed"); + goto scp_recv_error; + } + + *s = '\0'; + /* Make sure we don't get fooled by leftover values */ + session->scpRecv_size = scpsize_strtol(p, &e, 10); + if (e && *e) { + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "Invalid response from SCP server, invalid size"); + goto scp_recv_error; + } + + /* SCP ACK */ + session->scpRecv_response[0] = '\0'; + + session->scpRecv_state = libssh2_NB_state_sent6; + } + + if (session->scpRecv_state == libssh2_NB_state_sent6) { + rc = _libssh2_channel_write(session->scpRecv_channel, 0, + session->scpRecv_response, 1); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block sending SCP ACK"); + return NULL; + } else if (rc != 1) { + goto scp_recv_error; + } + _libssh2_debug(session, LIBSSH2_TRACE_SCP, + "mode = 0%lo size = %ld", session->scpRecv_mode, + session->scpRecv_size); + + /* We *should* check that basename is valid, but why let that + stop us? */ + break; + } + } + + session->scpRecv_state = libssh2_NB_state_sent7; + } + + if (sb) { + memset(sb, 0, sizeof(struct stat)); + + sb->st_mtime = session->scpRecv_mtime; + sb->st_atime = session->scpRecv_atime; + sb->st_size = session->scpRecv_size; + sb->st_mode = session->scpRecv_mode; + } + + session->scpRecv_state = libssh2_NB_state_idle; + return session->scpRecv_channel; + + scp_recv_empty_channel: + /* the code only jumps here as a result of a zero read from channel_read() + so we check EOF status to avoid getting stuck in a loop */ + if(libssh2_channel_eof(session->scpRecv_channel)) + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "Unexpected channel close"); + else + return session->scpRecv_channel; + /* fall-through */ + scp_recv_error: + tmp_err_code = session->err_code; + tmp_err_msg = session->err_msg; + while (libssh2_channel_free(session->scpRecv_channel) == + LIBSSH2_ERROR_EAGAIN); + session->err_code = tmp_err_code; + session->err_msg = tmp_err_msg; + session->scpRecv_channel = NULL; + session->scpRecv_state = libssh2_NB_state_idle; + return NULL; +} + +/* + * libssh2_scp_recv + * + * Open a channel and request a remote file via SCP + * + */ +LIBSSH2_API LIBSSH2_CHANNEL * +libssh2_scp_recv(LIBSSH2_SESSION *session, const char *path, struct stat * sb) +{ + LIBSSH2_CHANNEL *ptr; + BLOCK_ADJUST_ERRNO(ptr, session, scp_recv(session, path, sb)); + return ptr; +} + +/* + * scp_send() + * + * Send a file using SCP + * + */ +static LIBSSH2_CHANNEL * +scp_send(LIBSSH2_SESSION * session, const char *path, int mode, + libssh2_int64_t size, time_t mtime, time_t atime) +{ + int cmd_len; + int rc; + int tmp_err_code; + const char *tmp_err_msg; + + if (session->scpSend_state == libssh2_NB_state_idle) { + session->scpSend_command_len = + _libssh2_shell_quotedsize(path) + sizeof("scp -t ") + + ((mtime || atime)?1:0); + + session->scpSend_command = + LIBSSH2_ALLOC(session, session->scpSend_command_len); + if (!session->scpSend_command) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate a command buffer for scp session"); + return NULL; + } + + snprintf((char *)session->scpSend_command, session->scpSend_command_len, + "scp -%st ", (mtime || atime)?"p":""); + + cmd_len = strlen((char *)session->scpSend_command); + + (void)shell_quotearg(path, + &session->scpSend_command[cmd_len], + session->scpSend_command_len - cmd_len); + + session->scpSend_command[session->scpSend_command_len - 1] = '\0'; + + _libssh2_debug(session, LIBSSH2_TRACE_SCP, + "Opening channel for SCP send"); + /* Allocate a channel */ + + session->scpSend_state = libssh2_NB_state_created; + } + + if (session->scpSend_state == libssh2_NB_state_created) { + session->scpSend_channel = + _libssh2_channel_open(session, "session", sizeof("session") - 1, + LIBSSH2_CHANNEL_WINDOW_DEFAULT, + LIBSSH2_CHANNEL_PACKET_DEFAULT, NULL, 0); + if (!session->scpSend_channel) { + if (libssh2_session_last_errno(session) != LIBSSH2_ERROR_EAGAIN) { + /* previous call set libssh2_session_last_error(), pass it + through */ + LIBSSH2_FREE(session, session->scpSend_command); + session->scpSend_command = NULL; + session->scpSend_state = libssh2_NB_state_idle; + } + else { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block starting up channel"); + } + return NULL; + } + + session->scpSend_state = libssh2_NB_state_sent; + } + + if (session->scpSend_state == libssh2_NB_state_sent) { + /* Request SCP for the desired file */ + rc = _libssh2_channel_process_startup(session->scpSend_channel, "exec", + sizeof("exec") - 1, + (char *) session->scpSend_command, + session->scpSend_command_len); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block requesting SCP startup"); + return NULL; + } + else if (rc) { + /* previous call set libssh2_session_last_error(), pass it + through */ + LIBSSH2_FREE(session, session->scpSend_command); + session->scpSend_command = NULL; + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "Unknown error while getting error string"); + goto scp_send_error; + } + LIBSSH2_FREE(session, session->scpSend_command); + session->scpSend_command = NULL; + + session->scpSend_state = libssh2_NB_state_sent1; + } + + if (session->scpSend_state == libssh2_NB_state_sent1) { + /* Wait for ACK */ + rc = _libssh2_channel_read(session->scpSend_channel, 0, + (char *) session->scpSend_response, 1); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block waiting for response from remote"); + return NULL; + } + else if (rc < 0) { + _libssh2_error(session, rc, "SCP failure"); + goto scp_send_error; + } + else if(!rc) + /* remain in the same state */ + goto scp_send_empty_channel; + else if (session->scpSend_response[0] != 0) { + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "Invalid ACK response from remote"); + goto scp_send_error; + } + if (mtime || atime) { + /* Send mtime and atime to be used for file */ + session->scpSend_response_len = + snprintf((char *) session->scpSend_response, + LIBSSH2_SCP_RESPONSE_BUFLEN, "T%ld 0 %ld 0\n", + (long)mtime, (long)atime); + _libssh2_debug(session, LIBSSH2_TRACE_SCP, "Sent %s", + session->scpSend_response); + } + + session->scpSend_state = libssh2_NB_state_sent2; + } + + /* Send mtime and atime to be used for file */ + if (mtime || atime) { + if (session->scpSend_state == libssh2_NB_state_sent2) { + rc = _libssh2_channel_write(session->scpSend_channel, 0, + session->scpSend_response, + session->scpSend_response_len); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block sending time data for SCP file"); + return NULL; + } else if (rc != (int)session->scpSend_response_len) { + _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send time data for SCP file"); + goto scp_send_error; + } + + session->scpSend_state = libssh2_NB_state_sent3; + } + + if (session->scpSend_state == libssh2_NB_state_sent3) { + /* Wait for ACK */ + rc = _libssh2_channel_read(session->scpSend_channel, 0, + (char *) session->scpSend_response, 1); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block waiting for response"); + return NULL; + } + else if (rc < 0) { + _libssh2_error(session, rc, "SCP failure"); + goto scp_send_error; + } + else if(!rc) + /* remain in the same state */ + goto scp_send_empty_channel; + else if (session->scpSend_response[0] != 0) { + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "Invalid SCP ACK response"); + goto scp_send_error; + } + + session->scpSend_state = libssh2_NB_state_sent4; + } + } else { + if (session->scpSend_state == libssh2_NB_state_sent2) { + session->scpSend_state = libssh2_NB_state_sent4; + } + } + + if (session->scpSend_state == libssh2_NB_state_sent4) { + /* Send mode, size, and basename */ + const char *base = strrchr(path, '/'); + if (base) + base++; + else + base = path; + + session->scpSend_response_len = + snprintf((char *) session->scpSend_response, + LIBSSH2_SCP_RESPONSE_BUFLEN, "C0%o %" + LIBSSH2_INT64_T_FORMAT " %s\n", mode, + size, base); + _libssh2_debug(session, LIBSSH2_TRACE_SCP, "Sent %s", + session->scpSend_response); + + session->scpSend_state = libssh2_NB_state_sent5; + } + + if (session->scpSend_state == libssh2_NB_state_sent5) { + rc = _libssh2_channel_write(session->scpSend_channel, 0, + session->scpSend_response, + session->scpSend_response_len); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block send core file data for SCP file"); + return NULL; + } else if (rc != (int)session->scpSend_response_len) { + _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send core file data for SCP file"); + goto scp_send_error; + } + + session->scpSend_state = libssh2_NB_state_sent6; + } + + if (session->scpSend_state == libssh2_NB_state_sent6) { + /* Wait for ACK */ + rc = _libssh2_channel_read(session->scpSend_channel, 0, + (char *) session->scpSend_response, 1); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block waiting for response"); + return NULL; + } + else if (rc < 0) { + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "Invalid ACK response from remote"); + goto scp_send_error; + } + else if (rc == 0) + goto scp_send_empty_channel; + + else if (session->scpSend_response[0] != 0) { + size_t err_len; + char *err_msg; + + err_len = + _libssh2_channel_packet_data_len(session->scpSend_channel, 0); + err_msg = LIBSSH2_ALLOC(session, err_len + 1); + if (!err_msg) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "failed to get memory"); + goto scp_send_error; + } + + /* Read the remote error message */ + rc = _libssh2_channel_read(session->scpSend_channel, 0, + err_msg, err_len); + if (rc > 0) { + err_msg[err_len]=0; + _libssh2_debug(session, LIBSSH2_TRACE_SCP, + "got %02x %s", session->scpSend_response[0], + err_msg); + } + LIBSSH2_FREE(session, err_msg); + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "failed to send file"); + goto scp_send_error; + } + } + + session->scpSend_state = libssh2_NB_state_idle; + return session->scpSend_channel; + + scp_send_empty_channel: + /* the code only jumps here as a result of a zero read from channel_read() + so we check EOF status to avoid getting stuck in a loop */ + if(libssh2_channel_eof(session->scpSend_channel)) { + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "Unexpected channel close"); + } + else + return session->scpSend_channel; + /* fall-through */ + scp_send_error: + tmp_err_code = session->err_code; + tmp_err_msg = session->err_msg; + while (libssh2_channel_free(session->scpSend_channel) == + LIBSSH2_ERROR_EAGAIN); + session->err_code = tmp_err_code; + session->err_msg = tmp_err_msg; + session->scpSend_channel = NULL; + session->scpSend_state = libssh2_NB_state_idle; + return NULL; +} + +/* + * libssh2_scp_send_ex + * + * Send a file using SCP. Old API. + */ +LIBSSH2_API LIBSSH2_CHANNEL * +libssh2_scp_send_ex(LIBSSH2_SESSION *session, const char *path, int mode, + size_t size, long mtime, long atime) +{ + LIBSSH2_CHANNEL *ptr; + BLOCK_ADJUST_ERRNO(ptr, session, + scp_send(session, path, mode, size, + (time_t)mtime, (time_t)atime)); + return ptr; +} + +/* + * libssh2_scp_send64 + * + * Send a file using SCP + */ +LIBSSH2_API LIBSSH2_CHANNEL * +libssh2_scp_send64(LIBSSH2_SESSION *session, const char *path, int mode, + libssh2_int64_t size, time_t mtime, time_t atime) +{ + LIBSSH2_CHANNEL *ptr; + BLOCK_ADJUST_ERRNO(ptr, session, + scp_send(session, path, mode, size, mtime, atime)); + return ptr; +} diff --git a/libssh2/src/session.c b/libssh2/src/session.c new file mode 100644 index 0000000..9838d2b --- /dev/null +++ b/libssh2/src/session.c @@ -0,0 +1,1751 @@ +/* Copyright (c) 2004-2007 Sara Golemon + * Copyright (c) 2009-2011 by Daniel Stenberg + * Copyright (c) 2010 Simon Josefsson + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include "libssh2_priv.h" +#include +#ifdef HAVE_UNISTD_H +#include +#endif +#include +#include + +#ifdef HAVE_GETTIMEOFDAY +#include +#endif +#ifdef HAVE_ALLOCA_H +#include +#endif + +#include "transport.h" +#include "session.h" +#include "channel.h" +#include "mac.h" +#include "misc.h" + +/* libssh2_default_alloc + */ +static +LIBSSH2_ALLOC_FUNC(libssh2_default_alloc) +{ + (void) abstract; + return malloc(count); +} + +/* libssh2_default_free + */ +static +LIBSSH2_FREE_FUNC(libssh2_default_free) +{ + (void) abstract; + free(ptr); +} + +/* libssh2_default_realloc + */ +static +LIBSSH2_REALLOC_FUNC(libssh2_default_realloc) +{ + (void) abstract; + return realloc(ptr, count); +} + +/* + * banner_receive + * + * Wait for a hello from the remote host + * Allocate a buffer and store the banner in session->remote.banner + * Returns: 0 on success, LIBSSH2_ERROR_EAGAIN if read would block, negative + * on failure + */ +static int +banner_receive(LIBSSH2_SESSION * session) +{ + int ret; + int banner_len; + + if (session->banner_TxRx_state == libssh2_NB_state_idle) { + banner_len = 0; + + session->banner_TxRx_state = libssh2_NB_state_created; + } else { + banner_len = session->banner_TxRx_total_send; + } + + while ((banner_len < (int) sizeof(session->banner_TxRx_banner)) && + ((banner_len == 0) + || (session->banner_TxRx_banner[banner_len - 1] != '\n'))) { + char c = '\0'; + + /* no incoming block yet! */ + session->socket_block_directions &= ~LIBSSH2_SESSION_BLOCK_INBOUND; + + ret = LIBSSH2_RECV(session, &c, 1, + LIBSSH2_SOCKET_RECV_FLAGS(session)); + if (ret < 0) { + if(session->api_block_mode || (ret != -EAGAIN)) + /* ignore EAGAIN when non-blocking */ + _libssh2_debug(session, LIBSSH2_TRACE_SOCKET, + "Error recving %d bytes: %d", 1, -ret); + } + else + _libssh2_debug(session, LIBSSH2_TRACE_SOCKET, + "Recved %d bytes banner", ret); + + if (ret < 0) { + if (ret == -EAGAIN) { + session->socket_block_directions = + LIBSSH2_SESSION_BLOCK_INBOUND; + session->banner_TxRx_total_send = banner_len; + return LIBSSH2_ERROR_EAGAIN; + } + + /* Some kinda error */ + session->banner_TxRx_state = libssh2_NB_state_idle; + session->banner_TxRx_total_send = 0; + return LIBSSH2_ERROR_SOCKET_RECV; + } + + if (ret == 0) { + session->socket_state = LIBSSH2_SOCKET_DISCONNECTED; + return LIBSSH2_ERROR_SOCKET_DISCONNECT; + } + + if (c == '\0') { + /* NULLs are not allowed in SSH banners */ + session->banner_TxRx_state = libssh2_NB_state_idle; + session->banner_TxRx_total_send = 0; + return LIBSSH2_ERROR_BANNER_RECV; + } + + session->banner_TxRx_banner[banner_len++] = c; + } + + while (banner_len && + ((session->banner_TxRx_banner[banner_len - 1] == '\n') || + (session->banner_TxRx_banner[banner_len - 1] == '\r'))) { + banner_len--; + } + + /* From this point on, we are done here */ + session->banner_TxRx_state = libssh2_NB_state_idle; + session->banner_TxRx_total_send = 0; + + if (!banner_len) + return LIBSSH2_ERROR_BANNER_RECV; + + session->remote.banner = LIBSSH2_ALLOC(session, banner_len + 1); + if (!session->remote.banner) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Error allocating space for remote banner"); + } + memcpy(session->remote.banner, session->banner_TxRx_banner, banner_len); + session->remote.banner[banner_len] = '\0'; + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, "Received Banner: %s", + session->remote.banner); + return LIBSSH2_ERROR_NONE; +} + +/* + * banner_send + * + * Send the default banner, or the one set via libssh2_setopt_string + * + * Returns LIBSSH2_ERROR_EAGAIN if it would block - and if it does so, you + * should call this function again as soon as it is likely that more data can + * be sent, and this function should then be called with the same argument set + * (same data pointer and same data_len) until zero or failure is returned. + */ +static int +banner_send(LIBSSH2_SESSION * session) +{ + char *banner = (char *) LIBSSH2_SSH_DEFAULT_BANNER_WITH_CRLF; + int banner_len = sizeof(LIBSSH2_SSH_DEFAULT_BANNER_WITH_CRLF) - 1; + ssize_t ret; +#ifdef LIBSSH2DEBUG + char banner_dup[256]; +#endif + + if (session->banner_TxRx_state == libssh2_NB_state_idle) { + if (session->local.banner) { + /* setopt_string will have given us our \r\n characters */ + banner_len = strlen((char *) session->local.banner); + banner = (char *) session->local.banner; + } +#ifdef LIBSSH2DEBUG + /* Hack and slash to avoid sending CRLF in debug output */ + if (banner_len < 256) { + memcpy(banner_dup, banner, banner_len - 2); + banner_dup[banner_len - 2] = '\0'; + } else { + memcpy(banner_dup, banner, 255); + banner[255] = '\0'; + } + + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, "Sending Banner: %s", + banner_dup); +#endif + + session->banner_TxRx_state = libssh2_NB_state_created; + } + + /* no outgoing block yet! */ + session->socket_block_directions &= ~LIBSSH2_SESSION_BLOCK_OUTBOUND; + + ret = LIBSSH2_SEND(session, + banner + session->banner_TxRx_total_send, + banner_len - session->banner_TxRx_total_send, + LIBSSH2_SOCKET_SEND_FLAGS(session)); + if (ret < 0) + _libssh2_debug(session, LIBSSH2_TRACE_SOCKET, + "Error sending %d bytes: %d", + banner_len - session->banner_TxRx_total_send, -ret); + else + _libssh2_debug(session, LIBSSH2_TRACE_SOCKET, + "Sent %d/%d bytes at %p+%d", ret, + banner_len - session->banner_TxRx_total_send, + banner, session->banner_TxRx_total_send); + + if (ret != (banner_len - session->banner_TxRx_total_send)) { + if (ret >= 0 || ret == -EAGAIN) { + /* the whole packet could not be sent, save the what was */ + session->socket_block_directions = + LIBSSH2_SESSION_BLOCK_OUTBOUND; + if (ret > 0) + session->banner_TxRx_total_send += ret; + return LIBSSH2_ERROR_EAGAIN; + } + session->banner_TxRx_state = libssh2_NB_state_idle; + session->banner_TxRx_total_send = 0; + return LIBSSH2_ERROR_SOCKET_RECV; + } + + /* Set the state back to idle */ + session->banner_TxRx_state = libssh2_NB_state_idle; + session->banner_TxRx_total_send = 0; + + return 0; +} + +/* + * session_nonblock() sets the given socket to either blocking or + * non-blocking mode based on the 'nonblock' boolean argument. This function + * is copied from the libcurl sources with permission. + */ +static int +session_nonblock(libssh2_socket_t sockfd, /* operate on this */ + int nonblock /* TRUE or FALSE */ ) +{ +#undef SETBLOCK +#define SETBLOCK 0 +#ifdef HAVE_O_NONBLOCK + /* most recent unix versions */ + int flags; + + flags = fcntl(sockfd, F_GETFL, 0); + if (nonblock) + return fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); + else + return fcntl(sockfd, F_SETFL, flags & (~O_NONBLOCK)); +#undef SETBLOCK +#define SETBLOCK 1 +#endif + +#if defined(HAVE_FIONBIO) && (SETBLOCK == 0) + /* older unix versions and VMS*/ + int flags; + + flags = nonblock; + return ioctl(sockfd, FIONBIO, &flags); +#undef SETBLOCK +#define SETBLOCK 2 +#endif + +#if defined(HAVE_IOCTLSOCKET) && (SETBLOCK == 0) + /* Windows? */ + unsigned long flags; + flags = nonblock; + + return ioctlsocket(sockfd, FIONBIO, &flags); +#undef SETBLOCK +#define SETBLOCK 3 +#endif + +#if defined(HAVE_IOCTLSOCKET_CASE) && (SETBLOCK == 0) + /* presumably for Amiga */ + return IoctlSocket(sockfd, FIONBIO, (long) nonblock); +#undef SETBLOCK +#define SETBLOCK 4 +#endif + +#if defined(HAVE_SO_NONBLOCK) && (SETBLOCK == 0) + /* BeOS */ + long b = nonblock ? 1 : 0; + return setsockopt(sockfd, SOL_SOCKET, SO_NONBLOCK, &b, sizeof(b)); +#undef SETBLOCK +#define SETBLOCK 5 +#endif + +#ifdef HAVE_DISABLED_NONBLOCKING + return 0; /* returns success */ +#undef SETBLOCK +#define SETBLOCK 6 +#endif + +#if (SETBLOCK == 0) +#error "no non-blocking method was found/used/set" +#endif +} + +/* + * get_socket_nonblocking() + * + * gets the given blocking or non-blocking state of the socket. + */ +static int +get_socket_nonblocking(int sockfd) +{ /* operate on this */ +#undef GETBLOCK +#define GETBLOCK 0 +#ifdef HAVE_O_NONBLOCK + /* most recent unix versions */ + int flags; + + if ((flags = fcntl(sockfd, F_GETFL, 0)) == -1) { + /* Assume blocking on error */ + return 1; + } + return (flags & O_NONBLOCK); +#undef GETBLOCK +#define GETBLOCK 1 +#endif + +#if defined(WSAEWOULDBLOCK) && (GETBLOCK == 0) + /* Windows? */ + unsigned int option_value; + socklen_t option_len = sizeof(option_value); + + if (getsockopt + (sockfd, SOL_SOCKET, SO_ERROR, (void *) &option_value, &option_len)) { + /* Assume blocking on error */ + return 1; + } + return (int) option_value; +#undef GETBLOCK +#define GETBLOCK 2 +#endif + +#if defined(HAVE_SO_NONBLOCK) && (GETBLOCK == 0) + /* BeOS */ + long b; + if (getsockopt(sockfd, SOL_SOCKET, SO_NONBLOCK, &b, sizeof(b))) { + /* Assume blocking on error */ + return 1; + } + return (int) b; +#undef GETBLOCK +#define GETBLOCK 5 +#endif + +#if defined(SO_STATE) && defined( __VMS ) && (GETBLOCK == 0) + + /* VMS TCP/IP Services */ + + size_t sockstat = 0; + int callstat = 0; + size_t size = sizeof( int ); + + callstat = getsockopt(sockfd, SOL_SOCKET, SO_STATE, + (char *)&sockstat, &size); + if ( callstat == -1 ) return(0); + if ( (sockstat&SS_NBIO) )return(1); + return(0); + +#undef GETBLOCK +#define GETBLOCK 6 +#endif + +#ifdef HAVE_DISABLED_NONBLOCKING + return 1; /* returns blocking */ +#undef GETBLOCK +#define GETBLOCK 7 +#endif + +#if (GETBLOCK == 0) +#error "no non-blocking method was found/used/get" +#endif +} + +/* libssh2_session_banner_set + * Set the local banner to use in the server handshake. + */ +LIBSSH2_API int +libssh2_session_banner_set(LIBSSH2_SESSION * session, const char *banner) +{ + size_t banner_len = banner ? strlen(banner) : 0; + + if (session->local.banner) { + LIBSSH2_FREE(session, session->local.banner); + session->local.banner = NULL; + } + + if (!banner_len) + return 0; + + session->local.banner = LIBSSH2_ALLOC(session, banner_len + 3); + if (!session->local.banner) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for local banner"); + } + + memcpy(session->local.banner, banner, banner_len); + + /* first zero terminate like this so that the debug output is nice */ + session->local.banner[banner_len] = '\0'; + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, "Setting local Banner: %s", + session->local.banner); + session->local.banner[banner_len++] = '\r'; + session->local.banner[banner_len++] = '\n'; + session->local.banner[banner_len] = '\0'; + + return 0; +} + +/* libssh2_banner_set + * Set the local banner. DEPRECATED VERSION + */ +LIBSSH2_API int +libssh2_banner_set(LIBSSH2_SESSION * session, const char *banner) +{ + return libssh2_session_banner_set(session, banner); +} + +/* + * libssh2_session_init_ex + * + * Allocate and initialize a libssh2 session structure. Allows for malloc + * callbacks in case the calling program has its own memory manager It's + * allowable (but unadvisable) to define some but not all of the malloc + * callbacks An additional pointer value may be optionally passed to be sent + * to the callbacks (so they know who's asking) + */ +LIBSSH2_API LIBSSH2_SESSION * +libssh2_session_init_ex(LIBSSH2_ALLOC_FUNC((*my_alloc)), + LIBSSH2_FREE_FUNC((*my_free)), + LIBSSH2_REALLOC_FUNC((*my_realloc)), void *abstract) +{ + LIBSSH2_ALLOC_FUNC((*local_alloc)) = libssh2_default_alloc; + LIBSSH2_FREE_FUNC((*local_free)) = libssh2_default_free; + LIBSSH2_REALLOC_FUNC((*local_realloc)) = libssh2_default_realloc; + LIBSSH2_SESSION *session; + + if (my_alloc) { + local_alloc = my_alloc; + } + if (my_free) { + local_free = my_free; + } + if (my_realloc) { + local_realloc = my_realloc; + } + + session = local_alloc(sizeof(LIBSSH2_SESSION), &abstract); + if (session) { + memset(session, 0, sizeof(LIBSSH2_SESSION)); + session->alloc = local_alloc; + session->free = local_free; + session->realloc = local_realloc; + session->send = _libssh2_send; + session->recv = _libssh2_recv; + session->abstract = abstract; + session->api_timeout = 0; /* timeout-free API by default */ + session->api_block_mode = 1; /* blocking API by default */ + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, + "New session resource allocated"); + _libssh2_init_if_needed (); + } + return session; +} + +/* + * libssh2_session_callback_set + * + * Set (or reset) a callback function + * Returns the prior address + * + * FIXME: this function relies on that we can typecast function pointers + * to void pointers, which isn't allowed in ISO C! + */ +LIBSSH2_API void * +libssh2_session_callback_set(LIBSSH2_SESSION * session, + int cbtype, void *callback) +{ + void *oldcb; + + switch (cbtype) { + case LIBSSH2_CALLBACK_IGNORE: + oldcb = session->ssh_msg_ignore; + session->ssh_msg_ignore = callback; + return oldcb; + + case LIBSSH2_CALLBACK_DEBUG: + oldcb = session->ssh_msg_debug; + session->ssh_msg_debug = callback; + return oldcb; + + case LIBSSH2_CALLBACK_DISCONNECT: + oldcb = session->ssh_msg_disconnect; + session->ssh_msg_disconnect = callback; + return oldcb; + + case LIBSSH2_CALLBACK_MACERROR: + oldcb = session->macerror; + session->macerror = callback; + return oldcb; + + case LIBSSH2_CALLBACK_X11: + oldcb = session->x11; + session->x11 = callback; + return oldcb; + + case LIBSSH2_CALLBACK_SEND: + oldcb = session->send; + session->send = callback; + return oldcb; + + case LIBSSH2_CALLBACK_RECV: + oldcb = session->recv; + session->recv = callback; + return oldcb; + } + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, "Setting Callback %d", cbtype); + + return NULL; +} + +/* + * _libssh2_wait_socket() + * + * Utility function that waits for action on the socket. Returns 0 when ready + * to run again or error on timeout. + */ +int _libssh2_wait_socket(LIBSSH2_SESSION *session, time_t start_time) +{ + int rc; + int seconds_to_next; + int dir; + int has_timeout; + long ms_to_next = 0; + long elapsed_ms; + + /* since libssh2 often sets EAGAIN internally before this function is + called, we can decrease some amount of confusion in user programs by + resetting the error code in this function to reduce the risk of EAGAIN + being stored as error when a blocking function has returned */ + session->err_code = LIBSSH2_ERROR_NONE; + + rc = libssh2_keepalive_send (session, &seconds_to_next); + if (rc < 0) + return rc; + + ms_to_next = seconds_to_next * 1000; + + /* figure out what to wait for */ + dir = libssh2_session_block_directions(session); + + if(!dir) { + _libssh2_debug(session, LIBSSH2_TRACE_SOCKET, + "Nothing to wait for in wait_socket"); + /* To avoid that we hang below just because there's nothing set to + wait for, we timeout on 1 second to also avoid busy-looping + during this condition */ + ms_to_next = 1000; + } + + if (session->api_timeout > 0 && + (seconds_to_next == 0 || + seconds_to_next > session->api_timeout)) { + time_t now = time (NULL); + elapsed_ms = (long)(1000*difftime(start_time, now)); + if (elapsed_ms > session->api_timeout) { + session->err_code = LIBSSH2_ERROR_TIMEOUT; + return LIBSSH2_ERROR_TIMEOUT; + } + ms_to_next = (session->api_timeout - elapsed_ms); + has_timeout = 1; + } + else if (ms_to_next > 0) { + has_timeout = 1; + } + else + has_timeout = 0; + +#ifdef HAVE_POLL + { + struct pollfd sockets[1]; + + sockets[0].fd = session->socket_fd; + sockets[0].events = 0; + sockets[0].revents = 0; + + if(dir & LIBSSH2_SESSION_BLOCK_INBOUND) + sockets[0].events |= POLLIN; + + if(dir & LIBSSH2_SESSION_BLOCK_OUTBOUND) + sockets[0].events |= POLLOUT; + + rc = poll(sockets, 1, has_timeout?ms_to_next: -1); + } +#else + { + fd_set rfd; + fd_set wfd; + fd_set *writefd = NULL; + fd_set *readfd = NULL; + struct timeval tv; + + tv.tv_sec = ms_to_next / 1000; + tv.tv_usec = (ms_to_next - tv.tv_sec*1000) * 1000; + + if(dir & LIBSSH2_SESSION_BLOCK_INBOUND) { + FD_ZERO(&rfd); + FD_SET(session->socket_fd, &rfd); + readfd = &rfd; + } + + if(dir & LIBSSH2_SESSION_BLOCK_OUTBOUND) { + FD_ZERO(&wfd); + FD_SET(session->socket_fd, &wfd); + writefd = &wfd; + } + + rc = select(session->socket_fd + 1, readfd, writefd, NULL, + has_timeout ? &tv : NULL); + } +#endif + if(rc <= 0) { + /* timeout (or error), bail out with a timeout error */ + session->err_code = LIBSSH2_ERROR_TIMEOUT; + return LIBSSH2_ERROR_TIMEOUT; + } + + return 0; /* ready to try again */ +} + +static int +session_startup(LIBSSH2_SESSION *session, libssh2_socket_t sock) +{ + int rc; + + if (session->startup_state == libssh2_NB_state_idle) { + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, + "session_startup for socket %d", sock); + if (LIBSSH2_INVALID_SOCKET == sock) { + /* Did we forget something? */ + return _libssh2_error(session, LIBSSH2_ERROR_BAD_SOCKET, + "Bad socket provided"); + } + session->socket_fd = sock; + + session->socket_prev_blockstate = + !get_socket_nonblocking(session->socket_fd); + + if (session->socket_prev_blockstate) { + /* If in blocking state chang to non-blocking */ + session_nonblock(session->socket_fd, 1); + } + + session->startup_state = libssh2_NB_state_created; + } + + if (session->startup_state == libssh2_NB_state_created) { + rc = banner_send(session); + if (rc) { + return _libssh2_error(session, rc, + "Failed sending banner"); + } + session->startup_state = libssh2_NB_state_sent; + session->banner_TxRx_state = libssh2_NB_state_idle; + } + + if (session->startup_state == libssh2_NB_state_sent) { + do { + rc = banner_receive(session); + if (rc) + return _libssh2_error(session, rc, + "Failed getting banner"); + } while(strncmp("SSH-", (char *)session->remote.banner, 4)); + + session->startup_state = libssh2_NB_state_sent1; + } + + if (session->startup_state == libssh2_NB_state_sent1) { + rc = _libssh2_kex_exchange(session, 0, &session->startup_key_state); + if (rc) + return _libssh2_error(session, rc, + "Unable to exchange encryption keys"); + + session->startup_state = libssh2_NB_state_sent2; + } + + if (session->startup_state == libssh2_NB_state_sent2) { + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, + "Requesting userauth service"); + + /* Request the userauth service */ + session->startup_service[0] = SSH_MSG_SERVICE_REQUEST; + _libssh2_htonu32(session->startup_service + 1, + sizeof("ssh-userauth") - 1); + memcpy(session->startup_service + 5, "ssh-userauth", + sizeof("ssh-userauth") - 1); + + session->startup_state = libssh2_NB_state_sent3; + } + + if (session->startup_state == libssh2_NB_state_sent3) { + rc = _libssh2_transport_send(session, session->startup_service, + sizeof("ssh-userauth") + 5 - 1, + NULL, 0); + if (rc) { + return _libssh2_error(session, rc, + "Unable to ask for ssh-userauth service"); + } + + session->startup_state = libssh2_NB_state_sent4; + } + + if (session->startup_state == libssh2_NB_state_sent4) { + rc = _libssh2_packet_require(session, SSH_MSG_SERVICE_ACCEPT, + &session->startup_data, + &session->startup_data_len, 0, NULL, 0, + &session->startup_req_state); + if (rc) + return rc; + + session->startup_service_length = + _libssh2_ntohu32(session->startup_data + 1); + + if ((session->startup_service_length != (sizeof("ssh-userauth") - 1)) + || strncmp("ssh-userauth", (char *) session->startup_data + 5, + session->startup_service_length)) { + LIBSSH2_FREE(session, session->startup_data); + session->startup_data = NULL; + return _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Invalid response received from server"); + } + LIBSSH2_FREE(session, session->startup_data); + session->startup_data = NULL; + + session->startup_state = libssh2_NB_state_idle; + + return 0; + } + + /* just for safety return some error */ + return LIBSSH2_ERROR_INVAL; +} + +/* + * libssh2_session_handshake() + * + * session: LIBSSH2_SESSION struct allocated and owned by the calling program + * sock: *must* be populated with an opened and connected socket. + * + * Returns: 0 on success, or non-zero on failure + */ +LIBSSH2_API int +libssh2_session_handshake(LIBSSH2_SESSION *session, libssh2_socket_t sock) +{ + int rc; + + BLOCK_ADJUST(rc, session, session_startup(session, sock) ); + + return rc; +} + +/* + * libssh2_session_startup() + * + * DEPRECATED. Use libssh2_session_handshake() instead! This function is not + * portable enough. + * + * session: LIBSSH2_SESSION struct allocated and owned by the calling program + * sock: *must* be populated with an opened and connected socket. + * + * Returns: 0 on success, or non-zero on failure + */ +LIBSSH2_API int +libssh2_session_startup(LIBSSH2_SESSION *session, int sock) +{ + return libssh2_session_handshake(session, (libssh2_socket_t) sock); +} + +/* + * libssh2_session_free + * + * Frees the memory allocated to the session + * Also closes and frees any channels attached to this session + */ +static int +session_free(LIBSSH2_SESSION *session) +{ + int rc; + LIBSSH2_PACKET *pkg; + LIBSSH2_CHANNEL *ch; + LIBSSH2_LISTENER *l; + int packets_left = 0; + + if (session->free_state == libssh2_NB_state_idle) { + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, "Freeing session resource", + session->remote.banner); + + session->free_state = libssh2_NB_state_created; + } + + if (session->free_state == libssh2_NB_state_created) { + while ((ch = _libssh2_list_first(&session->channels))) { + + rc = _libssh2_channel_free(ch); + if (rc == LIBSSH2_ERROR_EAGAIN) + return rc; + } + + session->free_state = libssh2_NB_state_sent; + } + + if (session->free_state == libssh2_NB_state_sent) { + while ((l = _libssh2_list_first(&session->listeners))) { + rc = _libssh2_channel_forward_cancel(l); + if (rc == LIBSSH2_ERROR_EAGAIN) + return rc; + } + + session->free_state = libssh2_NB_state_sent1; + } + + if (session->state & LIBSSH2_STATE_NEWKEYS) { + /* hostkey */ + if (session->hostkey && session->hostkey->dtor) { + session->hostkey->dtor(session, &session->server_hostkey_abstract); + } + + /* Client to Server */ + /* crypt */ + if (session->local.crypt && session->local.crypt->dtor) { + session->local.crypt->dtor(session, + &session->local.crypt_abstract); + } + /* comp */ + if (session->local.comp && session->local.comp->dtor) { + session->local.comp->dtor(session, 1, + &session->local.comp_abstract); + } + /* mac */ + if (session->local.mac && session->local.mac->dtor) { + session->local.mac->dtor(session, &session->local.mac_abstract); + } + + /* Server to Client */ + /* crypt */ + if (session->remote.crypt && session->remote.crypt->dtor) { + session->remote.crypt->dtor(session, + &session->remote.crypt_abstract); + } + /* comp */ + if (session->remote.comp && session->remote.comp->dtor) { + session->remote.comp->dtor(session, 0, + &session->remote.comp_abstract); + } + /* mac */ + if (session->remote.mac && session->remote.mac->dtor) { + session->remote.mac->dtor(session, &session->remote.mac_abstract); + } + + /* session_id */ + if (session->session_id) { + LIBSSH2_FREE(session, session->session_id); + } + } + + /* Free banner(s) */ + if (session->remote.banner) { + LIBSSH2_FREE(session, session->remote.banner); + } + if (session->local.banner) { + LIBSSH2_FREE(session, session->local.banner); + } + + /* Free preference(s) */ + if (session->kex_prefs) { + LIBSSH2_FREE(session, session->kex_prefs); + } + if (session->hostkey_prefs) { + LIBSSH2_FREE(session, session->hostkey_prefs); + } + + if (session->local.kexinit) { + LIBSSH2_FREE(session, session->local.kexinit); + } + if (session->local.crypt_prefs) { + LIBSSH2_FREE(session, session->local.crypt_prefs); + } + if (session->local.mac_prefs) { + LIBSSH2_FREE(session, session->local.mac_prefs); + } + if (session->local.comp_prefs) { + LIBSSH2_FREE(session, session->local.comp_prefs); + } + if (session->local.lang_prefs) { + LIBSSH2_FREE(session, session->local.lang_prefs); + } + + if (session->remote.kexinit) { + LIBSSH2_FREE(session, session->remote.kexinit); + } + if (session->remote.crypt_prefs) { + LIBSSH2_FREE(session, session->remote.crypt_prefs); + } + if (session->remote.mac_prefs) { + LIBSSH2_FREE(session, session->remote.mac_prefs); + } + if (session->remote.comp_prefs) { + LIBSSH2_FREE(session, session->remote.comp_prefs); + } + if (session->remote.lang_prefs) { + LIBSSH2_FREE(session, session->remote.lang_prefs); + } + + /* + * Make sure all memory used in the state variables are free + */ + if (session->kexinit_data) { + LIBSSH2_FREE(session, session->kexinit_data); + } + if (session->startup_data) { + LIBSSH2_FREE(session, session->startup_data); + } + if (session->userauth_list_data) { + LIBSSH2_FREE(session, session->userauth_list_data); + } + if (session->userauth_pswd_data) { + LIBSSH2_FREE(session, session->userauth_pswd_data); + } + if (session->userauth_pswd_newpw) { + LIBSSH2_FREE(session, session->userauth_pswd_newpw); + } + if (session->userauth_host_packet) { + LIBSSH2_FREE(session, session->userauth_host_packet); + } + if (session->userauth_host_method) { + LIBSSH2_FREE(session, session->userauth_host_method); + } + if (session->userauth_host_data) { + LIBSSH2_FREE(session, session->userauth_host_data); + } + if (session->userauth_pblc_data) { + LIBSSH2_FREE(session, session->userauth_pblc_data); + } + if (session->userauth_pblc_packet) { + LIBSSH2_FREE(session, session->userauth_pblc_packet); + } + if (session->userauth_pblc_method) { + LIBSSH2_FREE(session, session->userauth_pblc_method); + } + if (session->userauth_kybd_data) { + LIBSSH2_FREE(session, session->userauth_kybd_data); + } + if (session->userauth_kybd_packet) { + LIBSSH2_FREE(session, session->userauth_kybd_packet); + } + if (session->userauth_kybd_auth_instruction) { + LIBSSH2_FREE(session, session->userauth_kybd_auth_instruction); + } + if (session->open_packet) { + LIBSSH2_FREE(session, session->open_packet); + } + if (session->open_data) { + LIBSSH2_FREE(session, session->open_data); + } + if (session->direct_message) { + LIBSSH2_FREE(session, session->direct_message); + } + if (session->fwdLstn_packet) { + LIBSSH2_FREE(session, session->fwdLstn_packet); + } + if (session->pkeyInit_data) { + LIBSSH2_FREE(session, session->pkeyInit_data); + } + if (session->scpRecv_command) { + LIBSSH2_FREE(session, session->scpRecv_command); + } + if (session->scpSend_command) { + LIBSSH2_FREE(session, session->scpSend_command); + } + + /* Cleanup all remaining packets */ + while ((pkg = _libssh2_list_first(&session->packets))) { + packets_left++; + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, + "packet left with id %d", pkg->data[0]); + /* unlink the node */ + _libssh2_list_remove(&pkg->node); + + /* free */ + LIBSSH2_FREE(session, pkg->data); + LIBSSH2_FREE(session, pkg); + } + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, + "Extra packets left %d", packets_left); + + if(session->socket_prev_blockstate) + /* if the socket was previously blocking, put it back so */ + session_nonblock(session->socket_fd, 0); + + if (session->server_hostkey) { + LIBSSH2_FREE(session, session->server_hostkey); + } + + LIBSSH2_FREE(session, session); + + return 0; +} + +/* + * libssh2_session_free + * + * Frees the memory allocated to the session + * Also closes and frees any channels attached to this session + */ +LIBSSH2_API int +libssh2_session_free(LIBSSH2_SESSION * session) +{ + int rc; + + BLOCK_ADJUST(rc, session, session_free(session) ); + + return rc; +} + +/* + * libssh2_session_disconnect_ex + */ +static int +session_disconnect(LIBSSH2_SESSION *session, int reason, + const char *description, + const char *lang) +{ + unsigned char *s; + unsigned long descr_len = 0, lang_len = 0; + int rc; + + if (session->disconnect_state == libssh2_NB_state_idle) { + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, + "Disconnecting: reason=%d, desc=%s, lang=%s", reason, + description, lang); + if (description) + descr_len = strlen(description); + + if (lang) + lang_len = strlen(lang); + + if(descr_len > 256) + return _libssh2_error(session, LIBSSH2_ERROR_INVAL, + "too long description"); + + /* 13 = packet_type(1) + reason code(4) + descr_len(4) + lang_len(4) */ + session->disconnect_data_len = descr_len + lang_len + 13; + + s = session->disconnect_data; + + *(s++) = SSH_MSG_DISCONNECT; + _libssh2_store_u32(&s, reason); + _libssh2_store_str(&s, description, descr_len); + /* store length only, lang is sent separately */ + _libssh2_store_u32(&s, lang_len); + + session->disconnect_state = libssh2_NB_state_created; + } + + rc = _libssh2_transport_send(session, session->disconnect_data, + session->disconnect_data_len, + (unsigned char *)lang, lang_len); + if (rc == LIBSSH2_ERROR_EAGAIN) + return rc; + + session->disconnect_state = libssh2_NB_state_idle; + + return 0; +} + +/* + * libssh2_session_disconnect_ex + */ +LIBSSH2_API int +libssh2_session_disconnect_ex(LIBSSH2_SESSION *session, int reason, + const char *desc, const char *lang) +{ + int rc; + + BLOCK_ADJUST(rc, session, + session_disconnect(session, reason, desc, lang)); + + return rc; +} + +/* libssh2_session_methods + * + * Return the currently active methods for method_type + * + * NOTE: Currently lang_cs and lang_sc are ALWAYS set to empty string + * regardless of actual negotiation Strings should NOT be freed + */ +LIBSSH2_API const char * +libssh2_session_methods(LIBSSH2_SESSION * session, int method_type) +{ + /* All methods have char *name as their first element */ + const LIBSSH2_KEX_METHOD *method = NULL; + + switch (method_type) { + case LIBSSH2_METHOD_KEX: + method = session->kex; + break; + + case LIBSSH2_METHOD_HOSTKEY: + method = (LIBSSH2_KEX_METHOD *) session->hostkey; + break; + + case LIBSSH2_METHOD_CRYPT_CS: + method = (LIBSSH2_KEX_METHOD *) session->local.crypt; + break; + + case LIBSSH2_METHOD_CRYPT_SC: + method = (LIBSSH2_KEX_METHOD *) session->remote.crypt; + break; + + case LIBSSH2_METHOD_MAC_CS: + method = (LIBSSH2_KEX_METHOD *) session->local.mac; + break; + + case LIBSSH2_METHOD_MAC_SC: + method = (LIBSSH2_KEX_METHOD *) session->remote.mac; + break; + + case LIBSSH2_METHOD_COMP_CS: + method = (LIBSSH2_KEX_METHOD *) session->local.comp; + break; + + case LIBSSH2_METHOD_COMP_SC: + method = (LIBSSH2_KEX_METHOD *) session->remote.comp; + break; + + case LIBSSH2_METHOD_LANG_CS: + return ""; + + case LIBSSH2_METHOD_LANG_SC: + return ""; + + default: + _libssh2_error(session, LIBSSH2_ERROR_INVAL, + "Invalid parameter specified for method_type"); + return NULL; + } + + if (!method) { + _libssh2_error(session, LIBSSH2_ERROR_METHOD_NONE, + "No method negotiated"); + return NULL; + } + + return method->name; +} + +/* libssh2_session_abstract + * Retrieve a pointer to the abstract property + */ +LIBSSH2_API void ** +libssh2_session_abstract(LIBSSH2_SESSION * session) +{ + return &session->abstract; +} + +/* libssh2_session_last_error + * + * Returns error code and populates an error string into errmsg If want_buf is + * non-zero then the string placed into errmsg must be freed by the calling + * program. Otherwise it is assumed to be owned by libssh2 + */ +LIBSSH2_API int +libssh2_session_last_error(LIBSSH2_SESSION * session, char **errmsg, + int *errmsg_len, int want_buf) +{ + size_t msglen = 0; + + /* No error to report */ + if (!session->err_code) { + if (errmsg) { + if (want_buf) { + *errmsg = LIBSSH2_ALLOC(session, 1); + if (*errmsg) { + **errmsg = 0; + } + } else { + *errmsg = (char *) ""; + } + } + if (errmsg_len) { + *errmsg_len = 0; + } + return 0; + } + + if (errmsg) { + const char *error = session->err_msg ? session->err_msg : ""; + + msglen = strlen(error); + + if (want_buf) { + /* Make a copy so the calling program can own it */ + *errmsg = LIBSSH2_ALLOC(session, msglen + 1); + if (*errmsg) { + memcpy(*errmsg, error, msglen); + (*errmsg)[msglen] = 0; + } + } + else + *errmsg = (char *)error; + } + + if (errmsg_len) { + *errmsg_len = msglen; + } + + return session->err_code; +} + +/* libssh2_session_last_errno + * + * Returns error code + */ +LIBSSH2_API int +libssh2_session_last_errno(LIBSSH2_SESSION * session) +{ + return session->err_code; +} + +/* libssh2_session_flag + * + * Set/Get session flags + * + * Return error code. + */ +LIBSSH2_API int +libssh2_session_flag(LIBSSH2_SESSION * session, int flag, int value) +{ + switch(flag) { + case LIBSSH2_FLAG_SIGPIPE: + session->flag.sigpipe = value; + break; + case LIBSSH2_FLAG_COMPRESS: + session->flag.compress = value; + break; + default: + /* unknown flag */ + return LIBSSH2_ERROR_INVAL; + } + + return LIBSSH2_ERROR_NONE; +} + +/* _libssh2_session_set_blocking + * + * Set a session's blocking mode on or off, return the previous status when + * this function is called. Note this function does not alter the state of the + * actual socket involved. + */ +int +_libssh2_session_set_blocking(LIBSSH2_SESSION *session, int blocking) +{ + int bl = session->api_block_mode; + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Setting blocking mode %s", blocking?"ON":"OFF"); + session->api_block_mode = blocking; + + return bl; +} + +/* libssh2_session_set_blocking + * + * Set a channel's blocking mode on or off, similar to a socket's + * fcntl(fd, F_SETFL, O_NONBLOCK); type command + */ +LIBSSH2_API void +libssh2_session_set_blocking(LIBSSH2_SESSION * session, int blocking) +{ + (void) _libssh2_session_set_blocking(session, blocking); +} + +/* libssh2_session_get_blocking + * + * Returns a session's blocking mode on or off + */ +LIBSSH2_API int +libssh2_session_get_blocking(LIBSSH2_SESSION * session) +{ + return session->api_block_mode; +} + + +/* libssh2_session_set_timeout + * + * Set a session's timeout (in msec) for blocking mode, + * or 0 to disable timeouts. + */ +LIBSSH2_API void +libssh2_session_set_timeout(LIBSSH2_SESSION * session, long timeout) +{ + session->api_timeout = timeout; +} + +/* libssh2_session_get_timeout + * + * Returns a session's timeout, or 0 if disabled + */ +LIBSSH2_API long +libssh2_session_get_timeout(LIBSSH2_SESSION * session) +{ + return session->api_timeout; +} + +/* + * libssh2_poll_channel_read + * + * Returns 0 if no data is waiting on channel, + * non-0 if data is available + */ +LIBSSH2_API int +libssh2_poll_channel_read(LIBSSH2_CHANNEL *channel, int extended) +{ + LIBSSH2_SESSION *session; + LIBSSH2_PACKET *packet; + + if(!channel) + return LIBSSH2_ERROR_BAD_USE; + + session = channel->session; + packet = _libssh2_list_first(&session->packets); + + while (packet) { + if ( channel->local.id == _libssh2_ntohu32(packet->data + 1)) { + if ( extended == 1 && + (packet->data[0] == SSH_MSG_CHANNEL_EXTENDED_DATA + || packet->data[0] == SSH_MSG_CHANNEL_DATA )) { + return 1; + } else if ( extended == 0 && + packet->data[0] == SSH_MSG_CHANNEL_DATA) { + return 1; + } + /* else - no data of any type is ready to be read */ + } + packet = _libssh2_list_next(&packet->node); + } + + return 0; +} + +/* + * poll_channel_write + * + * Returns 0 if writing to channel would block, + * non-0 if data can be written without blocking + */ +static inline int +poll_channel_write(LIBSSH2_CHANNEL * channel) +{ + return channel->local.window_size ? 1 : 0; +} + +/* poll_listener_queued + * + * Returns 0 if no connections are waiting to be accepted + * non-0 if one or more connections are available + */ +static inline int +poll_listener_queued(LIBSSH2_LISTENER * listener) +{ + return _libssh2_list_first(&listener->queue) ? 1 : 0; +} + +/* + * libssh2_poll + * + * Poll sockets, channels, and listeners for activity + */ +LIBSSH2_API int +libssh2_poll(LIBSSH2_POLLFD * fds, unsigned int nfds, long timeout) +{ + long timeout_remaining; + unsigned int i, active_fds; +#ifdef HAVE_POLL + LIBSSH2_SESSION *session = NULL; +#ifdef HAVE_ALLOCA + struct pollfd *sockets = alloca(sizeof(struct pollfd) * nfds); +#else + struct pollfd sockets[256]; + + if (nfds > 256) + /* systems without alloca use a fixed-size array, this can be fixed if + we really want to, at least if the compiler is a C99 capable one */ + return -1; +#endif + /* Setup sockets for polling */ + for(i = 0; i < nfds; i++) { + fds[i].revents = 0; + switch (fds[i].type) { + case LIBSSH2_POLLFD_SOCKET: + sockets[i].fd = fds[i].fd.socket; + sockets[i].events = fds[i].events; + sockets[i].revents = 0; + break; + + case LIBSSH2_POLLFD_CHANNEL: + sockets[i].fd = fds[i].fd.channel->session->socket_fd; + sockets[i].events = POLLIN; + sockets[i].revents = 0; + if (!session) + session = fds[i].fd.channel->session; + break; + + case LIBSSH2_POLLFD_LISTENER: + sockets[i].fd = fds[i].fd.listener->session->socket_fd; + sockets[i].events = POLLIN; + sockets[i].revents = 0; + if (!session) + session = fds[i].fd.listener->session; + break; + + default: + if (session) + _libssh2_error(session, LIBSSH2_ERROR_INVALID_POLL_TYPE, + "Invalid descriptor passed to libssh2_poll()"); + return -1; + } + } +#elif defined(HAVE_SELECT) + LIBSSH2_SESSION *session = NULL; + libssh2_socket_t maxfd = 0; + fd_set rfds, wfds; + struct timeval tv; + + FD_ZERO(&rfds); + FD_ZERO(&wfds); + for(i = 0; i < nfds; i++) { + fds[i].revents = 0; + switch (fds[i].type) { + case LIBSSH2_POLLFD_SOCKET: + if (fds[i].events & LIBSSH2_POLLFD_POLLIN) { + FD_SET(fds[i].fd.socket, &rfds); + if (fds[i].fd.socket > maxfd) + maxfd = fds[i].fd.socket; + } + if (fds[i].events & LIBSSH2_POLLFD_POLLOUT) { + FD_SET(fds[i].fd.socket, &wfds); + if (fds[i].fd.socket > maxfd) + maxfd = fds[i].fd.socket; + } + break; + + case LIBSSH2_POLLFD_CHANNEL: + FD_SET(fds[i].fd.channel->session->socket_fd, &rfds); + if (fds[i].fd.channel->session->socket_fd > maxfd) + maxfd = fds[i].fd.channel->session->socket_fd; + if (!session) + session = fds[i].fd.channel->session; + break; + + case LIBSSH2_POLLFD_LISTENER: + FD_SET(fds[i].fd.listener->session->socket_fd, &rfds); + if (fds[i].fd.listener->session->socket_fd > maxfd) + maxfd = fds[i].fd.listener->session->socket_fd; + if (!session) + session = fds[i].fd.listener->session; + break; + + default: + if (session) + _libssh2_error(session, LIBSSH2_ERROR_INVALID_POLL_TYPE, + "Invalid descriptor passed to libssh2_poll()"); + return -1; + } + } +#else + /* No select() or poll() + * no sockets sturcture to setup + */ + + timeout = 0; +#endif /* HAVE_POLL or HAVE_SELECT */ + + timeout_remaining = timeout; + do { +#if defined(HAVE_POLL) || defined(HAVE_SELECT) + int sysret; +#endif + + active_fds = 0; + + for(i = 0; i < nfds; i++) { + if (fds[i].events != fds[i].revents) { + switch (fds[i].type) { + case LIBSSH2_POLLFD_CHANNEL: + if ((fds[i].events & LIBSSH2_POLLFD_POLLIN) && + /* Want to be ready for read */ + ((fds[i].revents & LIBSSH2_POLLFD_POLLIN) == 0)) { + /* Not yet known to be ready for read */ + fds[i].revents |= + libssh2_poll_channel_read(fds[i].fd.channel, + 0) ? + LIBSSH2_POLLFD_POLLIN : 0; + } + if ((fds[i].events & LIBSSH2_POLLFD_POLLEXT) && + /* Want to be ready for extended read */ + ((fds[i].revents & LIBSSH2_POLLFD_POLLEXT) == 0)) { + /* Not yet known to be ready for extended read */ + fds[i].revents |= + libssh2_poll_channel_read(fds[i].fd.channel, + 1) ? + LIBSSH2_POLLFD_POLLEXT : 0; + } + if ((fds[i].events & LIBSSH2_POLLFD_POLLOUT) && + /* Want to be ready for write */ + ((fds[i].revents & LIBSSH2_POLLFD_POLLOUT) == 0)) { + /* Not yet known to be ready for write */ + fds[i].revents |= + poll_channel_write(fds[i].fd. channel) ? + LIBSSH2_POLLFD_POLLOUT : 0; + } + if (fds[i].fd.channel->remote.close + || fds[i].fd.channel->local.close) { + fds[i].revents |= LIBSSH2_POLLFD_CHANNEL_CLOSED; + } + if (fds[i].fd.channel->session->socket_state == + LIBSSH2_SOCKET_DISCONNECTED) { + fds[i].revents |= + LIBSSH2_POLLFD_CHANNEL_CLOSED | + LIBSSH2_POLLFD_SESSION_CLOSED; + } + break; + + case LIBSSH2_POLLFD_LISTENER: + if ((fds[i].events & LIBSSH2_POLLFD_POLLIN) && + /* Want a connection */ + ((fds[i].revents & LIBSSH2_POLLFD_POLLIN) == 0)) { + /* No connections known of yet */ + fds[i].revents |= + poll_listener_queued(fds[i].fd. listener) ? + LIBSSH2_POLLFD_POLLIN : 0; + } + if (fds[i].fd.listener->session->socket_state == + LIBSSH2_SOCKET_DISCONNECTED) { + fds[i].revents |= + LIBSSH2_POLLFD_LISTENER_CLOSED | + LIBSSH2_POLLFD_SESSION_CLOSED; + } + break; + } + } + if (fds[i].revents) { + active_fds++; + } + } + + if (active_fds) { + /* Don't block on the sockets if we have channels/listeners which + are ready */ + timeout_remaining = 0; + } +#ifdef HAVE_POLL + +#ifdef HAVE_LIBSSH2_GETTIMEOFDAY + { + struct timeval tv_begin, tv_end; + + _libssh2_gettimeofday((struct timeval *) &tv_begin, NULL); + sysret = poll(sockets, nfds, timeout_remaining); + _libssh2_gettimeofday((struct timeval *) &tv_end, NULL); + timeout_remaining -= (tv_end.tv_sec - tv_begin.tv_sec) * 1000; + timeout_remaining -= (tv_end.tv_usec - tv_begin.tv_usec) / 1000; + } +#else + /* If the platform doesn't support gettimeofday, + * then just make the call non-blocking and walk away + */ + sysret = poll(sockets, nfds, timeout_remaining); + timeout_remaining = 0; +#endif /* HAVE_GETTIMEOFDAY */ + + if (sysret > 0) { + for(i = 0; i < nfds; i++) { + switch (fds[i].type) { + case LIBSSH2_POLLFD_SOCKET: + fds[i].revents = sockets[i].revents; + sockets[i].revents = 0; /* In case we loop again, be nice */ + if (fds[i].revents) { + active_fds++; + } + break; + case LIBSSH2_POLLFD_CHANNEL: + if (sockets[i].events & POLLIN) { + /* Spin session until no data available */ + while (_libssh2_transport_read(fds[i].fd.channel->session) + > 0); + } + if (sockets[i].revents & POLLHUP) { + fds[i].revents |= + LIBSSH2_POLLFD_CHANNEL_CLOSED | + LIBSSH2_POLLFD_SESSION_CLOSED; + } + sockets[i].revents = 0; + break; + case LIBSSH2_POLLFD_LISTENER: + if (sockets[i].events & POLLIN) { + /* Spin session until no data available */ + while (_libssh2_transport_read(fds[i].fd.listener->session) + > 0); + } + if (sockets[i].revents & POLLHUP) { + fds[i].revents |= + LIBSSH2_POLLFD_LISTENER_CLOSED | + LIBSSH2_POLLFD_SESSION_CLOSED; + } + sockets[i].revents = 0; + break; + } + } + } +#elif defined(HAVE_SELECT) + tv.tv_sec = timeout_remaining / 1000; + tv.tv_usec = (timeout_remaining % 1000) * 1000; +#ifdef HAVE_LIBSSH2_GETTIMEOFDAY + { + struct timeval tv_begin, tv_end; + + _libssh2_gettimeofday((struct timeval *) &tv_begin, NULL); + sysret = select(maxfd+1, &rfds, &wfds, NULL, &tv); + _libssh2_gettimeofday((struct timeval *) &tv_end, NULL); + + timeout_remaining -= (tv_end.tv_sec - tv_begin.tv_sec) * 1000; + timeout_remaining -= (tv_end.tv_usec - tv_begin.tv_usec) / 1000; + } +#else + /* If the platform doesn't support gettimeofday, + * then just make the call non-blocking and walk away + */ + sysret = select(maxfd+1, &rfds, &wfds, NULL, &tv); + timeout_remaining = 0; +#endif + + if (sysret > 0) { + for(i = 0; i < nfds; i++) { + switch (fds[i].type) { + case LIBSSH2_POLLFD_SOCKET: + if (FD_ISSET(fds[i].fd.socket, &rfds)) { + fds[i].revents |= LIBSSH2_POLLFD_POLLIN; + } + if (FD_ISSET(fds[i].fd.socket, &wfds)) { + fds[i].revents |= LIBSSH2_POLLFD_POLLOUT; + } + if (fds[i].revents) { + active_fds++; + } + break; + + case LIBSSH2_POLLFD_CHANNEL: + if (FD_ISSET(fds[i].fd.channel->session->socket_fd, &rfds)) { + /* Spin session until no data available */ + while (_libssh2_transport_read(fds[i].fd.channel->session) + > 0); + } + break; + + case LIBSSH2_POLLFD_LISTENER: + if (FD_ISSET + (fds[i].fd.listener->session->socket_fd, &rfds)) { + /* Spin session until no data available */ + while (_libssh2_transport_read(fds[i].fd.listener->session) + > 0); + } + break; + } + } + } +#endif /* else no select() or poll() -- timeout (and by extension + * timeout_remaining) will be equal to 0 */ + } while ((timeout_remaining > 0) && !active_fds); + + return active_fds; +} + +/* + * libssh2_session_block_directions + * + * Get blocked direction when a function returns LIBSSH2_ERROR_EAGAIN + * Returns LIBSSH2_SOCKET_BLOCK_INBOUND if recv() blocked + * or LIBSSH2_SOCKET_BLOCK_OUTBOUND if send() blocked + */ +LIBSSH2_API int +libssh2_session_block_directions(LIBSSH2_SESSION *session) +{ + return session->socket_block_directions; +} + +/* libssh2_session_banner_get + * Get the remote banner (server ID string) + */ + +LIBSSH2_API const char * +libssh2_session_banner_get(LIBSSH2_SESSION *session) +{ + /* to avoid a coredump when session is NULL */ + if (NULL == session) + return NULL; + + if (NULL==session->remote.banner) + return NULL; + + return (const char *) session->remote.banner; +} diff --git a/libssh2/src/sftp.c b/libssh2/src/sftp.c new file mode 100644 index 0000000..d0536dd --- /dev/null +++ b/libssh2/src/sftp.c @@ -0,0 +1,3285 @@ +/* Copyright (c) 2004-2008, Sara Golemon + * Copyright (c) 2007 Eli Fant + * Copyright (c) 2009-2012 by Daniel Stenberg + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include + +#include "libssh2_priv.h" +#include "libssh2_sftp.h" +#include "channel.h" +#include "session.h" +#include "sftp.h" + +/* Note: Version 6 was documented at the time of writing + * However it was marked as "DO NOT IMPLEMENT" due to pending changes + * + * This release of libssh2 implements Version 5 with automatic downgrade + * based on server's declaration + */ + +/* SFTP packet types */ +#define SSH_FXP_INIT 1 +#define SSH_FXP_VERSION 2 +#define SSH_FXP_OPEN 3 +#define SSH_FXP_CLOSE 4 +#define SSH_FXP_READ 5 +#define SSH_FXP_WRITE 6 +#define SSH_FXP_LSTAT 7 +#define SSH_FXP_FSTAT 8 +#define SSH_FXP_SETSTAT 9 +#define SSH_FXP_FSETSTAT 10 +#define SSH_FXP_OPENDIR 11 +#define SSH_FXP_READDIR 12 +#define SSH_FXP_REMOVE 13 +#define SSH_FXP_MKDIR 14 +#define SSH_FXP_RMDIR 15 +#define SSH_FXP_REALPATH 16 +#define SSH_FXP_STAT 17 +#define SSH_FXP_RENAME 18 +#define SSH_FXP_READLINK 19 +#define SSH_FXP_SYMLINK 20 +#define SSH_FXP_STATUS 101 +#define SSH_FXP_HANDLE 102 +#define SSH_FXP_DATA 103 +#define SSH_FXP_NAME 104 +#define SSH_FXP_ATTRS 105 +#define SSH_FXP_EXTENDED 200 +#define SSH_FXP_EXTENDED_REPLY 201 + +/* S_IFREG */ +#define LIBSSH2_SFTP_ATTR_PFILETYPE_FILE 0100000 +/* S_IFDIR */ +#define LIBSSH2_SFTP_ATTR_PFILETYPE_DIR 0040000 + +#define SSH_FXE_STATVFS_ST_RDONLY 0x00000001 +#define SSH_FXE_STATVFS_ST_NOSUID 0x00000002 + +/* This is the maximum packet length to accept, as larger than this indicate + some kind of server problem. */ +#define LIBSSH2_SFTP_PACKET_MAXLEN 80000 + +static int sftp_close_handle(LIBSSH2_SFTP_HANDLE *handle); +static int sftp_packet_ask(LIBSSH2_SFTP *sftp, unsigned char packet_type, + uint32_t request_id, unsigned char **data, + size_t *data_len); +static void sftp_packet_flush(LIBSSH2_SFTP *sftp); + +/* sftp_attrsize + * Size that attr with this flagset will occupy when turned into a bin struct + */ +static int sftp_attrsize(unsigned long flags) +{ + return (4 + /* flags(4) */ + ((flags & LIBSSH2_SFTP_ATTR_SIZE) ? 8 : 0) + + ((flags & LIBSSH2_SFTP_ATTR_UIDGID) ? 8 : 0) + + ((flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) ? 4 : 0) + + ((flags & LIBSSH2_SFTP_ATTR_ACMODTIME) ? 8 : 0)); + /* atime + mtime as u32 */ +} + +/* _libssh2_store_u64 + */ +static void _libssh2_store_u64(unsigned char **ptr, libssh2_uint64_t value) +{ + uint32_t msl = (uint32_t)(value >> 32); + unsigned char *buf = *ptr; + + buf[0] = (unsigned char)((msl >> 24) & 0xFF); + buf[1] = (unsigned char)((msl >> 16) & 0xFF); + buf[2] = (unsigned char)((msl >> 8) & 0xFF); + buf[3] = (unsigned char)( msl & 0xFF); + + buf[4] = (unsigned char)((value >> 24) & 0xFF); + buf[5] = (unsigned char)((value >> 16) & 0xFF); + buf[6] = (unsigned char)((value >> 8) & 0xFF); + buf[7] = (unsigned char)( value & 0xFF); + + *ptr += 8; +} + +/* + * Search list of zombied FXP_READ request IDs. + * + * Returns NULL if ID not in list. + */ +static struct sftp_zombie_requests * +find_zombie_request(LIBSSH2_SFTP *sftp, uint32_t request_id) +{ + struct sftp_zombie_requests *zombie = + _libssh2_list_first(&sftp->zombie_requests); + + while(zombie) { + if(zombie->request_id == request_id) + break; + else + zombie = _libssh2_list_next(&zombie->node); + } + + return zombie; +} + +static void +remove_zombie_request(LIBSSH2_SFTP *sftp, uint32_t request_id) +{ + LIBSSH2_SESSION *session = sftp->channel->session; + + struct sftp_zombie_requests *zombie = find_zombie_request(sftp, + request_id); + if(zombie) { + _libssh2_debug(session, LIBSSH2_TRACE_SFTP, + "Removing request ID %ld from the list of zombie requests", + request_id); + + _libssh2_list_remove(&zombie->node); + LIBSSH2_FREE(session, zombie); + } +} + +static int +add_zombie_request(LIBSSH2_SFTP *sftp, uint32_t request_id) +{ + LIBSSH2_SESSION *session = sftp->channel->session; + + struct sftp_zombie_requests *zombie; + + _libssh2_debug(session, LIBSSH2_TRACE_SFTP, + "Marking request ID %ld as a zombie request", request_id); + + zombie = LIBSSH2_ALLOC(sftp->channel->session, + sizeof(struct sftp_zombie_requests)); + if (!zombie) + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "malloc fail for zombie request ID"); + else { + zombie->request_id = request_id; + _libssh2_list_add(&sftp->zombie_requests, &zombie->node); + return LIBSSH2_ERROR_NONE; + } +} + +/* + * sftp_packet_add + * + * Add a packet to the SFTP packet brigade + */ +static int +sftp_packet_add(LIBSSH2_SFTP *sftp, unsigned char *data, + size_t data_len) +{ + LIBSSH2_SESSION *session = sftp->channel->session; + LIBSSH2_SFTP_PACKET *packet; + uint32_t request_id; + + _libssh2_debug(session, LIBSSH2_TRACE_SFTP, "Received packet %d (len %d)", + (int) data[0], data_len); + + /* + * Experience shows that if we mess up EAGAIN handling somewhere or + * otherwise get out of sync with the channel, this is where we first get + * a wrong byte and if so we need to bail out at once to aid tracking the + * problem better. + */ + + switch(data[0]) { + case SSH_FXP_INIT: + case SSH_FXP_VERSION: + case SSH_FXP_OPEN: + case SSH_FXP_CLOSE: + case SSH_FXP_READ: + case SSH_FXP_WRITE: + case SSH_FXP_LSTAT: + case SSH_FXP_FSTAT: + case SSH_FXP_SETSTAT: + case SSH_FXP_FSETSTAT: + case SSH_FXP_OPENDIR: + case SSH_FXP_READDIR: + case SSH_FXP_REMOVE: + case SSH_FXP_MKDIR: + case SSH_FXP_RMDIR: + case SSH_FXP_REALPATH: + case SSH_FXP_STAT: + case SSH_FXP_RENAME: + case SSH_FXP_READLINK: + case SSH_FXP_SYMLINK: + case SSH_FXP_STATUS: + case SSH_FXP_HANDLE: + case SSH_FXP_DATA: + case SSH_FXP_NAME: + case SSH_FXP_ATTRS: + case SSH_FXP_EXTENDED: + case SSH_FXP_EXTENDED_REPLY: + break; + default: + return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, + "Out of sync with the world"); + } + + request_id = _libssh2_ntohu32(&data[1]); + + /* Don't add the packet if it answers a request we've given up on. */ + if((data[0] == SSH_FXP_STATUS || data[0] == SSH_FXP_DATA) + && find_zombie_request(sftp, request_id)) { + + /* If we get here, the file ended before the response arrived. We + are no longer interested in the request so we discard it */ + + LIBSSH2_FREE(session, data); + + remove_zombie_request(sftp, request_id); + return LIBSSH2_ERROR_NONE; + } + + packet = LIBSSH2_ALLOC(session, sizeof(LIBSSH2_SFTP_PACKET)); + if (!packet) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate datablock for SFTP packet"); + } + + packet->data = data; + packet->data_len = data_len; + packet->request_id = request_id; + + _libssh2_list_add(&sftp->packets, &packet->node); + + return LIBSSH2_ERROR_NONE; +} + +/* + * sftp_packet_read + * + * Frame an SFTP packet off the channel + */ +static int +sftp_packet_read(LIBSSH2_SFTP *sftp) +{ + LIBSSH2_CHANNEL *channel = sftp->channel; + LIBSSH2_SESSION *session = channel->session; + unsigned char *packet = NULL; + ssize_t rc; + unsigned long recv_window; + int packet_type; + + _libssh2_debug(session, LIBSSH2_TRACE_SFTP, "recv packet"); + + switch(sftp->packet_state) { + case libssh2_NB_state_sent: /* EAGAIN from window adjusting */ + sftp->packet_state = libssh2_NB_state_idle; + + packet = sftp->partial_packet; + goto window_adjust; + + case libssh2_NB_state_sent1: /* EAGAIN from channel read */ + sftp->packet_state = libssh2_NB_state_idle; + + packet = sftp->partial_packet; + + _libssh2_debug(session, LIBSSH2_TRACE_SFTP, + "partial read cont, len: %lu", sftp->partial_len); + _libssh2_debug(session, LIBSSH2_TRACE_SFTP, + "partial read cont, already recvd: %lu", + sftp->partial_received); + /* fall-through */ + default: + if(!packet) { + /* only do this if there's not already a packet buffer allocated + to use */ + + /* each packet starts with a 32 bit length field */ + rc = _libssh2_channel_read(channel, 0, + (char *)&sftp->partial_size[ + sftp->partial_size_len], + 4 - sftp->partial_size_len); + if (rc == LIBSSH2_ERROR_EAGAIN) + return rc; + else if (rc < 0) + return _libssh2_error(session, rc, "channel read"); + + sftp->partial_size_len += rc; + + if(4 != sftp->partial_size_len) + /* we got a short read for the length part */ + return LIBSSH2_ERROR_EAGAIN; + + sftp->partial_len = _libssh2_ntohu32(sftp->partial_size); + /* make sure we don't proceed if the packet size is unreasonably + large */ + if (sftp->partial_len > LIBSSH2_SFTP_PACKET_MAXLEN) + return _libssh2_error(session, + LIBSSH2_ERROR_CHANNEL_PACKET_EXCEEDED, + "SFTP packet too large"); + + _libssh2_debug(session, LIBSSH2_TRACE_SFTP, + "Data begin - Packet Length: %lu", + sftp->partial_len); + packet = LIBSSH2_ALLOC(session, sftp->partial_len); + if (!packet) + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate SFTP packet"); + sftp->partial_size_len = 0; + sftp->partial_received = 0; /* how much of the packet already + received */ + sftp->partial_packet = packet; + + window_adjust: + recv_window = libssh2_channel_window_read_ex(channel, NULL, NULL); + + if(sftp->partial_len > recv_window) { + /* ask for twice the data amount we need at once */ + rc = _libssh2_channel_receive_window_adjust(channel, + sftp->partial_len*2, + 1, NULL); + /* store the state so that we continue with the correct + operation at next invoke */ + sftp->packet_state = (rc == LIBSSH2_ERROR_EAGAIN)? + libssh2_NB_state_sent: + libssh2_NB_state_idle; + + if(rc == LIBSSH2_ERROR_EAGAIN) + return rc; + } + } + + /* Read as much of the packet as we can */ + while (sftp->partial_len > sftp->partial_received) { + rc = _libssh2_channel_read(channel, 0, + (char *)&packet[sftp->partial_received], + sftp->partial_len - + sftp->partial_received); + + if (rc == LIBSSH2_ERROR_EAGAIN) { + /* + * We received EAGAIN, save what we have and return EAGAIN to + * the caller. Set 'partial_packet' so that this function + * knows how to continue on the next invoke. + */ + sftp->packet_state = libssh2_NB_state_sent1; + return rc; + } + else if (rc < 0) { + LIBSSH2_FREE(session, packet); + sftp->partial_packet = NULL; + return _libssh2_error(session, rc, + "Error waiting for SFTP packet"); + } + sftp->partial_received += rc; + } + + sftp->partial_packet = NULL; + + /* sftp_packet_add takes ownership of the packet and might free it + so we take a copy of the packet type before we call it. */ + packet_type = packet[0]; + rc = sftp_packet_add(sftp, packet, sftp->partial_len); + if (rc) { + LIBSSH2_FREE(session, packet); + return rc; + } + else { + return packet_type; + } + } + /* WON'T REACH */ +} +/* + * sftp_packetlist_flush + * + * Remove all pending packets in the packet_list and the corresponding one(s) + * in the SFTP packet brigade. + */ +static void sftp_packetlist_flush(LIBSSH2_SFTP_HANDLE *handle) +{ + struct sftp_pipeline_chunk *chunk; + LIBSSH2_SFTP *sftp = handle->sftp; + LIBSSH2_SESSION *session = sftp->channel->session; + + /* remove pending packets, if any */ + chunk = _libssh2_list_first(&handle->packet_list); + while(chunk) { + unsigned char *data; + size_t data_len; + int rc; + struct sftp_pipeline_chunk *next = _libssh2_list_next(&chunk->node); + + rc = sftp_packet_ask(sftp, SSH_FXP_STATUS, + chunk->request_id, &data, &data_len); + if(rc) + rc = sftp_packet_ask(sftp, SSH_FXP_DATA, + chunk->request_id, &data, &data_len); + + if(!rc) + /* we found a packet, free it */ + LIBSSH2_FREE(session, data); + else if(chunk->sent) + /* there was no incoming packet for this request, mark this + request as a zombie if it ever sent the request */ + add_zombie_request(sftp, chunk->request_id); + + _libssh2_list_remove(&chunk->node); + LIBSSH2_FREE(session, chunk); + chunk = next; + } +} + + +/* + * sftp_packet_ask() + * + * Checks if there's a matching SFTP packet available. + */ +static int +sftp_packet_ask(LIBSSH2_SFTP *sftp, unsigned char packet_type, + uint32_t request_id, unsigned char **data, + size_t *data_len) +{ + LIBSSH2_SESSION *session = sftp->channel->session; + LIBSSH2_SFTP_PACKET *packet = _libssh2_list_first(&sftp->packets); + + if(!packet) + return -1; + + /* Special consideration when getting VERSION packet */ + + while (packet) { + if((packet->data[0] == packet_type) && + ((packet_type == SSH_FXP_VERSION) || + (packet->request_id == request_id))) { + + /* Match! Fetch the data */ + *data = packet->data; + *data_len = packet->data_len; + + /* unlink and free this struct */ + _libssh2_list_remove(&packet->node); + LIBSSH2_FREE(session, packet); + + return 0; + } + /* check next struct in the list */ + packet = _libssh2_list_next(&packet->node); + } + return -1; +} + +/* sftp_packet_require + * A la libssh2_packet_require + */ +static int +sftp_packet_require(LIBSSH2_SFTP *sftp, unsigned char packet_type, + uint32_t request_id, unsigned char **data, + size_t *data_len) +{ + LIBSSH2_SESSION *session = sftp->channel->session; + int rc; + + _libssh2_debug(session, LIBSSH2_TRACE_SFTP, "Requiring packet %d id %ld", + (int) packet_type, request_id); + + if (sftp_packet_ask(sftp, packet_type, request_id, data, data_len) == 0) { + /* The right packet was available in the packet brigade */ + _libssh2_debug(session, LIBSSH2_TRACE_SFTP, "Got %d", + (int) packet_type); + return LIBSSH2_ERROR_NONE; + } + + while (session->socket_state == LIBSSH2_SOCKET_CONNECTED) { + rc = sftp_packet_read(sftp); + if (rc < 0) + return rc; + + /* data was read, check the queue again */ + if (!sftp_packet_ask(sftp, packet_type, request_id, data, data_len)) { + /* The right packet was available in the packet brigade */ + _libssh2_debug(session, LIBSSH2_TRACE_SFTP, "Got %d", + (int) packet_type); + return LIBSSH2_ERROR_NONE; + } + } + + /* Only reached if the socket died */ + return LIBSSH2_ERROR_SOCKET_DISCONNECT; +} + +/* sftp_packet_requirev + * Require one of N possible reponses + */ +static int +sftp_packet_requirev(LIBSSH2_SFTP *sftp, int num_valid_responses, + const unsigned char *valid_responses, + uint32_t request_id, unsigned char **data, + size_t *data_len) +{ + int i; + int rc; + + /* If no timeout is active, start a new one */ + if (sftp->requirev_start == 0) + sftp->requirev_start = time(NULL); + + while (sftp->channel->session->socket_state == LIBSSH2_SOCKET_CONNECTED) { + for(i = 0; i < num_valid_responses; i++) { + if (sftp_packet_ask(sftp, valid_responses[i], request_id, + data, data_len) == 0) { + /* + * Set to zero before all returns to say + * the timeout is not active + */ + sftp->requirev_start = 0; + return LIBSSH2_ERROR_NONE; + } + } + + rc = sftp_packet_read(sftp); + if ((rc < 0) && (rc != LIBSSH2_ERROR_EAGAIN)) { + sftp->requirev_start = 0; + return rc; + } else if (rc <= 0) { + /* prevent busy-looping */ + long left = + LIBSSH2_READ_TIMEOUT - (long)(time(NULL) - sftp->requirev_start); + + if (left <= 0) { + sftp->requirev_start = 0; + return LIBSSH2_ERROR_TIMEOUT; + } + else if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } + } + } + + sftp->requirev_start = 0; + + /* Only reached if the socket died */ + return LIBSSH2_ERROR_SOCKET_DISCONNECT; +} + +/* sftp_attr2bin + * Populate attributes into an SFTP block + */ +static ssize_t +sftp_attr2bin(unsigned char *p, const LIBSSH2_SFTP_ATTRIBUTES * attrs) +{ + unsigned char *s = p; + uint32_t flag_mask = + LIBSSH2_SFTP_ATTR_SIZE | LIBSSH2_SFTP_ATTR_UIDGID | + LIBSSH2_SFTP_ATTR_PERMISSIONS | LIBSSH2_SFTP_ATTR_ACMODTIME; + + /* TODO: When we add SFTP4+ functionality flag_mask can get additional + bits */ + + if (!attrs) { + _libssh2_htonu32(s, 0); + return 4; + } + + _libssh2_store_u32(&s, attrs->flags & flag_mask); + + if (attrs->flags & LIBSSH2_SFTP_ATTR_SIZE) { + _libssh2_store_u64(&s, attrs->filesize); + } + + if (attrs->flags & LIBSSH2_SFTP_ATTR_UIDGID) { + _libssh2_store_u32(&s, attrs->uid); + _libssh2_store_u32(&s, attrs->gid); + } + + if (attrs->flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) { + _libssh2_store_u32(&s, attrs->permissions); + } + + if (attrs->flags & LIBSSH2_SFTP_ATTR_ACMODTIME) { + _libssh2_store_u32(&s, attrs->atime); + _libssh2_store_u32(&s, attrs->mtime); + } + + return (s - p); +} + +/* sftp_bin2attr + */ +static int +sftp_bin2attr(LIBSSH2_SFTP_ATTRIBUTES * attrs, const unsigned char *p) +{ + const unsigned char *s = p; + + memset(attrs, 0, sizeof(LIBSSH2_SFTP_ATTRIBUTES)); + attrs->flags = _libssh2_ntohu32(s); + s += 4; + + if (attrs->flags & LIBSSH2_SFTP_ATTR_SIZE) { + attrs->filesize = _libssh2_ntohu64(s); + s += 8; + } + + if (attrs->flags & LIBSSH2_SFTP_ATTR_UIDGID) { + attrs->uid = _libssh2_ntohu32(s); + s += 4; + attrs->gid = _libssh2_ntohu32(s); + s += 4; + } + + if (attrs->flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) { + attrs->permissions = _libssh2_ntohu32(s); + s += 4; + } + + if (attrs->flags & LIBSSH2_SFTP_ATTR_ACMODTIME) { + attrs->atime = _libssh2_ntohu32(s); + s += 4; + attrs->mtime = _libssh2_ntohu32(s); + s += 4; + } + + return (s - p); +} + +/* ************ + * SFTP API * + ************ */ + +LIBSSH2_CHANNEL_CLOSE_FUNC(libssh2_sftp_dtor); + +/* libssh2_sftp_dtor + * Shutdown an SFTP stream when the channel closes + */ +LIBSSH2_CHANNEL_CLOSE_FUNC(libssh2_sftp_dtor) +{ + LIBSSH2_SFTP *sftp = (LIBSSH2_SFTP *) (*channel_abstract); + + (void) session_abstract; + (void) channel; + + /* Free the partial packet storage for sftp_packet_read */ + if (sftp->partial_packet) { + LIBSSH2_FREE(session, sftp->partial_packet); + } + + /* Free the packet storage for _libssh2_sftp_packet_readdir */ + if (sftp->readdir_packet) { + LIBSSH2_FREE(session, sftp->readdir_packet); + } + + LIBSSH2_FREE(session, sftp); +} + +/* + * sftp_init + * + * Startup an SFTP session + */ +static LIBSSH2_SFTP *sftp_init(LIBSSH2_SESSION *session) +{ + unsigned char *data, *s; + size_t data_len; + ssize_t rc; + LIBSSH2_SFTP *sftp_handle; + + if (session->sftpInit_state == libssh2_NB_state_idle) { + _libssh2_debug(session, LIBSSH2_TRACE_SFTP, + "Initializing SFTP subsystem"); + + /* + * The 'sftpInit_sftp' and 'sftpInit_channel' struct fields within the + * session struct are only to be used during the setup phase. As soon + * as the SFTP session is created they are cleared and can thus be + * re-used again to allow any amount of SFTP handles per sessions. + * + * Note that you MUST NOT try to call libssh2_sftp_init() again to get + * another handle until the previous call has finished and either + * succesffully made a handle or failed and returned error (not + * including *EAGAIN). + */ + + assert(session->sftpInit_sftp == NULL); + session->sftpInit_sftp = NULL; + session->sftpInit_state = libssh2_NB_state_created; + } + + sftp_handle = session->sftpInit_sftp; + + if (session->sftpInit_state == libssh2_NB_state_created) { + session->sftpInit_channel = + _libssh2_channel_open(session, "session", sizeof("session") - 1, + LIBSSH2_CHANNEL_WINDOW_DEFAULT, + LIBSSH2_CHANNEL_PACKET_DEFAULT, NULL, 0); + if (!session->sftpInit_channel) { + if (libssh2_session_last_errno(session) == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block starting up channel"); + } + else { + _libssh2_error(session, LIBSSH2_ERROR_CHANNEL_FAILURE, + "Unable to startup channel"); + session->sftpInit_state = libssh2_NB_state_idle; + } + return NULL; + } + + session->sftpInit_state = libssh2_NB_state_sent; + } + + if (session->sftpInit_state == libssh2_NB_state_sent) { + int ret = _libssh2_channel_process_startup(session->sftpInit_channel, + "subsystem", + sizeof("subsystem") - 1, "sftp", + strlen("sftp")); + if (ret == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block to request SFTP subsystem"); + return NULL; + } else if (ret) { + _libssh2_error(session, LIBSSH2_ERROR_CHANNEL_FAILURE, + "Unable to request SFTP subsystem"); + goto sftp_init_error; + } + + session->sftpInit_state = libssh2_NB_state_sent1; + } + + if (session->sftpInit_state == libssh2_NB_state_sent1) { + rc = _libssh2_channel_extended_data(session->sftpInit_channel, + LIBSSH2_CHANNEL_EXTENDED_DATA_IGNORE); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block requesting handle extended data"); + return NULL; + } + + sftp_handle = + session->sftpInit_sftp = + LIBSSH2_ALLOC(session, sizeof(LIBSSH2_SFTP)); + if (!sftp_handle) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate a new SFTP structure"); + goto sftp_init_error; + } + memset(sftp_handle, 0, sizeof(LIBSSH2_SFTP)); + sftp_handle->channel = session->sftpInit_channel; + sftp_handle->request_id = 0; + + _libssh2_htonu32(session->sftpInit_buffer, 5); + session->sftpInit_buffer[4] = SSH_FXP_INIT; + _libssh2_htonu32(session->sftpInit_buffer + 5, LIBSSH2_SFTP_VERSION); + session->sftpInit_sent = 0; /* nothing's sent yet */ + + _libssh2_debug(session, LIBSSH2_TRACE_SFTP, + "Sending FXP_INIT packet advertising version %d support", + (int) LIBSSH2_SFTP_VERSION); + + session->sftpInit_state = libssh2_NB_state_sent2; + } + + if (session->sftpInit_state == libssh2_NB_state_sent2) { + /* sent off what's left of the init buffer to send */ + rc = _libssh2_channel_write(session->sftpInit_channel, 0, + session->sftpInit_buffer + + session->sftpInit_sent, + 9 - session->sftpInit_sent); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block sending SSH_FXP_INIT"); + return NULL; + } + else if(rc < 0) { + _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send SSH_FXP_INIT"); + goto sftp_init_error; + } + else { + /* add up the number of bytes sent */ + session->sftpInit_sent += rc; + + if(session->sftpInit_sent == 9) + /* move on */ + session->sftpInit_state = libssh2_NB_state_sent3; + + /* if less than 9, we remain in this state to send more later on */ + } + } + + rc = sftp_packet_require(sftp_handle, SSH_FXP_VERSION, + 0, &data, &data_len); + if (rc == LIBSSH2_ERROR_EAGAIN) + return NULL; + else if (rc) { + _libssh2_error(session, rc, + "Timeout waiting for response from SFTP subsystem"); + goto sftp_init_error; + } + if (data_len < 5) { + _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, + "Invalid SSH_FXP_VERSION response"); + goto sftp_init_error; + } + + s = data + 1; + sftp_handle->version = _libssh2_ntohu32(s); + s += 4; + if (sftp_handle->version > LIBSSH2_SFTP_VERSION) { + _libssh2_debug(session, LIBSSH2_TRACE_SFTP, + "Truncating remote SFTP version from %lu", + sftp_handle->version); + sftp_handle->version = LIBSSH2_SFTP_VERSION; + } + _libssh2_debug(session, LIBSSH2_TRACE_SFTP, + "Enabling SFTP version %lu compatability", + sftp_handle->version); + while (s < (data + data_len)) { + size_t extname_len, extdata_len; + + extname_len = _libssh2_ntohu32(s); + s += 4; + /* the extension name starts here */ + s += extname_len; + + extdata_len = _libssh2_ntohu32(s); + s += 4; + + /* TODO: Actually process extensions */ + s += extdata_len; + + } + LIBSSH2_FREE(session, data); + + /* Make sure that when the channel gets closed, the SFTP service is shut + down too */ + sftp_handle->channel->abstract = sftp_handle; + sftp_handle->channel->close_cb = libssh2_sftp_dtor; + + session->sftpInit_state = libssh2_NB_state_idle; + + /* clear the sftp and channel pointers in this session struct now */ + session->sftpInit_sftp = NULL; + session->sftpInit_channel = NULL; + + _libssh2_list_init(&sftp_handle->sftp_handles); + + return sftp_handle; + + sftp_init_error: + while (_libssh2_channel_free(session->sftpInit_channel) == + LIBSSH2_ERROR_EAGAIN); + session->sftpInit_channel = NULL; + if (session->sftpInit_sftp) { + LIBSSH2_FREE(session, session->sftpInit_sftp); + session->sftpInit_sftp = NULL; + } + session->sftpInit_state = libssh2_NB_state_idle; + return NULL; +} + +/* + * libssh2_sftp_init + * + * Startup an SFTP session + */ +LIBSSH2_API LIBSSH2_SFTP *libssh2_sftp_init(LIBSSH2_SESSION *session) +{ + LIBSSH2_SFTP *ptr; + + if(!session) + return NULL; + + if(!(session->state & LIBSSH2_STATE_AUTHENTICATED)) { + _libssh2_error(session, LIBSSH2_ERROR_INVAL, + "session not authenticated yet"); + return NULL; + } + + BLOCK_ADJUST_ERRNO(ptr, session, sftp_init(session)); + return ptr; +} + +/* + * sftp_shutdown + * + * Shutsdown the SFTP subsystem + */ +static int +sftp_shutdown(LIBSSH2_SFTP *sftp) +{ + int rc; + LIBSSH2_SESSION *session = sftp->channel->session; + /* + * Make sure all memory used in the state variables are free + */ + if (sftp->partial_packet) { + LIBSSH2_FREE(session, sftp->partial_packet); + sftp->partial_packet = NULL; + } + if (sftp->open_packet) { + LIBSSH2_FREE(session, sftp->open_packet); + sftp->open_packet = NULL; + } + if (sftp->readdir_packet) { + LIBSSH2_FREE(session, sftp->readdir_packet); + sftp->readdir_packet = NULL; + } + if (sftp->fstat_packet) { + LIBSSH2_FREE(session, sftp->fstat_packet); + sftp->fstat_packet = NULL; + } + if (sftp->unlink_packet) { + LIBSSH2_FREE(session, sftp->unlink_packet); + sftp->unlink_packet = NULL; + } + if (sftp->rename_packet) { + LIBSSH2_FREE(session, sftp->rename_packet); + sftp->rename_packet = NULL; + } + if (sftp->fstatvfs_packet) { + LIBSSH2_FREE(session, sftp->fstatvfs_packet); + sftp->fstatvfs_packet = NULL; + } + if (sftp->statvfs_packet) { + LIBSSH2_FREE(session, sftp->statvfs_packet); + sftp->statvfs_packet = NULL; + } + if (sftp->mkdir_packet) { + LIBSSH2_FREE(session, sftp->mkdir_packet); + sftp->mkdir_packet = NULL; + } + if (sftp->rmdir_packet) { + LIBSSH2_FREE(session, sftp->rmdir_packet); + sftp->rmdir_packet = NULL; + } + if (sftp->stat_packet) { + LIBSSH2_FREE(session, sftp->stat_packet); + sftp->stat_packet = NULL; + } + if (sftp->symlink_packet) { + LIBSSH2_FREE(session, sftp->symlink_packet); + sftp->symlink_packet = NULL; + } + + sftp_packet_flush(sftp); + + /* TODO: We should consider walking over the sftp_handles list and kill + * any remaining sftp handles ... */ + + rc = _libssh2_channel_free(sftp->channel); + + return rc; +} + +/* libssh2_sftp_shutdown + * Shutsdown the SFTP subsystem + */ +LIBSSH2_API int +libssh2_sftp_shutdown(LIBSSH2_SFTP *sftp) +{ + int rc; + if(!sftp) + return LIBSSH2_ERROR_BAD_USE; + BLOCK_ADJUST(rc, sftp->channel->session, sftp_shutdown(sftp)); + return rc; +} + +/* ******************************* + * SFTP File and Directory Ops * + ******************************* */ + +/* sftp_open + */ +static LIBSSH2_SFTP_HANDLE * +sftp_open(LIBSSH2_SFTP *sftp, const char *filename, + size_t filename_len, uint32_t flags, long mode, + int open_type) +{ + LIBSSH2_CHANNEL *channel = sftp->channel; + LIBSSH2_SESSION *session = channel->session; + LIBSSH2_SFTP_HANDLE *fp; + LIBSSH2_SFTP_ATTRIBUTES attrs = { + LIBSSH2_SFTP_ATTR_PERMISSIONS, 0, 0, 0, 0, 0, 0 + }; + unsigned char *s; + ssize_t rc; + int open_file = (open_type == LIBSSH2_SFTP_OPENFILE)?1:0; + + if (sftp->open_state == libssh2_NB_state_idle) { + /* packet_len(4) + packet_type(1) + request_id(4) + filename_len(4) + + flags(4) */ + sftp->open_packet_len = filename_len + 13 + + (open_file? (4 + sftp_attrsize(LIBSSH2_SFTP_ATTR_PERMISSIONS)) : 0); + + /* surprise! this starts out with nothing sent */ + sftp->open_packet_sent = 0; + s = sftp->open_packet = LIBSSH2_ALLOC(session, sftp->open_packet_len); + if (!sftp->open_packet) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for FXP_OPEN or " + "FXP_OPENDIR packet"); + return NULL; + } + /* Filetype in SFTP 3 and earlier */ + attrs.permissions = mode | + (open_file ? LIBSSH2_SFTP_ATTR_PFILETYPE_FILE : + LIBSSH2_SFTP_ATTR_PFILETYPE_DIR); + + _libssh2_store_u32(&s, sftp->open_packet_len - 4); + *(s++) = open_file? SSH_FXP_OPEN : SSH_FXP_OPENDIR; + sftp->open_request_id = sftp->request_id++; + _libssh2_store_u32(&s, sftp->open_request_id); + _libssh2_store_str(&s, filename, filename_len); + + if (open_file) { + _libssh2_store_u32(&s, flags); + s += sftp_attr2bin(s, &attrs); + } + + _libssh2_debug(session, LIBSSH2_TRACE_SFTP, "Sending %s open request", + open_file? "file" : "directory"); + + sftp->open_state = libssh2_NB_state_created; + } + + if (sftp->open_state == libssh2_NB_state_created) { + rc = _libssh2_channel_write(channel, 0, sftp->open_packet+ + sftp->open_packet_sent, + sftp->open_packet_len - + sftp->open_packet_sent); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block sending FXP_OPEN or FXP_OPENDIR command"); + return NULL; + } + else if(rc < 0) { + _libssh2_error(session, rc, "Unable to send FXP_OPEN*"); + LIBSSH2_FREE(session, sftp->open_packet); + sftp->open_packet = NULL; + sftp->open_state = libssh2_NB_state_idle; + return NULL; + } + + /* bump the sent counter and remain in this state until the whole + data is off */ + sftp->open_packet_sent += rc; + + if(sftp->open_packet_len == sftp->open_packet_sent) { + LIBSSH2_FREE(session, sftp->open_packet); + sftp->open_packet = NULL; + + sftp->open_state = libssh2_NB_state_sent; + } + } + + if (sftp->open_state == libssh2_NB_state_sent) { + size_t data_len; + unsigned char *data; + static const unsigned char fopen_responses[2] = + { SSH_FXP_HANDLE, SSH_FXP_STATUS }; + rc = sftp_packet_requirev(sftp, 2, fopen_responses, + sftp->open_request_id, &data, + &data_len); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block waiting for status message"); + return NULL; + } + sftp->open_state = libssh2_NB_state_idle; + if (rc) { + _libssh2_error(session, rc, "Timeout waiting for status message"); + return NULL; + } + + /* OPEN can basically get STATUS or HANDLE back, where HANDLE implies + a fine response while STATUS means error. It seems though that at + times we get an SSH_FX_OK back in a STATUS, followed the "real" + HANDLE so we need to properly deal with that. */ + if (data[0] == SSH_FXP_STATUS) { + int badness = 1; + + if(data_len < 9) { + _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, + "Too small FXP_STATUS"); + LIBSSH2_FREE(session, data); + return NULL; + } + + sftp->last_errno = _libssh2_ntohu32(data + 5); + + if(LIBSSH2_FX_OK == sftp->last_errno) { + _libssh2_debug(session, LIBSSH2_TRACE_SFTP, "got HANDLE FXOK!"); + + LIBSSH2_FREE(session, data); + + /* silly situation, but check for a HANDLE */ + rc = sftp_packet_require(sftp, SSH_FXP_HANDLE, + sftp->open_request_id, &data, + &data_len); + if(rc == LIBSSH2_ERROR_EAGAIN) { + /* go back to sent state and wait for something else */ + sftp->open_state = libssh2_NB_state_sent; + return NULL; + } + else if(!rc) + /* we got the handle so this is not a bad situation */ + badness = 0; + } + + if(badness) { + _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, + "Failed opening remote file"); + _libssh2_debug(session, LIBSSH2_TRACE_SFTP, "got FXP_STATUS %d", + sftp->last_errno); + LIBSSH2_FREE(session, data); + return NULL; + } + } + + if(data_len < 10) { + _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, + "Too small FXP_HANDLE"); + LIBSSH2_FREE(session, data); + return NULL; + } + + fp = LIBSSH2_ALLOC(session, sizeof(LIBSSH2_SFTP_HANDLE)); + if (!fp) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate new SFTP handle structure"); + LIBSSH2_FREE(session, data); + return NULL; + } + memset(fp, 0, sizeof(LIBSSH2_SFTP_HANDLE)); + fp->handle_type = open_file ? LIBSSH2_SFTP_HANDLE_FILE : + LIBSSH2_SFTP_HANDLE_DIR; + + fp->handle_len = _libssh2_ntohu32(data + 5); + if (fp->handle_len > SFTP_HANDLE_MAXLEN) + /* SFTP doesn't allow handles longer than 256 characters */ + fp->handle_len = SFTP_HANDLE_MAXLEN; + + if(fp->handle_len > (data_len - 9)) + /* do not reach beyond the end of the data we got */ + fp->handle_len = data_len - 9; + + memcpy(fp->handle, data + 9, fp->handle_len); + + LIBSSH2_FREE(session, data); + + /* add this file handle to the list kept in the sftp session */ + _libssh2_list_add(&sftp->sftp_handles, &fp->node); + + fp->sftp = sftp; /* point to the parent struct */ + + fp->u.file.offset = 0; + fp->u.file.offset_sent = 0; + + _libssh2_debug(session, LIBSSH2_TRACE_SFTP, "Open command successful"); + return fp; + } + return NULL; +} + +/* libssh2_sftp_open_ex + */ +LIBSSH2_API LIBSSH2_SFTP_HANDLE * +libssh2_sftp_open_ex(LIBSSH2_SFTP *sftp, const char *filename, + unsigned int filename_len, unsigned long flags, long mode, + int open_type) +{ + LIBSSH2_SFTP_HANDLE *hnd; + + if(!sftp) + return NULL; + + BLOCK_ADJUST_ERRNO(hnd, sftp->channel->session, + sftp_open(sftp, filename, filename_len, flags, mode, + open_type)); + return hnd; +} + +/* + * sftp_read + * + * Read from an SFTP file handle + * + */ +static ssize_t sftp_read(LIBSSH2_SFTP_HANDLE * handle, char *buffer, + size_t buffer_size) +{ + LIBSSH2_SFTP *sftp = handle->sftp; + LIBSSH2_CHANNEL *channel = sftp->channel; + LIBSSH2_SESSION *session = channel->session; + size_t count = 0; + struct sftp_pipeline_chunk *chunk; + struct sftp_pipeline_chunk *next; + ssize_t rc; + struct _libssh2_sftp_handle_file_data *filep = + &handle->u.file; + + /* This function can be interrupted in three different places where it + might need to wait for data from the network. It returns EAGAIN to + allow non-blocking clients to do other work but these client are + expected to call this function again (possibly many times) to finish + the operation. + + The tricky part is that if we previously aborted a sftp_read due to + EAGAIN, we must continue at the same spot to continue the previously + interrupted operation. This is done using a state machine to record + what phase of execution we were at. The state is stored in + sftp->read_state. + + libssh2_NB_state_idle: The first phase is where we prepare multiple + FXP_READ packets to do optimistic read-ahead. We send off as many as + possible in the second phase without waiting for a response to each + one; this is the key to fast reads. But we may have to adjust the + channel window size to do this which may interrupt this function while + waiting. The state machine saves the phase as libssh2_NB_state_idle so + it returns here on the next call. + + libssh2_NB_state_sent: The second phase is where we send the FXP_READ + packets. Writing them to the channel can be interrupted with EAGAIN + but the state machine ensures we skip the first phase on the next call + and resume sending. + + libssh2_NB_state_sent2: In the third phase (indicated by ) we read the + data from the responses that have arrived so far. Reading can be + interrupted with EAGAIN but the state machine ensures we skip the first + and second phases on the next call and resume sending. + */ + + switch (sftp->read_state) { + case libssh2_NB_state_idle: + + /* Some data may already have been read from the server in the + previous call but didn't fit in the buffer at the time. If so, we + return that now as we can't risk being interrupted later with data + partially written to the buffer. */ + if(filep->data_left) { + size_t copy = MIN(buffer_size, filep->data_left); + + memcpy(buffer, &filep->data[ filep->data_len - filep->data_left], + copy); + + filep->data_left -= copy; + filep->offset += copy; + + if(!filep->data_left) { + LIBSSH2_FREE(session, filep->data); + filep->data = NULL; + } + + return copy; + } + + /* We allow a number of bytes being requested at any given time + without having been acked - until we reach EOF. */ + if(!filep->eof) { + /* Number of bytes asked for that haven't been acked yet */ + size_t already = (filep->offset_sent - filep->offset); + + size_t max_read_ahead = buffer_size*4; + unsigned long recv_window; + + if(max_read_ahead > LIBSSH2_CHANNEL_WINDOW_DEFAULT*4) + max_read_ahead = LIBSSH2_CHANNEL_WINDOW_DEFAULT*4; + + /* if the buffer_size passed in now is smaller than what has + already been sent, we risk getting count become a very large + number */ + if(max_read_ahead > already) + count = max_read_ahead - already; + + /* 'count' is how much more data to ask for, and 'already' is how + much data that already has been asked for but not yet returned. + Specificly, 'count' means how much data that have or will be + asked for by the nodes that are already added to the linked + list. Some of those read requests may not actually have been + sent off successfully yet. + + If 'already' is very large it should be perfectly fine to have + count set to 0 as then we don't have to ask for more data + (right now). + + buffer_size*4 is just picked more or less out of the air. The + idea is that when reading SFTP from a remote server, we send + away multiple read requests guessing that the client will read + more than only this 'buffer_size' amount of memory. So we ask + for maximum buffer_size*4 amount of data so that we can return + them very fast in subsequent calls. + */ + + recv_window = libssh2_channel_window_read_ex(sftp->channel, + NULL, NULL); + if(max_read_ahead > recv_window) { + /* more data will be asked for than what the window currently + allows, expand it! */ + + rc = _libssh2_channel_receive_window_adjust(sftp->channel, + max_read_ahead*8, + 1, NULL); + /* if this returns EAGAIN, we will get back to this function + at next call */ + assert(rc != LIBSSH2_ERROR_EAGAIN || !filep->data_left); + assert(rc != LIBSSH2_ERROR_EAGAIN || !filep->eof); + if (rc) + return rc; + } + } + + while(count > 0) { + unsigned char *s; + uint32_t size = MIN(MAX_SFTP_READ_SIZE, count); + + /* 25 = packet_len(4) + packet_type(1) + request_id(4) + + handle_len(4) + offset(8) + count(4) */ + uint32_t packet_len = (uint32_t)handle->handle_len + 25; + uint32_t request_id; + + chunk = LIBSSH2_ALLOC(session, packet_len + + sizeof(struct sftp_pipeline_chunk)); + if (!chunk) + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "malloc fail for FXP_WRITE"); + + chunk->len = size; + chunk->lefttosend = packet_len; + chunk->sent = 0; + + s = chunk->packet; + + _libssh2_store_u32(&s, packet_len - 4); + *s++ = SSH_FXP_READ; + request_id = sftp->request_id++; + chunk->request_id = request_id; + _libssh2_store_u32(&s, request_id); + _libssh2_store_str(&s, handle->handle, handle->handle_len); + _libssh2_store_u64(&s, filep->offset_sent); + filep->offset_sent += size; /* advance offset at once */ + _libssh2_store_u32(&s, size); + + /* add this new entry LAST in the list */ + _libssh2_list_add(&handle->packet_list, &chunk->node); + count -= size; /* deduct the size we used, as we might have + to create more packets */ + } + + case libssh2_NB_state_sent: + + sftp->read_state = libssh2_NB_state_idle; + + /* move through the READ packets that haven't been sent and send as + many as possible - remember that we don't block */ + chunk = _libssh2_list_first(&handle->packet_list); + + while(chunk) { + if(chunk->lefttosend) { + + rc = _libssh2_channel_write(channel, 0, + &chunk->packet[chunk->sent], + chunk->lefttosend); + if(rc < 0) { + sftp->read_state = libssh2_NB_state_sent; + return rc; + } + + /* remember where to continue sending the next time */ + chunk->lefttosend -= rc; + chunk->sent += rc; + + if(chunk->lefttosend) + /* data left to send, get out of loop */ + break; + } + + /* move on to the next chunk with data to send */ + chunk = _libssh2_list_next(&chunk->node); + } + + case libssh2_NB_state_sent2: + + sftp->read_state = libssh2_NB_state_idle; + + /* + * Count all ACKed packets and act on the contents of them. + */ + chunk = _libssh2_list_first(&handle->packet_list); + + while(chunk) { + unsigned char *data; + size_t data_len; + uint32_t rc32; + static const unsigned char read_responses[2] = { + SSH_FXP_DATA, SSH_FXP_STATUS + }; + + if(chunk->lefttosend) + /* if the chunk still has data left to send, we shouldn't wait + for an ACK for it just yet */ + break; + + rc = sftp_packet_requirev(sftp, 2, read_responses, + chunk->request_id, &data, &data_len); + if (rc < 0) { + sftp->read_state = libssh2_NB_state_sent2; + return rc; + } + + /* + * We get DATA or STATUS back. STATUS can be error, or it is + * FX_EOF when we reach the end of the file. + */ + + switch (data[0]) { + case SSH_FXP_STATUS: + /* remove the chunk we just processed */ + + _libssh2_list_remove(&chunk->node); + LIBSSH2_FREE(session, chunk); + + /* we must remove all outstanding READ requests, as either we + got an error or we're at end of file */ + sftp_packetlist_flush(handle); + + rc32 = _libssh2_ntohu32(data + 5); + LIBSSH2_FREE(session, data); + + if (rc32 == LIBSSH2_FX_EOF) { + filep->eof = TRUE; + return 0; + } + else { + sftp->last_errno = rc32; + return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, + "SFTP READ error"); + } + break; + + case SSH_FXP_DATA: + rc32 = _libssh2_ntohu32(data + 5); + if (rc32 > (data_len - 9)) + return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, + "SFTP Protocol badness"); + + if(rc32 > chunk->len) { + /* A chunk larger than we requested was returned to us. + This is a protocol violation and we don't know how to + deal with it. Bail out! */ + return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, + "FXP_READ response too big"); + } + + if(rc32 != chunk->len) { + /* a short read does not imply end of file, but we must + adjust the offset_sent since it was advanced with a + full chunk->len before */ + filep->offset_sent -= (chunk->len - rc32); + } + + if(rc32 > buffer_size) { + /* figure out the overlap amount */ + filep->data_left = rc32 - buffer_size; + + /* getting the full packet would overflow the buffer, so + only get the correct amount and keep the remainder */ + rc32 = (uint32_t)buffer_size; + + /* store data to keep for next call */ + filep->data = data; + filep->data_len = data_len; + } + else + filep->data_len = 0; + + /* copy the received data from the received FXP_DATA packet to + the buffer at the correct index */ + memcpy(buffer, data + 9, rc32); + filep->offset += rc32; + + if(filep->data_len == 0) + /* free the allocated data if not stored to keep */ + LIBSSH2_FREE(session, data); + + + /* remove the chunk we just processed keeping track of the + * next one in case we need it */ + next = _libssh2_list_next(&chunk->node); + _libssh2_list_remove(&chunk->node); + LIBSSH2_FREE(session, chunk); + chunk = NULL; + + if(rc32 > 0) { + /* we must return as we wrote some data to the buffer */ + return rc32; + } else { + /* A zero-byte read is not necessarily EOF so we must not + * return 0 (that would signal EOF to the caller) so + * instead we carry on to the next chunk */ + chunk = next; + } + + break; + default: + return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, + "SFTP Protocol badness: unrecognised " + "read request response"); + } + } + + break; + + default: + assert(!"State machine error; unrecognised read state"); + } + + return 0; +} + +/* libssh2_sftp_read + * Read from an SFTP file handle + */ +LIBSSH2_API ssize_t +libssh2_sftp_read(LIBSSH2_SFTP_HANDLE *hnd, char *buffer, + size_t buffer_maxlen) +{ + ssize_t rc; + if(!hnd) + return LIBSSH2_ERROR_BAD_USE; + BLOCK_ADJUST(rc, hnd->sftp->channel->session, + sftp_read(hnd, buffer, buffer_maxlen)); + return rc; +} + +/* sftp_readdir + * Read from an SFTP directory handle + */ +static ssize_t sftp_readdir(LIBSSH2_SFTP_HANDLE *handle, char *buffer, + size_t buffer_maxlen, char *longentry, + size_t longentry_maxlen, + LIBSSH2_SFTP_ATTRIBUTES *attrs) +{ + LIBSSH2_SFTP *sftp = handle->sftp; + LIBSSH2_CHANNEL *channel = sftp->channel; + LIBSSH2_SESSION *session = channel->session; + size_t data_len; + uint32_t num_names; + /* 13 = packet_len(4) + packet_type(1) + request_id(4) + handle_len(4) */ + uint32_t packet_len = handle->handle_len + 13; + unsigned char *s, *data; + static const unsigned char read_responses[2] = { + SSH_FXP_NAME, SSH_FXP_STATUS }; + ssize_t retcode; + + if (sftp->readdir_state == libssh2_NB_state_idle) { + if (handle->u.dir.names_left) { + /* + * A prior request returned more than one directory entry, + * feed it back from the buffer + */ + LIBSSH2_SFTP_ATTRIBUTES attrs_dummy; + size_t real_longentry_len; + size_t real_filename_len; + size_t filename_len; + size_t longentry_len; + + s = (unsigned char *) handle->u.dir.next_name; + real_filename_len = _libssh2_ntohu32(s); + + s += 4; + + filename_len = real_filename_len; + if (filename_len >= buffer_maxlen) { + filename_len = LIBSSH2_ERROR_BUFFER_TOO_SMALL; + goto end; + } + + memcpy(buffer, s, filename_len); + buffer[filename_len] = '\0'; /* zero terminate */ + s += real_filename_len; + + real_longentry_len = _libssh2_ntohu32(s); + s += 4; + + if (longentry && (longentry_maxlen>1)) { + longentry_len = real_longentry_len; + + if (longentry_len >= longentry_maxlen) { + filename_len = LIBSSH2_ERROR_BUFFER_TOO_SMALL; + goto end; + } + + memcpy(longentry, s, longentry_len); + longentry[longentry_len] = '\0'; /* zero terminate */ + } + s += real_longentry_len; + + if (attrs) + memset(attrs, 0, sizeof(LIBSSH2_SFTP_ATTRIBUTES)); + + s += sftp_bin2attr(attrs ? attrs : &attrs_dummy, s); + + handle->u.dir.next_name = (char *) s; + end: + + if ((--handle->u.dir.names_left) == 0) + LIBSSH2_FREE(session, handle->u.dir.names_packet); + + _libssh2_debug(session, LIBSSH2_TRACE_SFTP, + "libssh2_sftp_readdir_ex() return %d", + filename_len); + return (ssize_t)filename_len; + } + + /* Request another entry(entries?) */ + + s = sftp->readdir_packet = LIBSSH2_ALLOC(session, packet_len); + if (!sftp->readdir_packet) + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for " + "FXP_READDIR packet"); + + _libssh2_store_u32(&s, packet_len - 4); + *(s++) = SSH_FXP_READDIR; + sftp->readdir_request_id = sftp->request_id++; + _libssh2_store_u32(&s, sftp->readdir_request_id); + _libssh2_store_str(&s, handle->handle, handle->handle_len); + + sftp->readdir_state = libssh2_NB_state_created; + } + + if (sftp->readdir_state == libssh2_NB_state_created) { + _libssh2_debug(session, LIBSSH2_TRACE_SFTP, + "Reading entries from directory handle"); + retcode = _libssh2_channel_write(channel, 0, sftp->readdir_packet, + packet_len); + if (retcode == LIBSSH2_ERROR_EAGAIN) { + return retcode; + } + else if ((ssize_t)packet_len != retcode) { + LIBSSH2_FREE(session, sftp->readdir_packet); + sftp->readdir_packet = NULL; + sftp->readdir_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "_libssh2_channel_write() failed"); + } + + LIBSSH2_FREE(session, sftp->readdir_packet); + sftp->readdir_packet = NULL; + + sftp->readdir_state = libssh2_NB_state_sent; + } + + retcode = sftp_packet_requirev(sftp, 2, read_responses, + sftp->readdir_request_id, &data, + &data_len); + if (retcode == LIBSSH2_ERROR_EAGAIN) + return retcode; + else if (retcode) { + sftp->readdir_state = libssh2_NB_state_idle; + return _libssh2_error(session, retcode, + "Timeout waiting for status message"); + } + + if (data[0] == SSH_FXP_STATUS) { + retcode = _libssh2_ntohu32(data + 5); + LIBSSH2_FREE(session, data); + if (retcode == LIBSSH2_FX_EOF) { + sftp->readdir_state = libssh2_NB_state_idle; + return 0; + } + else { + sftp->last_errno = retcode; + sftp->readdir_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, + "SFTP Protocol Error"); + } + } + + sftp->readdir_state = libssh2_NB_state_idle; + + num_names = _libssh2_ntohu32(data + 5); + _libssh2_debug(session, LIBSSH2_TRACE_SFTP, "%lu entries returned", + num_names); + if (!num_names) { + LIBSSH2_FREE(session, data); + return 0; + } + + handle->u.dir.names_left = num_names; + handle->u.dir.names_packet = data; + handle->u.dir.next_name = (char *) data + 9; + + /* use the name popping mechanism from the start of the function */ + return sftp_readdir(handle, buffer, buffer_maxlen, longentry, + longentry_maxlen, attrs); +} + +/* libssh2_sftp_readdir_ex + * Read from an SFTP directory handle + */ +LIBSSH2_API int +libssh2_sftp_readdir_ex(LIBSSH2_SFTP_HANDLE *hnd, char *buffer, + size_t buffer_maxlen, char *longentry, + size_t longentry_maxlen, + LIBSSH2_SFTP_ATTRIBUTES *attrs) +{ + int rc; + if(!hnd) + return LIBSSH2_ERROR_BAD_USE; + BLOCK_ADJUST(rc, hnd->sftp->channel->session, + sftp_readdir(hnd, buffer, buffer_maxlen, longentry, + longentry_maxlen, attrs)); + return rc; +} + +/* + * sftp_write + * + * Write data to an SFTP handle. Returns the number of bytes written, or + * a negative error code. + * + * We recommend sending very large data buffers to this function! + * + * Concept: + * + * - Detect how much of the given buffer that was already sent in a previous + * call by inspecting the linked list of outgoing chunks. Make sure to skip + * passed the data that has already been taken care of. + * + * - Split all (new) outgoing data in chunks no larger than N. + * + * - Each N bytes chunk gets created as a separate SFTP packet. + * + * - Add all created outgoing packets to the linked list. + * + * - Walk through the list and send the chunks that haven't been sent, + * as many as possible until EAGAIN. Some of the chunks may have been put + * in the list in a previous invoke. + * + * - For all the chunks in the list that have been completely sent off, check + * for ACKs. If a chunk has been ACKed, it is removed from the linked + * list and the "acked" counter gets increased with that data amount. + * + * - Return TOTAL bytes acked so far. + * + * Caveats: + * - be careful: we must not return a higher number than what was given! + * + * TODO: + * Introduce an option that disables this sort of "speculative" ahead writing + * as there's a risk that it will do harm to some app. + */ + +static ssize_t sftp_write(LIBSSH2_SFTP_HANDLE *handle, const char *buffer, + size_t count) +{ + LIBSSH2_SFTP *sftp = handle->sftp; + LIBSSH2_CHANNEL *channel = sftp->channel; + LIBSSH2_SESSION *session = channel->session; + size_t data_len; + uint32_t retcode; + uint32_t packet_len; + unsigned char *s, *data; + ssize_t rc; + struct sftp_pipeline_chunk *chunk; + struct sftp_pipeline_chunk *next; + size_t acked = 0; + size_t org_count = count; + size_t already; + + switch(sftp->write_state) { + default: + case libssh2_NB_state_idle: + + /* Number of bytes sent off that haven't been acked and therefor we + will get passed in here again. + + Also, add up the number of bytes that actually already have been + acked but we haven't been able to return as such yet, so we will + get that data as well passed in here again. + */ + already = (handle->u.file.offset_sent - handle->u.file.offset)+ + handle->u.file.acked; + + if(count >= already) { + /* skip the part already made into packets */ + buffer += already; + count -= already; + } + else + /* there is more data already fine than what we got in this call */ + count = 0; + + sftp->write_state = libssh2_NB_state_idle; + while(count) { + /* TODO: Possibly this should have some logic to prevent a very + very small fraction to be left but lets ignore that for now */ + uint32_t size = MIN(MAX_SFTP_OUTGOING_SIZE, count); + uint32_t request_id; + + /* 25 = packet_len(4) + packet_type(1) + request_id(4) + + handle_len(4) + offset(8) + count(4) */ + packet_len = handle->handle_len + size + 25; + + chunk = LIBSSH2_ALLOC(session, packet_len + + sizeof(struct sftp_pipeline_chunk)); + if (!chunk) + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "malloc fail for FXP_WRITE"); + + chunk->len = size; + chunk->sent = 0; + chunk->lefttosend = packet_len; + + s = chunk->packet; + _libssh2_store_u32(&s, packet_len - 4); + + *(s++) = SSH_FXP_WRITE; + request_id = sftp->request_id++; + chunk->request_id = request_id; + _libssh2_store_u32(&s, request_id); + _libssh2_store_str(&s, handle->handle, handle->handle_len); + _libssh2_store_u64(&s, handle->u.file.offset_sent); + handle->u.file.offset_sent += size; /* advance offset at once */ + _libssh2_store_str(&s, buffer, size); + + /* add this new entry LAST in the list */ + _libssh2_list_add(&handle->packet_list, &chunk->node); + + buffer += size; + count -= size; /* deduct the size we used, as we might have + to create more packets */ + } + + /* move through the WRITE packets that haven't been sent and send as many + as possible - remember that we don't block */ + chunk = _libssh2_list_first(&handle->packet_list); + + while(chunk) { + if(chunk->lefttosend) { + rc = _libssh2_channel_write(channel, 0, + &chunk->packet[chunk->sent], + chunk->lefttosend); + if(rc < 0) + /* remain in idle state */ + return rc; + + /* remember where to continue sending the next time */ + chunk->lefttosend -= rc; + chunk->sent += rc; + + if(chunk->lefttosend) + /* data left to send, get out of loop */ + break; + } + + /* move on to the next chunk with data to send */ + chunk = _libssh2_list_next(&chunk->node); + } + + /* fall-through */ + case libssh2_NB_state_sent: + + sftp->write_state = libssh2_NB_state_idle; + /* + * Count all ACKed packets + */ + chunk = _libssh2_list_first(&handle->packet_list); + + while(chunk) { + if(chunk->lefttosend) + /* if the chunk still has data left to send, we shouldn't wait + for an ACK for it just yet */ + break; + + else if(acked) + /* if we have sent data that is acked, we must return that + info before we call a function that might return EAGAIN */ + break; + + /* we check the packets in order */ + rc = sftp_packet_require(sftp, SSH_FXP_STATUS, + chunk->request_id, &data, &data_len); + if (rc < 0) { + if (rc == LIBSSH2_ERROR_EAGAIN) + sftp->write_state = libssh2_NB_state_sent; + return rc; + } + + retcode = _libssh2_ntohu32(data + 5); + LIBSSH2_FREE(session, data); + + sftp->last_errno = retcode; + if (retcode == LIBSSH2_FX_OK) { + acked += chunk->len; /* number of payload data that was acked + here */ + + /* we increase the offset value for all acks */ + handle->u.file.offset += chunk->len; + + next = _libssh2_list_next(&chunk->node); + + _libssh2_list_remove(&chunk->node); /* remove from list */ + LIBSSH2_FREE(session, chunk); /* free memory */ + + chunk = next; + } + else { + /* flush all pending packets from the outgoing list */ + sftp_packetlist_flush(handle); + + /* since we return error now, the applicaton will not get any + outstanding data acked, so we need to rewind the offset to + where the application knows it has reached with acked data */ + handle->u.file.offset -= handle->u.file.acked; + + /* then reset the offset_sent to be the same as the offset */ + handle->u.file.offset_sent = handle->u.file.offset; + + /* clear the acked counter since we can have no pending data to + ack after an error */ + handle->u.file.acked = 0; + + /* the server returned an error for that written chunk, propagate + this back to our parent function */ + return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, + "FXP write failed"); + } + } + break; + } + + /* if there were acked data in a previous call that wasn't returned then, + add that up and try to return it all now. This can happen if the app + first sends a huge buffer of data, and then in a second call it sends a + smaller one. */ + acked += handle->u.file.acked; + + if(acked) { + ssize_t ret = MIN(acked, org_count); + /* we got data acked so return that amount, but no more than what + was asked to get sent! */ + + /* store the remainder. 'ret' is always equal to or less than 'acked' + here */ + handle->u.file.acked = acked - ret; + + return ret; + } + + else + return 0; /* nothing was acked, and no EAGAIN was received! */ +} + +/* libssh2_sftp_write + * Write data to a file handle + */ +LIBSSH2_API ssize_t +libssh2_sftp_write(LIBSSH2_SFTP_HANDLE *hnd, const char *buffer, + size_t count) +{ + ssize_t rc; + if(!hnd) + return LIBSSH2_ERROR_BAD_USE; + BLOCK_ADJUST(rc, hnd->sftp->channel->session, + sftp_write(hnd, buffer, count)); + return rc; + +} + +/* + * sftp_fstat + * + * Get or Set stat on a file + */ +static int sftp_fstat(LIBSSH2_SFTP_HANDLE *handle, + LIBSSH2_SFTP_ATTRIBUTES *attrs, int setstat) +{ + LIBSSH2_SFTP *sftp = handle->sftp; + LIBSSH2_CHANNEL *channel = sftp->channel; + LIBSSH2_SESSION *session = channel->session; + size_t data_len; + /* 13 = packet_len(4) + packet_type(1) + request_id(4) + handle_len(4) */ + uint32_t packet_len = + handle->handle_len + 13 + (setstat ? sftp_attrsize(attrs->flags) : 0); + unsigned char *s, *data; + static const unsigned char fstat_responses[2] = + { SSH_FXP_ATTRS, SSH_FXP_STATUS }; + ssize_t rc; + + if (sftp->fstat_state == libssh2_NB_state_idle) { + _libssh2_debug(session, LIBSSH2_TRACE_SFTP, "Issuing %s command", + setstat ? "set-stat" : "stat"); + s = sftp->fstat_packet = LIBSSH2_ALLOC(session, packet_len); + if (!sftp->fstat_packet) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for " + "FSTAT/FSETSTAT packet"); + } + + _libssh2_store_u32(&s, packet_len - 4); + *(s++) = setstat ? SSH_FXP_FSETSTAT : SSH_FXP_FSTAT; + sftp->fstat_request_id = sftp->request_id++; + _libssh2_store_u32(&s, sftp->fstat_request_id); + _libssh2_store_str(&s, handle->handle, handle->handle_len); + + if (setstat) { + s += sftp_attr2bin(s, attrs); + } + + sftp->fstat_state = libssh2_NB_state_created; + } + + if (sftp->fstat_state == libssh2_NB_state_created) { + rc = _libssh2_channel_write(channel, 0, sftp->fstat_packet, + packet_len); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } + else if ((ssize_t)packet_len != rc) { + LIBSSH2_FREE(session, sftp->fstat_packet); + sftp->fstat_packet = NULL; + sftp->fstat_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + (setstat ? "Unable to send FXP_FSETSTAT" + : "Unable to send FXP_FSTAT command")); + } + LIBSSH2_FREE(session, sftp->fstat_packet); + sftp->fstat_packet = NULL; + + sftp->fstat_state = libssh2_NB_state_sent; + } + + rc = sftp_packet_requirev(sftp, 2, fstat_responses, + sftp->fstat_request_id, &data, + &data_len); + if (rc == LIBSSH2_ERROR_EAGAIN) + return rc; + else if (rc) { + sftp->fstat_state = libssh2_NB_state_idle; + return _libssh2_error(session, rc, + "Timeout waiting for status message"); + } + + sftp->fstat_state = libssh2_NB_state_idle; + + if (data[0] == SSH_FXP_STATUS) { + uint32_t retcode; + + retcode = _libssh2_ntohu32(data + 5); + LIBSSH2_FREE(session, data); + if (retcode == LIBSSH2_FX_OK) { + return 0; + } else { + sftp->last_errno = retcode; + return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, + "SFTP Protocol Error"); + } + } + + sftp_bin2attr(attrs, data + 5); + LIBSSH2_FREE(session, data); + + return 0; +} + +/* libssh2_sftp_fstat_ex + * Get or Set stat on a file + */ +LIBSSH2_API int +libssh2_sftp_fstat_ex(LIBSSH2_SFTP_HANDLE *hnd, + LIBSSH2_SFTP_ATTRIBUTES *attrs, int setstat) +{ + int rc; + if(!hnd || !attrs) + return LIBSSH2_ERROR_BAD_USE; + BLOCK_ADJUST(rc, hnd->sftp->channel->session, + sftp_fstat(hnd, attrs, setstat)); + return rc; +} + + +/* libssh2_sftp_seek64 + * Set the read/write pointer to an arbitrary position within the file + */ +LIBSSH2_API void +libssh2_sftp_seek64(LIBSSH2_SFTP_HANDLE *handle, libssh2_uint64_t offset) +{ + if(handle) { + handle->u.file.offset = handle->u.file.offset_sent = offset; + /* discard all pending requests and currently read data */ + sftp_packetlist_flush(handle); + + /* free the left received buffered data */ + if (handle->u.file.data_left) { + LIBSSH2_FREE(handle->sftp->channel->session, handle->u.file.data); + handle->u.file.data_left = handle->u.file.data_len = 0; + handle->u.file.data = NULL; + } + + /* reset EOF to False */ + handle->u.file.eof = FALSE; + } +} + +/* libssh2_sftp_seek + * Set the read/write pointer to an arbitrary position within the file + */ +LIBSSH2_API void +libssh2_sftp_seek(LIBSSH2_SFTP_HANDLE *handle, size_t offset) +{ + libssh2_sftp_seek64(handle, (libssh2_uint64_t)offset); +} + +/* libssh2_sftp_tell + * Return the current read/write pointer's offset + */ +LIBSSH2_API size_t +libssh2_sftp_tell(LIBSSH2_SFTP_HANDLE *handle) +{ + if(!handle) + return 0; /* no handle, no size */ + + /* NOTE: this may very well truncate the size if it is larger than what + size_t can hold, so libssh2_sftp_tell64() is really the function you + should use */ + return (size_t)(handle->u.file.offset); +} + +/* libssh2_sftp_tell64 + * Return the current read/write pointer's offset + */ +LIBSSH2_API libssh2_uint64_t +libssh2_sftp_tell64(LIBSSH2_SFTP_HANDLE *handle) +{ + if(!handle) + return 0; /* no handle, no size */ + + return handle->u.file.offset; +} + +/* + * Flush all remaining incoming SFTP packets and zombies. + */ +static void sftp_packet_flush(LIBSSH2_SFTP *sftp) +{ + LIBSSH2_CHANNEL *channel = sftp->channel; + LIBSSH2_SESSION *session = channel->session; + LIBSSH2_SFTP_PACKET *packet = _libssh2_list_first(&sftp->packets); + struct sftp_zombie_requests *zombie = + _libssh2_list_first(&sftp->zombie_requests); + + while(packet) { + LIBSSH2_SFTP_PACKET *next; + + /* check next struct in the list */ + next = _libssh2_list_next(&packet->node); + _libssh2_list_remove(&packet->node); + LIBSSH2_FREE(session, packet->data); + LIBSSH2_FREE(session, packet); + + packet = next; + } + + while(zombie) { + /* figure out the next node */ + struct sftp_zombie_requests *next = _libssh2_list_next(&zombie->node); + /* unlink the current one */ + _libssh2_list_remove(&zombie->node); + /* free the memory */ + LIBSSH2_FREE(session, zombie); + zombie = next; + } + +} + +/* sftp_close_handle + * + * Close a file or directory handle + * Also frees handle resource and unlinks it from the SFTP structure + */ +static int +sftp_close_handle(LIBSSH2_SFTP_HANDLE *handle) +{ + LIBSSH2_SFTP *sftp = handle->sftp; + LIBSSH2_CHANNEL *channel = sftp->channel; + LIBSSH2_SESSION *session = channel->session; + size_t data_len; + int retcode; + /* 13 = packet_len(4) + packet_type(1) + request_id(4) + handle_len(4) */ + uint32_t packet_len = handle->handle_len + 13; + unsigned char *s, *data = NULL; + int rc; + + if (handle->close_state == libssh2_NB_state_idle) { + _libssh2_debug(session, LIBSSH2_TRACE_SFTP, "Closing handle"); + s = handle->close_packet = LIBSSH2_ALLOC(session, packet_len); + if (!handle->close_packet) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for FXP_CLOSE " + "packet"); + } + + _libssh2_store_u32(&s, packet_len - 4); + *(s++) = SSH_FXP_CLOSE; + handle->close_request_id = sftp->request_id++; + _libssh2_store_u32(&s, handle->close_request_id); + _libssh2_store_str(&s, handle->handle, handle->handle_len); + handle->close_state = libssh2_NB_state_created; + } + + if (handle->close_state == libssh2_NB_state_created) { + rc = _libssh2_channel_write(channel, 0, handle->close_packet, + packet_len); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } else if ((ssize_t)packet_len != rc) { + LIBSSH2_FREE(session, handle->close_packet); + handle->close_packet = NULL; + handle->close_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send FXP_CLOSE command"); + } + LIBSSH2_FREE(session, handle->close_packet); + handle->close_packet = NULL; + + handle->close_state = libssh2_NB_state_sent; + } + + if (handle->close_state == libssh2_NB_state_sent) { + rc = sftp_packet_require(sftp, SSH_FXP_STATUS, + handle->close_request_id, &data, + &data_len); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } else if (rc) { + handle->close_state = libssh2_NB_state_idle; + return _libssh2_error(session, rc, + "Error waiting for status message"); + } + + handle->close_state = libssh2_NB_state_sent1; + } + + if(!data) + /* if it reaches this point with data unset, something unwanted + happened (like this function is called again when in + libssh2_NB_state_sent1 state) and we just bail out */ + return LIBSSH2_ERROR_INVAL; + + retcode = _libssh2_ntohu32(data + 5); + LIBSSH2_FREE(session, data); + + if (retcode != LIBSSH2_FX_OK) { + sftp->last_errno = retcode; + handle->close_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, + "SFTP Protocol Error"); + } + + /* remove this handle from the parent's list */ + _libssh2_list_remove(&handle->node); + + if ((handle->handle_type == LIBSSH2_SFTP_HANDLE_DIR) + && handle->u.dir.names_left) { + LIBSSH2_FREE(session, handle->u.dir.names_packet); + } + else { + if(handle->u.file.data) + LIBSSH2_FREE(session, handle->u.file.data); + } + + sftp_packetlist_flush(handle); + sftp->read_state = libssh2_NB_state_idle; + + handle->close_state = libssh2_NB_state_idle; + + LIBSSH2_FREE(session, handle); + + return 0; +} + +/* libssh2_sftp_close_handle + * + * Close a file or directory handle + * Also frees handle resource and unlinks it from the SFTP structure + */ +LIBSSH2_API int +libssh2_sftp_close_handle(LIBSSH2_SFTP_HANDLE *hnd) +{ + int rc; + if(!hnd) + return LIBSSH2_ERROR_BAD_USE; + BLOCK_ADJUST(rc, hnd->sftp->channel->session, sftp_close_handle(hnd)); + return rc; +} + +/* sftp_unlink + * Delete a file from the remote server + */ +static int sftp_unlink(LIBSSH2_SFTP *sftp, const char *filename, + size_t filename_len) +{ + LIBSSH2_CHANNEL *channel = sftp->channel; + LIBSSH2_SESSION *session = channel->session; + size_t data_len; + int retcode; + /* 13 = packet_len(4) + packet_type(1) + request_id(4) + filename_len(4) */ + uint32_t packet_len = filename_len + 13; + unsigned char *s, *data; + int rc; + + if (sftp->unlink_state == libssh2_NB_state_idle) { + _libssh2_debug(session, LIBSSH2_TRACE_SFTP, "Unlinking %s", filename); + s = sftp->unlink_packet = LIBSSH2_ALLOC(session, packet_len); + if (!sftp->unlink_packet) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for FXP_REMOVE " + "packet"); + } + + _libssh2_store_u32(&s, packet_len - 4); + *(s++) = SSH_FXP_REMOVE; + sftp->unlink_request_id = sftp->request_id++; + _libssh2_store_u32(&s, sftp->unlink_request_id); + _libssh2_store_str(&s, filename, filename_len); + sftp->unlink_state = libssh2_NB_state_created; + } + + if (sftp->unlink_state == libssh2_NB_state_created) { + rc = _libssh2_channel_write(channel, 0, sftp->unlink_packet, + packet_len); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } else if ((ssize_t)packet_len != rc) { + LIBSSH2_FREE(session, sftp->unlink_packet); + sftp->unlink_packet = NULL; + sftp->unlink_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send FXP_REMOVE command"); + } + LIBSSH2_FREE(session, sftp->unlink_packet); + sftp->unlink_packet = NULL; + + sftp->unlink_state = libssh2_NB_state_sent; + } + + rc = sftp_packet_require(sftp, SSH_FXP_STATUS, + sftp->unlink_request_id, &data, + &data_len); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } + else if (rc) { + sftp->unlink_state = libssh2_NB_state_idle; + return _libssh2_error(session, rc, + "Error waiting for FXP STATUS"); + } + + sftp->unlink_state = libssh2_NB_state_idle; + + retcode = _libssh2_ntohu32(data + 5); + LIBSSH2_FREE(session, data); + + if (retcode == LIBSSH2_FX_OK) { + return 0; + } else { + sftp->last_errno = retcode; + return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, + "SFTP Protocol Error"); + } +} + +/* libssh2_sftp_unlink_ex + * Delete a file from the remote server + */ +LIBSSH2_API int +libssh2_sftp_unlink_ex(LIBSSH2_SFTP *sftp, const char *filename, + unsigned int filename_len) +{ + int rc; + if(!sftp) + return LIBSSH2_ERROR_BAD_USE; + BLOCK_ADJUST(rc, sftp->channel->session, + sftp_unlink(sftp, filename, filename_len)); + return rc; +} + +/* + * sftp_rename + * + * Rename a file on the remote server + */ +static int sftp_rename(LIBSSH2_SFTP *sftp, const char *source_filename, + unsigned int source_filename_len, + const char *dest_filename, + unsigned int dest_filename_len, long flags) +{ + LIBSSH2_CHANNEL *channel = sftp->channel; + LIBSSH2_SESSION *session = channel->session; + size_t data_len; + int retcode; + uint32_t packet_len = + source_filename_len + dest_filename_len + 17 + (sftp->version >= + 5 ? 4 : 0); + /* packet_len(4) + packet_type(1) + request_id(4) + + source_filename_len(4) + dest_filename_len(4) + flags(4){SFTP5+) */ + unsigned char *data; + ssize_t rc; + + if (sftp->version < 2) { + return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, + "Server does not support RENAME"); + } + + if (sftp->rename_state == libssh2_NB_state_idle) { + _libssh2_debug(session, LIBSSH2_TRACE_SFTP, "Renaming %s to %s", + source_filename, dest_filename); + sftp->rename_s = sftp->rename_packet = + LIBSSH2_ALLOC(session, packet_len); + if (!sftp->rename_packet) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for FXP_RENAME " + "packet"); + } + + _libssh2_store_u32(&sftp->rename_s, packet_len - 4); + *(sftp->rename_s++) = SSH_FXP_RENAME; + sftp->rename_request_id = sftp->request_id++; + _libssh2_store_u32(&sftp->rename_s, sftp->rename_request_id); + _libssh2_store_str(&sftp->rename_s, source_filename, + source_filename_len); + _libssh2_store_str(&sftp->rename_s, dest_filename, dest_filename_len); + + if (sftp->version >= 5) + _libssh2_store_u32(&sftp->rename_s, flags); + + sftp->rename_state = libssh2_NB_state_created; + } + + if (sftp->rename_state == libssh2_NB_state_created) { + rc = _libssh2_channel_write(channel, 0, sftp->rename_packet, + sftp->rename_s - sftp->rename_packet); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } else if ((ssize_t)packet_len != rc) { + LIBSSH2_FREE(session, sftp->rename_packet); + sftp->rename_packet = NULL; + sftp->rename_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send FXP_RENAME command"); + } + LIBSSH2_FREE(session, sftp->rename_packet); + sftp->rename_packet = NULL; + + sftp->rename_state = libssh2_NB_state_sent; + } + + rc = sftp_packet_require(sftp, SSH_FXP_STATUS, + sftp->rename_request_id, &data, + &data_len); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } else if (rc) { + sftp->rename_state = libssh2_NB_state_idle; + return _libssh2_error(session, rc, + "Error waiting for FXP STATUS"); + } + + sftp->rename_state = libssh2_NB_state_idle; + + retcode = _libssh2_ntohu32(data + 5); + LIBSSH2_FREE(session, data); + + sftp->last_errno = retcode; + + /* now convert the SFTP error code to libssh2 return code or error + message */ + switch (retcode) { + case LIBSSH2_FX_OK: + retcode = LIBSSH2_ERROR_NONE; + break; + + case LIBSSH2_FX_FILE_ALREADY_EXISTS: + retcode = _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, + "File already exists and " + "SSH_FXP_RENAME_OVERWRITE not specified"); + break; + + case LIBSSH2_FX_OP_UNSUPPORTED: + retcode = _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, + "Operation Not Supported"); + break; + + default: + retcode = _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, + "SFTP Protocol Error"); + break; + } + + return retcode; +} + +/* libssh2_sftp_rename_ex + * Rename a file on the remote server + */ +LIBSSH2_API int +libssh2_sftp_rename_ex(LIBSSH2_SFTP *sftp, const char *source_filename, + unsigned int source_filename_len, + const char *dest_filename, + unsigned int dest_filename_len, long flags) +{ + int rc; + if(!sftp) + return LIBSSH2_ERROR_BAD_USE; + BLOCK_ADJUST(rc, sftp->channel->session, + sftp_rename(sftp, source_filename, source_filename_len, + dest_filename, dest_filename_len, flags)); + return rc; +} + +/* + * sftp_fstatvfs + * + * Get file system statistics + */ +static int sftp_fstatvfs(LIBSSH2_SFTP_HANDLE *handle, LIBSSH2_SFTP_STATVFS *st) +{ + LIBSSH2_SFTP *sftp = handle->sftp; + LIBSSH2_CHANNEL *channel = sftp->channel; + LIBSSH2_SESSION *session = channel->session; + size_t data_len; + /* 17 = packet_len(4) + packet_type(1) + request_id(4) + ext_len(4) + + handle_len (4) */ + /* 20 = strlen ("fstatvfs@openssh.com") */ + uint32_t packet_len = handle->handle_len + 20 + 17; + unsigned char *packet, *s, *data; + ssize_t rc; + unsigned int flag; + + if (sftp->fstatvfs_state == libssh2_NB_state_idle) { + _libssh2_debug(session, LIBSSH2_TRACE_SFTP, + "Getting file system statistics"); + s = packet = LIBSSH2_ALLOC(session, packet_len); + if (!packet) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for FXP_EXTENDED " + "packet"); + } + + _libssh2_store_u32(&s, packet_len - 4); + *(s++) = SSH_FXP_EXTENDED; + sftp->fstatvfs_request_id = sftp->request_id++; + _libssh2_store_u32(&s, sftp->fstatvfs_request_id); + _libssh2_store_str(&s, "fstatvfs@openssh.com", 20); + _libssh2_store_str(&s, handle->handle, handle->handle_len); + + sftp->fstatvfs_state = libssh2_NB_state_created; + } + else { + packet = sftp->fstatvfs_packet; + } + + if (sftp->fstatvfs_state == libssh2_NB_state_created) { + rc = _libssh2_channel_write(channel, 0, packet, packet_len); + if (rc == LIBSSH2_ERROR_EAGAIN || + (0 <= rc && rc < (ssize_t)packet_len)) { + sftp->fstatvfs_packet = packet; + return LIBSSH2_ERROR_EAGAIN; + } + + LIBSSH2_FREE(session, packet); + sftp->fstatvfs_packet = NULL; + + if (rc < 0) { + sftp->fstatvfs_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "_libssh2_channel_write() failed"); + } + sftp->fstatvfs_state = libssh2_NB_state_sent; + } + + rc = sftp_packet_require(sftp, SSH_FXP_EXTENDED_REPLY, + sftp->fstatvfs_request_id, &data, &data_len); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } else if (rc) { + sftp->fstatvfs_state = libssh2_NB_state_idle; + return _libssh2_error(session, rc, + "Error waiting for FXP EXTENDED REPLY"); + } else if (data_len < 93) { + LIBSSH2_FREE(session, data); + sftp->fstatvfs_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, + "SFTP Protocol Error: short response"); + } + + sftp->fstatvfs_state = libssh2_NB_state_idle; + + st->f_bsize = _libssh2_ntohu64(data + 5); + st->f_frsize = _libssh2_ntohu64(data + 13); + st->f_blocks = _libssh2_ntohu64(data + 21); + st->f_bfree = _libssh2_ntohu64(data + 29); + st->f_bavail = _libssh2_ntohu64(data + 37); + st->f_files = _libssh2_ntohu64(data + 45); + st->f_ffree = _libssh2_ntohu64(data + 53); + st->f_favail = _libssh2_ntohu64(data + 61); + st->f_fsid = _libssh2_ntohu64(data + 69); + flag = _libssh2_ntohu64(data + 77); + st->f_namemax = _libssh2_ntohu64(data + 85); + + st->f_flag = (flag & SSH_FXE_STATVFS_ST_RDONLY) + ? LIBSSH2_SFTP_ST_RDONLY : 0; + st->f_flag |= (flag & SSH_FXE_STATVFS_ST_NOSUID) + ? LIBSSH2_SFTP_ST_NOSUID : 0; + + LIBSSH2_FREE(session, data); + return 0; +} + +/* libssh2_sftp_fstatvfs + * Get filesystem space and inode utilization (requires fstatvfs@openssh.com + * support on the server) + */ +LIBSSH2_API int +libssh2_sftp_fstatvfs(LIBSSH2_SFTP_HANDLE *handle, LIBSSH2_SFTP_STATVFS *st) +{ + int rc; + if(!handle || !st) + return LIBSSH2_ERROR_BAD_USE; + BLOCK_ADJUST(rc, handle->sftp->channel->session, sftp_fstatvfs(handle, st)); + return rc; +} + +/* + * sftp_statvfs + * + * Get file system statistics + */ +static int sftp_statvfs(LIBSSH2_SFTP *sftp, const char *path, + unsigned int path_len, LIBSSH2_SFTP_STATVFS *st) +{ + LIBSSH2_CHANNEL *channel = sftp->channel; + LIBSSH2_SESSION *session = channel->session; + size_t data_len; + /* 17 = packet_len(4) + packet_type(1) + request_id(4) + ext_len(4) + + path_len (4) */ + /* 19 = strlen ("statvfs@openssh.com") */ + uint32_t packet_len = path_len + 19 + 17; + unsigned char *packet, *s, *data; + ssize_t rc; + unsigned int flag; + + if (sftp->statvfs_state == libssh2_NB_state_idle) { + _libssh2_debug(session, LIBSSH2_TRACE_SFTP, + "Getting file system statistics of %s", path); + s = packet = LIBSSH2_ALLOC(session, packet_len); + if (!packet) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for FXP_EXTENDED " + "packet"); + } + + _libssh2_store_u32(&s, packet_len - 4); + *(s++) = SSH_FXP_EXTENDED; + sftp->statvfs_request_id = sftp->request_id++; + _libssh2_store_u32(&s, sftp->statvfs_request_id); + _libssh2_store_str(&s, "statvfs@openssh.com", 19); + _libssh2_store_str(&s, path, path_len); + + sftp->statvfs_state = libssh2_NB_state_created; + } + else { + packet = sftp->statvfs_packet; + } + + if (sftp->statvfs_state == libssh2_NB_state_created) { + rc = _libssh2_channel_write(channel, 0, packet, packet_len); + if (rc == LIBSSH2_ERROR_EAGAIN || + (0 <= rc && rc < (ssize_t)packet_len)) { + sftp->statvfs_packet = packet; + return LIBSSH2_ERROR_EAGAIN; + } + + LIBSSH2_FREE(session, packet); + sftp->statvfs_packet = NULL; + + if (rc < 0) { + sftp->statvfs_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "_libssh2_channel_write() failed"); + } + sftp->statvfs_state = libssh2_NB_state_sent; + } + + rc = sftp_packet_require(sftp, SSH_FXP_EXTENDED_REPLY, + sftp->statvfs_request_id, &data, &data_len); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } else if (rc) { + sftp->statvfs_state = libssh2_NB_state_idle; + return _libssh2_error(session, rc, + "Error waiting for FXP EXTENDED REPLY"); + } else if (data_len < 93) { + LIBSSH2_FREE(session, data); + sftp->fstatvfs_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, + "SFTP Protocol Error: short response"); + } + + sftp->statvfs_state = libssh2_NB_state_idle; + + st->f_bsize = _libssh2_ntohu64(data + 5); + st->f_frsize = _libssh2_ntohu64(data + 13); + st->f_blocks = _libssh2_ntohu64(data + 21); + st->f_bfree = _libssh2_ntohu64(data + 29); + st->f_bavail = _libssh2_ntohu64(data + 37); + st->f_files = _libssh2_ntohu64(data + 45); + st->f_ffree = _libssh2_ntohu64(data + 53); + st->f_favail = _libssh2_ntohu64(data + 61); + st->f_fsid = _libssh2_ntohu64(data + 69); + flag = _libssh2_ntohu64(data + 77); + st->f_namemax = _libssh2_ntohu64(data + 85); + + st->f_flag = (flag & SSH_FXE_STATVFS_ST_RDONLY) + ? LIBSSH2_SFTP_ST_RDONLY : 0; + st->f_flag |= (flag & SSH_FXE_STATVFS_ST_NOSUID) + ? LIBSSH2_SFTP_ST_NOSUID : 0; + + LIBSSH2_FREE(session, data); + return 0; +} + +/* libssh2_sftp_statvfs_ex + * Get filesystem space and inode utilization (requires statvfs@openssh.com + * support on the server) + */ +LIBSSH2_API int +libssh2_sftp_statvfs(LIBSSH2_SFTP *sftp, const char *path, + size_t path_len, LIBSSH2_SFTP_STATVFS *st) +{ + int rc; + if(!sftp || !st) + return LIBSSH2_ERROR_BAD_USE; + BLOCK_ADJUST(rc, sftp->channel->session, sftp_statvfs(sftp, path, path_len, + st)); + return rc; +} + + +/* + * sftp_mkdir + * + * Create an SFTP directory + */ +static int sftp_mkdir(LIBSSH2_SFTP *sftp, const char *path, + unsigned int path_len, long mode) +{ + LIBSSH2_CHANNEL *channel = sftp->channel; + LIBSSH2_SESSION *session = channel->session; + LIBSSH2_SFTP_ATTRIBUTES attrs = { + LIBSSH2_SFTP_ATTR_PERMISSIONS, 0, 0, 0, 0, 0, 0 + }; + size_t data_len; + int retcode; + /* 13 = packet_len(4) + packet_type(1) + request_id(4) + path_len(4) */ + ssize_t packet_len = path_len + 13 + + sftp_attrsize(LIBSSH2_SFTP_ATTR_PERMISSIONS); + unsigned char *packet, *s, *data; + int rc; + + if (sftp->mkdir_state == libssh2_NB_state_idle) { + _libssh2_debug(session, LIBSSH2_TRACE_SFTP, + "Creating directory %s with mode 0%lo", path, mode); + s = packet = LIBSSH2_ALLOC(session, packet_len); + if (!packet) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for FXP_MKDIR " + "packet"); + } + /* Filetype in SFTP 3 and earlier */ + attrs.permissions = mode | LIBSSH2_SFTP_ATTR_PFILETYPE_DIR; + + _libssh2_store_u32(&s, packet_len - 4); + *(s++) = SSH_FXP_MKDIR; + sftp->mkdir_request_id = sftp->request_id++; + _libssh2_store_u32(&s, sftp->mkdir_request_id); + _libssh2_store_str(&s, path, path_len); + + s += sftp_attr2bin(s, &attrs); + + sftp->mkdir_state = libssh2_NB_state_created; + } + else { + packet = sftp->mkdir_packet; + } + + if (sftp->mkdir_state == libssh2_NB_state_created) { + rc = _libssh2_channel_write(channel, 0, packet, packet_len); + if (rc == LIBSSH2_ERROR_EAGAIN) { + sftp->mkdir_packet = packet; + return rc; + } + if (packet_len != rc) { + LIBSSH2_FREE(session, packet); + sftp->mkdir_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "_libssh2_channel_write() failed"); + } + LIBSSH2_FREE(session, packet); + sftp->mkdir_state = libssh2_NB_state_sent; + sftp->mkdir_packet = NULL; + } + + rc = sftp_packet_require(sftp, SSH_FXP_STATUS, sftp->mkdir_request_id, + &data, &data_len); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } else if (rc) { + sftp->mkdir_state = libssh2_NB_state_idle; + return _libssh2_error(session, rc, + "Error waiting for FXP STATUS"); + } + + sftp->mkdir_state = libssh2_NB_state_idle; + + retcode = _libssh2_ntohu32(data + 5); + LIBSSH2_FREE(session, data); + + if (retcode == LIBSSH2_FX_OK) { + _libssh2_debug(session, LIBSSH2_TRACE_SFTP, "OK!"); + return 0; + } else { + sftp->last_errno = retcode; + return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, + "SFTP Protocol Error"); + } +} + +/* + * libssh2_sftp_mkdir_ex + * + * Create an SFTP directory + */ +LIBSSH2_API int +libssh2_sftp_mkdir_ex(LIBSSH2_SFTP *sftp, const char *path, + unsigned int path_len, long mode) +{ + int rc; + if(!sftp) + return LIBSSH2_ERROR_BAD_USE; + BLOCK_ADJUST(rc, sftp->channel->session, + sftp_mkdir(sftp, path, path_len, mode)); + return rc; +} + +/* sftp_rmdir + * Remove a directory + */ +static int sftp_rmdir(LIBSSH2_SFTP *sftp, const char *path, + unsigned int path_len) +{ + LIBSSH2_CHANNEL *channel = sftp->channel; + LIBSSH2_SESSION *session = channel->session; + size_t data_len; + int retcode; + /* 13 = packet_len(4) + packet_type(1) + request_id(4) + path_len(4) */ + ssize_t packet_len = path_len + 13; + unsigned char *s, *data; + int rc; + + if (sftp->rmdir_state == libssh2_NB_state_idle) { + _libssh2_debug(session, LIBSSH2_TRACE_SFTP, "Removing directory: %s", + path); + s = sftp->rmdir_packet = LIBSSH2_ALLOC(session, packet_len); + if (!sftp->rmdir_packet) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for FXP_RMDIR " + "packet"); + } + + _libssh2_store_u32(&s, packet_len - 4); + *(s++) = SSH_FXP_RMDIR; + sftp->rmdir_request_id = sftp->request_id++; + _libssh2_store_u32(&s, sftp->rmdir_request_id); + _libssh2_store_str(&s, path, path_len); + + sftp->rmdir_state = libssh2_NB_state_created; + } + + if (sftp->rmdir_state == libssh2_NB_state_created) { + rc = _libssh2_channel_write(channel, 0, sftp->rmdir_packet, + packet_len); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } else if (packet_len != rc) { + LIBSSH2_FREE(session, sftp->rmdir_packet); + sftp->rmdir_packet = NULL; + sftp->rmdir_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send FXP_RMDIR command"); + } + LIBSSH2_FREE(session, sftp->rmdir_packet); + sftp->rmdir_packet = NULL; + + sftp->rmdir_state = libssh2_NB_state_sent; + } + + rc = sftp_packet_require(sftp, SSH_FXP_STATUS, + sftp->rmdir_request_id, &data, &data_len); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } else if (rc) { + sftp->rmdir_state = libssh2_NB_state_idle; + return _libssh2_error(session, rc, + "Error waiting for FXP STATUS"); + } + + sftp->rmdir_state = libssh2_NB_state_idle; + + retcode = _libssh2_ntohu32(data + 5); + LIBSSH2_FREE(session, data); + + if (retcode == LIBSSH2_FX_OK) { + return 0; + } else { + sftp->last_errno = retcode; + return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, + "SFTP Protocol Error"); + } +} + +/* libssh2_sftp_rmdir_ex + * Remove a directory + */ +LIBSSH2_API int +libssh2_sftp_rmdir_ex(LIBSSH2_SFTP *sftp, const char *path, + unsigned int path_len) +{ + int rc; + if(!sftp) + return LIBSSH2_ERROR_BAD_USE; + BLOCK_ADJUST(rc, sftp->channel->session, + sftp_rmdir(sftp, path, path_len)); + return rc; +} + +/* sftp_stat + * Stat a file or symbolic link + */ +static int sftp_stat(LIBSSH2_SFTP *sftp, const char *path, + unsigned int path_len, int stat_type, + LIBSSH2_SFTP_ATTRIBUTES * attrs) +{ + LIBSSH2_CHANNEL *channel = sftp->channel; + LIBSSH2_SESSION *session = channel->session; + size_t data_len; + /* 13 = packet_len(4) + packet_type(1) + request_id(4) + path_len(4) */ + ssize_t packet_len = + path_len + 13 + + ((stat_type == + LIBSSH2_SFTP_SETSTAT) ? sftp_attrsize(attrs->flags) : 0); + unsigned char *s, *data; + static const unsigned char stat_responses[2] = + { SSH_FXP_ATTRS, SSH_FXP_STATUS }; + int rc; + + if (sftp->stat_state == libssh2_NB_state_idle) { + _libssh2_debug(session, LIBSSH2_TRACE_SFTP, "%s %s", + (stat_type == LIBSSH2_SFTP_SETSTAT) ? "Set-statting" : + (stat_type == + LIBSSH2_SFTP_LSTAT ? "LStatting" : "Statting"), path); + s = sftp->stat_packet = LIBSSH2_ALLOC(session, packet_len); + if (!sftp->stat_packet) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for FXP_*STAT " + "packet"); + } + + _libssh2_store_u32(&s, packet_len - 4); + + switch (stat_type) { + case LIBSSH2_SFTP_SETSTAT: + *(s++) = SSH_FXP_SETSTAT; + break; + + case LIBSSH2_SFTP_LSTAT: + *(s++) = SSH_FXP_LSTAT; + break; + + case LIBSSH2_SFTP_STAT: + default: + *(s++) = SSH_FXP_STAT; + } + sftp->stat_request_id = sftp->request_id++; + _libssh2_store_u32(&s, sftp->stat_request_id); + _libssh2_store_str(&s, path, path_len); + + if (stat_type == LIBSSH2_SFTP_SETSTAT) + s += sftp_attr2bin(s, attrs); + + sftp->stat_state = libssh2_NB_state_created; + } + + if (sftp->stat_state == libssh2_NB_state_created) { + rc = _libssh2_channel_write(channel, 0, sftp->stat_packet, packet_len); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } else if (packet_len != rc) { + LIBSSH2_FREE(session, sftp->stat_packet); + sftp->stat_packet = NULL; + sftp->stat_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send STAT/LSTAT/SETSTAT command"); + } + LIBSSH2_FREE(session, sftp->stat_packet); + sftp->stat_packet = NULL; + + sftp->stat_state = libssh2_NB_state_sent; + } + + rc = sftp_packet_requirev(sftp, 2, stat_responses, + sftp->stat_request_id, &data, &data_len); + if (rc == LIBSSH2_ERROR_EAGAIN) + return rc; + else if (rc) { + sftp->stat_state = libssh2_NB_state_idle; + return _libssh2_error(session, rc, + "Timeout waiting for status message"); + } + + sftp->stat_state = libssh2_NB_state_idle; + + if (data[0] == SSH_FXP_STATUS) { + int retcode; + + retcode = _libssh2_ntohu32(data + 5); + LIBSSH2_FREE(session, data); + if (retcode == LIBSSH2_FX_OK) { + return 0; + } else { + sftp->last_errno = retcode; + return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, + "SFTP Protocol Error"); + } + } + + memset(attrs, 0, sizeof(LIBSSH2_SFTP_ATTRIBUTES)); + sftp_bin2attr(attrs, data + 5); + LIBSSH2_FREE(session, data); + + return 0; +} + +/* libssh2_sftp_stat_ex + * Stat a file or symbolic link + */ +LIBSSH2_API int +libssh2_sftp_stat_ex(LIBSSH2_SFTP *sftp, const char *path, + unsigned int path_len, int stat_type, + LIBSSH2_SFTP_ATTRIBUTES *attrs) +{ + int rc; + if(!sftp) + return LIBSSH2_ERROR_BAD_USE; + BLOCK_ADJUST(rc, sftp->channel->session, + sftp_stat(sftp, path, path_len, stat_type, attrs)); + return rc; +} + +/* sftp_symlink + * Read or set a symlink + */ +static int sftp_symlink(LIBSSH2_SFTP *sftp, const char *path, + unsigned int path_len, char *target, + unsigned int target_len, int link_type) +{ + LIBSSH2_CHANNEL *channel = sftp->channel; + LIBSSH2_SESSION *session = channel->session; + size_t data_len, link_len; + /* 13 = packet_len(4) + packet_type(1) + request_id(4) + path_len(4) */ + ssize_t packet_len = + path_len + 13 + + ((link_type == LIBSSH2_SFTP_SYMLINK) ? (4 + target_len) : 0); + unsigned char *s, *data; + static const unsigned char link_responses[2] = + { SSH_FXP_NAME, SSH_FXP_STATUS }; + int retcode; + + if ((sftp->version < 3) && (link_type != LIBSSH2_SFTP_REALPATH)) { + return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, + "Server does not support SYMLINK or READLINK"); + } + + if (sftp->symlink_state == libssh2_NB_state_idle) { + s = sftp->symlink_packet = LIBSSH2_ALLOC(session, packet_len); + if (!sftp->symlink_packet) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for " + "SYMLINK/READLINK/REALPATH packet"); + } + + _libssh2_debug(session, LIBSSH2_TRACE_SFTP, "%s %s on %s", + (link_type == + LIBSSH2_SFTP_SYMLINK) ? "Creating" : "Reading", + (link_type == + LIBSSH2_SFTP_REALPATH) ? "realpath" : "symlink", path); + + _libssh2_store_u32(&s, packet_len - 4); + + switch (link_type) { + case LIBSSH2_SFTP_REALPATH: + *(s++) = SSH_FXP_REALPATH; + break; + + case LIBSSH2_SFTP_SYMLINK: + *(s++) = SSH_FXP_SYMLINK; + break; + + case LIBSSH2_SFTP_READLINK: + default: + *(s++) = SSH_FXP_READLINK; + } + sftp->symlink_request_id = sftp->request_id++; + _libssh2_store_u32(&s, sftp->symlink_request_id); + _libssh2_store_str(&s, path, path_len); + + if (link_type == LIBSSH2_SFTP_SYMLINK) + _libssh2_store_str(&s, target, target_len); + + sftp->symlink_state = libssh2_NB_state_created; + } + + if (sftp->symlink_state == libssh2_NB_state_created) { + ssize_t rc = _libssh2_channel_write(channel, 0, sftp->symlink_packet, + packet_len); + if (rc == LIBSSH2_ERROR_EAGAIN) + return rc; + else if (packet_len != rc) { + LIBSSH2_FREE(session, sftp->symlink_packet); + sftp->symlink_packet = NULL; + sftp->symlink_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send SYMLINK/READLINK command"); + } + LIBSSH2_FREE(session, sftp->symlink_packet); + sftp->symlink_packet = NULL; + + sftp->symlink_state = libssh2_NB_state_sent; + } + + retcode = sftp_packet_requirev(sftp, 2, link_responses, + sftp->symlink_request_id, &data, + &data_len); + if (retcode == LIBSSH2_ERROR_EAGAIN) + return retcode; + else if (retcode) { + sftp->symlink_state = libssh2_NB_state_idle; + return _libssh2_error(session, retcode, + "Error waiting for status message"); + } + + sftp->symlink_state = libssh2_NB_state_idle; + + if (data[0] == SSH_FXP_STATUS) { + int retcode; + + retcode = _libssh2_ntohu32(data + 5); + LIBSSH2_FREE(session, data); + if (retcode == LIBSSH2_FX_OK) + return LIBSSH2_ERROR_NONE; + else { + sftp->last_errno = retcode; + return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, + "SFTP Protocol Error"); + } + } + + if (_libssh2_ntohu32(data + 5) < 1) { + LIBSSH2_FREE(session, data); + return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, + "Invalid READLINK/REALPATH response, " + "no name entries"); + } + + /* this reads a u32 and stores it into a signed 32bit value */ + link_len = _libssh2_ntohu32(data + 9); + if (link_len < target_len) { + memcpy(target, data + 13, link_len); + target[link_len] = 0; + retcode = (int)link_len; + } + else + retcode = LIBSSH2_ERROR_BUFFER_TOO_SMALL; + LIBSSH2_FREE(session, data); + + return retcode; +} + +/* libssh2_sftp_symlink_ex + * Read or set a symlink + */ +LIBSSH2_API int +libssh2_sftp_symlink_ex(LIBSSH2_SFTP *sftp, const char *path, + unsigned int path_len, char *target, + unsigned int target_len, int link_type) +{ + int rc; + if(!sftp) + return LIBSSH2_ERROR_BAD_USE; + BLOCK_ADJUST(rc, sftp->channel->session, + sftp_symlink(sftp, path, path_len, target, target_len, + link_type)); + return rc; +} + +/* libssh2_sftp_last_error + * Returns the last error code reported by SFTP + */ +LIBSSH2_API unsigned long +libssh2_sftp_last_error(LIBSSH2_SFTP *sftp) +{ + if(!sftp) + return 0; + + return sftp->last_errno; +} + +/* libssh2_sftp_get_channel + * Return the channel of sftp, then caller can control the channel's behavior. + */ +LIBSSH2_API LIBSSH2_CHANNEL * +libssh2_sftp_get_channel(LIBSSH2_SFTP *sftp) +{ + if (!sftp) + return NULL; + + return sftp->channel; +} diff --git a/libssh2/src/transport.c b/libssh2/src/transport.c new file mode 100644 index 0000000..b4ec037 --- /dev/null +++ b/libssh2/src/transport.c @@ -0,0 +1,886 @@ +/* Copyright (C) 2007 The Written Word, Inc. All rights reserved. + * Copyright (C) 2009-2010 by Daniel Stenberg + * Author: Daniel Stenberg + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * This file handles reading and writing to the SECSH transport layer. RFC4253. + */ + +#include "libssh2_priv.h" +#include +#include +#include +#ifdef LIBSSH2DEBUG +#include +#endif + +#include + +#include "transport.h" +#include "mac.h" + +#define MAX_BLOCKSIZE 32 /* MUST fit biggest crypto block size we use/get */ +#define MAX_MACSIZE 20 /* MUST fit biggest MAC length we support */ + +#ifdef LIBSSH2DEBUG +#define UNPRINTABLE_CHAR '.' +static void +debugdump(LIBSSH2_SESSION * session, + const char *desc, const unsigned char *ptr, size_t size) +{ + size_t i; + size_t c; + unsigned int width = 0x10; + char buffer[256]; /* Must be enough for width*4 + about 30 or so */ + size_t used; + static const char* hex_chars = "0123456789ABCDEF"; + + if (!(session->showmask & LIBSSH2_TRACE_TRANS)) { + /* not asked for, bail out */ + return; + } + + used = snprintf(buffer, sizeof(buffer), "=> %s (%d bytes)\n", + desc, (int) size); + if (session->tracehandler) + (session->tracehandler)(session, session->tracehandler_context, + buffer, used); + else + fprintf(stderr, "%s", buffer); + + for(i = 0; i < size; i += width) { + + used = snprintf(buffer, sizeof(buffer), "%04lx: ", (long)i); + + /* hex not disabled, show it */ + for(c = 0; c < width; c++) { + if (i + c < size) { + buffer[used++] = hex_chars[(ptr[i+c] >> 4) & 0xF]; + buffer[used++] = hex_chars[ptr[i+c] & 0xF]; + } + else { + buffer[used++] = ' '; + buffer[used++] = ' '; + } + + buffer[used++] = ' '; + if ((width/2) - 1 == c) + buffer[used++] = ' '; + } + + buffer[used++] = ':'; + buffer[used++] = ' '; + + for(c = 0; (c < width) && (i + c < size); c++) { + buffer[used++] = isprint(ptr[i + c]) ? + ptr[i + c] : UNPRINTABLE_CHAR; + } + buffer[used++] = '\n'; + buffer[used] = 0; + + if (session->tracehandler) + (session->tracehandler)(session, session->tracehandler_context, + buffer, used); + else + fprintf(stderr, "%s", buffer); + } +} +#else +#define debugdump(a,x,y,z) +#endif + + +/* decrypt() decrypts 'len' bytes from 'source' to 'dest'. + * + * returns 0 on success and negative on failure + */ + +static int +decrypt(LIBSSH2_SESSION * session, unsigned char *source, + unsigned char *dest, int len) +{ + struct transportpacket *p = &session->packet; + int blocksize = session->remote.crypt->blocksize; + + /* if we get called with a len that isn't an even number of blocksizes + we risk losing those extra bytes */ + assert((len % blocksize) == 0); + + while (len >= blocksize) { + if (session->remote.crypt->crypt(session, source, blocksize, + &session->remote.crypt_abstract)) { + LIBSSH2_FREE(session, p->payload); + return LIBSSH2_ERROR_DECRYPT; + } + + /* if the crypt() function would write to a given address it + wouldn't have to memcpy() and we could avoid this memcpy() + too */ + memcpy(dest, source, blocksize); + + len -= blocksize; /* less bytes left */ + dest += blocksize; /* advance write pointer */ + source += blocksize; /* advance read pointer */ + } + return LIBSSH2_ERROR_NONE; /* all is fine */ +} + +/* + * fullpacket() gets called when a full packet has been received and properly + * collected. + */ +static int +fullpacket(LIBSSH2_SESSION * session, int encrypted /* 1 or 0 */ ) +{ + unsigned char macbuf[MAX_MACSIZE]; + struct transportpacket *p = &session->packet; + int rc; + int compressed; + + if (session->fullpacket_state == libssh2_NB_state_idle) { + session->fullpacket_macstate = LIBSSH2_MAC_CONFIRMED; + session->fullpacket_payload_len = p->packet_length - 1; + + if (encrypted) { + + /* Calculate MAC hash */ + session->remote.mac->hash(session, macbuf, /* store hash here */ + session->remote.seqno, + p->init, 5, + p->payload, + session->fullpacket_payload_len, + &session->remote.mac_abstract); + + /* Compare the calculated hash with the MAC we just read from + * the network. The read one is at the very end of the payload + * buffer. Note that 'payload_len' here is the packet_length + * field which includes the padding but not the MAC. + */ + if (memcmp(macbuf, p->payload + session->fullpacket_payload_len, + session->remote.mac->mac_len)) { + session->fullpacket_macstate = LIBSSH2_MAC_INVALID; + } + } + + session->remote.seqno++; + + /* ignore the padding */ + session->fullpacket_payload_len -= p->padding_length; + + /* Check for and deal with decompression */ + compressed = + session->local.comp != NULL && + session->local.comp->compress && + ((session->state & LIBSSH2_STATE_AUTHENTICATED) || + session->local.comp->use_in_auth); + + if (compressed && session->remote.comp_abstract) { + /* + * The buffer for the decompression (remote.comp_abstract) is + * initialised in time when it is needed so as long it is NULL we + * cannot decompress. + */ + + unsigned char *data; + size_t data_len; + rc = session->remote.comp->decomp(session, + &data, &data_len, + LIBSSH2_PACKET_MAXDECOMP, + p->payload, + session->fullpacket_payload_len, + &session->remote.comp_abstract); + LIBSSH2_FREE(session, p->payload); + if(rc) + return rc; + + p->payload = data; + session->fullpacket_payload_len = data_len; + } + + session->fullpacket_packet_type = p->payload[0]; + + debugdump(session, "libssh2_transport_read() plain", + p->payload, session->fullpacket_payload_len); + + session->fullpacket_state = libssh2_NB_state_created; + } + + if (session->fullpacket_state == libssh2_NB_state_created) { + rc = _libssh2_packet_add(session, p->payload, + session->fullpacket_payload_len, + session->fullpacket_macstate); + if (rc) + return rc; + } + + session->fullpacket_state = libssh2_NB_state_idle; + + return session->fullpacket_packet_type; +} + + +/* + * _libssh2_transport_read + * + * Collect a packet into the input queue. + * + * Returns packet type added to input queue (0 if nothing added), or a + * negative error number. + */ + +/* + * This function reads the binary stream as specified in chapter 6 of RFC4253 + * "The Secure Shell (SSH) Transport Layer Protocol" + * + * DOES NOT call _libssh2_error() for ANY error case. + */ +int _libssh2_transport_read(LIBSSH2_SESSION * session) +{ + int rc; + struct transportpacket *p = &session->packet; + int remainbuf; + int remainpack; + int numbytes; + int numdecrypt; + unsigned char block[MAX_BLOCKSIZE]; + int blocksize; + int encrypted = 1; + size_t total_num; + + /* default clear the bit */ + session->socket_block_directions &= ~LIBSSH2_SESSION_BLOCK_INBOUND; + + /* + * All channels, systems, subsystems, etc eventually make it down here + * when looking for more incoming data. If a key exchange is going on + * (LIBSSH2_STATE_EXCHANGING_KEYS bit is set) then the remote end will + * ONLY send key exchange related traffic. In non-blocking mode, there is + * a chance to break out of the kex_exchange function with an EAGAIN + * status, and never come back to it. If LIBSSH2_STATE_EXCHANGING_KEYS is + * active, then we must redirect to the key exchange. However, if + * kex_exchange is active (as in it is the one that calls this execution + * of packet_read, then don't redirect, as that would be an infinite loop! + */ + + if (session->state & LIBSSH2_STATE_EXCHANGING_KEYS && + !(session->state & LIBSSH2_STATE_KEX_ACTIVE)) { + + /* Whoever wants a packet won't get anything until the key re-exchange + * is done! + */ + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, "Redirecting into the" + " key re-exchange from _libssh2_transport_read"); + rc = _libssh2_kex_exchange(session, 1, &session->startup_key_state); + if (rc) + return rc; + } + + /* + * =============================== NOTE =============================== + * I know this is very ugly and not a really good use of "goto", but + * this case statement would be even uglier to do it any other way + */ + if (session->readPack_state == libssh2_NB_state_jump1) { + session->readPack_state = libssh2_NB_state_idle; + encrypted = session->readPack_encrypted; + goto libssh2_transport_read_point1; + } + + do { + if (session->socket_state == LIBSSH2_SOCKET_DISCONNECTED) { + return LIBSSH2_ERROR_NONE; + } + + if (session->state & LIBSSH2_STATE_NEWKEYS) { + blocksize = session->remote.crypt->blocksize; + } else { + encrypted = 0; /* not encrypted */ + blocksize = 5; /* not strictly true, but we can use 5 here to + make the checks below work fine still */ + } + + /* read/use a whole big chunk into a temporary area stored in + the LIBSSH2_SESSION struct. We will decrypt data from that + buffer into the packet buffer so this temp one doesn't have + to be able to keep a whole SSH packet, just be large enough + so that we can read big chunks from the network layer. */ + + /* how much data there is remaining in the buffer to deal with + before we should read more from the network */ + remainbuf = p->writeidx - p->readidx; + + /* if remainbuf turns negative we have a bad internal error */ + assert(remainbuf >= 0); + + if (remainbuf < blocksize) { + /* If we have less than a blocksize left, it is too + little data to deal with, read more */ + ssize_t nread; + + /* move any remainder to the start of the buffer so + that we can do a full refill */ + if (remainbuf) { + memmove(p->buf, &p->buf[p->readidx], remainbuf); + p->readidx = 0; + p->writeidx = remainbuf; + } else { + /* nothing to move, just zero the indexes */ + p->readidx = p->writeidx = 0; + } + + /* now read a big chunk from the network into the temp buffer */ + nread = + LIBSSH2_RECV(session, &p->buf[remainbuf], + PACKETBUFSIZE - remainbuf, + LIBSSH2_SOCKET_RECV_FLAGS(session)); + if (nread <= 0) { + /* check if this is due to EAGAIN and return the special + return code if so, error out normally otherwise */ + if ((nread < 0) && (nread == -EAGAIN)) { + session->socket_block_directions |= + LIBSSH2_SESSION_BLOCK_INBOUND; + return LIBSSH2_ERROR_EAGAIN; + } + _libssh2_debug(session, LIBSSH2_TRACE_SOCKET, + "Error recving %d bytes (got %d)", + PACKETBUFSIZE - remainbuf, -nread); + return LIBSSH2_ERROR_SOCKET_RECV; + } + _libssh2_debug(session, LIBSSH2_TRACE_SOCKET, + "Recved %d/%d bytes to %p+%d", nread, + PACKETBUFSIZE - remainbuf, p->buf, remainbuf); + + debugdump(session, "libssh2_transport_read() raw", + &p->buf[remainbuf], nread); + /* advance write pointer */ + p->writeidx += nread; + + /* update remainbuf counter */ + remainbuf = p->writeidx - p->readidx; + } + + /* how much data to deal with from the buffer */ + numbytes = remainbuf; + + if (!p->total_num) { + /* No payload package area allocated yet. To know the + size of this payload, we need to decrypt the first + blocksize data. */ + + if (numbytes < blocksize) { + /* we can't act on anything less than blocksize, but this + check is only done for the initial block since once we have + got the start of a block we can in fact deal with fractions + */ + session->socket_block_directions |= + LIBSSH2_SESSION_BLOCK_INBOUND; + return LIBSSH2_ERROR_EAGAIN; + } + + if (encrypted) { + rc = decrypt(session, &p->buf[p->readidx], block, blocksize); + if (rc != LIBSSH2_ERROR_NONE) { + return rc; + } + /* save the first 5 bytes of the decrypted package, to be + used in the hash calculation later down. */ + memcpy(p->init, &p->buf[p->readidx], 5); + } else { + /* the data is plain, just copy it verbatim to + the working block buffer */ + memcpy(block, &p->buf[p->readidx], blocksize); + } + + /* advance the read pointer */ + p->readidx += blocksize; + + /* we now have the initial blocksize bytes decrypted, + * and we can extract packet and padding length from it + */ + p->packet_length = _libssh2_ntohu32(block); + if (p->packet_length < 1) + return LIBSSH2_ERROR_DECRYPT; + + p->padding_length = block[4]; + + /* total_num is the number of bytes following the initial + (5 bytes) packet length and padding length fields */ + total_num = + p->packet_length - 1 + + (encrypted ? session->remote.mac->mac_len : 0); + + /* RFC4253 section 6.1 Maximum Packet Length says: + * + * "All implementations MUST be able to process + * packets with uncompressed payload length of 32768 + * bytes or less and total packet size of 35000 bytes + * or less (including length, padding length, payload, + * padding, and MAC.)." + */ + if (total_num > LIBSSH2_PACKET_MAXPAYLOAD) { + return LIBSSH2_ERROR_OUT_OF_BOUNDARY; + } + + /* Get a packet handle put data into. We get one to + hold all data, including padding and MAC. */ + p->payload = LIBSSH2_ALLOC(session, total_num); + if (!p->payload) { + return LIBSSH2_ERROR_ALLOC; + } + p->total_num = total_num; + /* init write pointer to start of payload buffer */ + p->wptr = p->payload; + + if (blocksize > 5) { + /* copy the data from index 5 to the end of + the blocksize from the temporary buffer to + the start of the decrypted buffer */ + memcpy(p->wptr, &block[5], blocksize - 5); + p->wptr += blocksize - 5; /* advance write pointer */ + } + + /* init the data_num field to the number of bytes of + the package read so far */ + p->data_num = p->wptr - p->payload; + + /* we already dealt with a blocksize worth of data */ + numbytes -= blocksize; + } + + /* how much there is left to add to the current payload + package */ + remainpack = p->total_num - p->data_num; + + if (numbytes > remainpack) { + /* if we have more data in the buffer than what is going into this + particular packet, we limit this round to this packet only */ + numbytes = remainpack; + } + + if (encrypted) { + /* At the end of the incoming stream, there is a MAC, + and we don't want to decrypt that since we need it + "raw". We MUST however decrypt the padding data + since it is used for the hash later on. */ + int skip = session->remote.mac->mac_len; + + /* if what we have plus numbytes is bigger than the + total minus the skip margin, we should lower the + amount to decrypt even more */ + if ((p->data_num + numbytes) > (p->total_num - skip)) { + numdecrypt = (p->total_num - skip) - p->data_num; + } else { + int frac; + numdecrypt = numbytes; + frac = numdecrypt % blocksize; + if (frac) { + /* not an aligned amount of blocks, + align it */ + numdecrypt -= frac; + /* and make it no unencrypted data + after it */ + numbytes = 0; + } + } + } else { + /* unencrypted data should not be decrypted at all */ + numdecrypt = 0; + } + + /* if there are bytes to decrypt, do that */ + if (numdecrypt > 0) { + /* now decrypt the lot */ + rc = decrypt(session, &p->buf[p->readidx], p->wptr, numdecrypt); + if (rc != LIBSSH2_ERROR_NONE) { + return rc; + } + + /* advance the read pointer */ + p->readidx += numdecrypt; + /* advance write pointer */ + p->wptr += numdecrypt; + /* increse data_num */ + p->data_num += numdecrypt; + + /* bytes left to take care of without decryption */ + numbytes -= numdecrypt; + } + + /* if there are bytes to copy that aren't decrypted, simply + copy them as-is to the target buffer */ + if (numbytes > 0) { + memcpy(p->wptr, &p->buf[p->readidx], numbytes); + + /* advance the read pointer */ + p->readidx += numbytes; + /* advance write pointer */ + p->wptr += numbytes; + /* increse data_num */ + p->data_num += numbytes; + } + + /* now check how much data there's left to read to finish the + current packet */ + remainpack = p->total_num - p->data_num; + + if (!remainpack) { + /* we have a full packet */ + libssh2_transport_read_point1: + rc = fullpacket(session, encrypted); + if (rc == LIBSSH2_ERROR_EAGAIN) { + + if (session->packAdd_state != libssh2_NB_state_idle) + { + /* fullpacket only returns LIBSSH2_ERROR_EAGAIN if + * libssh2_packet_add returns LIBSSH2_ERROR_EAGAIN. If that + * returns LIBSSH2_ERROR_EAGAIN but the packAdd_state is idle, + * then the packet has been added to the brigade, but some + * immediate action that was taken based on the packet + * type (such as key re-exchange) is not yet complete. + * Clear the way for a new packet to be read in. + */ + session->readPack_encrypted = encrypted; + session->readPack_state = libssh2_NB_state_jump1; + } + + return rc; + } + + p->total_num = 0; /* no packet buffer available */ + + return rc; + } + } while (1); /* loop */ + + return LIBSSH2_ERROR_SOCKET_RECV; /* we never reach this point */ +} + +static int +send_existing(LIBSSH2_SESSION *session, const unsigned char *data, + size_t data_len, ssize_t *ret) +{ + ssize_t rc; + ssize_t length; + struct transportpacket *p = &session->packet; + + if (!p->olen) { + *ret = 0; + return LIBSSH2_ERROR_NONE; + } + + /* send as much as possible of the existing packet */ + if ((data != p->odata) || (data_len != p->olen)) { + /* When we are about to complete the sending of a packet, it is vital + that the caller doesn't try to send a new/different packet since + we don't add this one up until the previous one has been sent. To + make the caller really notice his/hers flaw, we return error for + this case */ + return LIBSSH2_ERROR_BAD_USE; + } + + *ret = 1; /* set to make our parent return */ + + /* number of bytes left to send */ + length = p->ototal_num - p->osent; + + rc = LIBSSH2_SEND(session, &p->outbuf[p->osent], length, + LIBSSH2_SOCKET_SEND_FLAGS(session)); + if (rc < 0) + _libssh2_debug(session, LIBSSH2_TRACE_SOCKET, + "Error sending %d bytes: %d", length, -rc); + else { + _libssh2_debug(session, LIBSSH2_TRACE_SOCKET, + "Sent %d/%d bytes at %p+%d", rc, length, p->outbuf, + p->osent); + debugdump(session, "libssh2_transport_write send()", + &p->outbuf[p->osent], rc); + } + + if (rc == length) { + /* the remainder of the package was sent */ + p->ototal_num = 0; + p->olen = 0; + /* we leave *ret set so that the parent returns as we MUST return back + a send success now, so that we don't risk sending EAGAIN later + which then would confuse the parent function */ + return LIBSSH2_ERROR_NONE; + + } + else if (rc < 0) { + /* nothing was sent */ + if (rc != -EAGAIN) + /* send failure! */ + return LIBSSH2_ERROR_SOCKET_SEND; + + session->socket_block_directions |= LIBSSH2_SESSION_BLOCK_OUTBOUND; + return LIBSSH2_ERROR_EAGAIN; + } + + p->osent += rc; /* we sent away this much data */ + + return rc < length ? LIBSSH2_ERROR_EAGAIN : LIBSSH2_ERROR_NONE; +} + +/* + * libssh2_transport_send + * + * Send a packet, encrypting it and adding a MAC code if necessary + * Returns 0 on success, non-zero on failure. + * + * The data is provided as _two_ data areas that are combined by this + * function. The 'data' part is sent immediately before 'data2'. 'data2' may + * be set to NULL to only use a single part. + * + * Returns LIBSSH2_ERROR_EAGAIN if it would block or if the whole packet was + * not sent yet. If it does so, the caller should call this function again as + * soon as it is likely that more data can be sent, and this function MUST + * then be called with the same argument set (same data pointer and same + * data_len) until ERROR_NONE or failure is returned. + * + * This function DOES NOT call _libssh2_error() on any errors. + */ +int _libssh2_transport_send(LIBSSH2_SESSION *session, + const unsigned char *data, size_t data_len, + const unsigned char *data2, size_t data2_len) +{ + int blocksize = + (session->state & LIBSSH2_STATE_NEWKEYS) ? + session->local.crypt->blocksize : 8; + int padding_length; + size_t packet_length; + int total_length; +#ifdef RANDOM_PADDING + int rand_max; + int seed = data[0]; /* FIXME: make this random */ +#endif + struct transportpacket *p = &session->packet; + int encrypted; + int compressed; + ssize_t ret; + int rc; + const unsigned char *orgdata = data; + size_t orgdata_len = data_len; + + /* + * If the last read operation was interrupted in the middle of a key + * exchange, we must complete that key exchange before continuing to write + * further data. + * + * See the similar block in _libssh2_transport_read for more details. + */ + if (session->state & LIBSSH2_STATE_EXCHANGING_KEYS && + !(session->state & LIBSSH2_STATE_KEX_ACTIVE)) { + /* Don't write any new packets if we're still in the middle of a key + * exchange. */ + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, "Redirecting into the" + " key re-exchange from _libssh2_transport_send"); + rc = _libssh2_kex_exchange(session, 1, &session->startup_key_state); + if (rc) + return rc; + } + + debugdump(session, "libssh2_transport_write plain", data, data_len); + if(data2) + debugdump(session, "libssh2_transport_write plain2", data2, data2_len); + + /* FIRST, check if we have a pending write to complete. send_existing + only sanity-check data and data_len and not data2 and data2_len!! */ + rc = send_existing(session, data, data_len, &ret); + if (rc) + return rc; + + session->socket_block_directions &= ~LIBSSH2_SESSION_BLOCK_OUTBOUND; + + if (ret) + /* set by send_existing if data was sent */ + return rc; + + encrypted = (session->state & LIBSSH2_STATE_NEWKEYS) ? 1 : 0; + + compressed = + session->local.comp != NULL && + session->local.comp->compress && + ((session->state & LIBSSH2_STATE_AUTHENTICATED) || + session->local.comp->use_in_auth); + + if (encrypted && compressed) { + /* the idea here is that these function must fail if the output gets + larger than what fits in the assigned buffer so thus they don't + check the input size as we don't know how much it compresses */ + size_t dest_len = MAX_SSH_PACKET_LEN-5-256; + size_t dest2_len = dest_len; + + /* compress directly to the target buffer */ + rc = session->local.comp->comp(session, + &p->outbuf[5], &dest_len, + data, data_len, + &session->local.comp_abstract); + if(rc) + return rc; /* compression failure */ + + if(data2 && data2_len) { + /* compress directly to the target buffer right after where the + previous call put data */ + dest2_len -= dest_len; + + rc = session->local.comp->comp(session, + &p->outbuf[5+dest_len], &dest2_len, + data2, data2_len, + &session->local.comp_abstract); + } + else + dest2_len = 0; + if(rc) + return rc; /* compression failure */ + + data_len = dest_len + dest2_len; /* use the combined length */ + } + else { + if((data_len + data2_len) >= (MAX_SSH_PACKET_LEN-0x100)) + /* too large packet, return error for this until we make this + function split it up and send multiple SSH packets */ + return LIBSSH2_ERROR_INVAL; + + /* copy the payload data */ + memcpy(&p->outbuf[5], data, data_len); + if(data2 && data2_len) + memcpy(&p->outbuf[5+data_len], data2, data2_len); + data_len += data2_len; /* use the combined length */ + } + + + /* RFC4253 says: Note that the length of the concatenation of + 'packet_length', 'padding_length', 'payload', and 'random padding' + MUST be a multiple of the cipher block size or 8, whichever is + larger. */ + + /* Plain math: (4 + 1 + packet_length + padding_length) % blocksize == 0 */ + + packet_length = data_len + 1 + 4; /* 1 is for padding_length field + 4 for the packet_length field */ + + /* at this point we have it all except the padding */ + + /* first figure out our minimum padding amount to make it an even + block size */ + padding_length = blocksize - (packet_length % blocksize); + + /* if the padding becomes too small we add another blocksize worth + of it (taken from the original libssh2 where it didn't have any + real explanation) */ + if (padding_length < 4) { + padding_length += blocksize; + } +#ifdef RANDOM_PADDING + /* FIXME: we can add padding here, but that also makes the packets + bigger etc */ + + /* now we can add 'blocksize' to the padding_length N number of times + (to "help thwart traffic analysis") but it must be less than 255 in + total */ + rand_max = (255 - padding_length) / blocksize + 1; + padding_length += blocksize * (seed % rand_max); +#endif + + packet_length += padding_length; + + /* append the MAC length to the total_length size */ + total_length = + packet_length + (encrypted ? session->local.mac->mac_len : 0); + + /* store packet_length, which is the size of the whole packet except + the MAC and the packet_length field itself */ + _libssh2_htonu32(p->outbuf, packet_length - 4); + /* store padding_length */ + p->outbuf[4] = padding_length; + + /* fill the padding area with random junk */ + _libssh2_random(p->outbuf + 5 + data_len, padding_length); + + if (encrypted) { + size_t i; + + /* Calculate MAC hash. Put the output at index packet_length, + since that size includes the whole packet. The MAC is + calculated on the entire unencrypted packet, including all + fields except the MAC field itself. */ + session->local.mac->hash(session, p->outbuf + packet_length, + session->local.seqno, p->outbuf, + packet_length, NULL, 0, + &session->local.mac_abstract); + + /* Encrypt the whole packet data, one block size at a time. + The MAC field is not encrypted. */ + for(i = 0; i < packet_length; i += session->local.crypt->blocksize) { + unsigned char *ptr = &p->outbuf[i]; + if (session->local.crypt->crypt(session, ptr, + session->local.crypt->blocksize, + &session->local.crypt_abstract)) + return LIBSSH2_ERROR_ENCRYPT; /* encryption failure */ + } + } + + session->local.seqno++; + + ret = LIBSSH2_SEND(session, p->outbuf, total_length, + LIBSSH2_SOCKET_SEND_FLAGS(session)); + if (ret < 0) + _libssh2_debug(session, LIBSSH2_TRACE_SOCKET, + "Error sending %d bytes: %d", total_length, -ret); + else { + _libssh2_debug(session, LIBSSH2_TRACE_SOCKET, "Sent %d/%d bytes at %p", + ret, total_length, p->outbuf); + debugdump(session, "libssh2_transport_write send()", p->outbuf, ret); + } + + if (ret != total_length) { + if (ret >= 0 || ret == -EAGAIN) { + /* the whole packet could not be sent, save the rest */ + session->socket_block_directions |= LIBSSH2_SESSION_BLOCK_OUTBOUND; + p->odata = orgdata; + p->olen = orgdata_len; + p->osent = ret <= 0 ? 0 : ret; + p->ototal_num = total_length; + return LIBSSH2_ERROR_EAGAIN; + } + return LIBSSH2_ERROR_SOCKET_SEND; + } + + /* the whole thing got sent away */ + p->odata = NULL; + p->olen = 0; + + return LIBSSH2_ERROR_NONE; /* all is good */ +} diff --git a/libssh2/src/userauth.c b/libssh2/src/userauth.c new file mode 100644 index 0000000..a0733d5 --- /dev/null +++ b/libssh2/src/userauth.c @@ -0,0 +1,1687 @@ +/* Copyright (c) 2004-2007, Sara Golemon + * Copyright (c) 2005 Mikhail Gusarov + * Copyright (c) 2009-2011 by Daniel Stenberg + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include "libssh2_priv.h" + +#include +#include + +#include + +/* Needed for struct iovec on some platforms */ +#ifdef HAVE_SYS_UIO_H +#include +#endif + +#include "transport.h" +#include "session.h" +#include "userauth.h" + +/* libssh2_userauth_list + * + * List authentication methods + * Will yield successful login if "none" happens to be allowable for this user + * Not a common configuration for any SSH server though + * username should be NULL, or a null terminated string + */ +static char *userauth_list(LIBSSH2_SESSION *session, const char *username, + unsigned int username_len) +{ + static const unsigned char reply_codes[3] = + { SSH_MSG_USERAUTH_SUCCESS, SSH_MSG_USERAUTH_FAILURE, 0 }; + /* packet_type(1) + username_len(4) + service_len(4) + + service(14)"ssh-connection" + method_len(4) = 27 */ + unsigned long methods_len; + unsigned char *s; + int rc; + + if (session->userauth_list_state == libssh2_NB_state_idle) { + /* Zero the whole thing out */ + memset(&session->userauth_list_packet_requirev_state, 0, + sizeof(session->userauth_list_packet_requirev_state)); + + session->userauth_list_data_len = username_len + 27; + + s = session->userauth_list_data = + LIBSSH2_ALLOC(session, session->userauth_list_data_len); + if (!session->userauth_list_data) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for userauth_list"); + return NULL; + } + + *(s++) = SSH_MSG_USERAUTH_REQUEST; + _libssh2_store_str(&s, username, username_len); + _libssh2_store_str(&s, "ssh-connection", 14); + _libssh2_store_u32(&s, 4); /* send "none" separately */ + + session->userauth_list_state = libssh2_NB_state_created; + } + + if (session->userauth_list_state == libssh2_NB_state_created) { + rc = _libssh2_transport_send(session, session->userauth_list_data, + session->userauth_list_data_len, + (unsigned char *)"none", 4); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block requesting userauth list"); + return NULL; + } + /* now free the packet that was sent */ + LIBSSH2_FREE(session, session->userauth_list_data); + session->userauth_list_data = NULL; + + if (rc) { + _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send userauth-none request"); + session->userauth_list_state = libssh2_NB_state_idle; + return NULL; + } + + session->userauth_list_state = libssh2_NB_state_sent; + } + + if (session->userauth_list_state == libssh2_NB_state_sent) { + rc = _libssh2_packet_requirev(session, reply_codes, + &session->userauth_list_data, + &session->userauth_list_data_len, 0, + NULL, 0, + &session->userauth_list_packet_requirev_state); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block requesting userauth list"); + return NULL; + } else if (rc) { + _libssh2_error(session, rc, "Failed getting response"); + session->userauth_list_state = libssh2_NB_state_idle; + return NULL; + } + + if (session->userauth_list_data[0] == SSH_MSG_USERAUTH_SUCCESS) { + /* Wow, who'dve thought... */ + _libssh2_error(session, LIBSSH2_ERROR_NONE, "No error"); + LIBSSH2_FREE(session, session->userauth_list_data); + session->userauth_list_data = NULL; + session->state |= LIBSSH2_STATE_AUTHENTICATED; + session->userauth_list_state = libssh2_NB_state_idle; + return NULL; + } + + methods_len = _libssh2_ntohu32(session->userauth_list_data + 1); + + /* Do note that the memory areas overlap! */ + memmove(session->userauth_list_data, session->userauth_list_data + 5, + methods_len); + session->userauth_list_data[methods_len] = '\0'; + _libssh2_debug(session, LIBSSH2_TRACE_AUTH, + "Permitted auth methods: %s", + session->userauth_list_data); + } + + session->userauth_list_state = libssh2_NB_state_idle; + return (char *) session->userauth_list_data; +} + +/* libssh2_userauth_list + * + * List authentication methods + * Will yield successful login if "none" happens to be allowable for this user + * Not a common configuration for any SSH server though + * username should be NULL, or a null terminated string + */ +LIBSSH2_API char * +libssh2_userauth_list(LIBSSH2_SESSION * session, const char *user, + unsigned int user_len) +{ + char *ptr; + BLOCK_ADJUST_ERRNO(ptr, session, + userauth_list(session, user, user_len)); + return ptr; +} + +/* + * libssh2_userauth_authenticated + * + * Returns: 0 if not yet authenticated + * 1 if already authenticated + */ +LIBSSH2_API int +libssh2_userauth_authenticated(LIBSSH2_SESSION * session) +{ + return (session->state & LIBSSH2_STATE_AUTHENTICATED)?1:0; +} + + + +/* userauth_password + * Plain ol' login + */ +static int +userauth_password(LIBSSH2_SESSION *session, + const char *username, unsigned int username_len, + const unsigned char *password, unsigned int password_len, + LIBSSH2_PASSWD_CHANGEREQ_FUNC((*passwd_change_cb))) +{ + unsigned char *s; + static const unsigned char reply_codes[4] = + { SSH_MSG_USERAUTH_SUCCESS, SSH_MSG_USERAUTH_FAILURE, + SSH_MSG_USERAUTH_PASSWD_CHANGEREQ, 0 + }; + int rc; + + if (session->userauth_pswd_state == libssh2_NB_state_idle) { + /* Zero the whole thing out */ + memset(&session->userauth_pswd_packet_requirev_state, 0, + sizeof(session->userauth_pswd_packet_requirev_state)); + + /* + * 40 = acket_type(1) + username_len(4) + service_len(4) + + * service(14)"ssh-connection" + method_len(4) + method(8)"password" + + * chgpwdbool(1) + password_len(4) */ + session->userauth_pswd_data_len = username_len + 40; + + session->userauth_pswd_data0 = ~SSH_MSG_USERAUTH_PASSWD_CHANGEREQ; + + /* TODO: remove this alloc with a fixed buffer in the session + struct */ + s = session->userauth_pswd_data = + LIBSSH2_ALLOC(session, session->userauth_pswd_data_len); + if (!session->userauth_pswd_data) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for " + "userauth-password request"); + } + + *(s++) = SSH_MSG_USERAUTH_REQUEST; + _libssh2_store_str(&s, username, username_len); + _libssh2_store_str(&s, "ssh-connection", sizeof("ssh-connection") - 1); + _libssh2_store_str(&s, "password", sizeof("password") - 1); + *s++ = '\0'; + _libssh2_store_u32(&s, password_len); + /* 'password' is sent separately */ + + _libssh2_debug(session, LIBSSH2_TRACE_AUTH, + "Attempting to login using password authentication"); + + session->userauth_pswd_state = libssh2_NB_state_created; + } + + if (session->userauth_pswd_state == libssh2_NB_state_created) { + rc = _libssh2_transport_send(session, session->userauth_pswd_data, + session->userauth_pswd_data_len, + password, password_len); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block writing password request"); + } + + /* now free the sent packet */ + LIBSSH2_FREE(session, session->userauth_pswd_data); + session->userauth_pswd_data = NULL; + + if (rc) { + session->userauth_pswd_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send userauth-password request"); + } + + session->userauth_pswd_state = libssh2_NB_state_sent; + } + + password_response: + + if ((session->userauth_pswd_state == libssh2_NB_state_sent) + || (session->userauth_pswd_state == libssh2_NB_state_sent1) + || (session->userauth_pswd_state == libssh2_NB_state_sent2)) { + if (session->userauth_pswd_state == libssh2_NB_state_sent) { + rc = _libssh2_packet_requirev(session, reply_codes, + &session->userauth_pswd_data, + &session->userauth_pswd_data_len, + 0, NULL, 0, + &session-> + userauth_pswd_packet_requirev_state); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block waiting"); + } else if (rc) { + session->userauth_pswd_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_TIMEOUT, + "Would block waiting"); + } + + if (session->userauth_pswd_data[0] == SSH_MSG_USERAUTH_SUCCESS) { + _libssh2_debug(session, LIBSSH2_TRACE_AUTH, + "Password authentication successful"); + LIBSSH2_FREE(session, session->userauth_pswd_data); + session->userauth_pswd_data = NULL; + session->state |= LIBSSH2_STATE_AUTHENTICATED; + session->userauth_pswd_state = libssh2_NB_state_idle; + return 0; + } else if (session->userauth_pswd_data[0] == SSH_MSG_USERAUTH_FAILURE) { + _libssh2_debug(session, LIBSSH2_TRACE_AUTH, + "Password authentication failed"); + LIBSSH2_FREE(session, session->userauth_pswd_data); + session->userauth_pswd_data = NULL; + session->userauth_pswd_state = libssh2_NB_state_idle; + return _libssh2_error(session, + LIBSSH2_ERROR_AUTHENTICATION_FAILED, + "Authentication failed " + "(username/password)"); + } + + session->userauth_pswd_newpw = NULL; + session->userauth_pswd_newpw_len = 0; + + session->userauth_pswd_state = libssh2_NB_state_sent1; + } + + if ((session->userauth_pswd_data[0] == + SSH_MSG_USERAUTH_PASSWD_CHANGEREQ) + || (session->userauth_pswd_data0 == + SSH_MSG_USERAUTH_PASSWD_CHANGEREQ)) { + session->userauth_pswd_data0 = SSH_MSG_USERAUTH_PASSWD_CHANGEREQ; + + if ((session->userauth_pswd_state == libssh2_NB_state_sent1) || + (session->userauth_pswd_state == libssh2_NB_state_sent2)) { + if (session->userauth_pswd_state == libssh2_NB_state_sent1) { + _libssh2_debug(session, LIBSSH2_TRACE_AUTH, + "Password change required"); + LIBSSH2_FREE(session, session->userauth_pswd_data); + session->userauth_pswd_data = NULL; + } + if (passwd_change_cb) { + if (session->userauth_pswd_state == libssh2_NB_state_sent1) { + passwd_change_cb(session, + &session->userauth_pswd_newpw, + &session->userauth_pswd_newpw_len, + &session->abstract); + if (!session->userauth_pswd_newpw) { + return _libssh2_error(session, + LIBSSH2_ERROR_PASSWORD_EXPIRED, + "Password expired, and " + "callback failed"); + } + + /* basic data_len + newpw_len(4) */ + session->userauth_pswd_data_len = + username_len + password_len + 44; + + s = session->userauth_pswd_data = + LIBSSH2_ALLOC(session, + session->userauth_pswd_data_len); + if (!session->userauth_pswd_data) { + LIBSSH2_FREE(session, + session->userauth_pswd_newpw); + session->userauth_pswd_newpw = NULL; + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory " + "for userauth password " + "change request"); + } + + *(s++) = SSH_MSG_USERAUTH_REQUEST; + _libssh2_store_str(&s, username, username_len); + _libssh2_store_str(&s, "ssh-connection", + sizeof("ssh-connection") - 1); + _libssh2_store_str(&s, "password", + sizeof("password") - 1); + *s++ = 0x01; + _libssh2_store_str(&s, (char *)password, password_len); + _libssh2_store_u32(&s, + session->userauth_pswd_newpw_len); + /* send session->userauth_pswd_newpw separately */ + + session->userauth_pswd_state = libssh2_NB_state_sent2; + } + + if (session->userauth_pswd_state == libssh2_NB_state_sent2) { + rc = _libssh2_transport_send(session, + session->userauth_pswd_data, + session->userauth_pswd_data_len, + (unsigned char *) + session->userauth_pswd_newpw, + session->userauth_pswd_newpw_len); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block waiting"); + } + + /* free the allocated packets again */ + LIBSSH2_FREE(session, session->userauth_pswd_data); + session->userauth_pswd_data = NULL; + LIBSSH2_FREE(session, session->userauth_pswd_newpw); + session->userauth_pswd_newpw = NULL; + + if (rc) { + return _libssh2_error(session, + LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send userauth " + "password-change request"); + } + + /* + * Ugliest use of goto ever. Blame it on the + * askN => requirev migration. + */ + session->userauth_pswd_state = libssh2_NB_state_sent; + goto password_response; + } + } + } else { + session->userauth_pswd_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_PASSWORD_EXPIRED, + "Password Expired, and no callback " + "specified"); + } + } + } + + /* FAILURE */ + LIBSSH2_FREE(session, session->userauth_pswd_data); + session->userauth_pswd_data = NULL; + session->userauth_pswd_state = libssh2_NB_state_idle; + + return _libssh2_error(session, LIBSSH2_ERROR_AUTHENTICATION_FAILED, + "Authentication failed"); +} + +/* + * libssh2_userauth_password_ex + * + * Plain ol' login + */ + +LIBSSH2_API int +libssh2_userauth_password_ex(LIBSSH2_SESSION *session, const char *username, + unsigned int username_len, const char *password, + unsigned int password_len, + LIBSSH2_PASSWD_CHANGEREQ_FUNC((*passwd_change_cb))) +{ + int rc; + BLOCK_ADJUST(rc, session, + userauth_password(session, username, username_len, + (unsigned char *)password, password_len, + passwd_change_cb)); + return rc; +} + +/* + * file_read_publickey + * + * Read a public key from an id_???.pub style file + * + * Returns an allocated string containing the decoded key in *pubkeydata + * on success. + * Returns an allocated string containing the key method (e.g. "ssh-dss") + * in method on success. + */ +static int +file_read_publickey(LIBSSH2_SESSION * session, unsigned char **method, + size_t *method_len, + unsigned char **pubkeydata, + size_t *pubkeydata_len, + const char *pubkeyfile) +{ + FILE *fd; + char c; + unsigned char *pubkey = NULL, *sp1, *sp2, *tmp; + size_t pubkey_len = 0; + unsigned int tmp_len; + + _libssh2_debug(session, LIBSSH2_TRACE_AUTH, "Loading public key file: %s", + pubkeyfile); + /* Read Public Key */ + fd = fopen(pubkeyfile, "r"); + if (!fd) { + return _libssh2_error(session, LIBSSH2_ERROR_FILE, + "Unable to open public key file"); + } + while (!feof(fd) && 1 == fread(&c, 1, 1, fd) && c != '\r' && c != '\n') + pubkey_len++; + if (feof(fd)) { + /* the last character was EOF */ + pubkey_len--; + } + rewind(fd); + + if (pubkey_len <= 1) { + fclose(fd); + return _libssh2_error(session, LIBSSH2_ERROR_FILE, + "Invalid data in public key file"); + } + + pubkey = LIBSSH2_ALLOC(session, pubkey_len); + if (!pubkey) { + fclose(fd); + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for public key data"); + } + if (fread(pubkey, 1, pubkey_len, fd) != pubkey_len) { + LIBSSH2_FREE(session, pubkey); + fclose(fd); + return _libssh2_error(session, LIBSSH2_ERROR_FILE, + "Unable to read public key from file"); + } + fclose(fd); + /* + * Remove trailing whitespace + */ + while (pubkey_len && isspace(pubkey[pubkey_len - 1])) + pubkey_len--; + + if (!pubkey_len) { + LIBSSH2_FREE(session, pubkey); + return _libssh2_error(session, LIBSSH2_ERROR_FILE, + "Missing public key data"); + } + + if ((sp1 = memchr(pubkey, ' ', pubkey_len)) == NULL) { + LIBSSH2_FREE(session, pubkey); + return _libssh2_error(session, LIBSSH2_ERROR_FILE, + "Invalid public key data"); + } + + sp1++; + + if ((sp2 = memchr(sp1, ' ', pubkey_len - (sp1 - pubkey - 1))) == NULL) { + /* Assume that the id string is missing, but that it's okay */ + sp2 = pubkey + pubkey_len; + } + + if (libssh2_base64_decode(session, (char **) &tmp, &tmp_len, + (char *) sp1, sp2 - sp1)) { + LIBSSH2_FREE(session, pubkey); + return _libssh2_error(session, LIBSSH2_ERROR_FILE, + "Invalid key data, not base64 encoded"); + } + + /* Wasting some bytes here (okay, more than some), but since it's likely + * to be freed soon anyway, we'll just avoid the extra free/alloc and call + * it a wash */ + *method = pubkey; + *method_len = sp1 - pubkey - 1; + + *pubkeydata = tmp; + *pubkeydata_len = tmp_len; + + return 0; +} + + + +/* libssh2_file_read_privatekey + * Read a PEM encoded private key from an id_??? style file + */ +static int +file_read_privatekey(LIBSSH2_SESSION * session, + const LIBSSH2_HOSTKEY_METHOD ** hostkey_method, + void **hostkey_abstract, + const unsigned char *method, int method_len, + const char *privkeyfile, const char *passphrase) +{ + const LIBSSH2_HOSTKEY_METHOD **hostkey_methods_avail = + libssh2_hostkey_methods(); + + _libssh2_debug(session, LIBSSH2_TRACE_AUTH, "Loading private key file: %s", + privkeyfile); + *hostkey_method = NULL; + *hostkey_abstract = NULL; + while (*hostkey_methods_avail && (*hostkey_methods_avail)->name) { + if ((*hostkey_methods_avail)->initPEM + && strncmp((*hostkey_methods_avail)->name, (const char *) method, + method_len) == 0) { + *hostkey_method = *hostkey_methods_avail; + break; + } + hostkey_methods_avail++; + } + if (!*hostkey_method) { + return _libssh2_error(session, LIBSSH2_ERROR_METHOD_NONE, + "No handler for specified private key"); + } + + if ((*hostkey_method)-> + initPEM(session, privkeyfile, (unsigned char *) passphrase, + hostkey_abstract)) { + return _libssh2_error(session, LIBSSH2_ERROR_FILE, + "Unable to initialize private key from file"); + } + + return 0; +} + +struct privkey_file { + const char *filename; + const char *passphrase; +}; + +static int +sign_fromfile(LIBSSH2_SESSION *session, unsigned char **sig, size_t *sig_len, + const unsigned char *data, size_t data_len, void **abstract) +{ + struct privkey_file *privkey_file = (struct privkey_file *) (*abstract); + const LIBSSH2_HOSTKEY_METHOD *privkeyobj; + void *hostkey_abstract; + struct iovec datavec; + int rc; + + rc = file_read_privatekey(session, &privkeyobj, &hostkey_abstract, + session->userauth_pblc_method, + session->userauth_pblc_method_len, + privkey_file->filename, + privkey_file->passphrase); + if(rc) + return rc; + + datavec.iov_base = (void *)data; + datavec.iov_len = data_len; + + if (privkeyobj->signv(session, sig, sig_len, 1, &datavec, + &hostkey_abstract)) { + if (privkeyobj->dtor) { + privkeyobj->dtor(session, abstract); + } + return -1; + } + + if (privkeyobj->dtor) { + privkeyobj->dtor(session, &hostkey_abstract); + } + return 0; +} + + + +/* userauth_hostbased_fromfile + * Authenticate using a keypair found in the named files + */ +static int +userauth_hostbased_fromfile(LIBSSH2_SESSION *session, + const char *username, size_t username_len, + const char *publickey, const char *privatekey, + const char *passphrase, const char *hostname, + size_t hostname_len, + const char *local_username, + size_t local_username_len) +{ + int rc; + + if (session->userauth_host_state == libssh2_NB_state_idle) { + const LIBSSH2_HOSTKEY_METHOD *privkeyobj; + unsigned char *pubkeydata, *sig; + size_t pubkeydata_len; + size_t sig_len; + void *abstract; + unsigned char buf[5]; + struct iovec datavec[4]; + + /* Zero the whole thing out */ + memset(&session->userauth_host_packet_requirev_state, 0, + sizeof(session->userauth_host_packet_requirev_state)); + + if (publickey) { + rc = file_read_publickey(session, &session->userauth_host_method, + &session->userauth_host_method_len, + &pubkeydata, &pubkeydata_len, publickey); + if(rc) + /* Note: file_read_publickey() calls _libssh2_error() */ + return rc; + } + else { + /* Compute public key from private key. */ + rc = _libssh2_pub_priv_keyfile(session, + &session->userauth_host_method, + &session->userauth_host_method_len, + &pubkeydata, &pubkeydata_len, + privatekey, passphrase); + if (rc) + /* libssh2_pub_priv_keyfile calls _libssh2_error() */ + return rc; + } + + /* + * 52 = packet_type(1) + username_len(4) + servicename_len(4) + + * service_name(14)"ssh-connection" + authmethod_len(4) + + * authmethod(9)"hostbased" + method_len(4) + pubkeydata_len(4) + + * hostname_len(4) + local_username_len(4) + */ + session->userauth_host_packet_len = + username_len + session->userauth_host_method_len + hostname_len + + local_username_len + pubkeydata_len + 52; + + /* + * Preallocate space for an overall length, method name again, + * and the signature, which won't be any larger than the size of + * the publickeydata itself + */ + session->userauth_host_s = session->userauth_host_packet = + LIBSSH2_ALLOC(session, + session->userauth_host_packet_len + 4 + + (4 + session->userauth_host_method_len) + + (4 + pubkeydata_len)); + if (!session->userauth_host_packet) { + LIBSSH2_FREE(session, session->userauth_host_method); + session->userauth_host_method = NULL; + LIBSSH2_FREE(session, pubkeydata); + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Out of memory"); + } + + *(session->userauth_host_s++) = SSH_MSG_USERAUTH_REQUEST; + _libssh2_store_str(&session->userauth_host_s, username, username_len); + _libssh2_store_str(&session->userauth_host_s, "ssh-connection", 14); + _libssh2_store_str(&session->userauth_host_s, "hostbased", 9); + _libssh2_store_str(&session->userauth_host_s, + (const char *)session->userauth_host_method, + session->userauth_host_method_len); + _libssh2_store_str(&session->userauth_host_s, (const char *)pubkeydata, + pubkeydata_len); + LIBSSH2_FREE(session, pubkeydata); + _libssh2_store_str(&session->userauth_host_s, hostname, hostname_len); + _libssh2_store_str(&session->userauth_host_s, local_username, + local_username_len); + + rc = file_read_privatekey(session, &privkeyobj, &abstract, + session->userauth_host_method, + session->userauth_host_method_len, + privatekey, passphrase); + if(rc) { + /* Note: file_read_privatekey() calls _libssh2_error() */ + LIBSSH2_FREE(session, session->userauth_host_method); + session->userauth_host_method = NULL; + LIBSSH2_FREE(session, session->userauth_host_packet); + session->userauth_host_packet = NULL; + return rc; + } + + _libssh2_htonu32(buf, session->session_id_len); + datavec[0].iov_base = (void *)buf; + datavec[0].iov_len = 4; + datavec[1].iov_base = (void *)session->session_id; + datavec[1].iov_len = session->session_id_len; + datavec[2].iov_base = (void *)session->userauth_host_packet; + datavec[2].iov_len = session->userauth_host_packet_len; + + if (privkeyobj->signv(session, &sig, &sig_len, 3, datavec, &abstract)) { + LIBSSH2_FREE(session, session->userauth_host_method); + session->userauth_host_method = NULL; + LIBSSH2_FREE(session, session->userauth_host_packet); + session->userauth_host_packet = NULL; + if (privkeyobj->dtor) { + privkeyobj->dtor(session, &abstract); + } + return -1; + } + + if (privkeyobj->dtor) { + privkeyobj->dtor(session, &abstract); + } + + if (sig_len > pubkeydata_len) { + unsigned char *newpacket; + /* Should *NEVER* happen, but...well.. better safe than sorry */ + newpacket = LIBSSH2_REALLOC(session, session->userauth_host_packet, + session->userauth_host_packet_len + 4 + + (4 + session->userauth_host_method_len) + + (4 + sig_len)); /* PK sigblob */ + if (!newpacket) { + LIBSSH2_FREE(session, sig); + LIBSSH2_FREE(session, session->userauth_host_packet); + session->userauth_host_packet = NULL; + LIBSSH2_FREE(session, session->userauth_host_method); + session->userauth_host_method = NULL; + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Failed allocating additional space for " + "userauth-hostbased packet"); + } + session->userauth_host_packet = newpacket; + } + + session->userauth_host_s = + session->userauth_host_packet + session->userauth_host_packet_len; + + _libssh2_store_u32(&session->userauth_host_s, + 4 + session->userauth_host_method_len + 4 + sig_len); + _libssh2_store_str(&session->userauth_host_s, + (const char *)session->userauth_host_method, + session->userauth_host_method_len); + LIBSSH2_FREE(session, session->userauth_host_method); + session->userauth_host_method = NULL; + + _libssh2_store_str(&session->userauth_host_s, (const char *)sig, + sig_len); + LIBSSH2_FREE(session, sig); + + _libssh2_debug(session, LIBSSH2_TRACE_AUTH, + "Attempting hostbased authentication"); + + session->userauth_host_state = libssh2_NB_state_created; + } + + if (session->userauth_host_state == libssh2_NB_state_created) { + rc = _libssh2_transport_send(session, session->userauth_host_packet, + session->userauth_host_s - + session->userauth_host_packet, + NULL, 0); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, "Would block"); + } + else if (rc) { + LIBSSH2_FREE(session, session->userauth_host_packet); + session->userauth_host_packet = NULL; + session->userauth_host_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send userauth-hostbased request"); + } + LIBSSH2_FREE(session, session->userauth_host_packet); + session->userauth_host_packet = NULL; + + session->userauth_host_state = libssh2_NB_state_sent; + } + + if (session->userauth_host_state == libssh2_NB_state_sent) { + static const unsigned char reply_codes[3] = + { SSH_MSG_USERAUTH_SUCCESS, SSH_MSG_USERAUTH_FAILURE, 0 }; + size_t data_len; + rc = _libssh2_packet_requirev(session, reply_codes, + &session->userauth_host_data, + &data_len, 0, NULL, 0, + &session-> + userauth_host_packet_requirev_state); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, "Would block"); + } + + session->userauth_host_state = libssh2_NB_state_idle; + if (rc) { + return _libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED, + "Auth failed"); + } + + if (session->userauth_host_data[0] == SSH_MSG_USERAUTH_SUCCESS) { + _libssh2_debug(session, LIBSSH2_TRACE_AUTH, + "Hostbased authentication successful"); + /* We are us and we've proved it. */ + LIBSSH2_FREE(session, session->userauth_host_data); + session->userauth_host_data = NULL; + session->state |= LIBSSH2_STATE_AUTHENTICATED; + return 0; + } + } + + /* This public key is not allowed for this user on this server */ + LIBSSH2_FREE(session, session->userauth_host_data); + session->userauth_host_data = NULL; + return _libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED, + "Invalid signature for supplied public key, or bad " + "username/public key combination"); +} + +/* libssh2_userauth_hostbased_fromfile_ex + * Authenticate using a keypair found in the named files + */ +LIBSSH2_API int +libssh2_userauth_hostbased_fromfile_ex(LIBSSH2_SESSION *session, + const char *user, + unsigned int user_len, + const char *publickey, + const char *privatekey, + const char *passphrase, + const char *host, + unsigned int host_len, + const char *localuser, + unsigned int localuser_len) +{ + int rc; + BLOCK_ADJUST(rc, session, + userauth_hostbased_fromfile(session, user, user_len, + publickey, privatekey, + passphrase, host, host_len, + localuser, localuser_len)); + return rc; +} + + + +int +_libssh2_userauth_publickey(LIBSSH2_SESSION *session, + const char *username, + unsigned int username_len, + const unsigned char *pubkeydata, + unsigned long pubkeydata_len, + LIBSSH2_USERAUTH_PUBLICKEY_SIGN_FUNC((*sign_callback)), + void *abstract) +{ + unsigned char reply_codes[4] = + { SSH_MSG_USERAUTH_SUCCESS, SSH_MSG_USERAUTH_FAILURE, + SSH_MSG_USERAUTH_PK_OK, 0 + }; + int rc; + unsigned char *s; + + if (session->userauth_pblc_state == libssh2_NB_state_idle) { + + /* + * The call to _libssh2_ntohu32 later relies on pubkeydata having at + * least 4 valid bytes containing the length of the method name. + */ + if (pubkeydata_len < 4) + return _libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED, + "Invalid public key, too short"); + + /* Zero the whole thing out */ + memset(&session->userauth_pblc_packet_requirev_state, 0, + sizeof(session->userauth_pblc_packet_requirev_state)); + + /* + * As an optimisation, userauth_publickey_fromfile reuses a + * previously allocated copy of the method name to avoid an extra + * allocation/free. + * For other uses, we allocate and populate it here. + */ + if (!session->userauth_pblc_method) { + session->userauth_pblc_method_len = _libssh2_ntohu32(pubkeydata); + + if(session->userauth_pblc_method_len > pubkeydata_len) + /* the method length simply cannot be longer than the entire + passed in data, so we use this to detect crazy input + data */ + return _libssh2_error(session, + LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED, + "Invalid public key"); + + session->userauth_pblc_method = + LIBSSH2_ALLOC(session, session->userauth_pblc_method_len); + if (!session->userauth_pblc_method) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for public key " + "data"); + } + memcpy(session->userauth_pblc_method, pubkeydata + 4, + session->userauth_pblc_method_len); + } + /* + * The length of the method name read from plaintext prefix in the + * file must match length embedded in the key. + * TODO: The data should match too but we don't check that. Should we? + */ + else if (session->userauth_pblc_method_len != + _libssh2_ntohu32(pubkeydata)) + return _libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED, + "Invalid public key"); + + /* + * 45 = packet_type(1) + username_len(4) + servicename_len(4) + + * service_name(14)"ssh-connection" + authmethod_len(4) + + * authmethod(9)"publickey" + sig_included(1)'\0' + algmethod_len(4) + + * publickey_len(4) + */ + session->userauth_pblc_packet_len = + username_len + session->userauth_pblc_method_len + pubkeydata_len + + 45; + + /* + * Preallocate space for an overall length, method name again, and the + * signature, which won't be any larger than the size of the + * publickeydata itself. + * + * Note that the 'pubkeydata_len' extra bytes allocated here will not + * be used in this first send, but will be used in the later one where + * this same allocation is re-used. + */ + s = session->userauth_pblc_packet = + LIBSSH2_ALLOC(session, + session->userauth_pblc_packet_len + 4 + + (4 + session->userauth_pblc_method_len) + + (4 + pubkeydata_len)); + if (!session->userauth_pblc_packet) { + LIBSSH2_FREE(session, session->userauth_pblc_method); + session->userauth_pblc_method = NULL; + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Out of memory"); + } + + *s++ = SSH_MSG_USERAUTH_REQUEST; + _libssh2_store_str(&s, username, username_len); + _libssh2_store_str(&s, "ssh-connection", 14); + _libssh2_store_str(&s, "publickey", 9); + + session->userauth_pblc_b = s; + /* Not sending signature with *this* packet */ + *s++ = 0; + + _libssh2_store_str(&s, (const char *)session->userauth_pblc_method, + session->userauth_pblc_method_len); + _libssh2_store_str(&s, (const char *)pubkeydata, pubkeydata_len); + + _libssh2_debug(session, LIBSSH2_TRACE_AUTH, + "Attempting publickey authentication"); + + session->userauth_pblc_state = libssh2_NB_state_created; + } + + if (session->userauth_pblc_state == libssh2_NB_state_created) { + rc = _libssh2_transport_send(session, session->userauth_pblc_packet, + session->userauth_pblc_packet_len, + NULL, 0); + if (rc == LIBSSH2_ERROR_EAGAIN) + return _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, "Would block"); + else if (rc) { + LIBSSH2_FREE(session, session->userauth_pblc_packet); + session->userauth_pblc_packet = NULL; + LIBSSH2_FREE(session, session->userauth_pblc_method); + session->userauth_pblc_method = NULL; + session->userauth_pblc_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send userauth-publickey request"); + } + + session->userauth_pblc_state = libssh2_NB_state_sent; + } + + if (session->userauth_pblc_state == libssh2_NB_state_sent) { + rc = _libssh2_packet_requirev(session, reply_codes, + &session->userauth_pblc_data, + &session->userauth_pblc_data_len, 0, + NULL, 0, + &session-> + userauth_pblc_packet_requirev_state); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, "Would block"); + } + else if (rc) { + LIBSSH2_FREE(session, session->userauth_pblc_packet); + session->userauth_pblc_packet = NULL; + LIBSSH2_FREE(session, session->userauth_pblc_method); + session->userauth_pblc_method = NULL; + session->userauth_pblc_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED, + "Waiting for USERAUTH response"); + } + + if (session->userauth_pblc_data[0] == SSH_MSG_USERAUTH_SUCCESS) { + _libssh2_debug(session, LIBSSH2_TRACE_AUTH, + "Pubkey authentication prematurely successful"); + /* + * God help any SSH server that allows an UNVERIFIED + * public key to validate the user + */ + LIBSSH2_FREE(session, session->userauth_pblc_data); + session->userauth_pblc_data = NULL; + LIBSSH2_FREE(session, session->userauth_pblc_packet); + session->userauth_pblc_packet = NULL; + LIBSSH2_FREE(session, session->userauth_pblc_method); + session->userauth_pblc_method = NULL; + session->state |= LIBSSH2_STATE_AUTHENTICATED; + session->userauth_pblc_state = libssh2_NB_state_idle; + return 0; + } + + if (session->userauth_pblc_data[0] == SSH_MSG_USERAUTH_FAILURE) { + /* This public key is not allowed for this user on this server */ + LIBSSH2_FREE(session, session->userauth_pblc_data); + session->userauth_pblc_data = NULL; + LIBSSH2_FREE(session, session->userauth_pblc_packet); + session->userauth_pblc_packet = NULL; + LIBSSH2_FREE(session, session->userauth_pblc_method); + session->userauth_pblc_method = NULL; + session->userauth_pblc_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_AUTHENTICATION_FAILED, + "Username/PublicKey combination invalid"); + } + + /* Semi-Success! */ + LIBSSH2_FREE(session, session->userauth_pblc_data); + session->userauth_pblc_data = NULL; + + *session->userauth_pblc_b = 0x01; + session->userauth_pblc_state = libssh2_NB_state_sent1; + } + + if (session->userauth_pblc_state == libssh2_NB_state_sent1) { + unsigned char *buf; + unsigned char *sig; + size_t sig_len; + + s = buf = LIBSSH2_ALLOC(session, 4 + session->session_id_len + + session->userauth_pblc_packet_len); + if (!buf) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for " + "userauth-publickey signed data"); + } + + _libssh2_store_str(&s, (const char *)session->session_id, + session->session_id_len); + + memcpy (s, session->userauth_pblc_packet, + session->userauth_pblc_packet_len); + s += session->userauth_pblc_packet_len; + + rc = sign_callback(session, &sig, &sig_len, buf, s - buf, abstract); + LIBSSH2_FREE(session, buf); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, "Would block"); + } else if (rc) { + LIBSSH2_FREE(session, session->userauth_pblc_method); + session->userauth_pblc_method = NULL; + LIBSSH2_FREE(session, session->userauth_pblc_packet); + session->userauth_pblc_packet = NULL; + session->userauth_pblc_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED, + "Callback returned error"); + } + + /* + * If this function was restarted, pubkeydata_len might still be 0 + * which will cause an unnecessary but harmless realloc here. + */ + if (sig_len > pubkeydata_len) { + unsigned char *newpacket; + /* Should *NEVER* happen, but...well.. better safe than sorry */ + newpacket = LIBSSH2_REALLOC(session, + session->userauth_pblc_packet, + session->userauth_pblc_packet_len + 4 + + (4 + session->userauth_pblc_method_len) + + (4 + sig_len)); /* PK sigblob */ + if (!newpacket) { + LIBSSH2_FREE(session, sig); + LIBSSH2_FREE(session, session->userauth_pblc_packet); + session->userauth_pblc_packet = NULL; + LIBSSH2_FREE(session, session->userauth_pblc_method); + session->userauth_pblc_method = NULL; + session->userauth_pblc_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Failed allocating additional space for " + "userauth-publickey packet"); + } + session->userauth_pblc_packet = newpacket; + } + + s = session->userauth_pblc_packet + session->userauth_pblc_packet_len; + session->userauth_pblc_b = NULL; + + _libssh2_store_u32(&s, + 4 + session->userauth_pblc_method_len + 4 + sig_len); + _libssh2_store_str(&s, (const char *)session->userauth_pblc_method, + session->userauth_pblc_method_len); + + LIBSSH2_FREE(session, session->userauth_pblc_method); + session->userauth_pblc_method = NULL; + + _libssh2_store_str(&s, (const char *)sig, sig_len); + LIBSSH2_FREE(session, sig); + + _libssh2_debug(session, LIBSSH2_TRACE_AUTH, + "Attempting publickey authentication -- phase 2"); + + session->userauth_pblc_s = s; + session->userauth_pblc_state = libssh2_NB_state_sent2; + } + + if (session->userauth_pblc_state == libssh2_NB_state_sent2) { + rc = _libssh2_transport_send(session, session->userauth_pblc_packet, + session->userauth_pblc_s - + session->userauth_pblc_packet, + NULL, 0); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, "Would block"); + } else if (rc) { + LIBSSH2_FREE(session, session->userauth_pblc_packet); + session->userauth_pblc_packet = NULL; + session->userauth_pblc_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send userauth-publickey request"); + } + LIBSSH2_FREE(session, session->userauth_pblc_packet); + session->userauth_pblc_packet = NULL; + + session->userauth_pblc_state = libssh2_NB_state_sent3; + } + + /* PK_OK is no longer valid */ + reply_codes[2] = 0; + + rc = _libssh2_packet_requirev(session, reply_codes, + &session->userauth_pblc_data, + &session->userauth_pblc_data_len, 0, NULL, 0, + &session->userauth_pblc_packet_requirev_state); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block requesting userauth list"); + } else if (rc) { + session->userauth_pblc_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED, + "Waiting for publickey USERAUTH response"); + } + + if (session->userauth_pblc_data[0] == SSH_MSG_USERAUTH_SUCCESS) { + _libssh2_debug(session, LIBSSH2_TRACE_AUTH, + "Publickey authentication successful"); + /* We are us and we've proved it. */ + LIBSSH2_FREE(session, session->userauth_pblc_data); + session->userauth_pblc_data = NULL; + session->state |= LIBSSH2_STATE_AUTHENTICATED; + session->userauth_pblc_state = libssh2_NB_state_idle; + return 0; + } + + /* This public key is not allowed for this user on this server */ + LIBSSH2_FREE(session, session->userauth_pblc_data); + session->userauth_pblc_data = NULL; + session->userauth_pblc_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED, + "Invalid signature for supplied public key, or bad " + "username/public key combination"); +} + +/* + * userauth_publickey_fromfile + * Authenticate using a keypair found in the named files + */ +static int +userauth_publickey_fromfile(LIBSSH2_SESSION *session, + const char *username, + size_t username_len, + const char *publickey, + const char *privatekey, + const char *passphrase) +{ + unsigned char *pubkeydata = NULL; + size_t pubkeydata_len = 0; + struct privkey_file privkey_file; + void *abstract = &privkey_file; + int rc; + + privkey_file.filename = privatekey; + privkey_file.passphrase = passphrase; + + if (session->userauth_pblc_state == libssh2_NB_state_idle) { + if (publickey) { + rc = file_read_publickey(session, &session->userauth_pblc_method, + &session->userauth_pblc_method_len, + &pubkeydata, &pubkeydata_len,publickey); + if (rc) + return rc; + } + else { + /* Compute public key from private key. */ + rc = _libssh2_pub_priv_keyfile(session, + &session->userauth_pblc_method, + &session->userauth_pblc_method_len, + &pubkeydata, &pubkeydata_len, + privatekey, passphrase); + + /* _libssh2_pub_priv_keyfile calls _libssh2_error() */ + if (rc) + return rc; + } + } + + rc = _libssh2_userauth_publickey(session, username, username_len, + pubkeydata, pubkeydata_len, + sign_fromfile, &abstract); + if(pubkeydata) + LIBSSH2_FREE(session, pubkeydata); + + return rc; +} + +/* libssh2_userauth_publickey_fromfile_ex + * Authenticate using a keypair found in the named files + */ +LIBSSH2_API int +libssh2_userauth_publickey_fromfile_ex(LIBSSH2_SESSION *session, + const char *user, + unsigned int user_len, + const char *publickey, + const char *privatekey, + const char *passphrase) +{ + int rc; + + if(NULL == passphrase) + /* if given a NULL pointer, make it point to a zero-length + string to save us from having to check this all over */ + passphrase=""; + + BLOCK_ADJUST(rc, session, + userauth_publickey_fromfile(session, user, user_len, + publickey, privatekey, + passphrase)); + return rc; +} + +/* libssh2_userauth_publickey_ex + * Authenticate using an external callback function + */ +LIBSSH2_API int +libssh2_userauth_publickey(LIBSSH2_SESSION *session, + const char *user, + const unsigned char *pubkeydata, + size_t pubkeydata_len, + LIBSSH2_USERAUTH_PUBLICKEY_SIGN_FUNC((*sign_callback)), + void **abstract) +{ + int rc; + + if(!session) + return LIBSSH2_ERROR_BAD_USE; + + BLOCK_ADJUST(rc, session, + _libssh2_userauth_publickey(session, user, strlen(user), + pubkeydata, pubkeydata_len, + sign_callback, abstract)); + return rc; +} + + + +/* + * userauth_keyboard_interactive + * + * Authenticate using a challenge-response authentication + */ +static int +userauth_keyboard_interactive(LIBSSH2_SESSION * session, + const char *username, + unsigned int username_len, + LIBSSH2_USERAUTH_KBDINT_RESPONSE_FUNC((*response_callback))) +{ + unsigned char *s; + int rc; + + static const unsigned char reply_codes[4] = { + SSH_MSG_USERAUTH_SUCCESS, + SSH_MSG_USERAUTH_FAILURE, SSH_MSG_USERAUTH_INFO_REQUEST, 0 + }; + unsigned int language_tag_len; + unsigned int i; + + if (session->userauth_kybd_state == libssh2_NB_state_idle) { + session->userauth_kybd_auth_name = NULL; + session->userauth_kybd_auth_instruction = NULL; + session->userauth_kybd_num_prompts = 0; + session->userauth_kybd_auth_failure = 1; + session->userauth_kybd_prompts = NULL; + session->userauth_kybd_responses = NULL; + + /* Zero the whole thing out */ + memset(&session->userauth_kybd_packet_requirev_state, 0, + sizeof(session->userauth_kybd_packet_requirev_state)); + + session->userauth_kybd_packet_len = + 1 /* byte SSH_MSG_USERAUTH_REQUEST */ + + 4 + username_len /* string user name (ISO-10646 UTF-8, as + defined in [RFC-3629]) */ + + 4 + 14 /* string service name (US-ASCII) */ + + 4 + 20 /* string "keyboard-interactive" (US-ASCII) */ + + 4 + 0 /* string language tag (as defined in + [RFC-3066]) */ + + 4 + 0 /* string submethods (ISO-10646 UTF-8) */ + ; + + session->userauth_kybd_data = s = + LIBSSH2_ALLOC(session, session->userauth_kybd_packet_len); + if (!s) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for " + "keyboard-interactive authentication"); + } + + *s++ = SSH_MSG_USERAUTH_REQUEST; + + /* user name */ + _libssh2_store_str(&s, username, username_len); + + /* service name */ + _libssh2_store_str(&s, "ssh-connection", sizeof("ssh-connection") - 1); + + /* "keyboard-interactive" */ + _libssh2_store_str(&s, "keyboard-interactive", + sizeof("keyboard-interactive") - 1); + /* language tag */ + _libssh2_store_u32(&s, 0); + + /* submethods */ + _libssh2_store_u32(&s, 0); + + _libssh2_debug(session, LIBSSH2_TRACE_AUTH, + "Attempting keyboard-interactive authentication"); + + session->userauth_kybd_state = libssh2_NB_state_created; + } + + if (session->userauth_kybd_state == libssh2_NB_state_created) { + rc = _libssh2_transport_send(session, session->userauth_kybd_data, + session->userauth_kybd_packet_len, + NULL, 0); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, "Would block"); + } else if (rc) { + LIBSSH2_FREE(session, session->userauth_kybd_data); + session->userauth_kybd_data = NULL; + session->userauth_kybd_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send keyboard-interactive request"); + } + LIBSSH2_FREE(session, session->userauth_kybd_data); + session->userauth_kybd_data = NULL; + + session->userauth_kybd_state = libssh2_NB_state_sent; + } + + for(;;) { + if (session->userauth_kybd_state == libssh2_NB_state_sent) { + rc = _libssh2_packet_requirev(session, reply_codes, + &session->userauth_kybd_data, + &session->userauth_kybd_data_len, + 0, NULL, 0, + &session-> + userauth_kybd_packet_requirev_state); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block"); + } else if (rc) { + session->userauth_kybd_state = libssh2_NB_state_idle; + return _libssh2_error(session, + LIBSSH2_ERROR_AUTHENTICATION_FAILED, + "Waiting for keyboard USERAUTH response"); + } + + if (session->userauth_kybd_data[0] == SSH_MSG_USERAUTH_SUCCESS) { + _libssh2_debug(session, LIBSSH2_TRACE_AUTH, + "Keyboard-interactive authentication successful"); + LIBSSH2_FREE(session, session->userauth_kybd_data); + session->userauth_kybd_data = NULL; + session->state |= LIBSSH2_STATE_AUTHENTICATED; + session->userauth_kybd_state = libssh2_NB_state_idle; + return 0; + } + + if (session->userauth_kybd_data[0] == SSH_MSG_USERAUTH_FAILURE) { + _libssh2_debug(session, LIBSSH2_TRACE_AUTH, + "Keyboard-interactive authentication failed"); + LIBSSH2_FREE(session, session->userauth_kybd_data); + session->userauth_kybd_data = NULL; + session->userauth_kybd_state = libssh2_NB_state_idle; + return _libssh2_error(session, + LIBSSH2_ERROR_AUTHENTICATION_FAILED, + "Authentication failed " + "(keyboard-interactive)"); + } + + /* server requested PAM-like conversation */ + s = session->userauth_kybd_data + 1; + + /* string name (ISO-10646 UTF-8) */ + session->userauth_kybd_auth_name_len = _libssh2_ntohu32(s); + s += 4; + if(session->userauth_kybd_auth_name_len) { + session->userauth_kybd_auth_name = + LIBSSH2_ALLOC(session, + session->userauth_kybd_auth_name_len); + if (!session->userauth_kybd_auth_name) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for " + "keyboard-interactive 'name' " + "request field"); + goto cleanup; + } + memcpy(session->userauth_kybd_auth_name, s, + session->userauth_kybd_auth_name_len); + s += session->userauth_kybd_auth_name_len; + } + + /* string instruction (ISO-10646 UTF-8) */ + session->userauth_kybd_auth_instruction_len = _libssh2_ntohu32(s); + s += 4; + if(session->userauth_kybd_auth_instruction_len) { + session->userauth_kybd_auth_instruction = + LIBSSH2_ALLOC(session, + session->userauth_kybd_auth_instruction_len); + if (!session->userauth_kybd_auth_instruction) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for " + "keyboard-interactive 'instruction' " + "request field"); + goto cleanup; + } + memcpy(session->userauth_kybd_auth_instruction, s, + session->userauth_kybd_auth_instruction_len); + s += session->userauth_kybd_auth_instruction_len; + } + + /* string language tag (as defined in [RFC-3066]) */ + language_tag_len = _libssh2_ntohu32(s); + s += 4; + + /* ignoring this field as deprecated */ + s += language_tag_len; + + /* int num-prompts */ + session->userauth_kybd_num_prompts = _libssh2_ntohu32(s); + s += 4; + + if(session->userauth_kybd_num_prompts) { + session->userauth_kybd_prompts = + LIBSSH2_ALLOC(session, + sizeof(LIBSSH2_USERAUTH_KBDINT_PROMPT) * + session->userauth_kybd_num_prompts); + if (!session->userauth_kybd_prompts) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for " + "keyboard-interactive prompts array"); + goto cleanup; + } + memset(session->userauth_kybd_prompts, 0, + sizeof(LIBSSH2_USERAUTH_KBDINT_PROMPT) * + session->userauth_kybd_num_prompts); + + session->userauth_kybd_responses = + LIBSSH2_ALLOC(session, + sizeof(LIBSSH2_USERAUTH_KBDINT_RESPONSE) * + session->userauth_kybd_num_prompts); + if (!session->userauth_kybd_responses) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for " + "keyboard-interactive responses array"); + goto cleanup; + } + memset(session->userauth_kybd_responses, 0, + sizeof(LIBSSH2_USERAUTH_KBDINT_RESPONSE) * + session->userauth_kybd_num_prompts); + + for(i = 0; i != session->userauth_kybd_num_prompts; ++i) { + /* string prompt[1] (ISO-10646 UTF-8) */ + session->userauth_kybd_prompts[i].length = + _libssh2_ntohu32(s); + s += 4; + session->userauth_kybd_prompts[i].text = + LIBSSH2_ALLOC(session, + session->userauth_kybd_prompts[i].length); + if (!session->userauth_kybd_prompts[i].text) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for " + "keyboard-interactive prompt message"); + goto cleanup; + } + memcpy(session->userauth_kybd_prompts[i].text, s, + session->userauth_kybd_prompts[i].length); + s += session->userauth_kybd_prompts[i].length; + + /* boolean echo[1] */ + session->userauth_kybd_prompts[i].echo = *s++; + } + } + + response_callback(session->userauth_kybd_auth_name, + session->userauth_kybd_auth_name_len, + session->userauth_kybd_auth_instruction, + session->userauth_kybd_auth_instruction_len, + session->userauth_kybd_num_prompts, + session->userauth_kybd_prompts, + session->userauth_kybd_responses, + &session->abstract); + + _libssh2_debug(session, LIBSSH2_TRACE_AUTH, + "Keyboard-interactive response callback function" + " invoked"); + + session->userauth_kybd_packet_len = + 1 /* byte SSH_MSG_USERAUTH_INFO_RESPONSE */ + + 4 /* int num-responses */ + ; + + for(i = 0; i != session->userauth_kybd_num_prompts; ++i) { + /* string response[1] (ISO-10646 UTF-8) */ + session->userauth_kybd_packet_len += + 4 + session->userauth_kybd_responses[i].length; + } + + /* A new userauth_kybd_data area is to be allocated, free the + former one. */ + LIBSSH2_FREE(session, session->userauth_kybd_data); + + session->userauth_kybd_data = s = + LIBSSH2_ALLOC(session, session->userauth_kybd_packet_len); + if (!s) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for keyboard-" + "interactive response packet"); + goto cleanup; + } + + *s = SSH_MSG_USERAUTH_INFO_RESPONSE; + s++; + _libssh2_store_u32(&s, session->userauth_kybd_num_prompts); + + for(i = 0; i != session->userauth_kybd_num_prompts; ++i) { + _libssh2_store_str(&s, + session->userauth_kybd_responses[i].text, + session->userauth_kybd_responses[i].length); + } + + session->userauth_kybd_state = libssh2_NB_state_sent1; + } + + if (session->userauth_kybd_state == libssh2_NB_state_sent1) { + rc = _libssh2_transport_send(session, session->userauth_kybd_data, + session->userauth_kybd_packet_len, + NULL, 0); + if (rc == LIBSSH2_ERROR_EAGAIN) + return _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block"); + if (rc) { + _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send userauth-keyboard-interactive" + " request"); + goto cleanup; + } + + session->userauth_kybd_auth_failure = 0; + } + + cleanup: + /* + * It's safe to clean all the data here, because unallocated pointers + * are filled by zeroes + */ + + LIBSSH2_FREE(session, session->userauth_kybd_data); + session->userauth_kybd_data = NULL; + + if (session->userauth_kybd_prompts) { + for(i = 0; i != session->userauth_kybd_num_prompts; ++i) { + LIBSSH2_FREE(session, session->userauth_kybd_prompts[i].text); + session->userauth_kybd_prompts[i].text = NULL; + } + } + + if (session->userauth_kybd_responses) { + for(i = 0; i != session->userauth_kybd_num_prompts; ++i) { + LIBSSH2_FREE(session, + session->userauth_kybd_responses[i].text); + session->userauth_kybd_responses[i].text = NULL; + } + } + + if(session->userauth_kybd_prompts) { + LIBSSH2_FREE(session, session->userauth_kybd_prompts); + session->userauth_kybd_prompts = NULL; + } + if(session->userauth_kybd_responses) { + LIBSSH2_FREE(session, session->userauth_kybd_responses); + session->userauth_kybd_responses = NULL; + } + if(session->userauth_kybd_auth_name) { + LIBSSH2_FREE(session, session->userauth_kybd_auth_name); + session->userauth_kybd_auth_name = NULL; + } + if(session->userauth_kybd_auth_instruction) { + LIBSSH2_FREE(session, session->userauth_kybd_auth_instruction); + session->userauth_kybd_auth_instruction = NULL; + } + + if (session->userauth_kybd_auth_failure) { + session->userauth_kybd_state = libssh2_NB_state_idle; + return -1; + } + + session->userauth_kybd_state = libssh2_NB_state_sent; + } +} + +/* + * libssh2_userauth_keyboard_interactive_ex + * + * Authenticate using a challenge-response authentication + */ +LIBSSH2_API int +libssh2_userauth_keyboard_interactive_ex(LIBSSH2_SESSION *session, + const char *user, + unsigned int user_len, + LIBSSH2_USERAUTH_KBDINT_RESPONSE_FUNC((*response_callback))) +{ + int rc; + BLOCK_ADJUST(rc, session, + userauth_keyboard_interactive(session, user, user_len, + response_callback)); + return rc; +} diff --git a/libssh2/src/version.c b/libssh2/src/version.c new file mode 100644 index 0000000..408f83a --- /dev/null +++ b/libssh2/src/version.c @@ -0,0 +1,54 @@ +/* Copyright (C) 2009 Daniel Stenberg. All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + */ + +#include "libssh2_priv.h" + +/* + libssh2_version() can be used like this: + + if (!libssh2_version(LIBSSH2_VERSION_NUM)) { + fprintf (stderr, "Runtime libssh2 version too old!\n"); + exit(1); + } +*/ +LIBSSH2_API +const char *libssh2_version(int req_version_num) +{ + if(req_version_num <= LIBSSH2_VERSION_NUM) + return LIBSSH2_VERSION; + return NULL; /* this is not a suitable library! */ +} diff --git a/Commit.h b/repo/Commit.h similarity index 99% rename from Commit.h rename to repo/Commit.h index 5d21549..43474e3 100644 --- a/Commit.h +++ b/repo/Commit.h @@ -7,7 +7,6 @@ // -#import "Blob.h" #import "BufferedInputStream.h" @class BlobKey; diff --git a/Commit.m b/repo/Commit.m similarity index 93% rename from Commit.m rename to repo/Commit.m index 927ab72..235e4ad 100644 --- a/Commit.m +++ b/repo/Commit.m @@ -10,10 +10,8 @@ #import "DateIO.h" #import "StringIO.h" #import "Commit.h" -#import "Blob.h" #import "DataInputStream.h" #import "RegexKitLite.h" -#import "StorageType.h" #import "CommitFailedFile.h" #import "BufferedInputStream.h" #import "BooleanIO.h" @@ -33,11 +31,11 @@ @synthesize commitVersion, -author = _author, -comment = _comment, -treeBlobKey = _treeBlobKey, +author = _author, +comment = _comment, +treeBlobKey = _treeBlobKey, parentCommitBlobKey = _parentCommitBlobKey, -location = _location, +location = _location, computer = _computer, creationDate = _creationDate, commitFailedFiles = _commitFailedFiles, @@ -63,8 +61,8 @@ bucketXMLData = _bucketXMLData; return self; } -- (id) initWithAuthor:(NSString *)theAuthor - comment:(NSString *)theComment +- (id) initWithAuthor:(NSString *)theAuthor + comment:(NSString *)theComment parentCommitBlobKey:(BlobKey *)theParentCommitBlobKey treeBlobKey:(BlobKey *)theTreeBlobKey location:(NSString *)theLocation @@ -128,7 +126,10 @@ bucketXMLData = _bucketXMLData; if (_parentCommitBlobKey != nil) { HSLogError(@"IGNORING EXTRA PARENT COMMIT BLOB KEY!"); } else { - _parentCommitBlobKey = [[BlobKey alloc] initWithSHA1:key storageType:StorageTypeS3 stretchEncryptionKey:cryptoKeyStretched compressed:NO]; + _parentCommitBlobKey = [[BlobKey alloc] initWithSHA1:key storageType:StorageTypeS3 stretchEncryptionKey:cryptoKeyStretched compressed:NO error:error]; + if (_parentCommitBlobKey == nil) { + goto init_error; + } } } @@ -148,7 +149,10 @@ bucketXMLData = _bucketXMLData; goto init_error; } } - _treeBlobKey = [[BlobKey alloc] initWithSHA1:treeSHA1 storageType:StorageTypeS3 stretchEncryptionKey:treeStretchedKey compressed:treeIsCompressed]; + _treeBlobKey = [[BlobKey alloc] initWithSHA1:treeSHA1 storageType:StorageTypeS3 stretchEncryptionKey:treeStretchedKey compressed:treeIsCompressed error:error]; + if (_treeBlobKey == nil) { + goto init_error; + } if (![StringIO read:&_location from:is error:error]) { goto init_error; @@ -175,9 +179,9 @@ bucketXMLData = _bucketXMLData; goto init_error; } } - // if (mergeCommonAncestorCommitSHA1 != nil) { - // _mergeCommonAncestorCommitBlobKey = [[BlobKey alloc] initWithSHA1:mergeCommonAncestorCommitSHA1 stretchEncryptionKey:mergeCommonAncestorCommitStretchedKey]; - // } +// if (mergeCommonAncestorCommitSHA1 != nil) { +// _mergeCommonAncestorCommitBlobKey = [[BlobKey alloc] initWithSHA1:mergeCommonAncestorCommitSHA1 stretchEncryptionKey:mergeCommonAncestorCommitStretchedKey]; +// } } if (![DateIO read:&_creationDate from:is error:error]) { diff --git a/CommitFailedFile.h b/repo/CommitFailedFile.h similarity index 88% rename from CommitFailedFile.h rename to repo/CommitFailedFile.h index 73606d6..a6885c0 100644 --- a/CommitFailedFile.h +++ b/repo/CommitFailedFile.h @@ -1,9 +1,8 @@ // // CommitFailedFile.h -// Arq // // Created by Stefan Reitshamer on 2/22/10. -// Copyright 2010 __MyCompanyName__. All rights reserved. +// Copyright 2010 Haystack Software. All rights reserved. // diff --git a/CommitFailedFile.m b/repo/CommitFailedFile.m similarity index 95% rename from CommitFailedFile.m rename to repo/CommitFailedFile.m index d8632b0..0c2e870 100644 --- a/CommitFailedFile.m +++ b/repo/CommitFailedFile.m @@ -1,9 +1,8 @@ // // CommitFailedFile.m -// Arq // // Created by Stefan Reitshamer on 2/22/10. -// Copyright 2010 __MyCompanyName__. All rights reserved. +// Copyright 2010 Haystack Software. All rights reserved. // #import "CommitFailedFile.h" diff --git a/repo/Fark.h b/repo/Fark.h new file mode 100644 index 0000000..f0a09c1 --- /dev/null +++ b/repo/Fark.h @@ -0,0 +1,53 @@ +// +// Fark.h +// Arq +// +// Created by Stefan Reitshamer on 12/31/13. +// Copyright (c) 2013 Stefan Reitshamer. All rights reserved. +// + +#import "StorageType.h" +@class BlobKey; +@class PackId; +@class PackIndexEntry; +@protocol DataTransferDelegate; + +@protocol Fark + +- (NSString *)errorDomain; + +- (BlobKey *)headBlobKeyForBucketUUID:(NSString *)theBucketUUID error:(NSError **)error; +- (BOOL)setHeadBlobKey:(BlobKey *)theHeadBlobKey forBucketUUID:(NSString *)theBucketUUID error:(NSError **)error; +- (BOOL)deleteHeadBlobKeyForBucketUUID:(NSString *)theBucketUUID error:(NSError **)error; + +- (NSNumber *)containsObjectForSHA1:(NSString *)theSHA1 storageType:(StorageType)theStorageType dataSize:(unsigned long long *)dataSize forceTargetCheck:(BOOL)forceTargetCheck error:(NSError **)error; + +- (NSNumber *)isObjectDownloadableForSHA1:(NSString *)theSHA1 storageType:(StorageType)theStorageType error:(NSError **)error; +- (BOOL)restoreObjectForSHA1:(NSString *)theSHA1 forDays:(NSUInteger)theDays storageType:(StorageType)theStorageType alreadyRestoredOrRestoring:(BOOL *)alreadyRestoredOrRestoring error:(NSError **)error; +- (NSData *)dataForSHA1:(NSString *)theSHA1 storageType:(StorageType)theStorageType error:(NSError **)error; + +- (BOOL)putData:(NSData *)theData forSHA1:(NSString *)theSHA1 storageType:(StorageType)theStorageType error:(NSError **)error; +- (BOOL)putData:(NSData *)theData forSHA1:(NSString *)theSHA1 storageType:(StorageType)theStorageType dataTransferDelegate:(id )theDelegate error:(NSError **)error; + +- (NSSet *)packIdsForPackSet:(NSString *)packSetName storageType:(StorageType)theStorageType error:(NSError **)error; + +// Indexes are always in S3; no StorageType needed. +- (NSData *)indexDataForPackId:(PackId *)packId error:(NSError **)error; +- (BOOL)putIndexData:(NSData *)theData forPackId:(PackId *)thePackId error:(NSError **)error; +- (BOOL)deleteIndex:(PackId *)thePackId error:(NSError **)error; + + +- (NSNumber *)sizeOfPackWithId:(PackId *)packId storageType:(StorageType)theStorageType error:(NSError **)error; +- (NSNumber *)isPackDownloadableWithId:(PackId *)packId storageType:(StorageType)theStorageType error:(NSError **)error; +- (BOOL)restorePackWithId:(PackId *)packId forDays:(NSUInteger)theDays storageType:(StorageType)theStorageType alreadyRestoredOrRestoring:(BOOL *)alreadyRestoredOrRestoring error:(NSError **)error; + +- (NSData *)packDataForPackId:(PackId *)packId storageType:(StorageType)theStorageType error:(NSError **)error; +- (NSData *)dataForPackIndexEntry:(PackIndexEntry *)thePIE storageType:(StorageType)theStorageType error:(NSError **)error; + +- (BOOL)putPackData:(NSData *)theData forPackId:(PackId *)thePackId storageType:(StorageType)theStorageType saveToCache:(BOOL)saveToCache error:(NSError **)error; + +- (BOOL)deletePack:(PackId *)thePackId storageType:(StorageType)theStorageType error:(NSError **)error; + +- (BOOL)putReflogItem:(NSData *)itemData forBucketUUID:(NSString *)theBucketUUID error:(NSError **)error; + +@end diff --git a/repo/FarkImpl.h b/repo/FarkImpl.h new file mode 100644 index 0000000..4dfcc27 --- /dev/null +++ b/repo/FarkImpl.h @@ -0,0 +1,34 @@ +// +// Fark.h +// Arq +// +// Created by Stefan Reitshamer on 12/30/13. +// Copyright (c) 2013 Stefan Reitshamer. All rights reserved. +// + +#import "TargetConnection.h" +#import "Fark.h" +@class Target; +@protocol TargetConnection; +@class BlobKey; +@class PackId; + + +@interface FarkImpl : NSObject { + Target *target; + id targetConnection; + NSString *computerUUID; + id targetConnectionDelegate; + uid_t uid; + gid_t gid; + + NSMutableSet *packIdsAlreadyPostedForRestore; + NSMutableSet *downloadablePackIds; +} +- (id)initWithTarget:(Target *)theTarget + computerUUID:(NSString *)theComputerUUID +targetConnectionDelegate:(id )theTargetConnectionDelegate + targetUID:(uid_t)theTargetUID + targetGID:(gid_t)theTargetGID; + +@end diff --git a/repo/FarkImpl.m b/repo/FarkImpl.m new file mode 100644 index 0000000..793b872 --- /dev/null +++ b/repo/FarkImpl.m @@ -0,0 +1,479 @@ +// +// Fark.m +// Arq +// +// Created by Stefan Reitshamer on 12/30/13. +// Copyright (c) 2013 Stefan Reitshamer. All rights reserved. +// + +#import "FarkImpl.h" +#import "S3AuthorizationProvider.h" +#import "S3Service.h" +#import "Target.h" +#import "SFTPTargetConnection.h" +#import "S3TargetConnection.h" +#import "BlobKey.h" +#import "RegexKitLite.h" +#import "PackId.h" +#import "NSFileManager_extra.h" +#import "PackIndexEntry.h" +#import "DataInputStream.h" +#import "BufferedInputStream.h" +#import "StringIO.h" +#import "IntegerIO.h" +#import "FDInputStream.h" +#import "Streams.h" +#import "UserLibrary_Arq.h" +#import "AWSRegion.h" + + +@implementation FarkImpl +- (id)initWithTarget:(Target *)theTarget + computerUUID:(NSString *)theComputerUUID +targetConnectionDelegate:(id )theTargetConnectionDelegate + targetUID:(uid_t)theTargetUID + targetGID:(gid_t)theTargetGID { + if (self = [super init]) { + target = [theTarget retain]; + targetConnection = [target newConnection]; + computerUUID = [theComputerUUID retain]; + targetConnectionDelegate = theTargetConnectionDelegate; + uid = theTargetUID; + gid = theTargetGID; + packIdsAlreadyPostedForRestore = [[NSMutableSet alloc] init]; + downloadablePackIds = [[NSMutableSet alloc] init]; + } + return self; +} +- (void)dealloc { + [target release]; + [targetConnection release]; + [computerUUID release]; + [packIdsAlreadyPostedForRestore release]; + [downloadablePackIds release]; + [super dealloc]; +} + + +#pragma mark Fark +- (NSString *)errorDomain { + return @"FarkErrorDomain"; +} + +- (BlobKey *)headBlobKeyForBucketUUID:(NSString *)theBucketUUID error:(NSError **)error { + NSError *myError = nil; + NSData *data = [targetConnection contentsOfFileAtPath:[self masterPathForBucketUUID:theBucketUUID] delegate:targetConnectionDelegate error:&myError]; + if (data == nil) { + SETERRORFROMMYERROR; + if ([myError code] == ERROR_NOT_FOUND) { + SETNSERROR([self errorDomain], ERROR_NOT_FOUND, @"head blob key not found for bucket %@", theBucketUUID); + } + return nil; + } + + NSString *sha1 = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]; + BOOL stretch = NO; + if ([sha1 length] > 40) { + stretch = [sha1 characterAtIndex:40] == 'Y'; + sha1 = [sha1 substringToIndex:40]; + } + return [[[BlobKey alloc] initWithSHA1:sha1 storageType:StorageTypeS3 stretchEncryptionKey:stretch compressed:NO error:error] autorelease]; +} +- (BOOL)setHeadBlobKey:(BlobKey *)theHeadBlobKey forBucketUUID:(NSString *)theBucketUUID error:(NSError **)error { + NSMutableString *str = [NSMutableString stringWithString:[theHeadBlobKey sha1]]; + if ([theHeadBlobKey stretchEncryptionKey]) { + [str appendString:@"Y"]; + } + NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding]; + return [targetConnection writeData:data toFileAtPath:[self masterPathForBucketUUID:theBucketUUID] dataTransferDelegate:nil targetConnectionDelegate:targetConnectionDelegate error:error]; +} +- (BOOL)deleteHeadBlobKeyForBucketUUID:(NSString *)theBucketUUID error:(NSError **)error { + return [targetConnection removeItemAtPath:[self masterPathForBucketUUID:theBucketUUID] delegate:targetConnectionDelegate error:error]; +} +- (NSNumber *)containsObjectForSHA1:(NSString *)theSHA1 storageType:(StorageType)theStorageType dataSize:(unsigned long long *)dataSize forceTargetCheck:(BOOL)forceTargetCheck error:(NSError **)error { + if (theStorageType == StorageTypeGlacier) { + // We assume that Glacier blobs are always there because we never delete them, + // and anyway it's impossible to check without waiting 4 hours for an inventory. + return [NSNumber numberWithBool:YES]; + } + + if (theStorageType != StorageTypeS3 && theStorageType != StorageTypeS3Glacier) { + HSLogError(@"containsObjectForSHA1: storage type %ld for blob %@ is unknown; returning NO", (unsigned long)theStorageType, theSHA1); + return [NSNumber numberWithBool:NO]; + } + + NSAssert(theStorageType == StorageTypeS3 || theStorageType == StorageTypeS3Glacier, @"must be S3 or S3Glacier"); + + BOOL contains = NO; + NSNumber *targetContains = [targetConnection fileExistsAtPath:[self objectPathForSHA1:theSHA1 storageType:theStorageType] dataSize:dataSize delegate:targetConnectionDelegate error:error]; + if (targetContains == nil) { + return nil; + } + contains = [targetContains boolValue]; + if (!contains && ([target targetType] == kTargetSFTP)) { + // We used to write all objects in the same dir for SFTP, just like we do for S3. + targetContains = [targetConnection fileExistsAtPath:[self legacy1SFTPObjectPathForSHA1:theSHA1] dataSize:dataSize delegate:targetConnectionDelegate error:error]; + if (targetContains == nil) { + return nil; + } + contains = [targetContains boolValue]; + } + if (!contains && ([target targetType] == kTargetSFTP)) { + // Version 2 wrote objects in dir1/dir2 form, but that resulted in too many opendir() calls. + targetContains = [targetConnection fileExistsAtPath:[self legacy2SFTPObjectPathForSHA1:theSHA1] dataSize:dataSize delegate:targetConnectionDelegate error:error]; + if (targetContains == nil) { + return nil; + } + contains = [targetContains boolValue]; + } + return [NSNumber numberWithBool:contains]; +} +- (NSNumber *)isObjectDownloadableForSHA1:(NSString *)theSHA1 storageType:(StorageType)theStorageType error:(NSError **)error { + NSNumber *ret = nil; + if (theStorageType == StorageTypeGlacier) { + ret = [NSNumber numberWithBool:NO]; + } else if (theStorageType == StorageTypeS3) { + ret = [targetConnection fileExistsAtPath:[self objectPathForSHA1:theSHA1 storageType:theStorageType] dataSize:NULL delegate:targetConnectionDelegate error:error]; + } else if (theStorageType == StorageTypeS3Glacier) { + ret = [targetConnection isObjectRestoredAtPath:[self objectPathForSHA1:theSHA1 storageType:theStorageType] delegate:targetConnectionDelegate error:error]; + } else { + SETNSERROR([self errorDomain], -1, @"unknown storage type"); + } + return ret; +} +- (BOOL)restoreObjectForSHA1:(NSString *)theSHA1 forDays:(NSUInteger)theDays storageType:(StorageType)theStorageType alreadyRestoredOrRestoring:(BOOL *)alreadyRestoredOrRestoring error:(NSError **)error { + NSError *myError = nil; + if (![targetConnection restoreObjectAtPath:[self objectPathForSHA1:theSHA1 storageType:theStorageType] forDays:theDays alreadyRestoredOrRestoring:alreadyRestoredOrRestoring delegate:targetConnectionDelegate error:&myError]) { + SETERRORFROMMYERROR; + if ([myError isErrorWithDomain:[S3Service errorDomain] code:ERROR_NOT_FOUND]) { + SETNSERROR([self errorDomain], ERROR_NOT_FOUND, @"object %@ can't be restored because it's not found", theSHA1); + } + return NO; + } + return YES; +} +- (NSData *)dataForSHA1:(NSString *)theSHA1 storageType:(StorageType)theStorageType error:(NSError **)error { + NSError *myError = nil; + NSData *ret = [targetConnection contentsOfFileAtPath:[self objectPathForSHA1:theSHA1 storageType:theStorageType] delegate:targetConnectionDelegate error:&myError]; + if (ret == nil && [myError code] == ERROR_NOT_FOUND && ([target targetType] == kTargetSFTP)) { + // We used to write all objects in the same dir for SFTP, just like we do for S3. + ret = [targetConnection contentsOfFileAtPath:[self legacy1SFTPObjectPathForSHA1:theSHA1] delegate:targetConnectionDelegate error:&myError]; + } + if (ret == nil && [myError code] == ERROR_NOT_FOUND && ([target targetType] == kTargetSFTP)) { + // Version 2 wrote objects in dir1/dir2 form, but that resulted in too many opendir() calls. + ret = [targetConnection contentsOfFileAtPath:[self legacy2SFTPObjectPathForSHA1:theSHA1] delegate:targetConnectionDelegate error:&myError]; + } + if (ret == nil) { + SETERRORFROMMYERROR; + if ([myError code] == ERROR_NOT_FOUND) { + SETNSERROR([self errorDomain], ERROR_NOT_FOUND, @"object not found at target for SHA1 %@", theSHA1); + } + if ([myError isErrorWithDomain:[S3Service errorDomain] code:S3SERVICE_ERROR_AMAZON_ERROR] && [[[myError userInfo] objectForKey:@"AmazonCode"] isEqualToString:@"InvalidObjectState"]) { + SETNSERROR([self errorDomain], ERROR_NOT_DOWNLOADABLE, @"S3 object %@ not downloadable", theSHA1); + } + } + return ret; +} +- (BOOL)putData:(NSData *)theData forSHA1:(NSString *)theSHA1 storageType:(StorageType)theStorageType error:(NSError **)error { + return [self putData:theData forSHA1:theSHA1 storageType:theStorageType dataTransferDelegate:nil error:error]; +} +- (BOOL)putData:(NSData *)theData forSHA1:(NSString *)theSHA1 storageType:(StorageType)theStorageType dataTransferDelegate:(id )theDelegate error:(NSError **)error { + NSString *s3Path = [self objectPathForSHA1:theSHA1 storageType:theStorageType]; + if (![targetConnection writeData:theData toFileAtPath:s3Path dataTransferDelegate:theDelegate targetConnectionDelegate:targetConnectionDelegate error:error]) { + return NO; + } + return YES; +} + +- (NSSet *)packIdsForPackSet:(NSString *)packSetName storageType:(StorageType)theStorageType error:(NSError **)error { + NSAssert(theStorageType == StorageTypeS3 || theStorageType == StorageTypeS3Glacier, @"must be S3 or S3Glacier"); + NSString *s3GlacierPrefix = theStorageType == StorageTypeS3Glacier ? @"glacier/" : @""; + NSString *thePrefix = [NSString stringWithFormat:@"%@/%@%@/packsets/%@/", [self pathPrefix], s3GlacierPrefix, computerUUID, packSetName]; + NSArray *paths = [targetConnection pathsWithPrefix:thePrefix delegate:targetConnectionDelegate error:error]; + if (paths == nil) { + return nil; + } + return [self packIdsForPackSet:packSetName paths:paths storageType:theStorageType]; +} + +- (NSData *)indexDataForPackId:(PackId *)thePackId error:(NSError **)error { + return [self dataForPackId:thePackId suffix:@"index" storageType:StorageTypeS3 error:error]; +} +- (BOOL)putIndexData:(NSData *)theData forPackId:(PackId *)thePackId error:(NSError **)error { + return [self putData:theData forPackId:thePackId suffix:@"index" storageType:StorageTypeS3 saveToCache:YES error:error]; +} +- (BOOL)deleteIndex:(PackId *)thePackId error:(NSError **)error { + return [self deleteDataForPackId:thePackId suffix:@"index" storageType:StorageTypeS3 error:error]; +} + +- (NSNumber *)sizeOfPackWithId:(PackId *)thePackId storageType:(StorageType)theStorageType error:(NSError **)error { + NSString *s3Path = [self s3PathForPackId:thePackId suffix:@"pack" storageType:theStorageType]; + NSError *myError = nil; + NSNumber *ret = [targetConnection sizeOfItemAtPath:s3Path delegate:targetConnectionDelegate error:&myError]; + if (ret == nil) { + SETERRORFROMMYERROR; + if ([myError code] == ERROR_NOT_FOUND) { + SETNSERROR([self errorDomain], ERROR_NOT_FOUND, @"pack %@ not found", thePackId); + } + } + return ret; +} +- (NSNumber *)isPackDownloadableWithId:(PackId *)packId storageType:(StorageType)theStorageType error:(NSError **)error { + NSNumber *ret = nil; + NSString *s3Path = [self s3PathForPackId:packId suffix:@"pack" storageType:theStorageType]; + if (theStorageType == StorageTypeGlacier) { + ret = [NSNumber numberWithBool:NO]; + } else if (theStorageType == StorageTypeS3) { + ret = [targetConnection fileExistsAtPath:s3Path dataSize:NULL delegate:targetConnectionDelegate error:error]; + } else if (theStorageType == StorageTypeS3Glacier) { + if ([downloadablePackIds containsObject:packId]) { + ret = [NSNumber numberWithBool:YES]; + } else { + ret = [targetConnection isObjectRestoredAtPath:s3Path delegate:targetConnectionDelegate error:error]; + if ([ret boolValue]) { + [downloadablePackIds addObject:packId]; + } + } + } else { + SETNSERROR([self errorDomain], -1, @"unknown storage type"); + } + return ret; +} +- (BOOL)restorePackWithId:(PackId *)packId forDays:(NSUInteger)theDays storageType:(StorageType)theStorageType alreadyRestoredOrRestoring:(BOOL *)alreadyRestoredOrRestoring error:(NSError **)error { + if (![packIdsAlreadyPostedForRestore containsObject:packId]) { + NSError *myError = nil; + if (![targetConnection restoreObjectAtPath:[self s3PathForPackId:packId suffix:@"pack" storageType:theStorageType] forDays:theDays alreadyRestoredOrRestoring:alreadyRestoredOrRestoring delegate:targetConnectionDelegate error:&myError]) { + SETERRORFROMMYERROR; + if ([myError isErrorWithDomain:[S3Service errorDomain] code:ERROR_NOT_FOUND]) { + SETNSERROR([self errorDomain], ERROR_NOT_FOUND, @"pack %@ can't be restored because it's not found", packId); + } + return NO; + } + [packIdsAlreadyPostedForRestore addObject:packId]; + } else { + HSLogDebug(@"already requested %@", packId); + } + return YES; +} + +- (NSData *)packDataForPackId:(PackId *)thePackId storageType:(StorageType)theStorageType error:(NSError **)error { + return [self dataForPackId:thePackId suffix:@"pack" storageType:theStorageType error:error]; +} +- (NSData *)dataForPackIndexEntry:(PackIndexEntry *)thePIE storageType:(StorageType)theStorageType error:(NSError **)error { + NSData *ret = [self cachedPackDataForPackIndexEntry:thePIE storageType:theStorageType error:NULL]; + if (ret == nil) { + NSData *packData = [self packDataForPackId:[thePIE packId] storageType:theStorageType error:error]; + if (packData == nil) { + return nil; + } + if ([packData length] == 0) { + SETNSERROR([self errorDomain], -1, @"packData for %@ is empty!", thePIE); + return nil; + } + NSData *subdata = [packData subdataWithRange:NSMakeRange([thePIE offset], [packData length] - [thePIE offset])]; + DataInputStream *dis = [[[DataInputStream alloc] initWithData:subdata description:@"blob"] autorelease]; + BufferedInputStream *bis = [[[BufferedInputStream alloc] initWithUnderlyingStream:dis] autorelease]; + NSString *mimeType; + NSString *downloadName; + if (![StringIO read:&mimeType from:bis error:error] || ![StringIO read:&downloadName from:bis error:error]) { + return nil; + } + uint64_t dataLen = 0; + if (![IntegerIO readUInt64:&dataLen from:bis error:error]) { + return nil; + } + if (dataLen > 0) { + unsigned char *buf = (unsigned char *)malloc((size_t)dataLen); + if (![bis readExactly:(NSUInteger)dataLen into:buf error:error]) { + free(buf); + return nil; + } + ret = [NSData dataWithBytesNoCopy:buf length:(NSUInteger)dataLen]; + } else { + ret = [NSData data]; + } + } + return ret; +} +- (BOOL)putPackData:(NSData *)theData forPackId:(PackId *)thePackId storageType:(StorageType)theStorageType saveToCache:(BOOL)saveToCache error:(NSError **)error { + return [self putData:theData forPackId:thePackId suffix:@"pack" storageType:theStorageType saveToCache:YES error:error]; +} +- (BOOL)deletePack:(PackId *)thePackId storageType:(StorageType)theStorageType error:(NSError **)error { + return [self deleteDataForPackId:thePackId suffix:@"pack" storageType:theStorageType error:error]; +} +- (BOOL)putReflogItem:(NSData *)itemData forBucketUUID:(NSString *)theBucketUUID error:(NSError **)error { + NSString *s3Path = [NSString stringWithFormat:@"%@/%@/bucketdata/%@/refs/logs/master/%0.0f", [self pathPrefix], computerUUID, theBucketUUID, [NSDate timeIntervalSinceReferenceDate]]; + return [targetConnection writeData:itemData toFileAtPath:s3Path dataTransferDelegate:nil targetConnectionDelegate:targetConnectionDelegate error:error]; +} + + +#pragma mark internal +- (NSString *)masterPathForBucketUUID:(NSString *)theBucketUUID { + return [NSString stringWithFormat:@"%@/%@/bucketdata/%@/refs/heads/master", [self pathPrefix], computerUUID, theBucketUUID]; +} +- (NSString *)objectPathForSHA1:(NSString *)theSHA1 storageType:(StorageType)theStorageType { + if (([target targetType] == kTargetSFTP) && [theSHA1 length] == 40) { + return [NSString stringWithFormat:@"%@/%@/objects/%@/%@", [self pathPrefix], computerUUID, [theSHA1 substringToIndex:2], [theSHA1 substringFromIndex:2]]; + } + + NSAssert(theStorageType == StorageTypeS3 || theStorageType == StorageTypeS3Glacier, @"must be S3 or S3Glacier"); + NSString *prefix = (theStorageType == StorageTypeS3) ? @"" : @"glacier/"; + + return [NSString stringWithFormat:@"%@/%@%@/objects/%@", [self pathPrefix], prefix, computerUUID, theSHA1]; +} +- (NSString *)legacy1SFTPObjectPathForSHA1:(NSString *)theSHA1 { + return [NSString stringWithFormat:@"%@/%@/objects/%@", [self pathPrefix], computerUUID, theSHA1]; +} +- (NSString *)legacy2SFTPObjectPathForSHA1:(NSString *)theSHA1 { + return [NSString stringWithFormat:@"%@/%@/objects/%@/%@/%@", [self pathPrefix], computerUUID, [theSHA1 substringToIndex:2], [theSHA1 substringWithRange:NSMakeRange(2, 2)], [theSHA1 substringFromIndex:4]]; +} +- (NSSet *)packIdsForPackSet:(NSString *)thePackSetName paths:(NSArray *)paths storageType:(StorageType)theStorageType { + NSAssert(theStorageType == StorageTypeS3 || theStorageType == StorageTypeS3Glacier, @"must be S3 or S3Glacier"); + NSString *prefix = (theStorageType == StorageTypeS3) ? @"" : @"glacier/"; + NSString *regex = [NSString stringWithFormat:@"^%@/%@%@/packsets/%@/([^/]+).pack$", [self pathPrefix], prefix, computerUUID, thePackSetName]; + + NSMutableSet *ret = [NSMutableSet set]; + for (NSString *path in paths) { + if ([path isMatchedByRegex:regex]) { + NSString *packSHA1 = [path substringWithRange:[path rangeOfRegex:regex capture:1]]; + PackId *packId = [[PackId alloc] initWithPackSetName:thePackSetName packSHA1:packSHA1]; + [ret addObject:packId]; + [packId release]; + } + } + return ret; +} +- (NSData *)cachedPackDataForPackIndexEntry:(PackIndexEntry *)thePIE storageType:(StorageType)theStorageType error:(NSError **)error { + NSString *cachePath = [self cachePathForPackId:[thePIE packId] suffix:@"pack" storageType:theStorageType]; + int fd = open([cachePath fileSystemRepresentation], O_RDONLY); + if (fd == -1) { + int errnum = errno; + if (errnum == ENOENT) { + SETNSERROR([self errorDomain], ERROR_NOT_FOUND, @"pack not found for %@", thePIE); + } else { + HSLogError(@"open(%@) error %d: %s", cachePath, errnum, strerror(errnum)); + SETNSERROR(@"UnixErrorDomain", errnum, @"failed to open %@: %s", cachePath, strerror(errnum)); + } + return nil; + } + NSData *ret = nil; + FDInputStream *fdis = [[FDInputStream alloc] initWithFD:fd label:cachePath]; + BufferedInputStream *bis = [[BufferedInputStream alloc] initWithUnderlyingStream:fdis]; + do { + if (lseek(fd, [thePIE offset], SEEK_SET) == -1) { + int errnum = errno; + HSLogError(@"lseek(%@, %qu) error %d: %s", cachePath, [thePIE offset], errnum, strerror(errnum)); + SETNSERROR(@"UnixErrorDomain", errnum, @"failed to seek to %qu in %@: %s", [thePIE offset], cachePath, strerror(errnum)); + break; + } + NSString *mimeType; + NSString *downloadName; + if (![StringIO read:&mimeType from:bis error:error] || ![StringIO read:&downloadName from:bis error:error]) { + break; + } + uint64_t dataLen = 0; + if (![IntegerIO readUInt64:&dataLen from:bis error:error]) { + break; + } + NSData *data = nil; + if (dataLen > 0) { + unsigned char *buf = (unsigned char *)malloc((size_t)dataLen); + if (![bis readExactly:(NSUInteger)dataLen into:buf error:error]) { + free(buf); + break; + } + data = [NSData dataWithBytesNoCopy:buf length:(NSUInteger)dataLen]; + } else { + data = [NSData data]; + } + ret = data; + } while (0); + close(fd); + [bis release]; + [fdis release]; + return ret; +} +- (NSData *)dataForPackId:(PackId *)thePackId suffix:(NSString *)theSuffix storageType:(StorageType)theStorageType error:(NSError **)error { + NSString *cachePath = [self cachePathForPackId:thePackId suffix:theSuffix storageType:theStorageType]; + NSData *ret = [NSData dataWithContentsOfFile:cachePath options:NSUncachedRead error:error]; + BOOL foundInCache = ret != nil; + if (ret == nil) { + HSLogDebug(@"downloading pack %@", thePackId); + NSString *s3Path = [self s3PathForPackId:thePackId suffix:theSuffix storageType:theStorageType]; + NSError *myError = nil; + ret = [targetConnection contentsOfFileAtPath:s3Path delegate:targetConnectionDelegate error:&myError]; + if(ret == nil) { + SETERRORFROMMYERROR; + if ([myError isErrorWithDomain:[S3Service errorDomain] code:ERROR_NOT_FOUND]) { + SETNSERROR([self errorDomain], ERROR_NOT_FOUND, @"%@ not found in S3", s3Path); + } + } + } + if (ret != nil && !foundInCache) { + NSError *myError = nil; + if (![[NSFileManager defaultManager] ensureParentPathExistsForPath:cachePath targetUID:uid targetGID:gid error:&myError] + || ![Streams writeData:ret atomicallyToFile:cachePath targetUID:uid targetGID:gid bytesWritten:NULL error:&myError]) { + HSLogError(@"error writing cache file %@: %@", cachePath, myError); + } + } + return ret; +} +- (BOOL)putData:(NSData *)theData forPackId:(PackId *)thePackId suffix:(NSString *)theSuffix storageType:(StorageType)theStorageType saveToCache:(BOOL)saveToCache error:(NSError **)error { + NSString *s3Path = [self s3PathForPackId:thePackId suffix:theSuffix storageType:theStorageType]; + if (![targetConnection writeData:theData toFileAtPath:s3Path dataTransferDelegate:nil targetConnectionDelegate:targetConnectionDelegate error:error]) { + return NO; + } + + if (saveToCache) { + NSError *myError = nil; + NSString *cachePath = [self cachePathForPackId:thePackId suffix:theSuffix storageType:theStorageType]; + if (![[NSFileManager defaultManager] ensureParentPathExistsForPath:cachePath targetUID:uid targetGID:gid error:&myError] + || ![Streams writeData:theData atomicallyToFile:cachePath targetUID:uid targetGID:gid bytesWritten:NULL error:&myError]) { + HSLogError(@"error writing cache file %@: %@", cachePath, myError); + } + } + + return YES; +} +- (BOOL)deleteDataForPackId:(PackId *)thePackId suffix:(NSString *)theSuffix storageType:(StorageType)theStorageType error:(NSError **)error { + NSAssert(theStorageType == StorageTypeS3 || theStorageType == StorageTypeS3Glacier, @"must be S3 or S3Glacier"); + + NSError *myError = nil; + NSString *cachePath = [self cachePathForPackId:thePackId suffix:theSuffix storageType:theStorageType]; + BOOL isDir; + if ([[NSFileManager defaultManager] fileExistsAtPath:cachePath isDirectory:&isDir] && ![[NSFileManager defaultManager] removeItemAtPath:cachePath error:&myError]) { + HSLogError(@"failed to delete %@: %@", cachePath, myError); + } + + NSString *s3Path = [self s3PathForPackId:thePackId suffix:theSuffix storageType:theStorageType]; + return [targetConnection removeItemAtPath:s3Path delegate:targetConnectionDelegate error:error]; +} +- (NSString *)cachePathForPackId:(PackId *)thePackId suffix:(NSString *)theSuffix storageType:(StorageType)theStorageType { + NSAssert(theStorageType == StorageTypeS3 || theStorageType == StorageTypeS3Glacier, @"must be S3 or S3Glacier"); + NSString *s3GlacierPrefix = theStorageType == StorageTypeS3Glacier ? @"/glacier" : @""; + + return [NSString stringWithFormat:@"%@/%@%@/%@/packsets/%@/%@/%@.%@", + [UserLibrary arqCachePath], + [target targetUUID], + s3GlacierPrefix, + computerUUID, + [thePackId packSetName], + [[thePackId packSHA1] substringToIndex:2], + [[thePackId packSHA1] substringFromIndex:2], + theSuffix]; +} +- (NSString *)s3PathForPackId:(PackId *)thePackId suffix:(NSString *)theSuffix storageType:(StorageType)theStorageType { + NSAssert(theStorageType == StorageTypeS3 || theStorageType == StorageTypeS3Glacier, @"must be S3 or S3Glacier"); + NSString *s3GlacierPrefix = theStorageType == StorageTypeS3Glacier ? @"glacier/" : @""; + + return [NSString stringWithFormat:@"%@/%@%@/packsets/%@/%@.%@", [self pathPrefix], s3GlacierPrefix, computerUUID, [thePackId packSetName], [thePackId packSHA1], theSuffix]; +} + +- (NSString *)pathPrefix { + NSString *ret = [[target endpoint] path]; + if ([ret isEqualToString:@"/"]) { + ret = @""; + } + return ret; +} +@end diff --git a/Node.h b/repo/Node.h similarity index 97% rename from Node.h rename to repo/Node.h index a1b5c12..742ea37 100644 --- a/Node.h +++ b/repo/Node.h @@ -55,7 +55,6 @@ @property(readonly,copy) NSArray *dataBlobKeys; @property(readonly) unsigned long long uncompressedDataSize; -@property(readonly) unsigned long long aggregateGlacierArchiveSize; @property(readonly,copy) BlobKey *xattrsBlobKey; @property(readonly) unsigned long long xattrsSize; @property(readonly,copy) BlobKey *aclBlobKey; diff --git a/Node.m b/repo/Node.m similarity index 96% rename from Node.m rename to repo/Node.m index cda603d..08cbb7e 100644 --- a/Node.m +++ b/repo/Node.m @@ -22,14 +22,13 @@ @synthesize isTree, treeContainsMissingItems, uncompressedDataSize, xattrsBlobKey, xattrsSize, aclBlobKey, uid, gid, mode, mtime_sec, mtime_nsec, flags, finderFlags, extendedFinderFlags, finderFileType, finderFileCreator, isFileExtensionHidden, st_dev, treeVersion, st_rdev; @synthesize ctime_sec, ctime_nsec, createTime_sec, createTime_nsec, st_nlink, st_ino, st_blocks, st_blksize; @dynamic treeBlobKey, dataBlobKeys; -@dynamic aggregateGlacierArchiveSize; - (id)initWithInputStream:(BufferedInputStream *)is treeVersion:(int)theTreeVersion error:(NSError **)error { if (self = [super init]) { treeVersion = theTreeVersion; dataBlobKeys = [[NSMutableArray alloc] init]; - + if (![BooleanIO read:&isTree from:is error:error]) { [self release]; return nil; @@ -144,15 +143,6 @@ - (NSArray *)dataBlobKeys { return dataBlobKeys; } -- (uint64_t)aggregateGlacierArchiveSize { - uint64_t ret = 0; - ret += [aclBlobKey archiveSize]; - ret += [xattrsBlobKey archiveSize]; - for (BlobKey *dataBlobKey in dataBlobKeys) { - ret += [dataBlobKey archiveSize]; - } - return ret; -} - (BOOL)dataMatchesStat:(struct stat *)st { return st->st_mtimespec.tv_sec == mtime_sec && st->st_mtimespec.tv_nsec == mtime_nsec && st->st_size == uncompressedDataSize; @@ -206,7 +196,7 @@ return NO; } Node *other = (Node *)object; - return treeVersion == [other treeVersion] + return treeVersion == [other treeVersion] && isTree == [other isTree] && uncompressedDataSize == [other uncompressedDataSize] && [dataBlobKeys isEqualToArray:[other dataBlobKeys]] diff --git a/repo/PackId.h b/repo/PackId.h new file mode 100644 index 0000000..ce3a493 --- /dev/null +++ b/repo/PackId.h @@ -0,0 +1,17 @@ +// +// PackId.h +// Arq +// +// Created by Stefan Reitshamer on 12/30/13. +// Copyright (c) 2013 Stefan Reitshamer. All rights reserved. +// + + +@interface PackId : NSObject { + NSString *packSetName; + NSString *packSHA1; +} +- (id)initWithPackSetName:(NSString *)thePackSetName packSHA1:(NSString *)thePackSHA1; +- (NSString *)packSetName; +- (NSString *)packSHA1; +@end diff --git a/repo/PackId.m b/repo/PackId.m new file mode 100644 index 0000000..ff90ab7 --- /dev/null +++ b/repo/PackId.m @@ -0,0 +1,47 @@ +// +// PackId.m +// Arq +// +// Created by Stefan Reitshamer on 12/30/13. +// Copyright (c) 2013 Stefan Reitshamer. All rights reserved. +// + +#import "PackId.h" + +@implementation PackId +- (id)initWithPackSetName:(NSString *)thePackSetName packSHA1:(NSString *)thePackSHA1 { + if (self = [super init]) { + packSetName = [thePackSetName retain]; + packSHA1 = [thePackSHA1 retain]; + } + return self; +} +- (void)dealloc { + [packSetName release]; + [packSHA1 release]; + [super dealloc]; +} + +- (NSString *)packSetName { + return packSetName; +} +- (NSString *)packSHA1 { + return packSHA1; +} + + +#pragma mark NSObject +- (NSString *)description { + return [NSString stringWithFormat:@"", packSetName, packSHA1]; +} +- (NSUInteger)hash { + return [packSetName hash] + [packSHA1 hash]; +} +- (BOOL)isEqual:(id)anObject { + if (![anObject isKindOfClass:[PackId class]]) { + return NO; + } + PackId *other = (PackId *)anObject; + return [packSetName isEqualToString:[other packSetName]] && [packSHA1 isEqualToString:[other packSHA1]]; +} +@end diff --git a/repo/PackIndex.h b/repo/PackIndex.h new file mode 100644 index 0000000..9268b0e --- /dev/null +++ b/repo/PackIndex.h @@ -0,0 +1,18 @@ +// +// PackIndex.h +// Arq +// +// Created by Stefan Reitshamer on 12/30/13. +// Copyright (c) 2013 Stefan Reitshamer. All rights reserved. +// + +@class PackId; +@protocol Fark; + +@interface PackIndex : NSObject { + PackId *packId; + NSData *indexData; +} +- (id)initWithPackId:(PackId *)thePackId indexData:(NSData *)theIndexData; +- (NSArray *)packIndexEntries:(NSError **)error; +@end diff --git a/repo/PackIndex.m b/repo/PackIndex.m new file mode 100644 index 0000000..3d731e7 --- /dev/null +++ b/repo/PackIndex.m @@ -0,0 +1,90 @@ +// +// PackIndex.m +// Arq +// +// Created by Stefan Reitshamer on 12/30/13. +// Copyright (c) 2013 Stefan Reitshamer. All rights reserved. +// + +#include +#import "PackIndex.h" +#import "PackIndexEntry.h" +#import "NSString_extra.h" +#import "Fark.h" +#import "StringIO.h" +#import "IntegerIO.h" +#import "DataInputStream.h" +#import "BufferedInputStream.h" + + +typedef struct index_object { + uint64_t nbo_offset; + uint64_t nbo_datalength; + unsigned char sha1[20]; + unsigned char filler[4]; +} index_object; + +typedef struct pack_index { + uint32_t magic_number; + uint32_t nbo_version; + uint32_t nbo_fanout[256]; + index_object first_index_object; +} pack_index; + + +@implementation PackIndex +- (id)initWithPackId:(PackId *)thePackId indexData:(NSData *)theIndexData { + if (self = [super init]) { + packId = [thePackId retain]; + indexData = [theIndexData retain]; + } + return self; +} +- (void)dealloc { + [packId release]; + [indexData release]; + [super dealloc]; +} + +- (NSArray *)packIndexEntries:(NSError **)error { + NSMutableArray *ret = [NSMutableArray array]; + + if ([indexData length] < sizeof(pack_index)) { + SETNSERROR([self errorDomain], -1, @"pack index data length %ld is smaller than size of pack_index", (unsigned long)[indexData length]); + return nil; + } + pack_index *the_pack_index = (pack_index *)[indexData bytes]; + uint32_t count = OSSwapBigToHostInt32(the_pack_index->nbo_fanout[255]); + +// HSLogDebug(@"count=%d sizeof(pack_index)=%ld sizeof(index_object)=%ld data length=%ld", count, sizeof(pack_index), sizeof(index_object), [indexData length]); + + if ([indexData length] < sizeof(pack_index) + (count - 1) * sizeof(index_object)) { + SETNSERROR([self errorDomain], -1, @"pack index data length %ld is smaller than size of pack_index + index_objects", (unsigned long)[indexData length]); + return nil; + } + index_object *indexObjects = &(the_pack_index->first_index_object); + NSAutoreleasePool *pool = nil; + for (uint32_t i = 0; i < count; i++) { + [pool drain]; + pool = [[NSAutoreleasePool alloc] init]; + uint64_t offset = OSSwapBigToHostInt64(indexObjects[i].nbo_offset); + uint64_t dataLength = OSSwapBigToHostInt64(indexObjects[i].nbo_datalength); + NSString *objectSHA1 = [NSString hexStringWithBytes:indexObjects[i].sha1 length:20]; + PackIndexEntry *pie = [[[PackIndexEntry alloc] initWithPackId:packId offset:offset dataLength:dataLength objectSHA1:objectSHA1] autorelease]; + [ret addObject:pie]; + } + [pool drain]; + pool = nil; + + return ret; +} + + +#pragma mark NSObject +- (NSString *)errorDomain { + return @"PackIndexErrorDomain"; +} +- (NSString *)description { + return [NSString stringWithFormat:@"", [packId description]]; +} +@end diff --git a/repo/PackIndexEntry.h b/repo/PackIndexEntry.h new file mode 100644 index 0000000..4bb4359 --- /dev/null +++ b/repo/PackIndexEntry.h @@ -0,0 +1,23 @@ +// +// PackIndexEntry.h +// Arq +// +// Created by Stefan Reitshamer on 12/30/09. +// Copyright 2009 Haystack Software. All rights reserved. +// + +@class PackId; + + +@interface PackIndexEntry : NSObject { + PackId *packId; + unsigned long long offset; + unsigned long long dataLength; + NSString *objectSHA1; +} +- (id)initWithPackId:(PackId *)thePackId offset:(unsigned long long)theOffset dataLength:(unsigned long long)theDataLength objectSHA1:(NSString *)theObjectSHA1; +- (PackId *)packId; +- (unsigned long long)offset; +- (unsigned long long)dataLength; +- (NSString *)objectSHA1; +@end diff --git a/repo/PackIndexEntry.m b/repo/PackIndexEntry.m new file mode 100644 index 0000000..06b0697 --- /dev/null +++ b/repo/PackIndexEntry.m @@ -0,0 +1,55 @@ +// +// PackIndexEntry.m +// Arq +// +// Created by Stefan Reitshamer on 12/30/09. +// Copyright 2009 Haystack Software. All rights reserved. +// + +#import "PackIndexEntry.h" +#import "PackId.h" + + +@implementation PackIndexEntry +- (id)initWithPackId:(PackId *)thePackId offset:(unsigned long long)theOffset dataLength:(unsigned long long)theDataLength objectSHA1:(NSString *)theObjectSHA1 { + if (self = [super init]) { + packId = [thePackId retain]; + offset = theOffset; + dataLength = theDataLength; + objectSHA1 = [theObjectSHA1 copy]; + } + return self; +} +- (void)dealloc { + [packId release]; + [objectSHA1 release]; + [super dealloc]; +} +- (PackId *)packId { + return packId; +} +- (unsigned long long)offset { + return offset; +} +- (unsigned long long)dataLength { + return dataLength; +} +- (NSString *)objectSHA1 { + return objectSHA1; +} + + +#pragma mark NSObject +- (NSString *)description { + return [NSString stringWithFormat:@"", packId, offset, dataLength, objectSHA1]; +} + + +#pragma mark NSCopying +- (id)copyWithZone:(NSZone *)zone { + return [[PackIndexEntry alloc] initWithPackId:packId + offset:offset + dataLength:dataLength + objectSHA1:objectSHA1]; +} +@end diff --git a/repo/PackSet.h b/repo/PackSet.h new file mode 100644 index 0000000..5a89a38 --- /dev/null +++ b/repo/PackSet.h @@ -0,0 +1,46 @@ +// +// PackSet.h +// Arq +// +// Created by Stefan Reitshamer on 12/30/13. +// Copyright (c) 2013 Stefan Reitshamer. All rights reserved. +// + +#import "StorageType.h" +@protocol Fark; +@class PackBuilder; +@class PackId; + + +@interface PackSet : NSObject { + id fark; + StorageType storageType; + NSString *packSetName; + BOOL savePacksToCache; + uid_t targetUID; + gid_t targetGID; + BOOL loadExistingMutablePackFiles; + NSMutableDictionary *packIndexEntriesByObjectSHA1; +} + + ++ (unsigned long long)maxPackFileSizeMB; ++ (unsigned long long)maxPackItemSizeBytes; + +- (id)initWithFark:(id )theFark + storageType:(StorageType)theStorageType + packSetName:(NSString *)thePackSetName + savePacksToCache:(BOOL)theSavePacksToCache + targetUID:(uid_t)theTargetUID + targetGID:(gid_t)theTargetGID +loadExistingMutablePackFiles:(BOOL)theLoadExistingMutablePackFiles; + +- (NSString *)errorDomain; + +- (NSString *)packSetName; +- (NSNumber *)containsBlobForSHA1:(NSString *)sha1 dataSize:(unsigned long long *)dataSize error:(NSError **)error; +- (PackId *)packIdForSHA1:(NSString *)theSHA1 error:(NSError **)error; +- (NSData *)dataForSHA1:(NSString *)sha1 withRetry:(BOOL)retry error:(NSError **)error; +- (BOOL)restorePackForBlobWithSHA1:(NSString *)theSHA1 forDays:(NSUInteger)theDays alreadyRestoredOrRestoring:(BOOL *)alreadyRestoredOrRestoring error:(NSError **)error; +- (NSNumber *)isObjectDownloadableForSHA1:(NSString *)theSHA1 error:(NSError **)error; +@end diff --git a/repo/PackSet.m b/repo/PackSet.m new file mode 100644 index 0000000..1149a59 --- /dev/null +++ b/repo/PackSet.m @@ -0,0 +1,227 @@ +// +// PackSet.m +// Arq +// +// Created by Stefan Reitshamer on 12/30/13. +// Copyright (c) 2013 Stefan Reitshamer. All rights reserved. +// + +#import "PackSet.h" +#import "Target.h" +#import "S3Service.h" +#import "PackIndexEntry.h" +#import "RegexKitLite.h" +#import "Fark.h" +#import "PackIndex.h" +#import "DataInputStream.h" +#import "BufferedInputStream.h" +#import "StringIO.h" +#import "IntegerIO.h" + + +#define MAX_RETRIES (10) + +static unsigned long long DEFAULT_MAX_PACK_FILE_SIZE_MB = 5; +static unsigned long long DEFAULT_MAX_PACK_ITEM_SIZE_BYTES = 65536; +static double DEFAULT_MAX_REUSABLE_PACK_FILE_SIZE_FRACTION = 0.6; + + +@implementation PackSet + ++ (unsigned long long)maxPackFileSizeMB { + return DEFAULT_MAX_PACK_FILE_SIZE_MB; +} ++ (unsigned long long)maxPackItemSizeBytes { + return DEFAULT_MAX_PACK_ITEM_SIZE_BYTES; +} + +- (id)initWithFark:(id )theFark + storageType:(StorageType)theStorageType + packSetName:(NSString *)thePackSetName + savePacksToCache:(BOOL)theSavePacksToCache + targetUID:(uid_t)theTargetUID + targetGID:(gid_t)theTargetGID +loadExistingMutablePackFiles:(BOOL)theLoadExistingMutablePackFiles { + if (self = [super init]) { + fark = [theFark retain]; + storageType = theStorageType; + packSetName = [thePackSetName retain]; + savePacksToCache = theSavePacksToCache; + targetUID = theTargetUID; + targetGID = theTargetGID; + loadExistingMutablePackFiles = theLoadExistingMutablePackFiles; + } + return self; +} +- (void)dealloc { + [fark release]; + [packSetName release]; + [packIndexEntriesByObjectSHA1 release]; + [super dealloc]; +} + +- (NSString *)errorDomain { + return @"PackSetErrorDomain"; +} + +- (NSString *)packSetName { + return packSetName; +} +- (NSNumber *)containsBlobForSHA1:(NSString *)sha1 dataSize:(unsigned long long *)dataSize error:(NSError **)error { + if (![self loadPackIndexEntries:error]) { + return nil; + } + PackIndexEntry *pie = [packIndexEntriesByObjectSHA1 objectForKey:sha1]; + BOOL contains; + if ([packIndexEntriesByObjectSHA1 objectForKey:sha1] != nil) { + contains = YES; + if (dataSize != NULL) { + *dataSize = [pie dataLength]; + } + } + return [NSNumber numberWithBool:contains]; +} +- (PackId *)packIdForSHA1:(NSString *)theSHA1 error:(NSError **)error { + if (![self loadPackIndexEntries:error]) { + return nil; + } + PackIndexEntry *pie = [packIndexEntriesByObjectSHA1 objectForKey:theSHA1]; + if (pie == nil) { + SETNSERROR([self errorDomain], ERROR_NOT_FOUND, @"object %@ not found in pack set %@", theSHA1, packSetName); + return nil; + } + return [pie packId]; +} +- (NSData *)dataForSHA1:(NSString *)sha1 withRetry:(BOOL)retry error:(NSError **)error { + NSData *ret = nil; + NSUInteger i = 0; + NSUInteger maxRetries = retry ? MAX_RETRIES : 1; + NSAutoreleasePool *pool = nil; + for (i = 0; i < maxRetries; i++) { + [pool drain]; + pool = [[NSAutoreleasePool alloc] init]; + NSError *myError = nil; + ret = [self doDataForSHA1:sha1 error:&myError]; + if (ret != nil) { + break; + } + + if ([myError isErrorWithDomain:[fark errorDomain] code:ERROR_NOT_FOUND]) { + // Pack was missing. Agent must have replaced it. Try again. + [packIndexEntriesByObjectSHA1 release]; + packIndexEntriesByObjectSHA1 = nil; + } else { + SETERRORFROMMYERROR; + break; + } + } + [ret retain]; + if (ret == nil && error != NULL) { + [*error retain]; + } + [pool drain]; + [ret autorelease]; + if (ret == nil && error != NULL) { + [*error autorelease]; + } + + return ret; +} +- (BOOL)restorePackForBlobWithSHA1:(NSString *)theSHA1 forDays:(NSUInteger)theDays alreadyRestoredOrRestoring:(BOOL *)alreadyRestoredOrRestoring error:(NSError **)error { + if (![self loadPackIndexEntries:error]) { + return NO; + } + PackIndexEntry *pie = [packIndexEntriesByObjectSHA1 objectForKey:theSHA1]; + if (pie == nil) { + SETNSERROR([self errorDomain], ERROR_NOT_FOUND, @"%@ not found in packset", theSHA1); + return NO; + } + NSError *myError = nil; + if (![fark restorePackWithId:[pie packId] forDays:theDays storageType:storageType alreadyRestoredOrRestoring:alreadyRestoredOrRestoring error:&myError]) { + SETERRORFROMMYERROR; + if ([myError isErrorWithDomain:[fark errorDomain] code:ERROR_NOT_FOUND]) { + SETNSERROR([self errorDomain], ERROR_NOT_FOUND, @"blob %@ not found because pack %@ not found", theSHA1, [pie packId]); + } + return NO; + } + return YES; +} +- (NSNumber *)isObjectDownloadableForSHA1:(NSString *)theSHA1 error:(NSError **)error { + if (![self loadPackIndexEntries:error]) { + return NO; + } + PackIndexEntry *pie = [packIndexEntriesByObjectSHA1 objectForKey:theSHA1]; + if (pie == nil) { + SETNSERROR([self errorDomain], ERROR_NOT_FOUND, @"%@ not found in packset", theSHA1); + return NO; + } + return [fark isPackDownloadableWithId:[pie packId] storageType:storageType error:error]; +} + +#pragma mark internal +- (NSData *)doDataForSHA1:(NSString *)sha1 error:(NSError **)error { + if (storageType == StorageTypeGlacier) { + SETNSERROR([self errorDomain], -1, @"cannot get pack data directly from Glacier"); + return nil; + } + + if (![self loadPackIndexEntries:error]) { + return nil; + } + + NSData *ret = nil; + PackIndexEntry *pie = [packIndexEntriesByObjectSHA1 objectForKey:sha1]; + if (pie != nil) { + ret = [fark dataForPackIndexEntry:pie storageType:storageType error:error]; + } + return ret; +} +- (BOOL)loadPackIndexEntries:(NSError **)error { + if (packIndexEntriesByObjectSHA1 == nil) { + NSMutableDictionary *theEntries = [[[NSMutableDictionary alloc] init] autorelease]; + NSSet *packIds = [fark packIdsForPackSet:packSetName storageType:storageType error:error]; + if (packIds == nil) { + return NO; + } + PackId *reusedPackId = nil; + for (PackId *packId in packIds) { + NSError *myError = nil; + PackIndex *packIndex = [self packIndexForPackId:packId error:&myError]; + if (packIndex == nil) { + HSLogWarn(@"failed to get pack index for %@: %@", packId, myError); + } else { + NSError *piesError = nil; + NSArray *pies = [packIndex packIndexEntries:&piesError]; + if (pies == nil) { + HSLogWarn(@"failed to read pack index entries for %@: %@", packId, piesError); + } else { + uint64_t packLength = 0; + for (PackIndexEntry *pie in pies) { + [theEntries setObject:pie forKey:[pie objectSHA1]]; + if (([pie offset] + [pie dataLength]) > packLength) { + packLength = [pie offset] + [pie dataLength]; + } + // HSLogDebug(@"loaded sha1 %@ from pack %@", [pie objectSHA1], [pie packId]); + } + if (reusedPackId == nil && packLength < [self maxReusablePackFileSizeBytes] && storageType == StorageTypeS3) { + reusedPackId = packId; + } + } + } + } + + packIndexEntriesByObjectSHA1 = [theEntries retain]; + } + return YES; +} +- (PackIndex *)packIndexForPackId:(PackId *)thePackId error:(NSError **)error { + NSData *indexData = [fark indexDataForPackId:thePackId error:error]; + if (indexData == nil) { + return nil; + } + return [[[PackIndex alloc] initWithPackId:thePackId indexData:indexData] autorelease]; +} +- (unsigned long long)maxReusablePackFileSizeBytes { + return (unsigned long long)((double)([PackSet maxPackFileSizeMB] * 1000000) * DEFAULT_MAX_REUSABLE_PACK_FILE_SIZE_FRACTION); +} +@end diff --git a/repo/Repo.h b/repo/Repo.h new file mode 100644 index 0000000..ab37047 --- /dev/null +++ b/repo/Repo.h @@ -0,0 +1,72 @@ +// +// Repo.h +// Arq +// +// Created by Stefan Reitshamer on 12/30/13. +// Copyright (c) 2013 Stefan Reitshamer. All rights reserved. +// + +@class Bucket; +@protocol Fark; +@class BlobKey; +@class Commit; +@class Tree; +@class CryptoKey; +@class PackSet; +@class Repo; +@class TargetObjectSet; +@class PackId; +@protocol DataTransferDelegate; +@protocol TargetConnectionDelegate; + + +@protocol RepoDelegate +- (void)repo:(Repo *)theRepo headBlobKeyDidChangeFrom:(BlobKey *)fromBlobKey to:(BlobKey *)toBlobKey rewrite:(BOOL)rewrite; +- (void)headBlobKeyWasDeletedForRepo:(Repo *)theRepo; +@end + +@interface Repo : NSObject { + Bucket *bucket; + uid_t targetUID; + gid_t targetGID; + CryptoKey *cryptoKey; + CryptoKey *stretchedCryptoKey; + id fark; + PackSet *treesPackSet; + PackSet *blobsPackSet; + id targetConnectionDelegate; + id repoDelegate; +} + +- (id)initWithBucket:(Bucket *)theBucket + encryptionPassword:(NSString *)theEncryptionPassword + targetUID:(uid_t)theTargetUID + targetGID:(gid_t)theTargetGID +loadExistingMutablePackFiles:(BOOL)theLoadExistingMutablePackFiles +targetConnectionDelegate:(id )theTCD + repoDelegate:(id )theRepoDelegate + error:(NSError **)error; + +- (NSString *)errorDomain; + +- (Bucket *)bucket; +- (BlobKey *)headBlobKey:(NSError **)error; +- (NSArray *)allCommitBlobKeys:(NSError **)error; +- (Commit *)commitForBlobKey:(BlobKey *)treeBlobKey error:(NSError **)error; +- (Commit *)commitForBlobKey:(BlobKey *)treeBlobKey dataSize:(unsigned long long *)dataSize error:(NSError **)error; +- (Tree *)treeForBlobKey:(BlobKey *)treeBlobKey error:(NSError **)error; +- (Tree *)treeForBlobKey:(BlobKey *)treeBlobKey dataSize:(unsigned long long *)dataSize error:(NSError **)error; +- (NSNumber *)containsBlobForBlobKey:(BlobKey *)theBlobKey error:(NSError **)error; +- (NSNumber *)containsBlobForBlobKey:(BlobKey *)theBlobKey dataSize:(unsigned long long *)dataSize error:(NSError **)error; +- (NSNumber *)containsBlobForBlobKey:(BlobKey *)theBlobKey dataSize:(unsigned long long *)dataSize forceTargetCheck:(BOOL)forceTargetCheck error:(NSError **)error; + +- (NSNumber *)isObjectDownloadableForBlobKey:(BlobKey *)theBlobKey error:(NSError **)error; +- (BOOL)restoreObjectForBlobKey:(BlobKey *)theBlobKey forDays:(NSUInteger)theDays alreadyRestoredOrRestoring:(BOOL *)alreadyRestoredOrRestoring error:(NSError **)error; +- (NSData *)dataForBlobKey:(BlobKey *)theBlobKey error:(NSError **)error; + +- (NSData *)decryptData:(NSData *)theData error:(NSError **)error; +- (NSData *)encryptData:(NSData *)theData error:(NSError **)error; + +- (BOOL)addSHA1sForCommitBlobKey:(BlobKey *)commitBlobKey toSet:(NSMutableSet *)theSet error:(NSError **)error; + +@end diff --git a/repo/Repo.m b/repo/Repo.m new file mode 100644 index 0000000..44bd2dd --- /dev/null +++ b/repo/Repo.m @@ -0,0 +1,490 @@ +// +// Repo.m +// Arq +// +// Created by Stefan Reitshamer on 12/30/13. +// Copyright (c) 2013 Stefan Reitshamer. All rights reserved. +// + +#import "Repo.h" +#import "FarkImpl.h" +#import "CryptoKey.h" +#import "Bucket.h" +#import "BlobKey.h" +#import "DataInputStream.h" +#import "BufferedInputStream.h" +#import "Target.h" +#import "Commit.h" +#import "Tree.h" +#import "PackSet.h" +#import "NSData-GZip.h" +#import "DictNode.h" +#import "SHA1Hash.h" +#import "Node.h" +#import "ArqSalt.h" + + +#define MAX_CONSISTENCY_TRIES (20) + + +@implementation Repo +- (id)initWithBucket:(Bucket *)theBucket + encryptionPassword:(NSString *)theEncryptionPassword + targetUID:(uid_t)theTargetUID + targetGID:(gid_t)theTargetGID +loadExistingMutablePackFiles:(BOOL)theLoadExistingMutablePackFiles +targetConnectionDelegate:(id)theTCD + repoDelegate:(id)theRepoDelegate + error:(NSError **)error { + if (self = [super init]) { + bucket = [theBucket retain]; + targetUID = theTargetUID; + targetGID = theTargetGID; + targetConnectionDelegate = theTCD; + repoDelegate = theRepoDelegate; + cryptoKey = [[CryptoKey alloc] initLegacyWithPassword:theEncryptionPassword error:error]; + if (cryptoKey == nil) { + [self release]; + return nil; + } + ArqSalt *arqSalt = [[[ArqSalt alloc] initWithTarget:[theBucket target] targetUID:theTargetUID targetGID:theTargetGID computerUUID:[theBucket computerUUID]] autorelease]; + NSData *theEncryptionSalt = [arqSalt saltWithTargetConnectionDelegate:theTCD error:error]; + if (theEncryptionSalt == nil) { + [self release]; + return nil; + } + stretchedCryptoKey = [[CryptoKey alloc] initWithPassword:theEncryptionPassword salt:theEncryptionSalt error:error]; + if (stretchedCryptoKey == nil) { + [self release]; + return nil; + } + fark = [[FarkImpl alloc] initWithTarget:[theBucket target] + computerUUID:[theBucket computerUUID] + targetConnectionDelegate:theTCD + targetUID:theTargetUID + targetGID:theTargetGID]; + treesPackSet = [[PackSet alloc] initWithFark:fark + storageType:StorageTypeS3 + packSetName:[[bucket bucketUUID] stringByAppendingString:@"-trees"] + savePacksToCache:YES + targetUID:theTargetUID + targetGID:theTargetGID + loadExistingMutablePackFiles:theLoadExistingMutablePackFiles]; + if (treesPackSet == nil) { + [self release]; + return nil; + } + + // For StorageTypeGlacier Buckets, use StorageTypeS3Glacier going forward. + StorageType convertedStorageType = ([bucket storageType] == StorageTypeGlacier) ? StorageTypeS3Glacier : [bucket storageType]; + blobsPackSet = [[PackSet alloc] initWithFark:fark + storageType:convertedStorageType + packSetName:[[bucket bucketUUID] stringByAppendingString:@"-blobs"] + savePacksToCache:NO + targetUID:theTargetUID + targetGID:theTargetGID + loadExistingMutablePackFiles:theLoadExistingMutablePackFiles]; + if (blobsPackSet == nil) { + [self release]; + return nil; + } + } + return self; +} +- (void)dealloc { + [bucket release]; + [cryptoKey release]; + [stretchedCryptoKey release]; + [fark release]; + [treesPackSet release]; + [blobsPackSet release]; + [super dealloc]; +} + +- (NSString *)errorDomain { + return @"RepoErrorDomain"; +} +- (Bucket *)bucket { + return bucket; +} +- (BlobKey *)headBlobKey:(NSError **)error { + NSError *myError = nil; + BlobKey *ret = [fark headBlobKeyForBucketUUID:[bucket bucketUUID] error:&myError]; + if (ret == nil) { + SETERRORFROMMYERROR; + if ([myError isErrorWithDomain:[fark errorDomain] code:ERROR_NOT_FOUND]) { + SETNSERROR([self errorDomain], ERROR_NOT_FOUND, @"head blob key not found for bucket %@", [bucket bucketUUID]); + } + } + return ret; +} +- (NSArray *)allCommitBlobKeys:(NSError **)error { + NSError *myError = nil; + BlobKey *commitBlobKey = [self headBlobKey:&myError]; + if (commitBlobKey == nil && ![myError isErrorWithDomain:[self errorDomain] code:ERROR_NOT_FOUND]) { + if (error != NULL) { + *error = myError; + } + return nil; + } + NSMutableArray *commitBlobKeys = [NSMutableArray array]; + while (commitBlobKey != nil) { + [commitBlobKeys addObject:commitBlobKey]; + Commit *commit = [self commitForBlobKey:commitBlobKey error:error]; + if (commit == nil) { + return nil; + } + commitBlobKey = [commit parentCommitBlobKey]; + } + return commitBlobKeys; +} +- (Commit *)commitForBlobKey:(BlobKey *)commitBlobKey error:(NSError **)error { + return [self commitForBlobKey:commitBlobKey dataSize:NULL error:error]; +} +- (Commit *)commitForBlobKey:(BlobKey *)commitBlobKey dataSize:(unsigned long long *)dataSize error:(NSError **)error { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + if (error != NULL) { + *error = nil; + } + Commit *ret = [self doCommitForBlobKey:commitBlobKey dataSize:dataSize error:error]; + [ret retain]; + if (!ret && error != NULL) { + [*error retain]; + } + [pool drain]; + [ret autorelease]; + if (!ret && error != NULL) { + [*error autorelease]; + } + return ret; +} +- (Tree *)treeForBlobKey:(BlobKey *)blobKey error:(NSError **)error { + return [self treeForBlobKey:blobKey dataSize:NULL error:error]; +} +- (Tree *)treeForBlobKey:(BlobKey *)treeBlobKey dataSize:(unsigned long long *)dataSize error:(NSError **)error { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + if (error != NULL) { + *error = nil; + } + Tree *ret = [self doTreeForBlobKey:treeBlobKey dataSize:dataSize error:error]; + [ret retain]; + if (!ret && error != NULL) { + [*error retain]; + } + [pool drain]; + [ret autorelease]; + if (!ret && error != NULL) { + [*error autorelease]; + } + return ret; +} +- (NSNumber *)containsBlobForBlobKey:(BlobKey *)theBlobKey error:(NSError **)error { + return [self containsBlobForBlobKey:theBlobKey dataSize:NULL error:error]; +} +- (NSNumber *)containsBlobForBlobKey:(BlobKey *)theBlobKey dataSize:(unsigned long long *)dataSize error:(NSError **)error { + return [self containsBlobForBlobKey:theBlobKey dataSize:dataSize forceTargetCheck:NO error:error]; +} +- (NSNumber *)containsBlobForBlobKey:(BlobKey *)theBlobKey dataSize:(unsigned long long *)dataSize forceTargetCheck:(BOOL)forceTargetCheck error:(NSError **)error { + if (theBlobKey == nil) { + SETNSERROR([self errorDomain], -1, @"containsBlobForBlobKey: theBlobKey is nil!"); + return nil; + } + + NSError *myError = nil; + NSNumber *ret = [blobsPackSet containsBlobForSHA1:[theBlobKey sha1] dataSize:dataSize error:&myError]; + if (ret == nil) { + HSLogError(@"error checking if pack set contains blob: %@", myError); + } else { + if ([ret boolValue]) { + return ret; + } + } + + if ([theBlobKey storageType] == StorageTypeGlacier) { + // (Legacy) Glacier archives are never deleted, so we return YES: + return [NSNumber numberWithBool:YES]; + } + + return [fark containsObjectForSHA1:[theBlobKey sha1] storageType:[theBlobKey storageType] dataSize:dataSize forceTargetCheck:forceTargetCheck error:error]; +} +- (NSNumber *)isObjectDownloadableForBlobKey:(BlobKey *)theBlobKey error:(NSError **)error { + NSNumber *contains = [treesPackSet containsBlobForSHA1:[theBlobKey sha1] dataSize:NULL error:error]; + if (contains == nil) { + return nil; + } + if ([contains boolValue]) { + return [treesPackSet isObjectDownloadableForSHA1:[theBlobKey sha1] error:error]; + } + + contains = [blobsPackSet containsBlobForSHA1:[theBlobKey sha1] dataSize:NULL error:error]; + if (contains == nil) { + return nil; + } + if ([contains boolValue]) { + return [blobsPackSet isObjectDownloadableForSHA1:[theBlobKey sha1] error:error]; + } + + return [fark isObjectDownloadableForSHA1:[theBlobKey sha1] storageType:[theBlobKey storageType] error:error]; +} +- (BOOL)restoreObjectForBlobKey:(BlobKey *)theBlobKey forDays:(NSUInteger)theDays alreadyRestoredOrRestoring:(BOOL *)alreadyRestoredOrRestoring error:(NSError **)error { + // If it's in a Pack, don't try to restore it because we + NSNumber *contains = [treesPackSet containsBlobForSHA1:[theBlobKey sha1] dataSize:NULL error:error]; + if (contains == nil) { + return NO; + } + if ([contains boolValue]) { + return [treesPackSet restorePackForBlobWithSHA1:[theBlobKey sha1] forDays:theDays alreadyRestoredOrRestoring:alreadyRestoredOrRestoring error:error]; + } + + contains = [blobsPackSet containsBlobForSHA1:[theBlobKey sha1] dataSize:NULL error:error]; + if (contains == nil) { + return NO; + } + if ([contains boolValue]) { + return [blobsPackSet restorePackForBlobWithSHA1:[theBlobKey sha1] forDays:theDays alreadyRestoredOrRestoring:alreadyRestoredOrRestoring error:error]; + } + + NSError *myError = nil; + if (![fark restoreObjectForSHA1:[theBlobKey sha1] forDays:theDays storageType:[theBlobKey storageType] alreadyRestoredOrRestoring:alreadyRestoredOrRestoring error:&myError]) { + SETERRORFROMMYERROR; + if ([myError isErrorWithDomain:[fark errorDomain] code:ERROR_NOT_FOUND]) { + SETNSERROR([self errorDomain], ERROR_NOT_FOUND, @"object %@ can't be restored because it's not found", theBlobKey); + } + return NO; + } + return YES; +} +- (NSData *)dataForBlobKey:(BlobKey *)theBlobKey error:(NSError **)error { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + if (error != NULL) { + *error = nil; + } + NSData *ret = [self doDataForBlobKey:theBlobKey error:error]; + [ret retain]; + if (!ret && error != NULL) { + [*error retain]; + } + [pool drain]; + [ret autorelease]; + if (!ret && error != NULL) { + [*error autorelease]; + } + return ret; +} + +- (NSData *)decryptData:(NSData *)theData error:(NSError **)error { + return [stretchedCryptoKey decrypt:theData error:error]; +} +- (NSData *)encryptData:(NSData *)theData error:(NSError **)error { + return [stretchedCryptoKey encrypt:theData error:error]; +} + +- (BOOL)addSHA1sForCommitBlobKey:(BlobKey *)commitBlobKey toSet:(NSMutableSet *)theSet error:(NSError **)error { + if ([theSet containsObject:[commitBlobKey sha1]]) { + return YES; + } + Commit *commit = [self commitForBlobKey:commitBlobKey error:error]; + if (commit == nil) { + return NO; + } + if (![self addSHA1sForTreeBlobKey:[commit treeBlobKey] toSet:theSet error:error]) { + return NO; + } + [theSet addObject:[commitBlobKey sha1]]; + return YES; +} + + +#pragma mark NSObject +- (NSString *)description { + return [NSString stringWithFormat:@"", bucket]; +} + + +#pragma mark internal +- (Commit *)doCommitForBlobKey:(BlobKey *)commitBlobKey dataSize:(unsigned long long *)dataSize error:(NSError **)error { + NSError *myError = nil; + NSData *data = [treesPackSet dataForSHA1:[commitBlobKey sha1] withRetry:YES error:&myError]; + if (data == nil) { + SETERRORFROMMYERROR; + if ([myError isErrorWithDomain:[treesPackSet errorDomain] code:ERROR_NOT_FOUND]) { + HSLogDebug(@"commit %@ not found in pack set", commitBlobKey); + SETNSERROR([self errorDomain], ERROR_NOT_FOUND, @"commit %@ not found", commitBlobKey); + } + return nil; + } + data = [self decryptData:data forBlobKey:commitBlobKey error:error]; + if (data == nil) { + return nil; + } + + if (dataSize != NULL) { + *dataSize = (unsigned long long)[data length]; + } + + DataInputStream *dis = [[DataInputStream alloc] initWithData:data description:[NSString stringWithFormat:@"Commit %@", commitBlobKey]]; + BufferedInputStream *bis = [[BufferedInputStream alloc] initWithUnderlyingStream:dis]; + Commit *commit = [[[Commit alloc] initWithBufferedInputStream:bis error:error] autorelease]; + [bis release]; + [dis release]; + return commit; +} +- (Tree *)doTreeForBlobKey:(BlobKey *)blobKey dataSize:(unsigned long long *)dataSize error:(NSError **)error { + NSError *myError = nil; + NSData *data = [treesPackSet dataForSHA1:[blobKey sha1] withRetry:YES error:&myError]; + if (data == nil) { + if ([myError isErrorWithDomain:[treesPackSet errorDomain] code:ERROR_NOT_FOUND]) { + HSLogDebug(@"tree %@ not found in pack set", blobKey); + SETNSERROR([self errorDomain], ERROR_NOT_FOUND, @"tree %@ not found", blobKey); + } else { + HSLogError(@"error reading tree %@: %@", blobKey, [myError localizedDescription]); + if (error != NULL) { + *error = myError; + } + } + return nil; + } + data = [self decryptData:data forBlobKey:blobKey error:error]; + if (data == nil) { + return nil; + } + if ([blobKey compressed]) { + data = [data gzipInflate:error]; + if (data == nil) { + return nil; + } + } + + if (dataSize != NULL) { + *dataSize = (unsigned long long)[data length]; + } + + DataInputStream *dis = [[[DataInputStream alloc] initWithData:data description:[NSString stringWithFormat:@"Tree %@", [blobKey description]]] autorelease]; + BufferedInputStream *bis = [[[BufferedInputStream alloc] initWithUnderlyingStream:dis] autorelease]; + Tree *tree = [[[Tree alloc] initWithBufferedInputStream:bis error:error] autorelease]; + return tree; +} +- (NSData *)doDataForBlobKey:(BlobKey *)theBlobKey error:(NSError **)error { + if ([theBlobKey storageType] == StorageTypeGlacier) { + SETNSERROR([self errorDomain], -1, @"invalid method doDataBlobForBlobKey: for Glacier BlobKey"); + return nil; + } + + NSError *myError = nil; + NSData *data = [blobsPackSet dataForSHA1:[theBlobKey sha1] withRetry:NO error:&myError]; + if (data == nil) { + SETERRORFROMMYERROR; + if (![myError isErrorWithDomain:[blobsPackSet errorDomain] code:ERROR_NOT_FOUND]) { + // Return nil if not a not-found error. + return nil; + } + data = [fark dataForSHA1:[theBlobKey sha1] storageType:[theBlobKey storageType] error:&myError]; + if (data == nil) { + SETERRORFROMMYERROR; + if ([myError isErrorWithDomain:[fark errorDomain] code:ERROR_NOT_DOWNLOADABLE]) { + SETNSERROR([self errorDomain], ERROR_NOT_DOWNLOADABLE, @"%@", [myError localizedDescription]); + return nil; + } + if (![myError isErrorWithDomain:[fark errorDomain] code:ERROR_NOT_FOUND]) { + // Return nil if not a not-found error. + return nil; + } + data = [blobsPackSet dataForSHA1:[theBlobKey sha1] withRetry:YES error:&myError]; + if (data == nil) { + SETERRORFROMMYERROR; + if ([myError isErrorWithDomain:[blobsPackSet errorDomain] code:ERROR_NOT_FOUND]) { + SETNSERROR([self errorDomain], ERROR_NOT_FOUND, @"object not found for %@", theBlobKey); + } + return nil; + } + } + } + + NSAssert(data != nil, @"data can't be nil at this point"); + + NSData *decrypted = [self decryptData:data forBlobKey:theBlobKey error:error]; + if (decrypted == nil) { + return nil; + } + return decrypted; +} +- (NSData *)decryptData:(NSData *)theData forBlobKey:(BlobKey *)theBlobKey error:(NSError **)error { + CryptoKey *selectedCryptoKey = [theBlobKey stretchEncryptionKey] ? stretchedCryptoKey : cryptoKey; + return [selectedCryptoKey decrypt:theData error:error]; +} +- (NSData *)encryptData:(NSData *)theData forBlobKey:(BlobKey *)theBlobKey error:(NSError **)error { + CryptoKey *selectedCryptoKey = [theBlobKey stretchEncryptionKey] ? stretchedCryptoKey : cryptoKey; + return [selectedCryptoKey encrypt:theData error:error]; +} + +- (BOOL)writeReflogForOldHeadBlobKey:(BlobKey *)oldHeadBlobKey newHeadBlobKey:(BlobKey *)newHeadBlobKey isRewrite:(BOOL)rewrite error:(NSError **)error { + DictNode *plist = [[[DictNode alloc] init] autorelease]; + [plist putString:[oldHeadBlobKey sha1] forKey:@"oldHeadSHA1"]; + [plist putBoolean:[oldHeadBlobKey stretchEncryptionKey] forKey:@"oldHeadStretchKey"]; + [plist putString:[newHeadBlobKey sha1] forKey:@"newHeadSHA1"]; + [plist putBoolean:[newHeadBlobKey stretchEncryptionKey] forKey:@"newHeadStretchKey"]; + [plist putBoolean:rewrite forKey:@"isRewrite"]; + NSData *data = [plist XMLData]; + return [fark putReflogItem:data forBucketUUID:[bucket bucketUUID] error:error]; +} +- (BOOL)addSHA1sForTreeBlobKey:(BlobKey *)treeBlobKey toSet:(NSMutableSet *)theSet error:(NSError **)error { + if (error != NULL) { + *error = nil; + } + NSAssert(treeBlobKey != nil, @"treeBlobKey can't be nil"); + if ([theSet containsObject:[treeBlobKey sha1]]) { + return YES; + } + BOOL ret = YES; + Tree *tree = [self treeForBlobKey:treeBlobKey error:error]; + if (tree == nil) { + ret = NO; + } else { + if ([tree xattrsBlobKey] != nil) { + [theSet addObject:[[tree xattrsBlobKey] sha1]]; + } + if ([tree aclBlobKey] != nil) { + [theSet addObject:[[tree aclBlobKey] sha1]]; + } + NSAutoreleasePool *pool = nil; + for (NSString *childNodeName in [tree childNodeNames]) { + [pool drain]; + pool = [[NSAutoreleasePool alloc] init]; + Node *node = [tree childNodeWithName:childNodeName]; + NSArray *dataBlobKeys = [node dataBlobKeys]; + if ([node isTree]) { + if ([dataBlobKeys count] != 1) { + SETNSERROR(@"CommitTrimmerErrorDomain", -1, @"unexpected tree %@ node %@ has %lu dataBloKeys (expected 1)", treeBlobKey, childNodeName, (unsigned long)[dataBlobKeys count]); + ret = NO; + break; + } + if (![self addSHA1sForTreeBlobKey:[dataBlobKeys objectAtIndex:0] toSet:theSet error:error]) { + ret = NO; + break; + } + } + for (BlobKey *dataBlobKey in dataBlobKeys) { + [theSet addObject:[dataBlobKey sha1]]; + } + if ([node xattrsBlobKey] != nil) { + [theSet addObject:[[node xattrsBlobKey] sha1]]; + } + if ([node aclBlobKey] != nil) { + [theSet addObject:[[node aclBlobKey] sha1]]; + } + } + if (error != NULL) { + [*error retain]; + } + [pool drain]; + if (error != NULL) { + [*error autorelease]; + } + } + if (ret) { + [theSet addObject:[treeBlobKey sha1]]; + } + return ret; +} + +@end diff --git a/Tree.h b/repo/Tree.h similarity index 97% rename from Tree.h rename to repo/Tree.h index b76b990..b9b000c 100644 --- a/Tree.h +++ b/repo/Tree.h @@ -8,7 +8,6 @@ #include -#import "Blob.h" @class BufferedInputStream; @class Node; @class BlobKey; @@ -53,6 +52,7 @@ - (BOOL)containsMissingItems; - (NSArray *)missingChildNodeNames; - (Node *)missingChildNodeWithName:(NSString *)name; +- (void)removeMissingChildNodeWithName:(NSString *)name; - (NSDictionary *)missingNodes; - (NSData *)toData; - (BOOL)ctimeMatchesStat:(struct stat *)st; diff --git a/Tree.m b/repo/Tree.m similarity index 88% rename from Tree.m rename to repo/Tree.m index f6ee8e6..8aed7d3 100644 --- a/Tree.m +++ b/repo/Tree.m @@ -12,7 +12,6 @@ #import "BooleanIO.h" #import "Node.h" #import "Tree.h" -#import "Blob.h" #import "DataInputStream.h" #import "RegexKitLite.h" #import "BufferedInputStream.h" @@ -47,7 +46,7 @@ if (self = [super init]) { nodes = [[NSMutableDictionary alloc] init]; missingNodes = [[NSMutableDictionary alloc] init]; - + if (![self readHeader:is error:error]) { [self release]; return nil; @@ -181,6 +180,9 @@ initDone: - (Node *)missingChildNodeWithName:(NSString *)name { return [missingNodes objectForKey:name]; } +- (void)removeMissingChildNodeWithName:(NSString *)name { + [missingNodes removeObjectForKey:name]; +} - (NSDictionary *)nodes { return nodes; } @@ -255,31 +257,31 @@ initDone: } Tree *other = (Tree *)object; BOOL ret = (treeVersion == [other treeVersion] - && xattrsAreCompressed == [other xattrsAreCompressed] - && [NSObject equalObjects:xattrsBlobKey and:[other xattrsBlobKey]] - && xattrsSize == [other xattrsSize] - && aclIsCompressed == [other aclIsCompressed] - && [NSObject equalObjects:aclBlobKey and:[other aclBlobKey]] - && uid == [other uid] - && gid == [other gid] - && mode == [other mode] - && mtime_sec == [other mtime_sec] - && mtime_nsec == [other mtime_nsec] - && flags == [other flags] - && finderFlags == [other finderFlags] - && extendedFinderFlags == [other extendedFinderFlags] - && st_dev == [other st_dev] - && st_ino == [other st_ino] - && st_nlink == [other st_nlink] - && st_rdev == [other st_rdev] - && ctime_sec == [other ctime_sec] - && ctime_nsec == [other ctime_nsec] - && createTime_sec == [other createTime_sec] - && createTime_nsec == [other createTime_nsec] - && [missingNodes isEqual:[other missingNodes]] - && st_blocks == [other st_blocks] - && st_blksize == [other st_blksize] - && [nodes isEqual:[other nodes]]); + && xattrsAreCompressed == [other xattrsAreCompressed] + && [NSObject equalObjects:xattrsBlobKey and:[other xattrsBlobKey]] + && xattrsSize == [other xattrsSize] + && aclIsCompressed == [other aclIsCompressed] + && [NSObject equalObjects:aclBlobKey and:[other aclBlobKey]] + && uid == [other uid] + && gid == [other gid] + && mode == [other mode] + && mtime_sec == [other mtime_sec] + && mtime_nsec == [other mtime_nsec] + && flags == [other flags] + && finderFlags == [other finderFlags] + && extendedFinderFlags == [other extendedFinderFlags] + && st_dev == [other st_dev] + && st_ino == [other st_ino] + && st_nlink == [other st_nlink] + && st_rdev == [other st_rdev] + && ctime_sec == [other ctime_sec] + && ctime_nsec == [other ctime_nsec] + && createTime_sec == [other createTime_sec] + && createTime_nsec == [other createTime_nsec] + && [missingNodes isEqual:[other missingNodes]] + && st_blocks == [other st_blocks] + && st_blksize == [other st_blksize] + && [nodes isEqual:[other nodes]]); return ret; } - (NSUInteger)hash { diff --git a/s3/BucketVerifier.h b/s3/BucketVerifier.h deleted file mode 100644 index 572cd64..0000000 --- a/s3/BucketVerifier.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - Copyright (c) 2009-2010, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - - -@class S3Service; -@class ArqRepo; - -@interface BucketVerifier : NSObject { - S3Service *s3; - NSString *s3BucketName; - NSString *computerUUID; - NSString *bucketUUID; - NSSet *objectSHA1s; - BOOL verbose; - ArqRepo *repo; - uint64_t packedBlobCount; - uint64_t nonPackedBlobCount; -} -- (id)initWithS3Service:(S3Service *)theS3 - s3BucketName:(NSString *)theS3BucketName - computerUUID:(NSString *)theComputerUUID - bucketUUID:(NSString *)theBucketUUID - s3ObjectSHA1s:(NSSet *)theObjectSHA1s - verbose:(BOOL)isVerbose - repo:(ArqRepo *)theRepo; -- (BOOL)verify:(NSError **)error; -@end diff --git a/s3/BucketVerifier.m b/s3/BucketVerifier.m deleted file mode 100644 index e6920f3..0000000 --- a/s3/BucketVerifier.m +++ /dev/null @@ -1,280 +0,0 @@ -/* - Copyright (c) 2009-2010, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#import "BucketVerifier.h" -#import "S3Service.h" -#import "ArqRepo.h" -#import "Commit.h" -#import "Tree.h" -#import "Node.h" -#import "SetNSError.h" -#import "NSError_extra.h" -#import "NSErrorCodes.h" -#import "BlobKey.h" - -@interface BucketVerifier (internal) -+ (NSString *)errorDomain; - -- (BOOL)verifyTree:(BlobKey *)theTreeBlobKey path:(NSString *)path error:(NSError **)error; -- (BOOL)verifyTree:(BlobKey *)theTreeBlobKey path:(NSString *)path childNodeName:(NSString *)childNodeName node:(Node *)node error:(NSError **)error; -- (BOOL)verify:(BlobKey *)theBlobKey error:(NSError **)error; -@end - -@implementation BucketVerifier -- (id)initWithS3Service:(S3Service *)theS3 - s3BucketName:(NSString *)theS3BucketName - computerUUID:(NSString *)theComputerUUID - bucketUUID:(NSString *)theBucketUUID - s3ObjectSHA1s:(NSSet *)theObjectSHA1s - verbose:(BOOL)isVerbose - repo:(ArqRepo *)theRepo { - if (self = [super init]) { - s3 = [theS3 retain]; - s3BucketName = [theS3BucketName retain]; - computerUUID = [theComputerUUID retain]; - bucketUUID = [theBucketUUID retain]; - objectSHA1s = [theObjectSHA1s retain]; - verbose = isVerbose; - repo = [theRepo retain]; - } - return self; -} -- (void)dealloc { - [s3 release]; - [s3BucketName release]; - [computerUUID release]; - [bucketUUID release]; - [objectSHA1s release]; - [repo release]; - [super dealloc]; -} -- (BOOL)verify:(NSError **)error { - printf("verifying all objects exist for commits in %s\n", [bucketUUID UTF8String]); - - NSError *myError = nil; - BlobKey *headBlobKey = [repo headBlobKey:&myError]; - if (headBlobKey == nil) { - if ([myError isErrorWithDomain:[ArqRepo errorDomain] code:ERROR_NOT_FOUND]) { - printf("no head commit for s3Bucket %s computerUUID %s bucketUUID %s\n", [s3BucketName UTF8String], [computerUUID UTF8String], [bucketUUID UTF8String]); - } else { - if (error != NULL) { - *error = myError; - } - return NO; - } - } else { - printf("head commit for s3Bucket %s computerUUID %s bucketUUID %s is %s\n", [s3BucketName UTF8String], [computerUUID UTF8String], [bucketUUID UTF8String], [[headBlobKey description] UTF8String]); - BlobKey *commitBlobKey = headBlobKey; - BOOL ret = YES; - NSAutoreleasePool *pool = nil; - while (commitBlobKey != nil) { - [commitBlobKey retain]; - [pool drain]; - pool = [[NSAutoreleasePool alloc] init]; - [commitBlobKey autorelease]; - - printf("verifying commit %s bucketUUID %s\n", [[commitBlobKey description] UTF8String], [bucketUUID UTF8String]); - Commit *commit = [repo commitForBlobKey:commitBlobKey error:error]; - if (commit == nil) { - ret = NO; - break; - } - if (verbose) { - printf("commit %s's tree is %s\n", [[commitBlobKey description] UTF8String], [[[commit treeBlobKey] description] UTF8String]); - } - if (![self verifyTree:[commit treeBlobKey] path:@"/" error:error]) { - ret = NO; - break; - } - commitBlobKey = [[commit parentCommitBlobKeys] anyObject]; - } - if (!ret && error != NULL) { - [*error retain]; - } - [pool drain]; - if (!ret && error != NULL) { - [*error autorelease]; - } - if (!ret) { - return NO; - } - } - printf("%qu packed blobs; %qu non-packed blobs\n", packedBlobCount, nonPackedBlobCount); - return YES; -} -@end - -@implementation BucketVerifier (internal) -+ (NSString *)errorDomain { - return @"BucketVerifierErrorDomain"; -} - -- (BOOL)verifyTree:(BlobKey *)theTreeBlobKey path:(NSString *)path error:(NSError **)error { - if (verbose) { - printf("verifying tree %s (path %s)\n", [[theTreeBlobKey description] UTF8String], [path UTF8String]); - } - Tree *tree = [repo treeForBlobKey:theTreeBlobKey error:error]; - if (tree == nil) { - SETNSERROR([BucketVerifier errorDomain], -1, @"tree %@ not found", theTreeBlobKey); - return NO; - } - if ([tree xattrsBlobKey] != nil) { - if (verbose) { - printf("verifying xattrs blobkey for tree %s\n", [[theTreeBlobKey description] UTF8String]); - } - if (![self verify:[tree xattrsBlobKey] error:error]) { - SETNSERROR([BucketVerifier errorDomain], -1, @"tree %@ xattrs blobkey %@ not found", theTreeBlobKey, [tree xattrsBlobKey]); - return NO; - } - } - if ([tree aclBlobKey] != nil) { - if (verbose) { - printf("verifying aclSHA1 for tree %s\n", [[theTreeBlobKey description] UTF8String]); - } - if (![self verify:[tree aclBlobKey] error:error]) { - SETNSERROR([BucketVerifier errorDomain], -1, @"tree %@ acl blobkey %@ not found", theTreeBlobKey, [tree aclBlobKey]); - return NO; - } - } - BOOL ret = YES; - NSArray *childNodeNames = [tree childNodeNames]; - NSAutoreleasePool *pool = nil; - for (NSString *childNodeName in childNodeNames) { - [childNodeName retain]; - [pool release]; - pool = [[NSAutoreleasePool alloc] init]; - [childNodeName autorelease]; - - Node *node = [tree childNodeWithName:childNodeName]; - if (![self verifyTree:theTreeBlobKey path:path childNodeName:childNodeName node:node error:error]) { - ret = NO; - break; - } - } - if (!ret && error != NULL) { - [*error retain]; - } - [pool drain]; - if (!ret && error != NULL) { - [*error autorelease]; - } - return ret; -} -- (BOOL)verifyTree:(BlobKey *)theTreeBlobKey path:(NSString *)path childNodeName:(NSString *)childNodeName node:(Node *)node error:(NSError **)error { - NSArray *dataBlobKeys = [node dataBlobKeys]; - NSString *childPath = [path stringByAppendingPathComponent:childNodeName]; - if ([node isTree]) { - NSAssert([dataBlobKeys count] == 1, ([NSString stringWithFormat:@"tree %@ node %@ must have exactly 1 dataBlobKey", [[theTreeBlobKey description] UTF8String], childNodeName])); - if (![self verifyTree:[dataBlobKeys objectAtIndex:0] path:childPath error:error]) { - return NO; - } - } else { - if (verbose) { - printf("verifying data sha1s for node %s\n", [childPath UTF8String]); - } - for (BlobKey *dataBlobKey in dataBlobKeys) { - if (![self verify:dataBlobKey error:error]) { - SETNSERROR([BucketVerifier errorDomain], -1, @"missing data blobkey %@ for node %@ in tree %@", dataBlobKey, childNodeName, theTreeBlobKey); - return NO; - } - } - if ([node thumbnailBlobKey] != nil) { - if (verbose) { - printf("verifying thumbnailSHA1 for node %s\n", [childPath UTF8String]); - } - if (![self verify:[node thumbnailBlobKey] error:error]) { - SETNSERROR([BucketVerifier errorDomain], -1, @"missing thumbnail blobkey %@ for node %@ in tree %@", [node thumbnailBlobKey], childNodeName, theTreeBlobKey); - return NO; - } - } - if ([node previewBlobKey] != nil) { - if (verbose) { - printf("verifying previewSHA1 for node %s\n", [childPath UTF8String]); - } - if (![self verify:[node previewBlobKey] error:error]) { - SETNSERROR([BucketVerifier errorDomain], -1, @"missing preview blobkey %@ for node %@ in tree %@", [node previewBlobKey], childNodeName, theTreeBlobKey); - return NO; - } - } - if ([node xattrsBlobKey] != nil) { - if (verbose) { - printf("verifying xattrsSHA1 for node %s\n", [childPath UTF8String]); - } - if (![self verify:[node xattrsBlobKey] error:error]) { - SETNSERROR([BucketVerifier errorDomain], -1, @"missing xattrs blobkey %@ for node %@ in tree %@", [node xattrsBlobKey], childNodeName, theTreeBlobKey); - return NO; - } - } - if ([node aclBlobKey] != nil) { - if (verbose) { - printf("verifying aclSHA1 for node %s\n", [childPath UTF8String]); - } - if (![self verify:[node aclBlobKey] error:error]) { - SETNSERROR([BucketVerifier errorDomain], -1, @"missing acl blobkey %@ for node %@ in tree %@", [node aclBlobKey], childNodeName, theTreeBlobKey); - return NO; - } - } - } - return YES; -} -- (BOOL)verify:(BlobKey *)theBlobKey error:(NSError **)error { - if (theBlobKey == nil) { - return YES; - } - - if ([objectSHA1s containsObject:[theBlobKey sha1]]) { - if (verbose) { - printf("blobkey %s: blob\n", [[theBlobKey description] UTF8String]); - } - nonPackedBlobCount++; - return YES; - } - - BOOL contains = NO; - NSString *packSetName = nil; - NSString *packSHA1 = nil; - if (![repo containsPackedBlob:&contains forBlobKey:theBlobKey packSetName:&packSetName packSHA1:&packSHA1 error:error]) { - return NO; - } - if (contains) { - if (verbose) { - printf("blobkey %s: pack set %s, packSHA1 %s\n", [[theBlobKey description] UTF8String], [packSetName UTF8String], [packSHA1 UTF8String]); - } - packedBlobCount++; - return YES; - } - - SETNSERROR([BucketVerifier errorDomain], ERROR_NOT_FOUND, @"blobkey %@ not found in packsets or objects", theBlobKey); - return NO; -} - -@end diff --git a/s3/NSError_S3.h b/s3/NSError_S3.h deleted file mode 100644 index 3f9a757..0000000 --- a/s3/NSError_S3.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - - - -#define S3_ERROR_DOMAIN @"S3" - - -@interface NSError (S3) -+ (NSError *)amazonErrorWithHTTPStatusCode:(int)theHTTPStatusCode responseBody:(NSData *)theResponseBody; -@end diff --git a/s3/NSError_S3.m b/s3/NSError_S3.m deleted file mode 100644 index 8cf2ed2..0000000 --- a/s3/NSError_S3.m +++ /dev/null @@ -1,102 +0,0 @@ -/* - Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#import "NSError_S3.h" -#import "S3Service.h" -#import "NSXMLNode_extra.h" -#import "NSError_extra.h" - - -@interface NSError (S3Internal) -+ (NSXMLNode *)errorNodeWithinXMLData:(NSData *)theXMLData; -@end - -@implementation NSError (S3) -+ (NSError *)amazonErrorWithHTTPStatusCode:(int)theHTTPStatusCode responseBody:(NSData *)theResponseBody { - NSXMLNode *errorNode = [NSError errorNodeWithinXMLData:theResponseBody]; - if (errorNode == nil) { - return [NSError errorWithDomain:[S3Service errorDomain] code:S3SERVICE_ERROR_UNEXPECTED_RESPONSE userInfo:[NSDictionary dictionaryWithObject:@"error parsing S3 response" forKey:NSLocalizedDescriptionKey]]; - } - NSString *errorCode = [[errorNode childNodeNamed:@"Code"] stringValue]; - NSString *errorMessage = [[errorNode childNodeNamed:@"Message"] stringValue]; - NSString *endpoint = [[errorNode childNodeNamed:@"Endpoint"] stringValue]; - NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; - [userInfo setObject:[NSNumber numberWithInt:theHTTPStatusCode] forKey:@"HTTPStatusCode"]; - if (errorCode != nil) { - [userInfo setObject:errorCode forKey:@"AmazonCode"]; - } - if (errorMessage != nil) { - [userInfo setObject:errorMessage forKey:@"AmazonMessage"]; - } - if (endpoint != nil) { - [userInfo setObject:endpoint forKey:@"AmazonEndpoint"]; - } - NSString *description = @"Amazon error"; - if (errorCode != nil && errorMessage != nil) { - description = errorMessage; - NSMutableString *details = [NSMutableString stringWithFormat:@"Code=%@; Message=%@", errorCode, errorMessage]; - if (endpoint != nil) { - [details appendFormat:@"; Endpoint=%@", endpoint]; - } - NSError *underlying = [NSError errorWithDomain:@"AmazonErrorDomain" code:theHTTPStatusCode description:details]; - [userInfo setObject:underlying forKey:NSUnderlyingErrorKey]; - } - [userInfo setObject:description forKey:NSLocalizedDescriptionKey]; - return [NSError errorWithDomain:[S3Service errorDomain] code:S3SERVICE_ERROR_AMAZON_ERROR userInfo:userInfo]; -} -@end - -@implementation NSError (S3Internal) -+ (NSXMLNode *)errorNodeWithinXMLData:(NSData *)theXMLData { - NSError *parseError = nil; - NSXMLDocument *xmlDoc = [[[NSXMLDocument alloc] initWithData:theXMLData options:0 error:&parseError] autorelease]; - if (xmlDoc == nil) { - HSLogError(@"error parsing S3 response: %@", [parseError localizedDescription]); - return nil; - } - NSArray *errorNodes = [[xmlDoc rootElement] nodesForXPath:@"//Error" error:&parseError]; - - if (errorNodes == nil) { - HSLogError(@"error finding Error node in Amazon error XML: %@", [parseError localizedDescription]); - return nil; - } - - if ([errorNodes count] == 0) { - HSLogWarn(@"missing Error node in S3 XML response"); - return nil; - } - if ([errorNodes count] > 1) { - HSLogWarn(@"ignoring additional Error nodes in S3 XML response"); - } - return [errorNodes objectAtIndex:0]; -} -@end diff --git a/s3/S3Region.h b/s3/S3Region.h deleted file mode 100644 index 91fb092..0000000 --- a/s3/S3Region.h +++ /dev/null @@ -1,40 +0,0 @@ -// -// S3Region.h -// Arq -// -// Created by Stefan Reitshamer on 2/11/12. -// Copyright 2012 __MyCompanyName__. All rights reserved. -// - - - - -@interface S3Region : NSObject { - NSString *bucketNameSuffix; - NSString *legacyBucketNameSuffix; - NSString *locationConstraint; - NSString *endpoint; - NSString *displayName; - double dollarsPerGBMonthStandard; - double dollarsPerGBMonthRRS; -} -+ (NSArray *)allS3Regions; -+ (S3Region *)s3RegionForBucketName:(NSString *)theBucketName; -+ (S3Region *)usStandard; -+ (S3Region *)usWestNorthernCalifornia; -+ (S3Region *)usWestOregon; -+ (S3Region *)euIreland; -+ (S3Region *)asiaPacificSingapore; -+ (S3Region *)asiaPacificSydney; -+ (S3Region *)asiaPacificTokyo; -+ (S3Region *)southAmericaSaoPaulo; - -- (NSString *)bucketNameSuffix; -- (NSString *)legacyBucketNameSuffix; -- (NSString *)locationConstraint; -- (NSString *)endpoint; -- (NSString *)displayName; -- (double)dollarsPerGBMonthStandard; -- (double)dollarsPerGBMonthRRS; -- (NSString *)bucketNameForAccessKeyID:(NSString *)theAccessKeyID; -@end diff --git a/s3/S3Region.m b/s3/S3Region.m deleted file mode 100644 index 5aa2121..0000000 --- a/s3/S3Region.m +++ /dev/null @@ -1,172 +0,0 @@ -// -// S3Region.m -// Arq -// -// Created by Stefan Reitshamer on 2/11/12. -// Copyright 2012 __MyCompanyName__. All rights reserved. -// - -#import "S3Region.h" - - -@interface S3Region (internal) -- (id)initWithBucketNameSuffix:(NSString *)theBucketNameSuffix - legacyBucketNameSuffix:(NSString *)theLegacyBucketNameSuffix - locationConstraint:(NSString *)theLocationConstraint - endpoint:(NSString *)theEndpoint - displayName:(NSString *)theDisplayName - dollarsPerGBMonthStandard:(double)theDollarsPerGBMonthStandard - dollarsPerGBMonthRRS:(double)theDollarsPerGBMonthRRS; -@end - -@implementation S3Region -+ (NSArray *)allS3Regions { - NSMutableArray *ret = [NSMutableArray array]; - [ret addObject:[S3Region usStandard]]; - [ret addObject:[S3Region usWestNorthernCalifornia]]; - [ret addObject:[S3Region usWestOregon]]; - [ret addObject:[S3Region euIreland]]; - [ret addObject:[S3Region asiaPacificSingapore]]; - [ret addObject:[S3Region asiaPacificSydney]]; - [ret addObject:[S3Region asiaPacificTokyo]]; - [ret addObject:[S3Region southAmericaSaoPaulo]]; - return ret; -} -+ (S3Region *)s3RegionForBucketName:(NSString *)theBucketName { - for (S3Region *region in [S3Region allS3Regions]) { - if ([[region bucketNameSuffix] length] > 0 && ([theBucketName hasSuffix:[region bucketNameSuffix]] || [theBucketName hasSuffix:[region legacyBucketNameSuffix]])) { - return region; - } - } - return [S3Region usStandard]; -} -+ (S3Region *)usStandard { - return [[[S3Region alloc] initWithBucketNameSuffix:@"" - legacyBucketNameSuffix:@"" - locationConstraint:nil - endpoint:@"s3.amazonaws.com" - displayName:@"US Standard" - dollarsPerGBMonthStandard:.125 - dollarsPerGBMonthRRS:.093] autorelease]; -} -+ (S3Region *)usWestNorthernCalifornia { - return [[[S3Region alloc] initWithBucketNameSuffix:@"-us-west-1" - legacyBucketNameSuffix:@"us-west-1" - locationConstraint:@"us-west-1" - endpoint:@"s3-us-west-1.amazonaws.com" - displayName:@"US West (Northern California)" - dollarsPerGBMonthStandard:.140 - dollarsPerGBMonthRRS:.103] autorelease]; -} -+ (S3Region *)usWestOregon { - return [[[S3Region alloc] initWithBucketNameSuffix:@"-us-west-2" - legacyBucketNameSuffix:@"us-west-2" - locationConstraint:@"us-west-2" - endpoint:@"s3-us-west-2.amazonaws.com" - displayName:@"US West (Oregon)" - dollarsPerGBMonthStandard:.125 - dollarsPerGBMonthRRS:.093] autorelease]; -} -+ (S3Region *)euIreland { - return [[[S3Region alloc] initWithBucketNameSuffix:@"-eu" - legacyBucketNameSuffix:@"eu" - locationConstraint:@"EU" - endpoint:@"s3-eu-west-1.amazonaws.com" - displayName:@"EU (Ireland)" - dollarsPerGBMonthStandard:.125 - dollarsPerGBMonthRRS:.093] autorelease]; -} -+ (S3Region *)asiaPacificSingapore { - return [[[S3Region alloc] initWithBucketNameSuffix:@"-ap-southeast-1" - legacyBucketNameSuffix:@"ap-southeast-1" - locationConstraint:@"ap-southeast-1" - endpoint:@"s3-ap-southeast-1.amazonaws.com" - displayName:@"Asia Pacific (Singapore)" - dollarsPerGBMonthStandard:.125 - dollarsPerGBMonthRRS:.093] autorelease]; -} -+ (S3Region *)asiaPacificSydney { - return [[[S3Region alloc] initWithBucketNameSuffix:@"-ap-southeast-2" - legacyBucketNameSuffix:@"ap-southeast-2" - locationConstraint:@"ap-southeast-2" - endpoint:@"s3-ap-southeast-2.amazonaws.com" - displayName:@"Asia Pacific (Sydney)" - dollarsPerGBMonthStandard:.105 - dollarsPerGBMonthRRS:.084] autorelease]; -} -+ (S3Region *)asiaPacificTokyo { - return [[[S3Region alloc] initWithBucketNameSuffix:@"-ap-northeast-1" - legacyBucketNameSuffix:@"ap-northeast-1" - locationConstraint:@"ap-northeast-1" - endpoint:@"s3-ap-northeast-1.amazonaws.com" - displayName:@"Asia Pacific (Tokyo)" - dollarsPerGBMonthStandard:.130 - dollarsPerGBMonthRRS:.100] autorelease]; -} -+ (S3Region *)southAmericaSaoPaulo { - return [[[S3Region alloc] initWithBucketNameSuffix:@"-sa-east-1" - legacyBucketNameSuffix:@"sa-east-1" - locationConstraint:@"sa-east-1" - endpoint:@"s3-sa-east-1.amazonaws.com" - displayName:@"South America (Sao Paulo)" - dollarsPerGBMonthStandard:.170 - dollarsPerGBMonthRRS:.127] autorelease]; -} - - -- (void)dealloc { - [bucketNameSuffix release]; - [displayName release]; - [super dealloc]; -} - -- (NSString *)bucketNameSuffix { - return bucketNameSuffix; -} -- (NSString *)legacyBucketNameSuffix { - return legacyBucketNameSuffix; -} -- (NSString *)locationConstraint { - return locationConstraint; -} -- (NSString *)endpoint { - return endpoint; -} -- (NSString *)displayName { - return displayName; -} -- (double)dollarsPerGBMonthStandard { - return dollarsPerGBMonthStandard; -} -- (double)dollarsPerGBMonthRRS { - return dollarsPerGBMonthRRS; -} -- (NSString *)bucketNameForAccessKeyID:(NSString *)theAccessKeyID { - return [[theAccessKeyID lowercaseString] stringByAppendingFormat:@"comhaystacksoftwarearq%@", bucketNameSuffix]; -} - -- (NSString *)description { - return displayName; -} -@end - -@implementation S3Region (internal) -- (id)initWithBucketNameSuffix:(NSString *)theBucketNameSuffix - legacyBucketNameSuffix:(NSString *)theLegacyBucketNameSuffix - locationConstraint:(NSString *)theLocationConstraint - endpoint:(NSString *)theEndpoint - displayName:(NSString *)theDisplayName - dollarsPerGBMonthStandard:(double)theDollarsPerGBMonthStandard - dollarsPerGBMonthRRS:(double)theDollarsPerGBMonthRRS { - if (self = [super init]) { - bucketNameSuffix = [theBucketNameSuffix retain]; - legacyBucketNameSuffix = [theLegacyBucketNameSuffix retain]; - locationConstraint = [theLocationConstraint retain]; - endpoint = [theEndpoint retain]; - displayName = [theDisplayName retain]; - dollarsPerGBMonthStandard = theDollarsPerGBMonthStandard; - dollarsPerGBMonthRRS = theDollarsPerGBMonthRRS; - } - return self; -} -@end diff --git a/s3/S3Request.h b/s3/S3Request.h deleted file mode 100644 index 03c0460..0000000 --- a/s3/S3Request.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - - -@class S3AuthorizationProvider; -@class ServerBlob; -@class Blob; -@protocol HTTPConnectionDelegate; -@class HTTPTimeoutSetting; - - -@interface S3Request : NSObject { - NSString *method; - NSURL *url; - S3AuthorizationProvider *sap; - BOOL withSSL; - BOOL retryOnTransientError; - HTTPTimeoutSetting *httpTimeoutSetting; - id httpConnectionDelegate; - Blob *blob; - NSData *blobData; - uint64_t length; - NSMutableDictionary *extraHeaders; - unsigned long long bytesUploaded; -} -- (id)initWithMethod:(NSString *)theMethod path:(NSString *)thePath queryString:(NSString *)theQueryString authorizationProvider:(S3AuthorizationProvider *)theSAP useSSL:(BOOL)ssl retryOnTransientError:(BOOL)retry error:(NSError **)error; -- (id)initWithMethod:(NSString *)theMethod path:(NSString *)thePath queryString:(NSString *)theQueryString authorizationProvider:(S3AuthorizationProvider *)theSAP useSSL:(BOOL)ssl retryOnTransientError:(BOOL)retry httpConnectionDelegate:(id )theHTTPConnectionDelegate error:(NSError **)error; -- (id)initWithMethod:(NSString *)theMethod path:(NSString *)thePath queryString:(NSString *)theQueryString authorizationProvider:(S3AuthorizationProvider *)theSAP useSSL:(BOOL)ssl retryOnTransientError:(BOOL)retry httpConnectionDelegate:(id )theHTTPConnectionDelegate httpTimeoutSetting:(HTTPTimeoutSetting *)theTimeoutSetting error:(NSError **)error; -- (void)setBlob:(Blob *)theBlob length:(uint64_t)theLength; -- (void)setHeader:(NSString *)value forKey:(NSString *)key; -- (ServerBlob *)newServerBlob:(NSError **)error; -@end diff --git a/s3/S3Request.m b/s3/S3Request.m deleted file mode 100644 index 9c7a6d2..0000000 --- a/s3/S3Request.m +++ /dev/null @@ -1,281 +0,0 @@ -/* - Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#import "S3Request.h" -#import "HTTP.h" -#import "URLConnection.h" -#import "ServerBlob.h" -#import "S3Service.h" -#import "SetNSError.h" -#import "RegexKitLite.h" -#import "NSErrorCodes.h" -#import "NSError_extra.h" -#import "S3AuthorizationProvider.h" -#import "NSError_S3.h" -#import "S3Region.h" -#import "HTTPConnectionFactory.h" -#import "HTTPTimeoutSetting.h" - - -#define INITIAL_RETRY_SLEEP (0.5) -#define RETRY_SLEEP_GROWTH_FACTOR (1.5) -#define MAX_RETRY_SLEEP (5.0) - -@interface S3Request (internal) -- (ServerBlob *)newServerBlobOnce:(NSError **)error; -@end - -@implementation S3Request -- (id)initWithMethod:(NSString *)theMethod path:(NSString *)thePath queryString:(NSString *)theQueryString authorizationProvider:(S3AuthorizationProvider *)theSAP useSSL:(BOOL)ssl retryOnTransientError:(BOOL)retry error:(NSError **)error { - return [self initWithMethod:theMethod path:thePath queryString:theQueryString authorizationProvider:theSAP useSSL:ssl retryOnTransientError:retry httpConnectionDelegate:nil error:error]; -} -- (id)initWithMethod:(NSString *)theMethod path:(NSString *)thePath queryString:(NSString *)theQueryString authorizationProvider:(S3AuthorizationProvider *)theSAP useSSL:(BOOL)ssl retryOnTransientError:(BOOL)retry httpConnectionDelegate:(id )theHTTPConnectionDelegate error:(NSError **)error { - HTTPTimeoutSetting *theTimeoutSetting = [[[HTTPTimeoutSetting alloc] init] autorelease]; - return [self initWithMethod:theMethod path:thePath queryString:theQueryString authorizationProvider:theSAP useSSL:ssl retryOnTransientError:retry httpConnectionDelegate:theHTTPConnectionDelegate httpTimeoutSetting:theTimeoutSetting error:error]; -} -- (id)initWithMethod:(NSString *)theMethod path:(NSString *)thePath queryString:(NSString *)theQueryString authorizationProvider:(S3AuthorizationProvider *)theSAP useSSL:(BOOL)ssl retryOnTransientError:(BOOL)retry httpConnectionDelegate:(id )theHTTPConnectionDelegate httpTimeoutSetting:(HTTPTimeoutSetting *)theTimeoutSetting error:(NSError **)error { - if (self = [super init]) { - method = [theMethod copy]; - sap = [theSAP retain]; - retryOnTransientError = retry; - httpTimeoutSetting = [theTimeoutSetting retain]; - httpConnectionDelegate = theHTTPConnectionDelegate; // Don't retain it. - extraHeaders = [[NSMutableDictionary alloc] init]; - - NSString *endpoint = nil; - if ([thePath isEqualToString:@"/"]) { - endpoint = [[S3Region usStandard] endpoint]; - } else { - NSRange s3BucketRange = [thePath rangeOfRegex:@"^/([^/]+)" capture:1]; - NSAssert(s3BucketRange.location != NSNotFound, @"invalid path -- missing s3 bucket name!"); - NSString *s3BucketName = [thePath substringWithRange:s3BucketRange]; - endpoint = [[S3Region s3RegionForBucketName:s3BucketName] endpoint]; - } - if (theQueryString != nil) { - thePath = [thePath stringByAppendingString:theQueryString]; - } - NSString *urlString = [NSString stringWithFormat:@"http%@://%@%@", (ssl ? @"s" : @""), endpoint, thePath]; - url = [[NSURL alloc] initWithString:urlString]; - if (url == nil) { - SETNSERROR([S3Service errorDomain], -1, @"invalid URL: %@", urlString); - [self release]; - return nil; - } - } - return self; -} -- (void)dealloc { - [method release]; - [url release]; - [httpTimeoutSetting release]; - [sap release]; - [blob release]; - [blobData release]; - [extraHeaders release]; - [super dealloc]; -} -- (void)setBlob:(Blob *)theBlob length:(uint64_t)theLength { - if (blob != theBlob) { - [blob release]; - blob = [theBlob retain]; - } - length = theLength; -} -- (void)setHeader:(NSString *)value forKey:(NSString *)key { - [extraHeaders setObject:value forKey:key]; -} -- (ServerBlob *)newServerBlob:(NSError **)error { - [blobData release]; - blobData = nil; - if (blob != nil) { - blobData = [[blob slurp:error] retain]; - if (blobData == nil) { - return nil; - } - } - NSAutoreleasePool *pool = nil; - NSTimeInterval sleepTime = INITIAL_RETRY_SLEEP; - ServerBlob *sb = nil; - NSError *myError = nil; - for (;;) { - [pool drain]; - pool = [[NSAutoreleasePool alloc] init]; - BOOL transientError = NO; - BOOL needSleep = NO; - myError = nil; - sb = [self newServerBlobOnce:&myError]; - if (sb != nil) { - break; - } - if ([myError isSSLError]) { - HSLogError(@"SSL error: %@", myError); - [myError logSSLCerts]; - } - if ([myError isErrorWithDomain:[S3Service errorDomain] code:ERROR_NOT_FOUND]) { - break; - } else if ([myError isErrorWithDomain:[S3Service errorDomain] code:ERROR_TEMPORARY_REDIRECT]) { - NSString *location = [[myError userInfo] objectForKey:@"location"]; - HSLogDebug(@"redirecting %@ to %@", url, location); - [url release]; - url = [[NSURL alloc] initWithString:location]; - if (url == nil) { - HSLogError(@"invalid redirect URL %@", location); - myError = [NSError errorWithDomain:[S3Service errorDomain] code:-1 description:[NSString stringWithFormat:@"invalid redirect URL %@", location]]; - break; - } - } else if ([myError isErrorWithDomain:[S3Service errorDomain] code:S3SERVICE_ERROR_AMAZON_ERROR]) { - int httpStatusCode = [[[myError userInfo] objectForKey:@"HTTPStatusCode"] intValue]; - NSString *amazonCode = [[myError userInfo] objectForKey:@"AmazonCode"]; - - if (retryOnTransientError && [amazonCode isEqualToString:@"RequestTimeout"]) { - transientError = YES; - - } else if (httpStatusCode == HTTP_INTERNAL_SERVER_ERROR) { - transientError = YES; - needSleep = YES; - - } else if (retryOnTransientError && httpStatusCode == HTTP_SERVICE_NOT_AVAILABLE) { - transientError = YES; - needSleep = YES; - - } else { - HSLogError(@"%@ %@ (blob %@): %@", method, url, blob, myError); - break; - } - } else if ([myError isConnectionResetError]) { - transientError = YES; - } else if (retryOnTransientError && [myError isTransientError]) { - transientError = YES; - needSleep = YES; - } else { - HSLogError(@"%@ %@ (blob %@): %@", method, url, blob, myError); - break; - } - - if (transientError) { - HSLogWarn(@"retrying %@ %@ (request body %@): %@", method, url, blob, myError); - } - if (needSleep) { - [NSThread sleepForTimeInterval:sleepTime]; - sleepTime *= RETRY_SLEEP_GROWTH_FACTOR; - if (sleepTime > MAX_RETRY_SLEEP) { - sleepTime = MAX_RETRY_SLEEP; - } - } - } - [myError retain]; - [pool drain]; - [myError autorelease]; - if (error != NULL) { *error = myError; } - return sb; -} -@end - -@implementation S3Request (internal) -- (ServerBlob *)newServerBlobOnce:(NSError **)error { - id conn = [[[HTTPConnectionFactory theFactory] newHTTPConnectionToURL:url method:method httpTimeoutSetting:httpTimeoutSetting httpConnectionDelegate:httpConnectionDelegate] autorelease]; - if (conn == nil) { - return nil; - } - [conn setRequestHostHeader]; - [conn setRFC822DateRequestHeader]; - if (blob != nil) { - if ([blob mimeType] != nil) { - [conn setRequestHeader:[blob mimeType] forKey:@"Content-Type"]; - } - if ([blob downloadName] != nil) { - [conn setRequestContentDispositionHeader:[blob downloadName]]; - } - [conn setRequestHeader:[NSString stringWithFormat:@"%qu", length] forKey:@"Content-Length"]; - } - for (NSString *headerKey in [extraHeaders allKeys]) { - [conn setRequestHeader:[extraHeaders objectForKey:headerKey] forKey:headerKey]; - } - if (![sap setAuthorizationRequestHeaderOnHTTPConnection:conn error:error]) { - return nil; - } - bytesUploaded = 0; - - HSLogDebug(@"%@ %@", method, url); - - BOOL execRet = [conn executeRequestWithBody:blobData error:error]; - if (!execRet) { - HSLogDebug(@"executeRequestWithBody failed"); - return nil; - } - ServerBlob *ret = nil; - id bodyStream = [conn newResponseBodyStream:error]; - if (bodyStream == nil) { - HSLogDebug(@"newResponseBodyStream failed"); - return nil; - } - NSData *response = [bodyStream slurp:error]; - [bodyStream release]; - if (response == nil) { - return nil; - } - int code = [conn responseCode]; - if (code >= 200 && code <= 299) { - ret = [[ServerBlob alloc] initWithData:response mimeType:[conn responseContentType] downloadName:[conn responseDownloadName]]; - HSLogDebug(@"HTTP %d; returning response length=%ld", code, [response length]); - return ret; - } - - HSLogTrace(@"http response body: %@", [[[NSString alloc] initWithBytes:[response bytes] length:[response length] encoding:NSUTF8StringEncoding] autorelease]); - if (code == HTTP_NOT_FOUND) { - SETNSERROR([S3Service errorDomain], ERROR_NOT_FOUND, @"%@ not found", url); - HSLogDebug(@"returning not-found error"); - return nil; - } - if (code == HTTP_METHOD_NOT_ALLOWED) { - HSLogError(@"%@ 405 error", url); - SETNSERROR([S3Service errorDomain], ERROR_RRS_NOT_FOUND, @"%@ 405 error", url); - } - if (code == HTTP_MOVED_TEMPORARILY) { - NSString *location = [conn responseHeaderForKey:@"Location"]; - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:location forKey:@"location"]; - NSError *myError = [NSError errorWithDomain:[S3Service errorDomain] code:ERROR_TEMPORARY_REDIRECT userInfo:userInfo]; - if (error != NULL) { - *error = myError; - } - HSLogDebug(@"returning moved-temporarily error"); - return nil; - } - - NSError *myError = [NSError amazonErrorWithHTTPStatusCode:code responseBody:response]; - HSLogDebug(@"%@ %@ error: %@", method, conn, myError); - if (error != NULL) { - *error = myError; - } - return nil; -} -@end diff --git a/s3/S3Service.h b/s3/S3Service.h deleted file mode 100644 index 291bcf0..0000000 --- a/s3/S3Service.h +++ /dev/null @@ -1,76 +0,0 @@ -/* - Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - - -#import "S3Receiver.h" -#import "Blob.h" -#import "InputStream.h" -@class S3AuthorizationProvider; -@class S3Owner; -@class ServerBlob; - -#define S3_INITIAL_RETRY_SLEEP (0.5) -#define S3_RETRY_SLEEP_GROWTH_FACTOR (1.5) -#define S3_MAX_RETRY (5) - -enum { - S3SERVICE_ERROR_UNEXPECTED_RESPONSE = -51001, - S3SERVICE_ERROR_AMAZON_ERROR = -51002, - S3SERVICE_INVALID_PARAMETERS = -51003 -}; - -@interface S3Service : NSObject { - S3AuthorizationProvider *sap; - BOOL useSSL; - BOOL retryOnTransientError; -} -+ (NSString *)errorDomain; -- (id)initWithS3AuthorizationProvider:(S3AuthorizationProvider *)theSAP useSSL:(BOOL)useSSL retryOnTransientError:(BOOL)retry; -- (S3Owner *)s3Owner:(NSError **)error; -- (NSArray *)s3BucketNames:(NSError **)error; -- (BOOL)s3BucketExists:(NSString *)s3BucketName; - -- (NSArray *)pathsWithPrefix:(NSString *)prefix error:(NSError **)error; -- (NSArray *)pathsWithPrefix:(NSString *)prefix delimiter:(NSString *)delimiter error:(NSError **)error; -- (NSArray *)commonPrefixesForPathPrefix:(NSString *)prefix delimiter:(NSString *)delimiter error:(NSError **)error; -- (NSArray *)objectsWithPrefix:(NSString *)prefix error:(NSError **)error; -- (BOOL)listObjectsWithPrefix:(NSString *)prefix receiver:(id )receiver error:(NSError **)error; -- (BOOL)containsBlob:(BOOL *)contains atPath:(NSString *)path dataSize:(unsigned long long *)dataSize error:(NSError **)error; - -- (NSData *)dataAtPath:(NSString *)path error:(NSError **)error; -- (ServerBlob *)newServerBlobAtPath:(NSString *)path error:(NSError **)error; - -- (BOOL)aclXMLData:(NSData **)aclXMLData atPath:(NSString *)path error:(NSError **)error; -- (BOOL)acl:(int *)acl atPath:(NSString *)path error:(NSError **)error; - -- (S3AuthorizationProvider *)s3AuthorizationProvider; -@end diff --git a/s3/S3Service.m b/s3/S3Service.m deleted file mode 100644 index 74d2114..0000000 --- a/s3/S3Service.m +++ /dev/null @@ -1,351 +0,0 @@ -/* - Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#import "BlobACL.h" -#import "InputStream.h" -#import "Blob.h" -#import "RegexKitLite.h" -#import "NSError_S3.h" -#import "S3Owner.h" -#import "S3Lister.h" -#import "S3AuthorizationProvider.h" -#import "S3Service.h" -#import "PathReceiver.h" -#import "SetNSError.h" -#import "DataInputStream.h" -#import "HTTP.h" -#import "Streams.h" -#import "S3ObjectReceiver.h" -#import "ServerBlob.h" -#import "NSErrorCodes.h" -#import "NSData-InputStream.h" -#import "S3Request.h" -#import "NSError_extra.h" - - -/* - * WARNING: - * This class *must* be reentrant! - */ - -@interface S3Service (internal) -- (NSXMLDocument *)listBuckets:(NSError **)error; -- (BOOL)internalACL:(int *)acl atPath:(NSString *)path error:(NSError **)error; -@end - -@implementation S3Service -+ (NSString *)errorDomain { - return @"S3ServiceErrorDomain"; -} - -- (id)initWithS3AuthorizationProvider:(S3AuthorizationProvider *)theSAP useSSL:(BOOL)isUseSSL retryOnTransientError:(BOOL)retry { - if (self = [super init]) { - sap = [theSAP retain]; - useSSL = isUseSSL; - retryOnTransientError = retry; - } - return self; -} -- (void)dealloc { - [sap release]; - [super dealloc]; -} -- (S3Owner *)s3Owner:(NSError **)error { - if (error) { - *error = 0; - } - NSXMLDocument *doc = [self listBuckets:error]; - if (!doc) { - return nil; - } - NSXMLElement *rootElem = [doc rootElement]; - NSArray *idNodes = [rootElem nodesForXPath:@"//ListAllMyBucketsResult/Owner/ID" error:error]; - if (!idNodes) { - return nil; - } - if ([idNodes count] == 0) { - HSLogError(@"ListAllMyBucketsResult/Owner/ID node not found"); - return nil; - } - NSXMLNode *ownerIDNode = [idNodes objectAtIndex:0]; - NSArray *displayNameNodes = [rootElem nodesForXPath:@"//ListAllMyBucketsResult/Owner/DisplayName" error:error]; - if (!displayNameNodes) { - return nil; - } - if ([displayNameNodes count] == 0) { - HSLogError(@"ListAllMyBucketsResult/Owner/DisplayName not found"); - return nil; - } - NSXMLNode *displayNameNode = [displayNameNodes objectAtIndex:0]; - HSLogDebug(@"s3 owner ID: %@", [displayNameNode stringValue]); - return [[[S3Owner alloc] initWithDisplayName:[displayNameNode stringValue] idString:[ownerIDNode stringValue]] autorelease]; -} -- (NSArray *)s3BucketNames:(NSError **)error { - NSXMLDocument *doc = [self listBuckets:error]; - if (!doc) { - return nil; - } - NSXMLElement *rootElem = [doc rootElement]; - NSArray *nameNodes = [rootElem nodesForXPath:@"//ListAllMyBucketsResult/Buckets/Bucket/Name" error:error]; - if (!nameNodes) { - return nil; - } - NSMutableArray *bucketNames = [[[NSMutableArray alloc] init] autorelease]; - for (NSXMLNode *nameNode in nameNodes) { - [bucketNames addObject:[nameNode stringValue]]; - } - return bucketNames; -} -- (BOOL)s3BucketExists:(NSString *)s3BucketName { - NSError *error = nil; - NSArray *s3BucketNames = [self s3BucketNames:&error]; - if (!s3BucketNames) { - HSLogDebug(@"error getting S3 bucket names: %@", [error localizedDescription]); - return NO; - } - return [s3BucketNames containsObject:s3BucketName]; -} -- (NSArray *)pathsWithPrefix:(NSString *)prefix error:(NSError **)error { - return [self pathsWithPrefix:prefix delimiter:nil error:error]; -} -- (NSArray *)pathsWithPrefix:(NSString *)prefix delimiter:(NSString *)delimiter error:(NSError **)error { - PathReceiver *rec = [[[PathReceiver alloc] init] autorelease]; - S3Lister *lister = [[[S3Lister alloc] initWithS3AuthorizationProvider:sap useSSL:useSSL retryOnTransientError:retryOnTransientError prefix:prefix delimiter:delimiter receiver:rec] autorelease]; - if (![lister listObjects:error]) { - return nil; - } - NSMutableArray *ret = [NSMutableArray arrayWithArray:[rec paths]]; - [ret addObjectsFromArray:[lister foundPrefixes]]; - [ret sortUsingSelector:@selector(compare:)]; - return ret; -} -- (NSArray *)commonPrefixesForPathPrefix:(NSString *)prefix delimiter:(NSString *)delimiter error:(NSError **)error { - NSArray *paths = [self pathsWithPrefix:prefix delimiter:delimiter error:error]; - if (paths == nil) { - return nil; - } - NSMutableArray *ret = [NSMutableArray array]; - for (NSString *path in paths) { - [ret addObject:[path lastPathComponent]]; - } - return ret; -} -- (NSArray *)objectsWithPrefix:(NSString *)prefix error:(NSError **)error { - S3ObjectReceiver *receiver = [[[S3ObjectReceiver alloc] init] autorelease]; - if (![self listObjectsWithPrefix:prefix receiver:receiver error:error]) { - return NO; - } - return [receiver objects]; -} -- (BOOL)listObjectsWithPrefix:(NSString *)prefix receiver:(id )receiver error:(NSError **)error { - S3Lister *lister = [[[S3Lister alloc] initWithS3AuthorizationProvider:sap useSSL:useSSL retryOnTransientError:retryOnTransientError prefix:prefix delimiter:nil receiver:receiver] autorelease]; - return lister && [lister listObjects:error]; -} -- (BOOL)containsBlob:(BOOL *)contains atPath:(NSString *)path dataSize:(unsigned long long *)dataSize error:(NSError **)error { - BOOL ret = YES; - S3Request *s3r = [[S3Request alloc] initWithMethod:@"HEAD" path:path queryString:nil authorizationProvider:sap useSSL:useSSL retryOnTransientError:retryOnTransientError error:error]; - if (s3r == nil) { - return NO; - } - NSError *myError = nil; - ServerBlob *sb = [s3r newServerBlob:&myError]; - if (sb != nil) { - *contains = YES; - HSLogTrace(@"S3 path %@ exists", path); - } else if ([myError isErrorWithDomain:[S3Service errorDomain] code:ERROR_NOT_FOUND]) { - *contains = NO; - HSLogDebug(@"S3 path %@ does NOT exist", path); - } else if ([myError isErrorWithDomain:[S3Service errorDomain] code:ERROR_RRS_NOT_FOUND]) { - *contains = NO; - HSLogDebug(@"S3 path %@ returns 405 error", path); - } else { - *contains = NO; - ret = NO; - HSLogDebug(@"error getting HEAD for %@: %@", path, myError); - if (error != NULL) { *error = myError; } - } - [sb release]; - [s3r release]; - return ret; -} -- (NSData *)dataAtPath:(NSString *)path error:(NSError **)error { - ServerBlob *sb = [self newServerBlobAtPath:path error:error]; - if (sb == nil) { - return nil; - } - NSData *data = [sb slurp:error]; - [sb release]; - return data; -} -- (ServerBlob *)newServerBlobAtPath:(NSString *)path error:(NSError **)error { - HSLogDebug(@"getting %@", path); - S3Request *s3r = [[S3Request alloc] initWithMethod:@"GET" path:path queryString:nil authorizationProvider:sap useSSL:useSSL retryOnTransientError:retryOnTransientError error:error]; - if (s3r == nil) { - return nil; - } - ServerBlob *sb = [s3r newServerBlob:error]; - [s3r release]; - return sb; -} -- (BOOL)aclXMLData:(NSData **)aclXMLData atPath:(NSString *)path error:(NSError **)error { - *aclXMLData = nil; - HSLogDebug(@"getting %@", path); - S3Request *s3r = [[S3Request alloc] initWithMethod:@"GET" path:path queryString:@"?acl" authorizationProvider:sap useSSL:useSSL retryOnTransientError:retryOnTransientError error:error]; - if (s3r == nil) { - return NO; - } - ServerBlob *sb = [s3r newServerBlob:error]; - [s3r release]; - if (sb == nil) { - return NO; - } - NSData *output = [sb slurp:error]; - [sb release]; - if (output == nil) { - return NO; - } - *aclXMLData = output; - return YES; -} -- (BOOL)acl:(int *)acl atPath:(NSString *)path error:(NSError **)error { - if (error != NULL) { - *error = nil; - } - *acl = 0; - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - BOOL ret = [self internalACL:acl atPath:path error:error]; - if (!ret && error != NULL) { - [*error retain]; - } - [pool drain]; - if (!ret && error != NULL) { - [*error autorelease]; - } - return ret; -} - -- (S3AuthorizationProvider *)s3AuthorizationProvider { - return sap; -} - - -#pragma mark NSCopying -- (id)copyWithZone:(NSZone *)zone { - return [[S3Service alloc] initWithS3AuthorizationProvider:sap useSSL:useSSL retryOnTransientError:retryOnTransientError]; -} -@end - -@implementation S3Service (internal) -- (NSXMLDocument *)listBuckets:(NSError **)error { - S3Request *s3r = [[S3Request alloc] initWithMethod:@"GET" path:@"/" queryString:nil authorizationProvider:sap useSSL:useSSL retryOnTransientError:retryOnTransientError error:error]; - if (s3r == nil) { - return nil; - } - ServerBlob *sb = [s3r newServerBlob:error]; - [s3r release]; - if (sb == nil) { - return nil; - } - NSData *data = [sb slurp:error]; - [sb release]; - if (data == nil) { - return nil; - } - NSError *myError = nil; - NSXMLDocument *ret = [[[NSXMLDocument alloc] initWithData:data options:0 error:&myError] autorelease]; - if (ret == nil) { - HSLogDebug(@"error parsing List Buckets result XML %@", [[[NSString alloc] initWithBytes:[data bytes] length:[data length] encoding:NSUTF8StringEncoding] autorelease]); - SETNSERROR([S3Service errorDomain], [myError code], @"error parsing S3 List Buckets result XML: %@", [myError description]); - } - return ret; -} -- (BOOL)internalACL:(int *)acl atPath:(NSString *)path error:(NSError **)error { - NSData *aclData; - if (![self aclXMLData:&aclData atPath:path error:error]) { - return NO; - } - NSError *myError = nil; - NSXMLDocument *xmlDoc = [[[NSXMLDocument alloc] initWithData:aclData options:0 error:&myError] autorelease]; - if (!xmlDoc) { - SETNSERROR([S3Service errorDomain], [myError code], @"error parsing S3 Get ACL response: %@", myError); - return NO; - } - HSLogTrace(@"ACL XML: %@", xmlDoc); - NSArray *grants = [xmlDoc nodesForXPath:@"AccessControlPolicy/AccessControlList/Grant" error:error]; - if (!grants) { - return NO; - } - BOOL publicRead = NO; - BOOL publicWrite = NO; - for (NSXMLElement *grant in grants) { - NSArray *grantees = [grant nodesForXPath:@"Grantee" error:error]; - if (!grantees) { - return NO; - } - for (NSXMLElement *grantee in grantees) { - NSString *xsiType = [[grantee attributeForName:@"xsi:type"] stringValue]; - if ([xsiType isEqualToString:@"Group"]) { - NSArray *uris = [grantee nodesForXPath:@"URI" error:error]; - if (!uris) { - return NO; - } - if ([uris count] > 0) { - if ([[[uris objectAtIndex:0] stringValue] isEqualToString:@"http://acs.amazonaws.com/groups/global/AllUsers"]) { - NSArray *permissions = [grant nodesForXPath:@"Permission" error:error]; - if (!permissions) { - return NO; - } - for (NSXMLElement *permission in permissions) { - if ([[permission stringValue] isEqualToString:@"WRITE"]) { - publicWrite = YES; - } else if ([[permission stringValue] isEqualToString:@"READ"]) { - publicRead = YES; - } else { - SETNSERROR([S3Service errorDomain], S3SERVICE_ERROR_UNEXPECTED_RESPONSE, @"unexpected permission"); - return NO; - } - } - } - } - } - } - } - if (publicRead && publicWrite) { - *acl = PUBLIC_READ_WRITE; - } else if (publicRead) { - *acl = PUBLIC_READ; - } else { - *acl = PRIVATE; - } - return YES; -} -@end diff --git a/shared/BinarySHA1.h b/shared/BinarySHA1.h deleted file mode 100644 index 732911c..0000000 --- a/shared/BinarySHA1.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - - - - -@interface BinarySHA1 : NSObject { -} -+ (NSComparisonResult)compare:(const void *)a to:(const void *)b; -@end diff --git a/shared/BinarySHA1.m b/shared/BinarySHA1.m deleted file mode 100644 index 669a258..0000000 --- a/shared/BinarySHA1.m +++ /dev/null @@ -1,50 +0,0 @@ -/* - Copyright (c) 2009, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#import "BinarySHA1.h" - - -@implementation BinarySHA1 -+ (NSComparisonResult)compare:(const void *)a to:(const void *)b { - unsigned char *left = (unsigned char *)a; - unsigned char *right = (unsigned char *)b; - for (int i = 0; i < 20; i++) { - if (left[i] < right[i]) { - return NSOrderedAscending; - } - if (left[i] > right[i]) { - return NSOrderedDescending; - } - } - return NSOrderedSame; -} -@end diff --git a/shared/Blob.h b/shared/Blob.h deleted file mode 100644 index 9634b01..0000000 --- a/shared/Blob.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - - -#import "InputStreamFactory.h" -@class CryptoKey; - -@interface Blob : NSObject { - NSString *mimeType; - NSString *downloadName; - id inputStreamFactory; -} -- (id)initWithInputStreamFactory:(id )theFactory mimeType:(NSString *)theMimeType downloadName:(NSString *)theDownloadName; -- (id)initWithData:(NSData *)theData mimeType:(NSString *)theMimeType downloadName:(NSString *)theDownloadName dataDescription:(NSString *)theDataDescription; -- (NSString *)mimeType; -- (NSString *)downloadName; -- (id )inputStreamFactory; -- (NSData *)slurp:(NSError **)error; -- (Blob *)encryptedBlobWithCryptoKey:(CryptoKey *)theCryptoKey error:(NSError **)error; -@end diff --git a/shared/Blob.m b/shared/Blob.m deleted file mode 100644 index b9ce1f1..0000000 --- a/shared/Blob.m +++ /dev/null @@ -1,103 +0,0 @@ -/* - Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#import "Blob.h" -#import "DataInputStreamFactory.h" -#import "NSData-Base64Extensions.h" -#import "EncryptedInputStreamFactory.h" -#import "NSData-Encrypt.h" - -@implementation Blob -- (id)initWithInputStreamFactory:(id )theFactory mimeType:(NSString *)theMimeType downloadName:(NSString *)theDownloadName { - if (self = [super init]) { - mimeType = [theMimeType copy]; - downloadName = [theDownloadName copy]; - inputStreamFactory = [theFactory retain]; - } - return self; -} -- (id)initWithData:(NSData *)theData mimeType:(NSString *)theMimeType downloadName:(NSString *)theDownloadName dataDescription:(NSString *)theDataDescription { - if (self = [super init]) { - mimeType = [theMimeType copy]; - downloadName = [theDownloadName copy]; - inputStreamFactory = [[DataInputStreamFactory alloc] initWithData:theData dataDescription:theDataDescription]; - } - return self; -} -- (void)dealloc { - [mimeType release]; - [downloadName release]; - [inputStreamFactory release]; - [super dealloc]; -} -- (NSString *)mimeType { - return mimeType; -} -- (NSString *)downloadName { - return downloadName; -} -- (id )inputStreamFactory { - return inputStreamFactory; -} -- (NSData *)slurp:(NSError **)error { - id is = [inputStreamFactory newInputStream]; - NSData *data = [is slurp:error]; - [is release]; - return data; -} -- (Blob *)encryptedBlobWithCryptoKey:(CryptoKey *)theCryptoKey error:(NSError **)error { - NSString *base64EncryptedDownloadName = nil; - if (downloadName != nil) { - NSData *encryptedDownloadNameData = [[downloadName dataUsingEncoding:NSUTF8StringEncoding] encryptWithCryptoKey:theCryptoKey error:error]; - if (encryptedDownloadNameData == nil) { - return nil; - } - base64EncryptedDownloadName = [encryptedDownloadNameData encodeBase64]; - } - EncryptedInputStreamFactory *eisf = [[EncryptedInputStreamFactory alloc] initWithCryptoKey:theCryptoKey underlyingFactory:inputStreamFactory]; - NSString *dataDescription = [eisf description]; - id is = [eisf newInputStream]; - NSData *encryptedData = [is slurp:error]; - [is release]; - [eisf release]; - if (encryptedData == nil) { - return nil; - } - Blob *blob = [[[Blob alloc] initWithData:encryptedData mimeType:mimeType downloadName:base64EncryptedDownloadName dataDescription:dataDescription] autorelease]; - return blob; -} - -#pragma mark NSObject -- (NSString *)description { - return [NSString stringWithFormat:@"", [inputStreamFactory description]]; -} -@end diff --git a/shared/BlobACL.h b/shared/BlobACL.h deleted file mode 100644 index e95b66c..0000000 --- a/shared/BlobACL.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -enum { - PRIVATE = 1, - PUBLIC_READ = 2, - PUBLIC_READ_WRITE = 6, - AUTHENTICATED_READ = 8 -}; - - - -@interface BlobACL : NSObject { - -} -+ (NSString *)displayNameForBlobACL:(int)blobACL; -+ (NSString *)s3NameForBlobACL:(int)blobACL; -+ (int)blobACLForS3Name:(NSString *)s3ACLName; -@end diff --git a/shared/BlobACL.m b/shared/BlobACL.m deleted file mode 100644 index 09a82d2..0000000 --- a/shared/BlobACL.m +++ /dev/null @@ -1,79 +0,0 @@ -/* - Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#import "BlobACL.h" - -@implementation BlobACL -+ (NSString *)s3NameForBlobACL:(int)blobACL { - switch(blobACL) { - case PUBLIC_READ: - return @"public-read"; - case PUBLIC_READ_WRITE: - return @"public-read-write"; - case AUTHENTICATED_READ: - return @"authenticated-read"; - default: - return @"private"; - } -} -+ (NSString *)displayNameForBlobACL:(int)blobACL { - switch(blobACL) { - case PUBLIC_READ: - return @"Public (unlisted)"; - case PUBLIC_READ_WRITE: - return @"Public read/write"; - case AUTHENTICATED_READ: - return @"Authenticated Read"; - default: - return @"Private"; - } -} -+ (int)blobACLForS3Name:(NSString *)s3ACLName { - if (!s3ACLName) { - return 0; - } - if ([s3ACLName caseInsensitiveCompare:@"public-read"] == NSOrderedSame) { - return PUBLIC_READ; - } - if ([s3ACLName caseInsensitiveCompare:@"public-read-write"] == NSOrderedSame) { - return PUBLIC_READ_WRITE; - } - if ([s3ACLName caseInsensitiveCompare:@"authenticated-read"] == NSOrderedSame) { - return AUTHENTICATED_READ; - } - if ([s3ACLName caseInsensitiveCompare:@"private"] == NSOrderedSame) { - return PRIVATE; - } - return 0; -} -@end - diff --git a/shared/DNS_SDErrors.h b/shared/DNS_SDErrors.h deleted file mode 100644 index 461d3ed..0000000 --- a/shared/DNS_SDErrors.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - - - - -@interface DNS_SDErrors : NSObject { - -} -+ (NSString *)descriptionForDNS_SDError:(int)code; -@end diff --git a/shared/DNS_SDErrors.m b/shared/DNS_SDErrors.m deleted file mode 100644 index c614aa6..0000000 --- a/shared/DNS_SDErrors.m +++ /dev/null @@ -1,101 +0,0 @@ -/* - Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "dns_sd.h" -#import "DNS_SDErrors.h" - - -@implementation DNS_SDErrors -+ (NSString *)descriptionForDNS_SDError:(int)code { - switch(code) { - case kDNSServiceErr_NoError: - return @"DNS service no error"; - case kDNSServiceErr_Unknown: - return @"DNS service unknown error"; - case kDNSServiceErr_NoSuchName: - return @"DNS service: no such name"; - case kDNSServiceErr_NoMemory: - return @"DNS service: no memory"; - case kDNSServiceErr_BadParam: - return @"DNS service: bad parameter"; - case kDNSServiceErr_BadReference: - return @"DNS service: bad reference"; - case kDNSServiceErr_BadState: - return @"DNS service: bad state"; - case kDNSServiceErr_BadFlags: - return @"DNS service: bad flags"; - case kDNSServiceErr_Unsupported: - return @"DNS service: unsupported"; - case kDNSServiceErr_NotInitialized: - return @"DNS service: not initialized"; - case kDNSServiceErr_AlreadyRegistered: - return @"DNS service: already registered"; - case kDNSServiceErr_NameConflict: - return @"DNS service: name conflict"; - case kDNSServiceErr_Invalid: - return @"DNS service: invalid"; - case kDNSServiceErr_Firewall: - return @"DNS service: firewall error"; - case kDNSServiceErr_Incompatible: - return @"DNS service: incompatible"; - case kDNSServiceErr_BadInterfaceIndex: - return @"DNS service: bad interface index"; - case kDNSServiceErr_Refused: - return @"DNS service: refused"; - case kDNSServiceErr_NoSuchRecord: - return @"DNS service: no such record"; - case kDNSServiceErr_NoAuth: - return @"DNS service: no auth"; - case kDNSServiceErr_NoSuchKey: - return @"DNS service: no such key"; - case kDNSServiceErr_NATTraversal: - return @"DNS service: NAT traversal error"; - case kDNSServiceErr_DoubleNAT: - return @"DNS service: double NAT error"; - case kDNSServiceErr_BadTime: - return @"DNS service: bad time"; - case kDNSServiceErr_BadSig: - return @"DNS service: bad sig"; - case kDNSServiceErr_BadKey: - return @"DNS service: bad key"; - case kDNSServiceErr_Transient: - return @"DNS service: transient"; - case kDNSServiceErr_ServiceNotRunning: - return @"DNS service not running"; - case kDNSServiceErr_NATPortMappingUnsupported: - return @"DNS service: NAT port mapping unsupported"; - case kDNSServiceErr_NATPortMappingDisabled: - return @"DNS service: NAT port mapping disabled"; - } - return @"unknown DNS service error"; -} -@end diff --git a/shared/FileACL.m b/shared/FileACL.m deleted file mode 100644 index 319b85e..0000000 --- a/shared/FileACL.m +++ /dev/null @@ -1,97 +0,0 @@ -/* - Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#import "FileACL.h" -#import "SetNSError.h" -#import "NSError_extra.h" - -@implementation FileACL -+ (BOOL)aclText:(NSString **)aclText forFile:(NSString *)path error:(NSError **)error { - *aclText = nil; - const char *pathChars = [path fileSystemRepresentation]; - acl_t acl = acl_get_link_np(pathChars, ACL_TYPE_EXTENDED); - if (!acl) { - if (errno != ENOENT) { - int errnum = errno; - HSLogError(@"acl_get_link_np(%@) error %d: %s", path, errnum, strerror(errnum)); - SETNSERROR(@"UnixErrorDomain", errnum, @"failed to get ACL of %@: %s", path, strerror(errnum)); - return NO; - } - } else { - char *aclTextChars = acl_to_text(acl, NULL); - if (!aclTextChars) { - acl_free(acl); - int errnum = errno; - HSLogError(@"acl_to_text from %@ error %d: %s", path, errnum, strerror(errnum)); - SETNSERROR(@"UnixErrorDomain", errnum, @"failed to convert ACL of %@ to text: %s", path, strerror(errnum)); - return NO; - } - *aclText = [NSString stringWithUTF8String:aclTextChars]; - acl_free(aclTextChars); - acl_free(acl); - } - return YES; -} -+ (BOOL)writeACLText:(NSString *)aclText toFile:(NSString *)path error:(NSError **)error { - HSLogTrace(@"applying ACL %@ to %@", aclText, path); - const char *pathChars = [path fileSystemRepresentation]; - acl_t acl = acl_from_text([aclText UTF8String]); - if (!acl) { - int errnum = errno; - HSLogError(@"acl_from_text(%@) error %d: %s", aclText, errnum, strerror(errnum)); - SETNSERROR(@"UnixErrorDomain", errnum, @"failed to convert ACL text '%@' to ACL: %s", aclText, strerror(errnum)); - return NO; - } - struct stat st; - if (lstat(pathChars, &st) == -1) { - int errnum = errno; - HSLogError(@"lstat(%@) error %d: %s", path, errnum, strerror(errnum)); - SETNSERROR(@"UnixErrorDomain", errnum, @"%@: %s", path, strerror(errnum)); - return NO; - } - int ret = 0; - if (S_ISLNK(st.st_mode)) { - ret = acl_set_link_np(pathChars, ACL_TYPE_EXTENDED, acl); - } else { - ret = acl_set_file(pathChars, ACL_TYPE_EXTENDED, acl); - } - if (ret == -1) { - int errnum = errno; - HSLogError(@"acl_set(%@) error %d: %s", path, errnum, strerror(errnum)); - SETNSERROR(@"UnixErrorDomain", errnum, @"failed to set ACL '%@' on %@: %s", aclText, path, strerror(errnum)); - return NO; - } - return YES; -} -@end diff --git a/shared/OSStatusDescription.h b/shared/OSStatusDescription.h deleted file mode 100644 index fea6891..0000000 --- a/shared/OSStatusDescription.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - - - - -@interface OSStatusDescription : NSObject { - -} -+ (NSString *)descriptionForMacOSStatus:(OSStatus)status; -@end diff --git a/shared/OSStatusDescription.m b/shared/OSStatusDescription.m deleted file mode 100644 index 00a9b28..0000000 --- a/shared/OSStatusDescription.m +++ /dev/null @@ -1,81 +0,0 @@ -/* - Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#import "OSStatusDescription.h" - - -@implementation OSStatusDescription -+ (NSString *)descriptionForMacOSStatus:(OSStatus)status { - NSString *msg = nil; - switch (status) { - case ioErr: - return @"I/O error"; // GetMacOSStatusCommentString() returns "I/O error (bummers)", which isn't appropriate! - case nsvErr: - return @"No such volume"; - case bdNamErr: - return @"Bad file name"; - case fnfErr: - return @"File not found"; - case errAuthorizationSuccess: - return @"The operation completed successfully."; - case errAuthorizationInvalidSet: - return @"The set parameter is invalid."; - case errAuthorizationInvalidRef: - return @"The authorization parameter is invalid."; - case errAuthorizationInvalidPointer: - return @"The authorizedRights parameter is invalid."; - case errAuthorizationDenied: - return @"The Security Server denied authorization for one or more requested rights. This error is also returned if there was no definition found in the policy database, or a definition could not be created."; - case errAuthorizationCanceled: - return @"The user canceled the operation"; - case errAuthorizationInteractionNotAllowed: - return @"The Security Server denied authorization because no user interaction is allowed."; - case errAuthorizationInternal: - return @"An unrecognized internal error occurred."; - case errAuthorizationExternalizeNotAllowed: - return @"The Security Server denied externalization of the authorization reference."; - case errAuthorizationInternalizeNotAllowed: - return @"The Security Server denied internalization of the authorization reference."; - case errAuthorizationInvalidFlags: - return @"The flags parameter is invalid."; - case errAuthorizationToolExecuteFailure: - return @"The tool failed to execute."; - case errAuthorizationToolEnvironmentError: - return @"The attempt to execute the tool failed to return a success or an error code."; - } - [NSString stringWithUTF8String:GetMacOSStatusCommentString(status)]; - if ([msg length] == 0) { - msg = [NSString stringWithFormat:@"error %d", status]; - } - return msg; -} -@end diff --git a/shared/ServerBlob.h b/shared/ServerBlob.h deleted file mode 100644 index 0baf86f..0000000 --- a/shared/ServerBlob.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - - -#import "InputStream.h" - -@interface ServerBlob : NSObject { - id is; - NSString *mimeType; - NSString *downloadName; -} -- (id)initWithInputStream:(id )theIS mimeType:(NSString *)theMimeType downloadName:(NSString *)theDownloadName; -- (id)initWithData:(NSData *)data mimeType:(NSString *)theMimeType downloadName:(NSString *)theDownloadName; -- (id )newInputStream; -- (NSString *)mimeType; -- (NSString *)downloadName; -- (NSData *)slurp:(NSError **)error; -@end diff --git a/shared/ServerBlob.m b/shared/ServerBlob.m deleted file mode 100644 index a651772..0000000 --- a/shared/ServerBlob.m +++ /dev/null @@ -1,72 +0,0 @@ -/* - Copyright (c) 2009-2011, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#import "ServerBlob.h" -#import "DataInputStream.h" - -@implementation ServerBlob -- (id)initWithInputStream:(id )theIS mimeType:(NSString *)theMimeType downloadName:(NSString *)theDownloadName { - if (self = [super init]) { - is = [theIS retain]; - mimeType = [theMimeType copy]; - downloadName = [theDownloadName copy]; - } - return self; -} -- (id)initWithData:(NSData *)data mimeType:(NSString *)theMimeType downloadName:(NSString *)theDownloadName { - if (self = [super init]) { - is = [[DataInputStream alloc] initWithData:data]; - mimeType = [theMimeType copy]; - downloadName = [theDownloadName copy]; - } - return self; -} -- (void)dealloc { - [is release]; - [mimeType release]; - [downloadName release]; - [super dealloc]; -} -- (id )newInputStream { - return [is retain]; -} -- (NSString *)mimeType { - return [[mimeType retain] autorelease]; -} -- (NSString *)downloadName { - return [[downloadName retain] autorelease]; -} -- (NSData *)slurp:(NSError **)error { - NSData *data = [is slurp:error]; - return data; -} -@end