Revision: 27852 http://sourceforge.net/p/bibdesk/svn/27852 Author: hofman Date: 2022-09-03 17:10:36 +0000 (Sat, 03 Sep 2022) Log Message: ----------- Authenticate sharing clients through method send through connection. NSConnection delegate is called for every communication, and is actually meant for signinging the message, not actual authentication. Still support authentication through delegate methods when either the client or the server is an old version. Retry password while authentication fails, as we now know whether we do. Save to keychain only when successful.
Modified Paths: -------------- trunk/bibdesk/BDSKPasswordController.h trunk/bibdesk/BDSKPasswordController.m trunk/bibdesk/BDSKSharingClient.m trunk/bibdesk/BDSKSharingServer.h trunk/bibdesk/BDSKSharingServer.m trunk/bibdesk/BibPref_Sharing.m trunk/bibdesk/NSString_BDSKExtensions.h trunk/bibdesk/NSString_BDSKExtensions.m Modified: trunk/bibdesk/BDSKPasswordController.h =================================================================== --- trunk/bibdesk/BDSKPasswordController.h 2022-08-31 14:31:17 UTC (rev 27851) +++ trunk/bibdesk/BDSKPasswordController.h 2022-09-03 17:10:36 UTC (rev 27852) @@ -49,11 +49,11 @@ @property (nonatomic, assign) IBOutlet NSSecureTextField *passwordField; @property (nonatomic, assign) IBOutlet NSTextField *statusField; -+ (NSData *)passwordForKeychainService:(NSString *)service account:(NSString *)account name:(NSString *)name; ++ (NSString *)passwordForKeychainService:(NSString *)service account:(NSString *)account name:(NSString *)name; + (BOOL)addOrModifyPassword:(NSString *)password forKeychainService:(NSString *)service account:(NSString *)account name:(NSString *)name; -+ (NSData *)runModalPanelForKeychainService:(NSString *)service account:(NSString *)account name:(NSString *)aName message:(NSString *)status; ++ (NSString *)passwordFromPanelWithMessage:(NSString *)status; - (IBAction)buttonAction:(id)sender; Modified: trunk/bibdesk/BDSKPasswordController.m =================================================================== --- trunk/bibdesk/BDSKPasswordController.m 2022-08-31 14:31:17 UTC (rev 27851) +++ trunk/bibdesk/BDSKPasswordController.m 2022-09-03 17:10:36 UTC (rev 27852) @@ -63,7 +63,7 @@ return attributes; } -+ (NSData *)passwordForKeychainService:(NSString *)service account:(NSString *)account name:(NSString *)name { ++ (NSString *)passwordForKeychainService:(NSString *)service account:(NSString *)account name:(NSString *)name { // use the service name to get password from keychain and hash it with sha1 for comparison purposes OSStatus err; @@ -72,7 +72,7 @@ const char *nameCString = [name UTF8String]; void *password = NULL; UInt32 passwordLength = 0; - NSData *pwData = nil; + NSString *passwordString = nil; // see if the password exists in the keychain err = SecKeychainFindGenericPassword(NULL, strlen(serviceCString), serviceCString, account ? strlen(accountCString) : 0, accountCString, &passwordLength, &password, NULL); @@ -88,12 +88,12 @@ } } if (err == noErr) { - pwData = [NSData dataWithBytes:password length:passwordLength]; + passwordString = [[[NSString alloc] initWithBytes:password length:passwordLength encoding:NSUTF8StringEncoding] autorelease]; SecKeychainItemFreeContent(NULL, password); } else { logError(@"getting", err); } - return pwData; + return passwordString; } + (BOOL)addOrModifyPassword:(NSString *)password forKeychainService:(NSString *)service account:(NSString *)account name:(NSString *)name { @@ -155,26 +155,24 @@ return (err == noErr); } -- (NSData *)runModalForKeychainService:(NSString *)service account:(NSString *)account name:(NSString *)name message:(NSString *)status { +- (NSString *)runModalWithMessage:(NSString *)status { NSString *password = nil; [self window]; // load window before seting the status [statusField setStringValue:status]; if (NSOKButton == [NSApp runModalForWindow:[self window]]) { - NSAssert(name != nil, @"name is nil"); password = [[[passwordField stringValue] retain] autorelease]; NSParameterAssert(password != nil); - [[self class] addOrModifyPassword:password forKeychainService:service account:account name:name]; } [[self window] orderOut:self]; if (password == nil) return nil; - return [password dataUsingEncoding:NSUTF8StringEncoding]; + return password; } -+ (NSData *)runModalPanelForKeychainService:(NSString *)service account:(NSString *)account name:(NSString *)name message:(NSString *)status { ++ (NSString *)passwordFromPanelWithMessage:(NSString *)status { BDSKPasswordController *pwc = [[[self alloc] initWithWindowNibName:@"BDSKPasswordController"] autorelease]; - return [pwc runModalForKeychainService:service account:account name:name message:status]; + return [pwc runModalWithMessage:status]; } - (IBAction)buttonAction:(id)sender { Modified: trunk/bibdesk/BDSKSharingClient.m =================================================================== --- trunk/bibdesk/BDSKSharingClient.m 2022-08-31 14:31:17 UTC (rev 27851) +++ trunk/bibdesk/BDSKSharingClient.m 2022-09-03 17:10:36 UTC (rev 27852) @@ -43,8 +43,6 @@ #import "NSData_BDSKExtensions.h" #import "CFString_BDSKExtensions.h" -NSString *BDSKSharingClientFailedKey = @"failed"; - static NSString *BDSKClientServiceNameForKeychain = @"BibDesk Sharing Access"; typedef struct _BDSKSharingClientFlags { @@ -66,7 +64,7 @@ - (void)setArchivedPublicationsAndMacros:(bycopy NSDictionary *)dictionary; - (NSInteger)runAuthenticationFailedAlert; -- (NSData *)runPasswordPrompt; +- (bycopy NSString *)passwordFromPanel; @end @@ -77,7 +75,6 @@ NSNetService *service; // service with information about the remote server (BDSKSharingServer) BDSKSharingClient *client; // the owner of the local server (BDSKSharingClient) id remoteServer; // proxy for the remote sharing server to which we connect - id protocolChecker; // proxy wrapping self for security BDSKSharingClientFlags flags; // state variables NSString *uniqueIdentifier; // used by the remote server NSString *errorMessage; @@ -87,15 +84,12 @@ - (id)initWithClient:(BDSKSharingClient *)aClient andService:(NSNetService *)aService; -@property (nonatomic, getter=isRetrieving) BOOL retrieving; -@property (nonatomic, readonly) BOOL needsAuthentication; -@property (nonatomic, readonly) BOOL authenticationFailed; -@property (nonatomic, readonly) BOOL failedDownload; +@property (getter=isRetrieving) BOOL retrieving; +@property (readonly) BOOL needsAuthentication; +@property (readonly) BOOL authenticationFailed; +@property (readonly) BOOL failedDownload; @property (copy) NSString *errorMessage; -// proxy object for messaging the remote server -- (id <BDSKSharingServer>)remoteServer; - - (void)retrievePublicationsInBackground; @end @@ -161,8 +155,7 @@ // we need to do this after setting the archivedPublications but before sending the notification [server setRetrieving:NO]; - NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:dictionary == nil], BDSKSharingClientFailedKey, nil]; - [[NSNotificationCenter defaultCenter] postNotificationName:BDSKSharingClientUpdatedNotification object:self userInfo:userInfo]; + [[NSNotificationCenter defaultCenter] postNotificationName:BDSKSharingClientUpdatedNotification object:self]; } - (BOOL)isRetrieving { @@ -196,7 +189,7 @@ // If we introduce incompatible changes in future, bump this to avoid sharing breakage // Note we should always support -invalidate, as this can be called when we're not accepted -+ (NSString *)supportedProtocolVersion { return @"0"; } ++ (NSString *)supportedProtocolVersion { return @"1"; } - (id)initWithClient:(BDSKSharingClient *)aClient andService:(NSNetService *)aService; { @@ -246,8 +239,6 @@ [client setNeedsUpdate:flag]; } -- (BOOL)isAlive{ return YES; } - - (BOOL)isRetrieving { OSMemoryBarrier(); return flags.isRetrieving == 1; @@ -260,12 +251,12 @@ - (BOOL)needsAuthentication { OSMemoryBarrier(); - return flags.needsAuthentication == 1; + return flags.needsAuthentication != 0; } - (BOOL)authenticationFailed { OSMemoryBarrier(); - return flags.authenticationFailed == 1; + return flags.authenticationFailed == 1 || flags.canceledAuthentication == 1; } - (BOOL)failedDownload { @@ -273,10 +264,168 @@ return flags.failedDownload == 1; } +#pragma mark Authentication + +- (bycopy NSString *)passwordFromPanel; +{ + NSAssert([NSThread isMainThread] == 1, @"password controller must be run from the main thread"); + return [BDSKPasswordController passwordFromPanelWithMessage:[NSString stringWithFormat:NSLocalizedString(@"Enter password for %@", @"Prompt for Password dialog"), [service name]]]; +} + +- (NSInteger)runAuthenticationFailedAlert; +{ + NSAssert([NSThread isMainThread] == 1, @"runAuthenticationFailedAlert must be run from the main thread"); + NSAlert *alert = [[[NSAlert alloc] init] autorelease]; + [alert setMessageText:NSLocalizedString(@"Authentication Failed", @"Message in alert dialog when authentication failed")]; + [alert setInformativeText:[NSString stringWithFormat:NSLocalizedString(@"Incorrect password for BibDesk Sharing on server %@. Reselect to try again.", @"Informative text in alert dialog"), [service name]]]; + return [alert runModal]; +} + +- (NSString *)passwordFromKeychain { + return [BDSKPasswordController + passwordForKeychainService:BDSKClientServiceNameForKeychain + account:[service name] + name:[NSString stringWithFormat:@"%@ - %@", [service name], BDSKServiceNameForKeychain]]; +} + +- (void)setPasswordFromKeychain:(NSString *)password { + [BDSKPasswordController + addOrModifyPassword:password + forKeychainService:BDSKClientServiceNameForKeychain + account:[service name] + name:[NSString stringWithFormat:@"%@ - %@", [service name], BDSKServiceNameForKeychain]]; +} + +- (BOOL)authenticateIfNeeded:(BOOL)isNew { + // authenticate to a server of version 1+ + OSMemoryBarrier(); + if (flags.needsAuthentication != 1 || (isNew == NO && flags.canceledAuthentication == 0 && flags.authenticationFailed == 0)) { + // no need to authenticate, or legacy server, or we already authenticated + return YES; + } + while ([self shouldKeepRunning] && uniqueIdentifier != nil) { + NSString *password = nil; + BOOL fromPanel = NO; + + OSAtomicCompareAndSwap32Barrier(1, 0, &flags.canceledAuthentication); + + OSMemoryBarrier(); + if(flags.authenticationFailed == 0) + password = [self passwordFromKeychain]; + + if ([self shouldKeepRunning] == NO) + return NO; + + if (password == nil) { + // run the prompt on the main thread + password = [[self serverOnMainThread] passwordFromPanel]; + + if (password == nil) { + // cancceled by user + OSAtomicCompareAndSwap32Barrier(0, 1, &flags.canceledAuthentication); + return NO; + } else { + fromPanel = YES; + } + } + + if ([self shouldKeepRunning] == NO || uniqueIdentifier == nil) + return NO; + + BOOL authenticated = NO; + @try { + authenticated = [remoteServer authenticateClientForIdentifier:uniqueIdentifier withData:[password sha1Signature]]; + } + @catch (id exception) { + NSLog(@"%@: unable to authenticate with remote server %@", [self class], [service hostName]); + OSAtomicCompareAndSwap32Barrier(0, 1, &flags.authenticationFailed); + // don't show the alert when we couldn't authenticate when cleaning up + if ([self shouldKeepRunning]) { + [[self serverOnMainThread] runAuthenticationFailedAlert]; + } + return NO; + } + + if (authenticated) { + // save the valid password in the keychain when we got it from the user + if (fromPanel) + [self setPasswordFromKeychain:password]; + return YES; + } else { + // set the flag and try again, until we succeed or user cancels + OSAtomicCompareAndSwap32Barrier(0, 1, &flags.authenticationFailed); + } + } + return NO; +} + +// this can be called from any thread +- (NSData *)authenticationDataForComponents:(NSArray *)components; +{ + OSMemoryBarrier(); + if (flags.needsAuthentication == 3) { + // legacy server of version 0 expect authentication through this method + + NSString *password = nil; + + OSAtomicCompareAndSwap32Barrier(1, 0, &flags.canceledAuthentication); + + OSMemoryBarrier(); + if(flags.authenticationFailed == 0) + password = [self passwordFromKeychain]; + + if(password == nil && [self shouldKeepRunning]){ + + // run the prompt on the main thread + password = [([NSThread isMainThread] ? self : [self serverOnMainThread]) passwordFromPanel]; + + // retry from the keychain + if (password){ + // assume we succeeded; the exception handler for the connection will change it back if we fail again + OSAtomicCompareAndSwap32Barrier(1, 0, &flags.authenticationFailed); + [self setPasswordFromKeychain:password]; + }else{ + // nil return, will throw NSGenericException + OSAtomicCompareAndSwap32Barrier(0, 1, &flags.canceledAuthentication); + } + } + return [password sha1Signature]; + } else { + // no authentication or server of version 1+ + static NSData *zeroData = nil; + if (zeroData == nil) { + // some default value to indicate we're not using this to authenticate + // empty data is not accepted, even though the docs say it is + // empty data sha1Signature could come from a legacy client with an empty string as password + char zero = 0; + zeroData = [[NSData alloc] initWithBytes:&zero length:1]; + } + return zeroData; + } +} + +// monitor the TXT record in case the server changes password requirements +- (void)netService:(NSNetService *)sender didUpdateTXTRecordData:(NSData *)data; +{ + BDSKASSERT(sender == service); + BDSKASSERT(data != nil); + if(data){ + NSDictionary *dict = [NSNetService dictionaryFromTXTRecordData:data]; + int32_t val = [[[[NSString alloc] initWithData:[dict objectForKey:BDSKTXTAuthenticateKey] encoding:NSUTF8StringEncoding] autorelease] integerValue]; + if (val == 1 && [[dict objectForKey:BDSKTXTVersionKey] isEqual:[@"0" dataUsingEncoding:NSUTF8StringEncoding]]) + val |= 2; + OSMemoryBarrier(); + int32_t oldVal = flags.needsAuthentication; + OSAtomicCompareAndSwap32Barrier(oldVal, val, &flags.needsAuthentication); + } +} + #pragma mark Proxies - (id <BDSKSharingServer>)remoteServer; { + BOOL isNew = NO; + if (remoteServer == nil) { NSConnection *conn = nil; @@ -289,8 +438,8 @@ @try { conn = [NSConnection connectionWithReceivePort:nil sendPort:sendPort]; [conn setRequestTimeout:60]; - [conn setReplyTimeout:60]; - // ask for password + [conn setReplyTimeout:300]; + // legacy server will check password [conn setDelegate:self]; proxy = [conn rootProxy]; } @@ -336,8 +485,10 @@ // use uniqueIdentifier as the notification identifier for this host on the other end uniqueIdentifier = (id)BDCreateUniqueString(); @try { - protocolChecker = [[NSProtocolChecker protocolCheckerWithTarget:self protocol:@protocol(BDSKSharingClient)] retain]; - [proxy registerClient:protocolChecker forIdentifier:uniqueIdentifier version:[BDSKSharingClientServer supportedProtocolVersion]]; + NSProtocolChecker *checker = [NSProtocolChecker protocolCheckerWithTarget:self protocol:@protocol(BDSKSharingClient)]; + [proxy registerClient:checker forIdentifier:uniqueIdentifier version:[BDSKSharingClientServer supportedProtocolVersion]]; + // mark as not yet authenticated + isNew = YES; } @catch(id exception) { BDSKDESTROY(uniqueIdentifier); @@ -349,82 +500,13 @@ remoteServer = [proxy retain]; } - return uniqueIdentifier ? remoteServer : nil; -} - -#pragma mark Authentication - -- (NSString *)keychainItemName { - return [NSString stringWithFormat:@"%@ - %@", [service name], BDSKServiceNameForKeychain]; -} - -- (NSData *)runPasswordPrompt; -{ - NSAssert([NSThread isMainThread] == 1, @"password controller must be run from the main thread"); - return [BDSKPasswordController runModalPanelForKeychainService:BDSKClientServiceNameForKeychain - account:[service name] - name:[self keychainItemName] - message:[NSString stringWithFormat:NSLocalizedString(@"Enter password for %@", @"Prompt for Password dialog"), [service name]]]; -} - -- (NSInteger)runAuthenticationFailedAlert; -{ - NSAssert([NSThread isMainThread] == 1, @"runAuthenticationFailedAlert must be run from the main thread"); - NSAlert *alert = [[[NSAlert alloc] init] autorelease]; - [alert setMessageText:NSLocalizedString(@"Authentication Failed", @"Message in alert dialog when authentication failed")]; - [alert setInformativeText:[NSString stringWithFormat:NSLocalizedString(@"Incorrect password for BibDesk Sharing on server %@. Reselect to try again.", @"Informative text in alert dialog"), [service name]]]; - return [alert runModal]; -} - -// this can be called from any thread -- (NSData *)authenticationDataForComponents:(NSArray *)components; -{ - NSData *password = nil; - OSMemoryBarrier(); - if(flags.needsAuthentication == 0) { - password = [NSData data]; - } else { - OSAtomicCompareAndSwap32Barrier(1, 0, &flags.canceledAuthentication); - - OSMemoryBarrier(); - if(flags.authenticationFailed == 0) - password = [BDSKPasswordController passwordForKeychainService:BDSKClientServiceNameForKeychain - account:[service name] - name:[self keychainItemName]]; - - if(password == nil && [self shouldKeepRunning]){ - - // run the prompt on the main thread - password = [([NSThread isMainThread] ? self : [self serverOnMainThread]) runPasswordPrompt]; - - // retry from the keychain - if (password){ - // assume we succeeded; the exception handler for the connection will change it back if we fail again - OSAtomicCompareAndSwap32Barrier(1, 0, &flags.authenticationFailed); - }else{ - // nil return, will throw NSGenericException - OSAtomicCompareAndSwap32Barrier(0, 1, &flags.canceledAuthentication); - } - } - } - return [password sha1Signature]; + // we may need to authenticate for server of version 1+ + BOOL valid = uniqueIdentifier != nil && [self authenticateIfNeeded:isNew]; + + return valid ? remoteServer : nil; } -// monitor the TXT record in case the server changes password requirements -- (void)netService:(NSNetService *)sender didUpdateTXTRecordData:(NSData *)data; -{ - BDSKASSERT(sender == service); - BDSKASSERT(data != nil); - if(data){ - NSDictionary *dict = [NSNetService dictionaryFromTXTRecordData:data]; - int32_t val = [[[[NSString alloc] initWithData:[dict objectForKey:BDSKTXTAuthenticateKey] encoding:NSUTF8StringEncoding] autorelease] integerValue]; - OSMemoryBarrier(); - int32_t oldVal = flags.needsAuthentication; - OSAtomicCompareAndSwap32Barrier(oldVal, val, &flags.needsAuthentication); - } -} - #pragma mark ServerThread - (Protocol *)protocolForServerThread { return @protocol(BDSKSharingClientServerLocalThread); } @@ -458,10 +540,6 @@ NSString *errorStr = [NSString stringWithFormat:@"Error reading shared data: %@", error]; @throw errorStr; } - } else { - OSMemoryBarrier(); - if (flags.authenticationFailed == 0 && flags.canceledAuthentication == 0) - archive = [NSDictionary dictionary]; } // use the main thread; this avoids an extra (un)archiving between threads and it ends up posting notifications for UI updates [[self serverOnMainThread] setArchivedPublicationsAndMacros:archive]; @@ -501,7 +579,6 @@ [[conn receivePort] invalidate]; [conn invalidate]; BDSKDESTROY(remoteServer); - BDSKDESTROY(protocolChecker); } } Modified: trunk/bibdesk/BDSKSharingServer.h =================================================================== --- trunk/bibdesk/BDSKSharingServer.h 2022-08-31 14:31:17 UTC (rev 27851) +++ trunk/bibdesk/BDSKSharingServer.h 2022-09-03 17:10:36 UTC (rev 27852) @@ -52,7 +52,6 @@ @protocol BDSKSharingClient - (oneway void)setNeedsUpdate:(BOOL)flag; -- (BOOL)isAlive; - (oneway void)invalidate; @end @@ -62,6 +61,7 @@ - (bycopy NSData *)archivedSnapshotOfPublications; - (oneway void)registerClient:(byref id)clientObject forIdentifier:(bycopy NSString *)identifier version:(bycopy NSString *)version; +- (BOOL)authenticateClientForIdentifier:(bycopy NSString *)identifier withData:(bycopy NSData *)authData; - (oneway void)removeClientForIdentifier:(bycopy NSString *)identifier; @end Modified: trunk/bibdesk/BDSKSharingServer.m =================================================================== --- trunk/bibdesk/BDSKSharingServer.m 2022-08-31 14:31:17 UTC (rev 27851) +++ trunk/bibdesk/BDSKSharingServer.m 2022-09-03 17:10:36 UTC (rev 27852) @@ -97,6 +97,24 @@ #pragma mark - +@interface BDSKConnectedClient : NSObject <NSConnectionDelegate> { + NSConnection *connection; + id proxy; + BOOL authenticated; +} + +- (id)initWithConnection:(NSConnection *)aConnection; + +@property (nonatomic, readonly) NSConnection *connection; +@property (nonatomic, retain) id proxy; +@property (nonatomic, getter=isAuthenticated) BOOL authenticated; + +- (void)invalidate; + +@end + +#pragma mark - + // private protocols for inter-thread messaging @protocol BDSKSharingServerLocalThread <BDSKAsyncDOServerThread> @@ -113,7 +131,8 @@ BDSKSharingServer *sharingServer; NSString *sharingName; NSConnection *connection; - NSMutableDictionary *remoteClients; + NSMutableArray *connectedClients; + NSMutableDictionary *registeredClients; NSUInteger numberOfConnections; BDSKReadWriteLock *rwLock; } @@ -124,8 +143,6 @@ @property NSUInteger numberOfConnections; -- (void)notifyClientConnectionsChanged; - @end #pragma mark - @@ -203,7 +220,7 @@ } // If we introduce incompatible changes in future, bump this to avoid sharing breakage -+ (NSString *)supportedProtocolVersion { return @"0"; } ++ (NSString *)supportedProtocolVersion { return @"1"; } + (id)defaultServer; { @@ -575,7 +592,8 @@ if (self) { sharingServer = aSharingServer; sharingName = [[sharingServer sharingName] retain]; - remoteClients = [[NSMutableDictionary alloc] init]; + connectedClients = [[NSMutableArray alloc] init]; + registeredClients = [[NSMutableDictionary alloc] init]; numberOfConnections = 0; rwLock = [[BDSKReadWriteLock alloc] init]; [self startDOServerAsync]; @@ -587,7 +605,8 @@ { sharingServer = nil; BDSKDESTROY(sharingName); - BDSKDESTROY(remoteClients); + BDSKDESTROY(connectedClients); + BDSKDESTROY(registeredClients); BDSKDESTROY(rwLock); [super dealloc]; } @@ -615,11 +634,6 @@ [super stopDOServer]; } -- (void)notifyClientConnectionsChanged; -{ - [[NSNotificationCenter defaultCenter] postNotificationName:BDSKClientConnectionsChangedNotification object:nil]; -} - - (oneway void)localThreadServerDidSetup:(BOOL)success withPort:(int)port { [sharingServer server:(BDSKSharingDOServer *)self didSetup:success withPort:port]; @@ -672,34 +686,25 @@ if (connection == nil) return; - // don't use [remoteClients objectEnumerator], because the remoteClients may change during enumeration - for (id proxyObject in [remoteClients allValues]) { - @try { - [proxyObject invalidate]; - } - @catch (id exception) { - NSLog(@"%@: ignoring exception \"%@\" raised while invalidating client %p", [self class], exception, proxyObject); - } - @try { - [[proxyObject connectionForProxy] invalidate]; - } - @catch (id exception) { - NSLog(@"%@: ignoring exception \"%@\" raised while invalidating connection to client %p", [self class], exception, proxyObject); - } + [connectedClients makeObjectsPerformSelector:@selector(invalidate)]; + [connectedClients removeAllObjects]; + [registeredClients removeAllObjects]; + if ([self numberOfConnections] > 0){ + [self setNumberOfConnections:0]; + dispatch_async(dispatch_get_main_queue(), ^{ + [[NSNotificationCenter defaultCenter] postNotificationName:BDSKClientConnectionsChangedNotification object:nil]; + }); } - [remoteClients removeAllObjects]; - [self setNumberOfConnections:0]; - dispatch_async(dispatch_get_main_queue(), ^{ - [self notifyClientConnectionsChanged]; - }); - NSPort *port = [[NSSocketPortNameServer sharedInstance] portForName:sharingName]; - [[NSSocketPortNameServer sharedInstance] removePortForName:sharingName]; - [port invalidate]; - [connection setDelegate:nil]; - [connection setRootObject:nil]; - [connection invalidate]; - BDSKDESTROY(connection); + if (connection) { + NSPort *port = [[NSSocketPortNameServer sharedInstance] portForName:sharingName]; + [[NSSocketPortNameServer sharedInstance] removePortForName:sharingName]; + [port invalidate]; + [connection setDelegate:nil]; + [connection setRootObject:nil]; + [connection invalidate]; + BDSKDESTROY(connection); + } } #pragma mark | NSConnection delegate @@ -712,9 +717,15 @@ if(maxConnections == 0) maxConnections = MAX(20, [[NSUserDefaults standardUserDefaults] integerForKey:BDSKSharingServerMaxConnectionsKey]); - BOOL allowConnection = [remoteClients count] < maxConnections; + BOOL allowConnection = [registeredClients count] < maxConnections; if(allowConnection){ - [newConnection setDelegate:self]; + BDSKConnectedClient *client = [[BDSKConnectedClient alloc] initWithConnection:newConnection]; + if ([[NSUserDefaults standardUserDefaults] boolForKey:BDSKSharingRequiresPasswordKey]) + [newConnection setDelegate:client]; + else + [newConnection setDelegate:self]; + [connectedClients addObject:client]; + [client release]; } else { NSLog(@"*** WARNING *** Maximum number of sharing clients (%ld) exceeded.", (long)maxConnections); NSLog(@"Use `defaults write %@ BDSKSharingServerMaxConnections N` to change the limit to N.", [[NSBundle mainBundle] bundleIdentifier]); @@ -722,57 +733,69 @@ return allowConnection; } +// this delegate method is only used when we don't authenticate - (BOOL)authenticateComponents:(NSArray *)components withData:(NSData *)authenticationData { - BOOL status = YES; - if([[NSUserDefaults standardUserDefaults] boolForKey:BDSKSharingRequiresPasswordKey]){ - NSData *myPasswordHashed = [[BDSKPasswordController passwordForKeychainService:BDSKServiceNameForKeychain account:nil name:nil] sha1Signature]; - status = [authenticationData isEqual:myPasswordHashed]; - } - return status; + return YES; } +#pragma mark | BDSKSharingServer + - (oneway void)registerClient:(byref id)clientObject forIdentifier:(bycopy NSString *)identifier version:(bycopy NSString *)version; { NSParameterAssert(clientObject != nil && identifier != nil && version != nil); + NSConnection *clientConnection = [clientObject connectionForProxy]; + BDSKConnectedClient *client = nil; + for (client in connectedClients) + if ([client connection] == clientConnection) break; + // we don't register clients that have a version we don't support // version is [BDSKSharingClientServer supportedProtocolVersion] from the client - if([version numericCompare:[BDSKSharingDOServer requiredProtocolVersion]] == NSOrderedAscending){ - @try { - [clientObject invalidate]; - } - @catch (id exception) { - NSLog(@"%@: ignoring exception \"%@\" raised while invalidating client %p", [self class], exception, clientObject); - } - @try { - [[clientObject connectionForProxy] invalidate]; - } - @catch (id exception) { - NSLog(@"%@: ignoring exception \"%@\" raised while invalidating connection to client %p", [self class], exception, clientObject); - } + if(client == nil){ + // should never happen + client = [[BDSKConnectedClient alloc] initWithConnection:clientConnection]; + [client setProxy:clientObject]; + [client invalidate]; + [client release]; + }else if([version numericCompare:[BDSKSharingDOServer requiredProtocolVersion]] == NSOrderedAscending){ + [client invalidate]; + [connectedClients removeObject:client]; }else{ [clientObject setProtocolForProxy:@protocol(BDSKSharingClient)]; - [remoteClients setObject:clientObject forKey:identifier]; - [self setNumberOfConnections:[remoteClients count]]; + [client setProxy:clientObject]; + [registeredClients setObject:client forKey:identifier]; + [self setNumberOfConnections:[registeredClients count]]; dispatch_async(dispatch_get_main_queue(), ^{ - [self notifyClientConnectionsChanged]; + [[NSNotificationCenter defaultCenter] postNotificationName:BDSKClientConnectionsChangedNotification object:nil]; }); } } -#pragma mark | BDSKSharingServer +- (BOOL)authenticateClientForIdentifier:(bycopy NSString *)identifier withData:(bycopy NSData *)authData { + BOOL status = YES; + if([[NSUserDefaults standardUserDefaults] boolForKey:BDSKSharingRequiresPasswordKey]){ + NSData *myPasswordHashed = [[BDSKPasswordController passwordForKeychainService:BDSKServiceNameForKeychain account:nil name:nil] sha1Signature]; + status = [authData isEqual:myPasswordHashed]; + if (status) + [[registeredClients objectForKey:identifier] setAuthenticated:YES]; + } + return status; +} - (oneway void)removeClientForIdentifier:(bycopy NSString *)identifier; { NSParameterAssert(identifier != nil); - id proxyObject = [[remoteClients objectForKey:identifier] retain]; - [remoteClients removeObjectForKey:identifier]; - [self setNumberOfConnections:[remoteClients count]]; - [[proxyObject connectionForProxy] invalidate]; - [proxyObject release]; + BDSKConnectedClient *client = [[registeredClients objectForKey:identifier] retain]; + if (client) { + [registeredClients removeObjectForKey:identifier]; + [connectedClients removeObject:client]; + [[client connection] invalidate]; + [client release]; + } + [self setNumberOfConnections:[registeredClients count]]; dispatch_async(dispatch_get_main_queue(), ^{ - [self notifyClientConnectionsChanged]; + [[NSNotificationCenter defaultCenter] postNotificationName:BDSKClientConnectionsChangedNotification object:nil]; }); } @@ -779,9 +802,9 @@ - (oneway void)notifyClientsOfChange; { // here is where we notify other hosts that something changed - for (NSString *key in [remoteClients allKeys]) { + for (NSString *key in [registeredClients allKeys]) { - id proxyObject = [remoteClients objectForKey:key]; + id proxyObject = [[registeredClients objectForKey:key] proxy]; @try { [proxyObject setNeedsUpdate:YES]; @@ -794,31 +817,26 @@ } } -- (NSDictionary *)copyArchivedPublicationsAndMacros { - NSMutableDictionary *pubsAndMacros = [[NSMutableDictionary alloc] init]; - NSMutableSet *allPubs = (NSMutableSet *)CFSetCreateMutable(CFAllocatorGetDefault(), 0, &kBDSKBibItemEqualitySetCallBacks); - NSMutableDictionary *allMacros = [[NSMutableDictionary alloc] init]; - for (BibDocument *doc in [NSApp orderedDocuments]) { - [allPubs addObjectsFromArray:[doc publications]]; - [allMacros addEntriesFromDictionary:[[doc macroResolver] macroDefinitions]]; - } - NSData *data = [NSKeyedArchiver archivedDataWithRootObject:[allPubs allObjects]]; - if ([data length]) { - [pubsAndMacros setObject:data forKey:BDSKSharedArchivedDataKey]; - data = [NSKeyedArchiver archivedDataWithRootObject:allMacros]; - if ([data length]) - [pubsAndMacros setObject:data forKey:BDSKSharedArchivedMacroDataKey]; - } - [allPubs release]; - [allMacros release]; - return pubsAndMacros; -} - - (bycopy NSData *)archivedSnapshotOfPublications { __block NSDictionary *pubsAndMacros = nil; + dispatch_sync(dispatch_get_main_queue(), ^{ - pubsAndMacros = [self copyArchivedPublicationsAndMacros]; + NSMutableSet *allPubs = (NSMutableSet *)CFSetCreateMutable(CFAllocatorGetDefault(), 0, &kBDSKBibItemEqualitySetCallBacks); + NSData *pubsData = [NSKeyedArchiver archivedDataWithRootObject:[allPubs allObjects]]; + if ([pubsData length]) { + NSMutableDictionary *allMacros = [[NSMutableDictionary alloc] init]; + for (BibDocument *doc in [NSApp orderedDocuments]) { + [allPubs addObjectsFromArray:[doc publications]]; + [allMacros addEntriesFromDictionary:[[doc macroResolver] macroDefinitions]]; + } + NSData *macrosData = [NSKeyedArchiver archivedDataWithRootObject:allMacros]; + if ([macrosData length] == 0) + macrosData = nil; + pubsAndMacros = [[NSDictionary alloc] initWithObjectsAndKeys:pubsData, BDSKSharedArchivedDataKey, macrosData, BDSKSharedArchivedDataKey, nil]; + [allMacros release]; + } + [allPubs release]; }); NSData *dataToSend = nil; @@ -839,3 +857,63 @@ } @end + +#pragma mark - + +@implementation BDSKConnectedClient + +@synthesize connection, proxy, authenticated; + +- (id)initWithConnection:(NSConnection *)aConnection { + self = [super init]; + if (self) { + connection = [aConnection retain]; + } + return self; +} + +// this is the delegate only when we need authentication +- (BOOL)authenticateComponents:(NSArray *)components withData:(NSData *)authenticationData { + static NSData *zeroData = nil; + if (zeroData == nil) { + // this is send by the sharing client when not using this to authenticate + char zero = 0; + zeroData = [[NSData alloc] initWithBytes:&zero length:1]; + } + BOOL status = YES; + // legacy clients use this method to authenticate + if ([authenticationData isEqual:zeroData] == NO && [self isAuthenticated] == NO) { + NSData *myPasswordHashed = [[BDSKPasswordController passwordForKeychainService:BDSKServiceNameForKeychain account:nil name:nil] sha1Signature]; + status = [authenticationData isEqual:myPasswordHashed]; + if (status) + [self setAuthenticated:YES]; + } + return status; +} + +- (BOOL)connection:(NSConnection *)aConnection handleRequest:(NSDistantObjectRequest *)doRequest { + // accept all messages from the server protocol + // but archivedSnapshotOfPublications only when authenticated + if ([[doRequest invocation] selector] == @selector(archivedSnapshotOfPublications) && [self isAuthenticated] == NO){ + [doRequest replyWithException:[NSException exceptionWithName:@"BDSKUnauthenticatedException" reason:@"Client was not authenticated" userInfo:nil]]; + return YES; + } + return NO; +} + +- (void)invalidate { + @try { + [proxy invalidate]; + } + @catch (id exception) { + NSLog(@"%@: ignoring exception \"%@\" raised while invalidating client %p", [self class], exception, proxy); + } + @try { + [connection invalidate]; + } + @catch (id exception) { + NSLog(@"%@: ignoring exception \"%@\" raised while invalidating connection to client %p", [self class], exception, proxy); + } +} + +@end Modified: trunk/bibdesk/BibPref_Sharing.m =================================================================== --- trunk/bibdesk/BibPref_Sharing.m 2022-08-31 14:31:17 UTC (rev 27851) +++ trunk/bibdesk/BibPref_Sharing.m 2022-09-03 17:10:36 UTC (rev 27852) @@ -73,10 +73,8 @@ // avoid accessing the keychain before it will be displayed to the user if (didDisplay == NO) return @""; - NSData *pwData = [BDSKPasswordController passwordForKeychainService:BDSKServiceNameForKeychain account:nil name:nil]; - if (pwData == nil) - return @""; - return [[[NSString alloc] initWithData:pwData encoding:NSUTF8StringEncoding] autorelease]; + NSString *password = [BDSKPasswordController passwordForKeychainService:BDSKServiceNameForKeychain account:nil name:nil]; + return password ?: @""; } - (void)setPassword:(NSString *)password { Modified: trunk/bibdesk/NSString_BDSKExtensions.h =================================================================== --- trunk/bibdesk/NSString_BDSKExtensions.h 2022-08-31 14:31:17 UTC (rev 27851) +++ trunk/bibdesk/NSString_BDSKExtensions.h 2022-09-03 17:10:36 UTC (rev 27852) @@ -478,6 +478,8 @@ - (NSString *)stringByCollapsingWhitespaceAndNewlinesAndRemovingSurroundingWhitespaceAndNewlines; - (NSString *)fullyEncodeAsIURI; +- (NSData *)sha1Signature; + - (NSString *)stringByRemovingAliens; + (NSString *)pathSeparator; Modified: trunk/bibdesk/NSString_BDSKExtensions.m =================================================================== --- trunk/bibdesk/NSString_BDSKExtensions.m 2022-08-31 14:31:17 UTC (rev 27851) +++ trunk/bibdesk/NSString_BDSKExtensions.m 2022-09-03 17:10:36 UTC (rev 27852) @@ -81,6 +81,7 @@ #import "BDSKTypeManager.h" #import "NSFileManager_BDSKExtensions.h" #import "NSAttributedString_BDSKExtensions.h" +#import "NSData_BDSKExtensions.h" @implementation NSString (BDSKExtensions) @@ -1410,6 +1411,10 @@ return [resultString autorelease]; } +- (NSData *)sha1Signature { + return [[self dataUsingEncoding:NSUTF8StringEncoding] sha1Signature]; +} + // NS and CF character sets won't find these, due to the way CFString handles surrogate pairs. The surrogate pair inlines were borrowed from CFCharacterSetPriv.h in CF-lite-476.13. static inline bool __SKIsSurrogateHighCharacter(const UniChar character) { return ((character >= 0xD800UL) && (character <= 0xDBFFUL) ? true : false); This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. _______________________________________________ Bibdesk-commit mailing list Bibdesk-commit@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/bibdesk-commit