root/trunk/Controller.m

Revision 119, 16.1 kB (checked in by eric, 1 year ago)

Handpatched from branches/eric since almost every file confliceted. Includes * rewritted screensaver check * rewritten key timeout * proper UTF8 handling with Keychain * removed unecessary thread spawn for add all keys without interaction.

Line 
1 #import "Controller.h"
2
3 #include <sys/types.h>
4 #include <unistd.h>
5 #include <utime.h>
6
7 #import "PreferenceController.h"
8 #import "UpdateController.h"
9 #import "TokenController.h"
10
11 #import "Libs/SSHAgent.h"
12 #import "Libs/SSHKeychain.h"
13 #import "Libs/SSHTunnel.h"
14
15 #include <objc/objc-class.h>
16 #include <objc/objc-runtime.h>
17
18 #include "SSHKeychain_Prefix.pch"
19
20 Controller *sharedController;
21
22 NSString *local(NSString *theString)
23 {
24         return NSLocalizedString(theString, nil);
25 }       
26
27 @implementation Controller
28
29 - (id)init
30 {
31         NSMutableDictionary *defaults, *dict;
32         NSConnection *conn;
33         NSString *path;
34         NSTask *theTask;
35        
36         if(!(self = [super init]))
37         {
38                 return nil;
39         }
40
41         conn = [NSConnection defaultConnection];
42        
43         [conn runInNewThread];
44         [conn removeRunLoop:[NSRunLoop currentRunLoop]];
45
46         /* Register the default settings */
47         defaults = [NSMutableDictionary dictionaryWithObjects:
48                 [NSArray arrayWithObjects:
49                         @"/usr/bin/",
50                         [NSString stringWithFormat:@"/tmp/%d/SSHKeychain.socket", getuid()],
51                         @"YES",
52                         @"NO",
53                         @"1",
54                         @"4",
55                         @"4",
56                         @"0",
57                         @"NO",
58                         @"3",
59                         [NSArray arrayWithObjects:@"~/.ssh/identity", @"~/.ssh/id_dsa", nil],
60                         @"NO",
61                         @"30",
62                         @"0",
63                         nil
64                 ]
65                 forKeys:
66                 [NSArray arrayWithObjects:
67                         SSHToolsPathString,
68                         SocketPathString,
69                         AddKeysOnConnectionString,
70                         AskForConfirmationString,
71                         OnSleepString,
72                         OnScreensaverString,
73                         FollowKeychainString,
74                         MinutesOfSleepString,
75                         CheckForUpdatesOnStartupString,
76                         DisplayString,
77                         @"Keys",
78                         ManageGlobalEnvironmentString,
79                         CheckScreensaverIntervalString,
80                         KeyTimeoutString,
81                         nil
82                 ]
83         ];
84
85
86         [[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
87
88         path = [[[NSBundle mainBundle] bundlePath] stringByAppendingString:@"/Contents/Info.plist"];
89         dict = [[[NSMutableDictionary alloc] initWithContentsOfFile:path] autorelease];
90
91         if(dict == nil)
92         {
93                 dict = [NSMutableDictionary dictionary];
94         }
95
96         if([[NSUserDefaults standardUserDefaults] integerForKey:DisplayString] == 1)
97         {
98                 if((![[dict objectForKey:@"LSUIElement"] isEqualToString:@"1"]) &&
99                    ([[NSFileManager defaultManager] isWritableFileAtPath:path]))
100                 {
101                         [dict setObject:@"1" forKey:@"LSUIElement"];
102                         if(![dict writeToFile:path atomically:YES])
103                         {
104                                 NSLog(@"DEBUG: Couldn't write Info.plist.");
105                                 exit(0);
106                         }
107        
108                         /* Change the bundle's modification time to let LaunchServices know we've
109                          * changed something. */
110                         if(utime([[[NSBundle mainBundle] bundlePath] fileSystemRepresentation], nil) == -1)
111                         {
112                                 NSLog(@"DEBUG: utime on bundlePath failed.");
113                                 exit(0);
114                         }
115
116                         theTask = [[NSTask alloc] init];
117                         [theTask setLaunchPath:@"/usr/bin/open"];
118                         [theTask setArguments:[NSArray arrayWithObject:[[NSBundle mainBundle] bundlePath]]];
119                         [theTask launch];
120                         exit(0);
121                 }
122         }
123        
124         else
125         {
126                 if((![[dict objectForKey:@"LSUIElement"] isEqualToString:@"0"]) && ([dict objectForKey:@"LSUIElement"]))
127                 {
128                         [dict setObject:@"0" forKey:@"LSUIElement"];
129                         [dict writeToFile:path atomically:YES];
130        
131                         /* Change the bundle's modification time to let LaunchServices know we've
132                                 * changed something. */
133                         if(utime([[[NSBundle mainBundle] bundlePath] fileSystemRepresentation], nil) == -1)
134                         {
135                                 NSLog(@"DEBUG: utime on bundlePath failed.");
136                         }
137        
138                         theTask = [[NSTask alloc] init];
139                         [theTask setLaunchPath:@"/usr/bin/open"];
140                         [theTask setArguments:[NSArray arrayWithObject:[[NSBundle mainBundle] bundlePath]]];
141                         [theTask launch];
142                         exit(0);
143                 }
144         }
145        
146
147         [conn setRootObject:self];
148         if([conn registerName:@"SSHKeychain"] == NO)
149         {
150                 NSLog(@"SSHKeychain already running");
151                 exit(0);
152         }
153
154         else {
155                 NSLog(@"Registered connection as SSHKeychain");
156         }
157
158         [NSApp setApplicationIconImage:[[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForImageResource:@"SSHKeychain"]]];
159
160         [[NSNotificationCenter defaultCenter] addObserver:self
161                 selector:@selector(appleKeychainNotification:) name:@"AppleKeychainLocked" object:nil];
162         [[NSNotificationCenter defaultCenter] addObserver:self
163                 selector:@selector(appleKeychainNotification:) name:@"AppleKeychainUnlocked" object:nil];
164
165         passphraseIsRequestedLock = [[NSLock alloc] init];
166         appleKeychainUnlockedLock = [[NSLock alloc] init];
167         statusitemLock = [[NSLock alloc] init];
168
169         sharedController = self;
170
171         timestamp = 0;
172
173         return self;
174 }
175
176 + (Controller *)sharedController
177 {
178         if(!sharedController) {
179                 return [[Controller alloc] init];
180         }
181
182         return sharedController;
183 }
184
185 - (void)dealloc
186 {
187         [passphraseIsRequestedLock dealloc];
188         [appleKeychainUnlockedLock dealloc];
189         [statusitemLock dealloc];
190
191         [super dealloc];
192 }
193
194 - (void)awakeFromNib
195 {
196         NSStatusBar *statusbar;
197
198         /* Create a statusbar item if needed. */
199         int display = [[NSUserDefaults standardUserDefaults] integerForKey:DisplayString];
200        
201         if((display == 1) || (display == 3))
202         {
203                 statusbar = [NSStatusBar systemStatusBar];
204                 [statusitemLock lock];
205                 statusitem = [statusbar statusItemWithLength:NSVariableStatusItemLength];
206
207                 [statusitem retain];
208                 [statusitem setHighlightMode:YES];
209                 [statusitem setImage:[NSImage imageNamed:@"small_icon_empty"]];
210                 [statusitem setMenu:statusbarMenu];
211        
212                 [statusitemLock unlock];
213
214                 [NSApp unhide];
215         }
216
217         SecKeychainStatus status;
218         SecKeychainGetStatus(nil, &status);
219
220         if(status & 1)
221         {
222                 [appleKeychainUnlockedLock lock];
223                 appleKeychainUnlocked = YES;
224                 [appleKeychainUnlockedLock unlock];
225
226                 [dockMenuAppleKeychainItem setTitle:local(@"LockAppleKeychain")];
227                 [statusbarMenuAppleKeychainItem setTitle:local(@"LockAppleKeychain")];
228         }
229
230         else
231         {
232                 [appleKeychainUnlockedLock lock];
233                 appleKeychainUnlocked = NO;
234                 [appleKeychainUnlockedLock unlock];
235
236                 [dockMenuAppleKeychainItem setTitle:local(@"UnlockAppleKeychain")];
237                 [statusbarMenuAppleKeychainItem setTitle:local(@"UnlockAppleKeychain")];
238         }
239 }
240
241 - (void)applicationDidFinishLaunching:(NSNotification *)aNotification
242 {
243         if([[NSUserDefaults standardUserDefaults] boolForKey:UseGlobalEnvironmentString] == YES)
244         {
245                 NSString *path = [[NSString stringWithString:@"~/.MacOSX/environment.plist"] stringByExpandingTildeInPath];
246                 NSString *socketPath = [[NSUserDefaults standardUserDefaults] stringForKey:SocketPathString];
247                 NSString *macOSXDir = [[NSString stringWithString:@"~/.MacOSX"] stringByExpandingTildeInPath];
248                 NSMutableDictionary *dict;
249
250                 BOOL isDirectory;
251
252                 /* If ~/.MacOSX/ doesn't exists, create a directory. */
253                 if(![[NSFileManager defaultManager] fileExistsAtPath:macOSXDir isDirectory:&isDirectory])
254                 {
255                         [[NSFileManager defaultManager] createDirectoryAtPath:macOSXDir attributes:nil];
256                 }
257
258                 /* If ~/.MacOSX is a file, log and error and return */
259                 else if(isDirectory == NO)
260                 {
261                         NSLog(@"~/.MacOSX is a file, can not create environemnt variables");
262                         return;
263 /*                      [[NSFileManager defaultManager] removeFileAtPath:macOSXDir handler:nil];
264                         [[NSFileManager defaultManager] createDirectoryAtPath:macOSXDir attributes:nil]; */
265                 }
266
267                 /* If ~/.MacOSX/environment.plist doesn't exists, make a new dictionary. */
268                 if((dict = [[[NSMutableDictionary alloc] initWithContentsOfFile:path] autorelease]) == nil)
269                 {
270                         dict = [NSMutableDictionary dictionary];
271                 }
272
273                 if([dict objectForKey:@"SSH_AUTH_SOCK"] == nil)
274                 {
275                         [dict setObject:socketPath forKey:@"SSH_AUTH_SOCK"];
276                         [dict writeToFile:path atomically:YES];
277                         [self warningPanelWithTitle:@"SSH_AUTH_SOCK"
278                                   andMessage:local(@"AddedAuthsockToEnvironment")];
279                 }
280
281                 if(!([[dict objectForKey:@"SSH_AUTH_SOCK"] isEqualToString:socketPath]))
282                 {
283                         [dict setObject:socketPath forKey:@"SSH_AUTH_SOCK"];
284                         [dict writeToFile:path atomically:YES];
285                         [self warningPanelWithTitle:@"SSH_AUTH_SOCK"
286                                 andMessage:local(@"ChangedAuthsockInEnvironment")];
287                 }
288         }
289
290         if([[NSUserDefaults standardUserDefaults] boolForKey:CheckForUpdatesOnStartupString] == YES)
291         {
292                 [[UpdateController sharedController] checkForUpdatesWithWarnings:NO];
293         }
294 }
295
296 - (void)setStatus:(BOOL)status
297 {
298         if(status) {
299                 [statusitemLock lock];
300                 [statusitem setImage:[NSImage imageNamed:@"small_icon"]];
301                 [statusitemLock unlock];
302         } else {
303                 [statusitemLock lock];
304                 [statusitem setImage:[NSImage imageNamed:@"small_icon_empty"]];
305                 [statusitemLock unlock];
306         }
307 }
308
309 - (void)setToolTip:(NSString *)tooltip
310 {
311         [statusitemLock lock];
312         [statusitem setToolTip:tooltip];
313         [statusitemLock unlock];
314 }
315
316 - (IBAction)checkForUpdatesFromUI:(id)sender
317 {
318         [[UpdateController sharedController] checkForUpdatesWithWarnings:YES];
319 }
320
321 - (IBAction)preferences:(id)sender
322 {
323         /* The preferences class can handle things itself. Just tell it to open. */
324         [PreferenceController openPreferencesWindow];
325 }
326
327 - (IBAction)toggleAppleKeychainLock:(id)sender
328 {
329         ProcessSerialNumber focusSerialNumber;
330
331         [appleKeychainUnlockedLock lock];
332         if(appleKeychainUnlocked == YES)
333         {
334                 [appleKeychainUnlockedLock unlock];
335                 SecKeychainLock(nil);
336         }
337
338         else
339         {
340                 [appleKeychainUnlockedLock unlock];
341
342                 GetFrontProcess(&focusSerialNumber);
343
344                 [NSApp activateIgnoringOtherApps:YES];
345                 SecKeychainUnlock(nil, 0, nil, 0);
346
347                 SetFrontProcess(&focusSerialNumber);
348         }
349 }
350
351 - (NSString *)askPassphrase:(NSString *)question withToken:(NSString *)token andInteraction:(BOOL)interaction
352 {
353         char *serviceName;
354         const char *accountName = nil;
355         char *kcPassword;
356         UInt32 passwordLength;
357         SecKeychainStatus keychainStatus;
358         OSStatus returnStatus = -1;
359         SecKeychainRef keychain;
360
361         CFArrayRef searchList;
362
363         SInt32 error;
364         CFUserNotificationRef notification;
365         CFOptionFlags response;
366         CFStringRef enteredPassphrase;
367
368         NSString *passphrase, *firstQuestion;
369         NSMutableDictionary *dict;
370         BOOL consultKeychain = NO;
371
372         ProcessSerialNumber focusSerialNumber;
373
374         // Check if the token is valid.
375         if(![[TokenController sharedController] checkToken:token])
376         {
377                 return nil;
378         }
379        
380         GetFrontProcess(&focusSerialNumber);
381
382         SecKeychainSetUserInteractionAllowed(TRUE);
383
384         int i;
385                
386         [passphraseIsRequestedLock lock];
387         if (passphraseIsRequested)
388         {
389                 [passphraseIsRequestedLock unlock];
390                 SetFrontProcess(&focusSerialNumber);
391                 return nil;
392         }
393
394         passphraseIsRequested = YES;
395
396         [passphraseIsRequestedLock unlock];
397
398         firstQuestion = @"Enter passphrase for ";
399
400         if ([question hasPrefix:firstQuestion])
401         {
402                 consultKeychain = YES;
403                 accountName = [[[[question substringFromIndex:[firstQuestion length]]
404                                                 componentsSeparatedByString:@": "] objectAtIndex:0] UTF8String];
405         }
406
407         else if ([question hasSuffix:@"'s password: "])
408         {
409                 consultKeychain = YES;
410                 accountName = [[[question componentsSeparatedByString:@"'s"] objectAtIndex:0] UTF8String];
411         }
412
413         else if ([question hasPrefix:@"The authenticity of host"])
414         {
415                 [passphraseIsRequestedLock lock];
416                 passphraseIsRequested = NO;
417                 [passphraseIsRequestedLock unlock];
418                
419                 if (! interaction)
420                         return @"no";
421                
422
423                 int r = NSRunAlertPanel(local(@"UnknownHostKey"), question, local(@"No"), local(@"Yes"), nil);
424
425                 SetFrontProcess(&focusSerialNumber);
426
427                 NSString *response = ( r == NSAlertAlternateReturn) ? @"yes" : @"no";
428
429                 return response;
430         }
431        
432         if(consultKeychain)
433         {
434                 serviceName = "SSHKeychain";
435
436                 if(!interaction)
437                 {
438                         SecKeychainCopySearchList(&searchList);
439                        
440                         for(i=0; i < [(NSArray *)searchList count]; i++) {
441                                 keychain = (SecKeychainRef)[(NSArray *)searchList objectAtIndex:i];
442
443                                 SecKeychainGetStatus(keychain, &keychainStatus);
444                                
445                                 if(keychainStatus & 1) {
446                                         returnStatus = SecKeychainFindGenericPassword(
447                                                 keychain, strlen(serviceName), serviceName,
448                                                 strlen(accountName), accountName, &passwordLength,
449                                                 (void **)&kcPassword, nil);
450                                        
451                                         if(returnStatus == 0) {
452                                                 break;
453                                         }
454                                 }
455                         }
456                        
457                         CFRelease(searchList);
458                 }
459                
460                 else
461                 {
462                         returnStatus = SecKeychainFindGenericPassword(
463                                 nil, strlen(serviceName), serviceName, strlen(accountName),
464                                 accountName, &passwordLength, (void **)&kcPassword, nil);
465                 }
466                
467                 SetFrontProcess(&focusSerialNumber);
468                
469                 [passphraseIsRequestedLock lock];
470                 passphraseIsRequested = NO;
471                 [passphraseIsRequestedLock unlock];
472                
473                 if(returnStatus == 0)
474                 {
475                         NSString *returnString;
476                        
477                         if ( kcPassword[passwordLength] != 0 ) {
478                                 /* Don't trust memory allocated from system, copy it over
479                                 First before making it a CString */
480
481                                 NSLog(@"Buggy password in keycahin workaround");
482                                 char * buffer = (char*)malloc((passwordLength+1)*sizeof(char));
483                                 strncpy(buffer, kcPassword, passwordLength);
484                                 buffer[passwordLength] = '\0';
485                        
486
487                                 returnString = [NSString stringWithUTF8String:buffer];
488
489                                 SecKeychainItemFreeContent(NULL, kcPassword);
490                                 free(buffer);
491                         } else {
492                                 returnString = [NSString stringWithUTF8String:kcPassword];
493
494                                 SecKeychainItemFreeContent(NULL, kcPassword);
495                         }
496                        
497                         return returnString;
498                 }
499         }
500
501         if(interaction)
502         {
503
504                 /* Dictionary for the panel. */
505                 dict = [NSMutableDictionary dictionary];
506
507                 [dict setObject:local(@"Passphrase") forKey:(NSString *)kCFUserNotificationAlertHeaderKey];
508                 [dict setObject:question forKey:(NSString *)kCFUserNotificationAlertMessageKey];
509
510                 if(consultKeychain)
511                 {
512                         [dict setObject:local(@"AddPassphraseToAppleKeychain") forKey:(NSString *)kCFUserNotificationCheckBoxTitlesKey];
513                 }
514
515                 [dict setObject:[NSURL fileURLWithPath:[[[NSBundle mainBundle] resourcePath]
516                                         stringByAppendingString:@"/SSHKeychain.icns"]] forKey:(NSString *)kCFUserNotificationIconURLKey];
517
518                 [dict setObject:@"" forKey:(NSString *)kCFUserNotificationTextFieldTitlesKey];
519                 [dict setObject:local(@"Ok") forKey:(NSString *)kCFUserNotificationDefaultButtonTitleKey];
520                 [dict setObject:local(@"Cancel") forKey:(NSString *)kCFUserNotificationAlternateButtonTitleKey];
521
522                 /* Display a passphrase request notification. */
523                 notification = CFUserNotificationCreate(nil, 30, CFUserNotificationSecureTextField(0), &error, (CFDictionaryRef)dict);
524
525                 /* If there was an error, return nil. */
526                 if(error)
527                 {
528                         [passphraseIsRequestedLock lock];
529                         passphraseIsRequested = NO;
530                         [passphraseIsRequestedLock unlock];
531                         SetFrontProcess(&focusSerialNumber);
532                         return nil;
533                 }
534                
535                 /* If we couldn't receive a response, return nil. */
536                 if(CFUserNotificationReceiveResponse(notification, 0, &response))
537                 {
538                         [passphraseIsRequestedLock lock];
539                         passphraseIsRequested = NO;
540                         [passphraseIsRequestedLock unlock];
541                         SetFrontProcess(&focusSerialNumber);
542                         return nil;
543                 }
544
545                 /* If OK wasn't pressed, return nil. */
546                 if((response & 0x3) != kCFUserNotificationDefaultResponse)
547                 {
548                         [passphraseIsRequestedLock lock];
549                         passphraseIsRequested = NO;
550                         [passphraseIsRequestedLock unlock];
551                         SetFrontProcess(&focusSerialNumber);
552                         return nil;
553                 }
554                
555                 /* Get the passphrase from the textfield. */
556                 enteredPassphrase = CFUserNotificationGetResponseValue(notification, kCFUserNotificationTextFieldValuesKey, 0);
557
558                 if(enteredPassphrase != nil)
559                 {
560                         passphrase = [NSString stringWithString:(NSString *)enteredPassphrase];
561                         CFRelease(notification);
562                        
563                         if(consultKeychain && (response & CFUserNotificationCheckBoxChecked(0)))
564                         {
565                                 serviceName = "SSHKeychain";
566                                
567                                 const char * utf8password = [passphrase UTF8String];
568                                
569                                 SecKeychainAddGenericPassword(nil, strlen(serviceName),
570                                         serviceName, strlen(accountName), accountName,
571                                         strlen(utf8password) + 1,
572                                         (const void *)utf8password, nil);
573                         }
574                        
575                         [passphraseIsRequestedLock lock];
576                         passphraseIsRequested = NO;
577                         [passphraseIsRequestedLock unlock];
578                        
579                         SetFrontProcess(&focusSerialNumber);
580
581                         return passphrase;
582                 }
583
584         }
585        
586         SetFrontProcess(&focusSerialNumber);
587        
588         [passphraseIsRequestedLock lock];
589         passphraseIsRequested = NO;
590         [passphraseIsRequestedLock unlock];
591
592         return nil;
593 }
594
595 - (IBAction)showAboutPanel:(id)sender
596 {
597         [NSApp activateIgnoringOtherApps:YES];
598         [NSApp orderFrontStandardAboutPanel:self];
599 }
600
601 - (void)warningPanelWithTitle:(NSString *)title andMessage:(NSString *)message
602 {
603         [NSApp activateIgnoringOtherApps:YES];
604         NSRunAlertPanel(title, message, nil, nil, nil);
605 }
606
607 - (NSData *)statusbarMenu
608 {
609         return [NSArchiver archivedDataWithRootObject:statusbarMenu];
610 }
611
612 - (void)appleKeychainNotification:(NSNotification *)notification
613 {
614         if([[notification name] isEqualToString:@"AppleKeychainLocked"])
615         {
616                 [appleKeychainUnlockedLock lock];
617                 appleKeychainUnlocked = NO;
618                 [appleKeychainUnlockedLock unlock];
619
620                 [dockMenuAppleKeychainItem setTitle:local(@"UnlockAppleKeychain")];
621                 [statusbarMenuAppleKeychainItem setTitle:local(@"UnlockAppleKeychain")];
622         }
623
624         else if([[notification name] isEqualToString:@"AppleKeychainUnlocked"])
625         {
626                 [appleKeychainUnlockedLock lock];
627                 appleKeychainUnlocked = YES;
628                 [appleKeychainUnlockedLock unlock];
629
630                 [dockMenuAppleKeychainItem setTitle:local(@"LockAppleKeychain")];
631                 [statusbarMenuAppleKeychainItem setTitle:local(@"LockAppleKeychain")];
632         }
633 }
634
635 @end
Note: See TracBrowser for help on using the browser.