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

Reply via email to