KerberosHelperController.m [plain text]
/*
* KerberosHelperController.m
*
* $Header$
*
* Copyright 2006 Massachusetts Institute of Technology.
* All Rights Reserved.
*
* Export of this software from the United States of America may
* require a specific license from the United States Government.
* It is the responsibility of any person or organization contemplating
* export to obtain such a license before exporting.
*
* WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
* distribute this software and its documentation for any purpose and
* without fee is hereby granted, provided that the above copyright
* notice appear in all copies and that both that copyright notice and
* this permission notice appear in supporting documentation, and that
* the name of M.I.T. not be used in advertising or publicity pertaining
* to distribution of the software without specific, written prior
* permission. Furthermore if you modify this software you must label
* your software as modified software and not distribute it in such a
* fashion that it might be confused with the original M.I.T. software.
* M.I.T. makes no representations about the suitability of
* this software for any purpose. It is provided "as is" without express
* or implied warranty.
*/
/*
* KerberosHelper is an NSStatusItem version of Kerberos.app.
* - auto-renews tickets in the background
* - provides the same actions as Kerberos.app's dock menu
* - doesn't take up any space in the dock
*/
#import "KerberosHelperController.h"
#import "KerberosErrorAlert.h"
#import "Utilities.h"
#import <Kerberos/Kerberos.h>
#import <IOKit/pwr_mgt/IOPMLib.h>
#import <IOKit/IOMessage.h>
#define kCacheCollectionUpdateTimerInterval 2
#define KerberosHelperModePreferenceKey CFSTR("KMShowMode")
static io_connect_t gRootPort = MACH_PORT_NULL;
// ---------------------------------------------------------------------------
static void HandlePowerManagerEvent (void *inContext,
io_service_t inIOService,
natural_t inMessageType,
void *inMessageArgument)
{
switch (inMessageType) {
case kIOMessageSystemWillSleep:
IOAllowPowerChange (gRootPort, (long) inMessageArgument);
dprintf("Going to sleep...");
break;
case kIOMessageCanSystemSleep:
IOAllowPowerChange (gRootPort, (long) inMessageArgument);
dprintf("Allowing system to go to sleep...");
break;
case kIOMessageSystemHasPoweredOn:
// Post notification to things with timers which fire on a particular date
// so that they can be reset to fire now if the fire time already passed
dprintf("Waking from sleep...");
// Note that we cannot post the notification directly from this callback
// so insert it into the notification queue instead
NSNotification *notification = [NSNotification notificationWithName: WakeFromSleepNotification
object: (KerberosHelperController *) inContext];
if (notification) {
[[NSNotificationQueue defaultQueue] enqueueNotification: notification
postingStyle: NSPostWhenIdle
coalesceMask: NSNotificationCoalescingOnName
forModes: NULL];
}
dprintf ("Done waking from sleep.");
break;
}
}
@implementation KerberosHelperController
#pragma mark -
// ---------------------------------------------------------------------------
- (id) init
{
if ((self = [super init])) {
cacheCollection = [[KerberosCacheCollection sharedCacheCollection] retain];
if (!cacheCollection) {
[self release];
return NULL;
}
context = NULL;
lastChangeTime = 0;
updateTimer = nil;
minuteTimer = nil;
}
return self;
}
// ---------------------------------------------------------------------------
- (void) dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver: self];
if (cacheCollection ) { [cacheCollection release]; }
if (updateTimer ) { [updateTimer release]; }
if (minuteTimer ) { [minuteTimer release]; }
if (context) { cc_context_release (context); }
[super dealloc];
}
#pragma mark --- Actions --
// ---------------------------------------------------------------------------
- (IBAction) menuBarStatusItemModeDidChange: (id) sender
{
[self setModePreference:[statusModesMenu indexOfItem:sender]];
NSArray *modeItems = [statusModesMenu itemArray];
unsigned i;
for (i = 0; i < [modeItems count]; i++) {
[[modeItems objectAtIndex: i] setState: NSOffState];
}
[sender setState: NSOnState];
[self updateStatusItem];
}
// ---------------------------------------------------------------------------
- (IBAction) openKerberosApplication: (id) sender
{
[[NSWorkspace sharedWorkspace] launchAppWithBundleIdentifier: @"edu.mit.Kerberos.KerberosApp" options: NSWorkspaceLaunchDefault additionalEventParamDescriptor: nil launchIdentifier: nil];
}
// ---------------------------------------------------------------------------
- (IBAction) getTickets: (id) sender
{
KLStatus err = [KerberosPrincipal getTickets];
if (!err) {
[cacheCollection update];
} else if (err != klUserCanceledErr) {
[KerberosErrorAlert alertForError: err
action: KerberosGetTicketsAction];
}
}
// ---------------------------------------------------------------------------
- (IBAction) changePasswordForActiveUser: (id) sender
{
KerberosCache *cache = [cacheCollection defaultCache];
if (cache) {
KLStatus err = [[cache principal] changePassword];
if (!err) {
[cacheCollection update];
} else if (err != klUserCanceledErr) {
[KerberosErrorAlert alertForError: err
action: KerberosChangePasswordAction];
}
}
}
// ---------------------------------------------------------------------------
- (IBAction) destroyTicketsForActiveUser: (id) sender
{
KerberosCache *cache = [cacheCollection defaultCache];
if (cache) {
KLStatus err = [[cache principal] destroyTickets];
if (!err) {
[cacheCollection update];
} else if (err != klUserCanceledErr) {
[KerberosErrorAlert alertForError: err
action: KerberosDestroyTicketsAction];
}
}
}
// ---------------------------------------------------------------------------
- (IBAction) renewTicketsForActiveUser: (id) sender
{
KerberosCache *cache = [cacheCollection defaultCache];
if (cache) {
KLStatus err = [[cache principal] renewTickets];
if (!err) {
[cacheCollection update];
} else if (err != klUserCanceledErr) {
[KerberosErrorAlert alertForError: err
action: KerberosRenewTicketsAction];
}
}
}
// ---------------------------------------------------------------------------
- (IBAction) validateTicketsForActiveUser: (id) sender
{
KerberosCache *cache = [cacheCollection defaultCache];
if (cache) {
KLStatus err = [[cache principal] validateTickets];
if (!err) {
[cacheCollection update];
} else if (err != klUserCanceledErr) {
[KerberosErrorAlert alertForError: err
action: KerberosValidateTicketsAction];
}
}
}
// ---------------------------------------------------------------------------
- (IBAction) changeActiveUser: (id) sender
{
if ([sender isMemberOfClass: [NSMenuItem class]]) {
NSMenuItem *item = sender;
NSMenu *menu = [item menu];
int offset = 0;
if (menu == statusMenu) {
offset += [statusMenu indexOfItem: statusSeparatorItem] + 2; // include header item
}
if ([cacheCollection numberOfCaches] > 0) {
KerberosCache *cache = [cacheCollection cacheAtIndex: ([menu indexOfItem: item] - offset)];
if (![cache isDefault]) {
KLStatus err = [[cache principal] setDefault];
if (!err) {
[cacheCollection update];
} else if (err != klUserCanceledErr) {
[KerberosErrorAlert alertForError: err
action: KerberosChangeActiveUserAction];
}
}
}
}
}
#pragma mark --- Delegate Methods --
// ---------------------------------------------------------------------------
- (void) awakeFromNib
{
IONotificationPortRef notify;
io_object_t iterator;
dprintf ("Entering awakeFromNib...");
// Make sure we are always prompted with the dialog even if we have a controlling terminal
__KLSetPromptMechanism (klPromptMechanism_GUI);
[statusMenu setAutoenablesItems: YES];
[self addMenuBarStatusItem];
// Load preferences
int index = [self modePreference];
if ((index < 0) || (index >= [statusModesMenu numberOfItems])) {
NSLog(@"Invalid menu mode preference ( index = 0;
}
[self menuBarStatusItemModeDidChange:[statusModesMenu itemAtIndex:index]];
// Set up timers
/*
updateTimer = [[NSTimer scheduledTimerWithTimeInterval: 1 // keeps the default user reflected in the status item
target: self
selector: @selector(updateStatusItemIfNeeded)
userInfo: NULL
repeats: YES] retain];
*/
minuteTimer = [[NSTimer scheduledTimerWithTimeInterval: 30 // controls the minute counter in the status item
target: self
selector: @selector(updateStatusItem)
userInfo: NULL
repeats: YES] retain];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: @selector (cacheCollectionDidChange:)
name: CacheCollectionDidChangeNotification
object: nil];
// Register for wake/sleep power events so we can reset our credential timers
gRootPort = IORegisterForSystemPower (self, ¬ify, HandlePowerManagerEvent, &iterator);
if (gRootPort == MACH_PORT_NULL) {
NSLog (@"IORegisterForSystemPower failed.");
} else {
CFRunLoopAddSource (CFRunLoopGetCurrent (),
IONotificationPortGetRunLoopSource (notify),
kCFRunLoopDefaultMode);
}
// Set up default cache state
[self cacheCollectionDidChange: NULL];
}
#pragma mark --- Notifications --
// ---------------------------------------------------------------------------
- (void) applicationWillTerminate: (NSNotification *) notification
{
[updateTimer invalidate];
[minuteTimer invalidate];
}
// ---------------------------------------------------------------------------
- (void) cacheCollectionDidChange: (NSNotification *) notification
{
[self updateStatusItem];
}
#pragma mark -- Callbacks --
// ---------------------------------------------------------------------------
- (void) updateStatusItemIfNeeded
{
cc_int32 err = ccNoError;
cc_time_t new_changed_time = 0;
cc_ccache_t default_ccache = NULL;
if (!context) {
err = cc_initialize(&context, ccapi_version_4, NULL, NULL);
}
if (!err) {
err = cc_context_get_change_time(context, &new_changed_time);
}
if (!err) {
if (new_changed_time > lastChangeTime) {
[cacheCollection update];
[minuteTimer fire];
// re-checking change time because updating cacheCollection causes the last change time to increment
err = cc_context_get_change_time(context, &new_changed_time);
lastChangeTime = new_changed_time;
}
}
}
// ---------------------------------------------------------------------------
- (void) updateStatusItem
{
KerberosCache *defaultCache = [cacheCollection defaultCache];
// Message is sent with a delay for a cosmetic reason. Changing the title before the menu highlight disappears causes an ugly double flash of the NSStatusItem. Use the commented line below instead if delayed calls end up causing problems.
[menuBarStatusItem performSelector:@selector(setTitle:) withObject:[self menuBarStatusItemTitleForCache: defaultCache] afterDelay:0];
//[menuBarStatusItem setTitle:[self menuBarStatusItemTitleForCache: defaultCache]];
}
#pragma mark -- Ticket List State --
// ---------------------------------------------------------------------------
- (BOOL) haveDefaultCache
{
return ([cacheCollection defaultCache] != NULL);
}
// ---------------------------------------------------------------------------
- (BOOL) defaultCacheNeedsValidation
{
KerberosCache *defaultCache = [cacheCollection defaultCache];
return ((defaultCache != NULL) && [defaultCache needsValidation]);
}
#pragma mark -- Menu Bar StatusItem --
// ---------------------------------------------------------------------------
- (void) addMenuBarStatusItem
{
menuBarStatusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
[menuBarStatusItem retain];
[menuBarStatusItem setTitle:@""];
[menuBarStatusItem setHighlightMode:YES];
NSImage *anImage = [[NSImage imageNamed:@"Kerberos"] copy];
[anImage setScalesWhenResized:YES];
[anImage setSize:NSMakeSize(22, 22)];
[menuBarStatusItem setImage:[anImage autorelease]];
[menuBarStatusItem setMenu: statusMenu];
}
// ---------------------------------------------------------------------------
- (NSString *) menuBarStatusItemTitleForCache: (KerberosCache *) cache
{
NSString *string = NULL;
NSString *modeString = [self menuBarStatusItemModeString];
if (cache) {
if ([cache state] == CredentialValid) {
if ([modeString isEqualToString:@"Full Principal"]) {
string = [NSString stringWithFormat:@" } else if ([modeString isEqualToString:@"Name"]) {
string = [NSString stringWithFormat:@" } else if ([modeString isEqualToString:@"Realm"]) {
string = [NSString stringWithFormat:@" } else if ([modeString isEqualToString:@"Time"]) {
string = [NSString stringWithFormat:@"( }
}
}
return string;
}
// ---------------------------------------------------------------------------
- (NSString *) menuBarStatusItemModeString
{
NSString *string = nil;
NSArray *items = [statusModesMenu itemArray];
NSMenuItem *anItem = nil;
unsigned i = 0;
do {
anItem = [items objectAtIndex:i++];
} while (([anItem state] != NSOnState) && (i < [items count]));
if ([anItem state] == NSOnState) {
string = [anItem title];
}
// it's an error if no mode is set, so we default to Off
return string;
}
// ---------------------------------------------------------------------------
- (BOOL)validateMenuItem:(NSMenuItem *)item
{
BOOL haveDefaultCache = [self haveDefaultCache];
if ([item action] == @selector(menuBarStatusItemModeDidChange:)) { return YES; }
else if ([item action] == @selector(getTickets:)) { return YES; }
else if ([item action] == @selector(destroyTicketsForActiveUser:)) { return haveDefaultCache; }
else if ([item action] == @selector(renewTicketsForActiveUser:)) { return haveDefaultCache; }
else if ([item action] == @selector(validateTicketsForActiveUser:)) { return [self defaultCacheNeedsValidation]; }
else if ([item action] == @selector(changePasswordForActiveUser:)) { return haveDefaultCache; }
// Since there's no apparent protocol for validation of the entire menu, use something innocuous and unique, the Open Kerb.app item, to act as a hint that the list of available tickets needs updating
else if (item == statusOpenKerberosAppMenuItem) {
// Remove and then add back the "Open Kerberos Application..." menu item and its preceding separator to avoid having to alter Utilities' -synchronizeCacheMenu:::::::: and to keep as much of the UI in the nib as possible (as opposed to creating the "Open Kerberos Application..." item in code everytime the menu is updated
[statusOpenKerberosAppMenuItem retain];
[statusMenu removeItemAtIndex: [statusMenu numberOfItems] - 1];
[statusMenu removeItemAtIndex: [statusMenu numberOfItems] - 1];
[cacheCollection update];
[Utilities synchronizeCacheMenu: statusMenu
controlType: kUtilitiesStatusItemControlType
staticPrefixItemsCount: [statusMenu indexOfItem: statusSeparatorItem] + 1
headerItem: YES
checkDefaultCache: YES
defaultCacheIndex: NULL
selector: @selector (changeActiveUser:)
sender: self];
[statusMenu addItem: [NSMenuItem separatorItem]];
[statusMenu addItem: statusOpenKerberosAppMenuItem];
[statusOpenKerberosAppMenuItem release];
return YES;
}
return NO;
}
#pragma mark -- Preferences --
// ---------------------------------------------------------------------------
- (int) modePreference
{
CFPropertyListRef aValue = CFPreferencesCopyAppValue(KerberosHelperModePreferenceKey, kCFPreferencesCurrentApplication);
int value = 0;
if ((aValue != NULL) && (CFGetTypeID (aValue) == CFNumberGetTypeID ())) {
CFNumberGetValue(aValue, kCFNumberSInt32Type, &value);
CFRelease(aValue);
}
return value;
}
// ---------------------------------------------------------------------------
- (void) setModePreference: (int) value
{
CFNumberRef aValue = CFNumberCreate (kCFAllocatorDefault, kCFNumberSInt32Type, &value);
CFPreferencesSetAppValue(KerberosHelperModePreferenceKey, aValue, kCFPreferencesCurrentApplication);
CFPreferencesAppSynchronize (kCFPreferencesCurrentApplication);
CFRelease(aValue);
}
@end