OSX Preference Pane and AFS Backgrounder
[openafs.git] / src / platform / DARWIN / AFSPreference / AFSBackgrounder / AFSBackgrounderDelegate.m
1 //
2 //  AFSBackgrounder.m
3 //  OpenAFS
4 //
5 //  Created by Claudio Bisegni on 29/07/09.
6 //  Copyright 2009 Infn. All rights reserved.
7 //
8
9 #import "AFSBackgrounderDelegate.h"
10 #import "AFSMenuExtraView.h"
11 #import "AFSPropertyManager.h"
12 #import "TaskUtil.h"
13 #import "TokenCredentialController.h"
14 #include <sys/param.h>
15 #include <sys/stat.h>
16 #include <sys/wait.h>
17 #include <sys/types.h>
18 #include <sys/fcntl.h>
19 #include <sys/errno.h>
20 #include <unistd.h>
21 #include <stdlib.h>
22 #include <string.h>
23
24 @implementation AFSBackgrounderDelegate
25 #pragma mark NSApp Delegate
26 - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
27         afsMngr = [[AFSPropertyManager alloc] initWithAfsPath:afsSysPath];
28         // allocate the lock for concurent afs check state
29         tokensLock = [[NSLock alloc] init];
30         
31         //remove the auto eanble on menu item
32         [backgrounderMenu setAutoenablesItems:NO];
33     //Sets the images in our NSStatusItem
34         statusItem = nil;
35         
36   
37         // Get the imge for menu
38         //Load image for menu rappresentation
39         hasTokenImage = [self getImageFromBundle:@"hasToken" 
40                                                                          fileExt:@"png"];
41         
42         noTokenImage = [self getImageFromBundle:@"noToken" 
43                                                                         fileExt:@"png"];
44         //get the sazi of the menu icon
45         menuSize = [hasTokenImage size];
46         //Start to read the afs path
47         [self readPreferenceFile:nil];  
48         [self startTimer];
49
50         
51         
52         // Register for preference user change
53         [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(readPreferenceFile:) 
54                                                                                                                         name:kAFSMenuExtraID object:kPrefChangeNotification];
55         
56         // Register for afs state change
57         [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(afsVolumeMountChange:) 
58                                                                                                                         name:kAFSMenuExtraID object:kMExtraAFSStateChange];
59         
60         // Register for menu state change
61         [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(chageMenuVisibility:) 
62                                                                                                                         name:kAFSMenuExtraID object:kMExtraAFSMenuChangeState];
63         
64         //Register for mount/unmount afs volume
65         [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self 
66                                                                                                                    selector:@selector(afsVolumeMountChange:) 
67                                                                                                                            name:NSWorkspaceDidMountNotification object:nil];
68         
69         [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self 
70                                                                                                                    selector:@selector(afsVolumeMountChange:) 
71                                                                                                                            name:NSWorkspaceDidUnmountNotification object:nil];
72         
73         //try to see if we need tho show the menu at startup
74         
75         [self setStatusItem:[showStatusMenu boolValue]];
76         
77         NSLog(@"Check if we need to get token at login time %d", [aklogTokenAtLogin intValue]);
78         if([aklogTokenAtLogin boolValue] && afsState && !gotToken) {
79                 NSLog(@"Proceed to get token");
80                 //check if we must get the aklog at logint(first run of backgrounder
81                 [self getToken:nil];
82         }       
83 }
84
85 - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
86         if(afsSysPath) [afsSysPath release];
87         
88         //release the lock
89         [self stopTimer];
90         
91         if(hasTokenImage) [hasTokenImage release];
92         if(noTokenImage) [noTokenImage release];
93         
94         // Unregister for preference change
95         [[NSDistributedNotificationCenter defaultCenter] removeObserver:self name:kAFSMenuExtraID object:kPrefChangeNotification];
96         [[NSDistributedNotificationCenter defaultCenter] removeObserver:self name:kAFSMenuExtraID object:kMExtraAFSStateChange];
97         [[NSDistributedNotificationCenter defaultCenter] removeObserver:self name:kAFSMenuExtraID object:kMExtraAFSMenuChangeState];
98         [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:self name:NSWorkspaceDidMountNotification object:nil];
99         [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:self name:NSWorkspaceDidUnmountNotification object:nil];
100         
101
102         // send notify that menuextra has closed
103         [[NSDistributedNotificationCenter defaultCenter] postNotificationName:kAfsCommanderID object:kPrefChangeNotification];
104         
105         if(tokensLock) [tokensLock release];
106         if(afsMngr) [afsMngr release];
107         return NSTerminateNow;
108 }
109 // -------------------------------------------------------------------------------
110 //  -(void) readPreferenceFile
111 // -------------------------------------------------------------------------------
112 - (void) readPreferenceFile:(NSNotification *)notification
113 {
114         if(afsSysPath) {
115                 [afsSysPath release];
116                 afsSysPath = nil;
117         }
118         CFPreferencesSynchronize((CFStringRef)kAfsCommanderID,  kCFPreferencesAnyUser, kCFPreferencesAnyHost);
119         CFPreferencesSynchronize((CFStringRef)kAfsCommanderID,  kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
120         
121         afsSysPath = PREFERENCE_AFS_SYS_PAT_STATIC;
122         
123         // read the preference for aklog use
124         useAklogPrefValue = (NSNumber*)CFPreferencesCopyValue((CFStringRef)PREFERENCE_USE_AKLOG, 
125                                                                                                                   (CFStringRef)kAfsCommanderID, 
126                                                                                                                   kCFPreferencesCurrentUser, 
127                                                                                                                   kCFPreferencesAnyHost);
128         
129         showStatusMenu = (NSNumber*)CFPreferencesCopyValue((CFStringRef)PREFERENCE_SHOW_STATUS_MENU, 
130                                                                                                            (CFStringRef)kAfsCommanderID, 
131                                                                                                            kCFPreferencesCurrentUser, 
132                                                                                                            kCFPreferencesAnyHost);
133         
134         aklogTokenAtLogin = (NSNumber*)CFPreferencesCopyValue((CFStringRef)PREFERENCE_AKLOG_TOKEN_AT_LOGIN, (CFStringRef)kAfsCommanderID,  
135                                                                                                                                         kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
136
137         //set the menu name
138         [self updateAfsStatus:nil];
139 }
140
141 // -------------------------------------------------------------------------------
142 //  - (void)chageMenuVisibility:(NSNotification *)notification
143 // -------------------------------------------------------------------------------
144 /**/
145 - (void)chageMenuVisibility:(NSNotification *)notification {
146         [self readPreferenceFile:nil];
147         [self setStatusItem:[showStatusMenu boolValue]];
148 }
149
150 // -------------------------------------------------------------------------------
151 //  - (void)startStopAfs:(id)sender
152 // -------------------------------------------------------------------------------
153 - (void)startStopAfs:(id)sender
154 {
155         if(!afsSysPath) return;
156         
157         OSStatus status = noErr;
158         NSString *afsdPath = [TaskUtil searchExecutablePath:@"afsd"];
159         NSString *rootHelperApp = nil;
160         BOOL currentAfsState = NO;
161         
162         @try {
163                 if(afsdPath == nil) return;
164                 currentAfsState = [afsMngr checkAfsStatus];
165                 rootHelperApp = [[NSBundle mainBundle] pathForResource:@"afshlp" ofType:@""];
166
167                 //Check helper app
168                 [self repairHelperTool];
169                 
170                 // make the parameter to call the root helper app
171                 status = [[AuthUtil shared] autorize];
172                 if(status == noErr){
173                         if(currentAfsState){
174                                 //shutdown afs
175                                 NSMutableString *afsKextPath = [[NSMutableString alloc] initWithCapacity:256];
176                                 [afsKextPath setString:afsSysPath];
177                                 [afsKextPath appendString:@"/etc/afs.kext"];
178                                 
179                                 const char *stopAfsArgs[] = {"stop_afs", [afsKextPath  UTF8String], [afsdPath UTF8String], 0L};
180                                 [[AuthUtil shared] execUnixCommand:[rootHelperApp UTF8String] 
181                                                                                           args:stopAfsArgs
182                                                                                         output:nil];
183                         } else {
184                                 const char *startAfsArgs[] = {[[ [NSBundle mainBundle] pathForResource:@"start_afs" ofType:@"sh"]  UTF8String], [afsSysPath UTF8String], [afsdPath UTF8String], 0L};
185                                 [[AuthUtil shared] execUnixCommand:[rootHelperApp UTF8String] 
186                                                                                           args:startAfsArgs
187                                                                                         output:nil];
188                         }
189                 }
190         }
191         @catch (NSException * e) {
192                 NSLog([e reason]);
193         }
194         @finally {
195                 [[AuthUtil shared] deautorize];
196                 [self updateAfsStatus:nil];
197                 //Send notification to preferencepane
198                 [[NSDistributedNotificationCenter defaultCenter] postNotificationName:kAfsCommanderID object:kMenuExtraEventOccured];
199         }
200         
201 }
202
203 // -------------------------------------------------------------------------------
204 //  -(void) getToken
205 // -------------------------------------------------------------------------------
206 - (void)getToken:(id)sender
207 {
208         
209         NSRect globalRect;
210         globalRect.origin = [[[statusItem view] window] convertBaseToScreen:[[statusItem view] frame].origin];
211         globalRect.size = [[statusItem view] frame].size;
212         AFSPropertyManager *afsPropMngr = [[AFSPropertyManager alloc] initWithAfsPath:afsSysPath ];
213         [afsPropMngr loadConfiguration]; 
214         
215         if([useAklogPrefValue boolValue]) {
216                 [afsPropMngr getTokens:false 
217                                                    usr:nil 
218                                                    pwd:nil];
219                 [self klogUserEven:nil];
220         } else {
221                 // register for user event
222                 [[NSDistributedNotificationCenter defaultCenter] addObserver:self 
223                                                                                                                         selector:@selector(klogUserEven:) 
224                                                                                                                                 name:kAFSMenuExtraID 
225                                                                                                                           object:kLogWindowClosed];
226                 
227                 credentialMenuController = [[AFSMenuCredentialContoller alloc] initWhitRec:globalRect 
228                                                                                                                                         afsPropManager:afsPropMngr];
229                 [credentialMenuController showWindow];
230         }
231         
232         //Dispose afs manager
233         [afsPropMngr release];
234 }
235
236 // -------------------------------------------------------------------------------
237 //  -(void) releaseToken
238 // -------------------------------------------------------------------------------
239 - (void)releaseToken:(id)sender
240 {
241         [afsMngr unlog:nil];
242         [self updateAfsStatus:nil];
243 }
244
245
246 // -------------------------------------------------------------------------------
247 //  -(void) afsVolumeMountChange - Track for mount unmount afs volume
248 // -------------------------------------------------------------------------------
249 - (void) afsVolumeMountChange:(NSNotification *)notification{
250         [self updateAfsStatus:nil];
251 }
252
253 // -------------------------------------------------------------------------------
254 //  -(void) updateAfsStatus
255 // -------------------------------------------------------------------------------
256 - (void)updateAfsStatus:(NSTimer*)timer
257 {
258         //Try to locking
259         if(![tokensLock tryLock]) return;
260         
261         // check the afs state in esclusive mode
262         afsState = [afsMngr checkAfsStatus];
263         
264         NSArray *tokens = [afsMngr getTokenList];
265         gotToken = [tokens count] > 0;
266         [tokens release];
267         
268         //update the menu icon
269         [[statusItem view] setNeedsDisplay:YES];
270         //unlock
271         [tokensLock unlock];
272 }
273
274 // -------------------------------------------------------------------------------
275 //  -(void) klogUserEven
276 // -------------------------------------------------------------------------------
277 -(void) klogUserEven:(NSNotification *)notification
278 {
279         if(credentialMenuController) {
280                 [[NSDistributedNotificationCenter defaultCenter] removeObserver:self name:kAFSMenuExtraID object:kLogWindowClosed];
281                 [credentialMenuController closeWindow];
282                 [credentialMenuController release];
283                 credentialMenuController = nil;
284         }
285         //Send notification to PreferencePane
286         [[NSDistributedNotificationCenter defaultCenter] postNotificationName:kAfsCommanderID object:kMenuExtraEventOccured];
287         
288         [self updateAfsStatus:nil];
289 }
290 #pragma mark Operational Function
291 // -------------------------------------------------------------------------------
292 //  startTimer:
293 // -------------------------------------------------------------------------------
294 - (void)startTimer{
295         //start the time for check tokens validity
296         if(timerForCheckTokensList) return;
297         timerForCheckTokensList = [NSTimer scheduledTimerWithTimeInterval:TOKENS_REFRESH_TIME_IN_SEC 
298                                                                                                                            target:self 
299                                                                                                                          selector:@selector(updateAfsStatus:) 
300                                                                                                                          userInfo:nil 
301                                                                                                                           repeats:YES];
302         [timerForCheckTokensList fire]; 
303 }
304
305 // -------------------------------------------------------------------------------
306 //  stopTimer:
307 // -------------------------------------------------------------------------------
308 - (void)stopTimer{
309         if(!timerForCheckTokensList) return;
310         [timerForCheckTokensList invalidate];   
311         timerForCheckTokensList = nil;
312 }
313 // -------------------------------------------------------------------------------
314 //  -(void) getImageFromBundle
315 // -------------------------------------------------------------------------------
316 - (NSImage*)getImageFromBundle:(NSString*)fileName fileExt:(NSString*)ext
317 {
318         return [[NSImage alloc]initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:fileName 
319                                                                                                                                                   ofType:ext]];
320 }
321
322
323 // -------------------------------------------------------------------------------
324 //  - (void)menuNeedsUpdate:(NSMenu *)menu
325 // -------------------------------------------------------------------------------
326 - (void)menuNeedsUpdate:(NSMenu *)menu {
327         if (menu == backgrounderMenu)
328         {
329                 [startStopMenuItem setTitle:afsState?@"Shutdown AFS":@"Startup AFS"];
330                 [getReleaseTokenMenuItem setTitle:gotToken?@"Release token":@"Get New Token"];
331                 [getReleaseTokenMenuItem setEnabled:afsState];
332                 [[statusItem view] setNeedsDisplay:YES];
333                 
334         }
335 }
336
337
338 // -------------------------------------------------------------------------------
339 //  -(void) useAklogPrefValue
340 // -------------------------------------------------------------------------------
341 - (BOOL)useAklogPrefValue
342 {
343         if(useAklogPrefValue) return [useAklogPrefValue intValue] == NSOnState; 
344         else return NSOffState;
345 }
346
347 // -------------------------------------------------------------------------------
348 //  repairHelperTool:
349 // -------------------------------------------------------------------------------
350 - (void) repairHelperTool
351 {
352         struct stat st;
353     int fdTool;
354         int status = 0; 
355         NSString *afshlpPath = [[NSBundle mainBundle] pathForResource:@"afshlp" ofType:nil];
356         
357         
358     
359     // Open tool exclusively, so nobody can change it while we bless it.
360     fdTool = open([afshlpPath UTF8String], O_NONBLOCK | O_RDONLY | O_EXLOCK, 0);
361     
362     if(fdTool == -1)
363     {
364         NSLog(@"Exclusive open while repairing tool failed: %d.", errno);
365         exit(-1);
366     }
367     
368     if(fstat(fdTool, &st))
369     {
370         NSLog(@"fstat failed.");
371         exit(-1);
372     }
373     
374     if(st.st_uid != 0)
375     {
376                 status = [[AuthUtil shared] autorize];
377                 if(status == noErr){
378                         fchown(fdTool, 0, st.st_gid);
379                         
380                         // Disable group and world writability and make setuid root.
381                         fchmod(fdTool, (st.st_mode & (~(S_IWGRP | S_IWOTH)))/* | S_ISUID*/);
382                         const char *args[] = {"root", [afshlpPath UTF8String],0L};
383                         [[AuthUtil shared] execUnixCommand:"/usr/sbin/chown" 
384                                                                                   args:args
385                                                                                 output:nil];
386                         [[AuthUtil shared] deautorize];
387                 }
388     } else  NSLog(@"st_uid = 0");
389     close(fdTool);
390     NSLog(@"Self-repair done.");
391         
392 }
393
394 #pragma mark accessor
395 // -------------------------------------------------------------------------------
396 //  statusItem
397 // -------------------------------------------------------------------------------
398 -(NSStatusItem*)statusItem {
399                 return statusItem;
400 }
401
402 // -------------------------------------------------------------------------------
403 //  setStatusItem
404 // -------------------------------------------------------------------------------
405 -(void)setStatusItem:(BOOL)show {
406         if(show) {
407                 if(statusItem) return;
408                 statusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength:menuSize.width] retain];
409                 [statusItem setView:[[AFSMenuExtraView alloc] initWithFrame:[[statusItem view] frame]  
410                                                                                                            backgrounder:self
411                                                                                                                            menu:backgrounderMenu]];
412                 //Tells the NSStatusItem what menu to load
413                 [statusItem setMenu:backgrounderMenu];
414                 //Sets the tooptip for our item
415                 [statusItem setToolTip:@"OpenAFS Preference"];
416                 //Enables highlighting
417                 [statusItem setHighlightMode:YES];
418                 [statusItem setImage:noTokenImage];
419         } else {
420                 if(!statusItem) return;
421                 [[NSStatusBar systemStatusBar] removeStatusItem:statusItem];
422                 [statusItem autorelease];
423                 statusItem = nil;
424         }
425 }
426 // -------------------------------------------------------------------------------
427 //  -(void) imageToRender
428 // -------------------------------------------------------------------------------
429 - (NSImage*)imageToRender
430 {
431         if(gotToken){
432                 return hasTokenImage;
433         } else {
434                 return noTokenImage;
435         }
436 }
437
438
439 -(IBAction) startStopEvent:(id)sender {
440         [self startStopAfs:sender];
441 }
442
443 -(IBAction) getReleaseTokenEvent:(id)sender {
444         [self getToken:sender];
445 }
446 @end