KerberosController.m [plain text]
/*
* KerberosController.m
*
* $Header$
*
* Copyright 2004 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.
*/
#import "KerberosController.h"
#import "ErrorAlert.h"
#import "Utilities.h"
#import <IOKit/pwr_mgt/IOPMLib.h>
#import <IOKit/IOMessage.h>
#import <Kerberos/KerberosLoginPrivate.h>
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: (KerberosController *) inContext];
if (notification != NULL) {
[[NSNotificationQueue defaultQueue] enqueueNotification: notification
postingStyle: NSPostWhenIdle
coalesceMask: NSNotificationCoalescingOnName
forModes: NULL];
}
dprintf ("Done waking from sleep.");
break;
}
}
@implementation KerberosController
#pragma mark -
// ---------------------------------------------------------------------------
- (id) init
{
if ((self = [super init])) {
cacheCollection = [[CacheCollection sharedCacheCollection] retain];
if (cacheCollection == NULL) {
[self release];
return NULL;
}
minuteTimer = NULL;
preferencesController = NULL;
ticketListController = NULL;
realmsEditorController = NULL;
ticketsKerberosIconImage = NULL;
warningKerberosIconImage = NULL;
noTicketsKerberosIconImage = NULL;
kerberosAppIconImage = NULL;
}
return self;
}
// ---------------------------------------------------------------------------
- (void) dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver: self];
if (cacheCollection != NULL) { [cacheCollection release]; }
if (minuteTimer != NULL) { [minuteTimer release]; }
if (preferencesController != NULL) { [preferencesController release]; }
if (ticketListController != NULL) { [ticketListController release]; }
if (ticketsKerberosIconImage != NULL) { [ticketsKerberosIconImage release]; }
if (warningKerberosIconImage != NULL) { [warningKerberosIconImage release]; }
if (noTicketsKerberosIconImage != NULL) { [noTicketsKerberosIconImage release]; }
if (kerberosAppIconImage != NULL) { [kerberosAppIconImage release]; }
[super dealloc];
}
#pragma mark --- Actions --
// ---------------------------------------------------------------------------
- (IBAction) getTickets: (id) sender
{
KLStatus err = [Principal getTickets];
if (err == klNoErr) {
[cacheCollection update];
} else if (err != klUserCanceledErr) {
[ErrorAlert alertForError: err
action: KerberosGetTicketsAction];
}
}
// ---------------------------------------------------------------------------
- (IBAction) changePasswordForSelectedCache: (id) sender
{
if ([self haveTicketListWindow]) {
[ticketListController changePasswordForSelectedCache: self];
}
}
// ---------------------------------------------------------------------------
- (IBAction) changePasswordForActiveUser: (id) sender
{
Cache *cache = [cacheCollection defaultCache];
if (cache != NULL) {
KLStatus err = [[cache principal] changePassword];
if (err == klNoErr) {
[cacheCollection update];
} else if (err != klUserCanceledErr) {
[ErrorAlert alertForError: err
action: KerberosChangePasswordAction];
}
}
}
// ---------------------------------------------------------------------------
- (IBAction) destroyTicketsForSelectedCache: (id) sender
{
if ([self haveTicketListWindow]) {
[ticketListController destroyTicketsForSelectedCache: self];
}
}
// ---------------------------------------------------------------------------
- (IBAction) destroyTicketsForActiveUser: (id) sender
{
Cache *cache = [cacheCollection defaultCache];
if (cache != NULL) {
KLStatus err = [[cache principal] destroyTickets];
if (err == klNoErr) {
[cacheCollection update];
} else if (err != klUserCanceledErr) {
[ErrorAlert alertForError: err
action: KerberosDestroyTicketsAction];
}
}
}
// ---------------------------------------------------------------------------
- (IBAction) renewTicketsForSelectedCache: (id) sender
{
if ([self haveTicketListWindow]) {
[ticketListController renewTicketsForSelectedCache: self];
}
}
// ---------------------------------------------------------------------------
- (IBAction) renewTicketsForActiveUser: (id) sender
{
Cache *cache = [cacheCollection defaultCache];
if (cache != NULL) {
KLStatus err = [[cache principal] renewTickets];
if (err == klNoErr) {
[cacheCollection update];
} else if (err != klUserCanceledErr) {
[ErrorAlert alertForError: err
action: KerberosRenewTicketsAction];
}
}
}
// ---------------------------------------------------------------------------
- (IBAction) validateTicketsForSelectedCache: (id) sender
{
if ([self haveTicketListWindow]) {
[ticketListController validateTicketsForSelectedCache: self];
}
}
// ---------------------------------------------------------------------------
- (IBAction) validateTicketsForActiveUser: (id) sender
{
Cache *cache = [cacheCollection defaultCache];
if (cache != NULL) {
KLStatus err = [[cache principal] validateTickets];
if (err == klNoErr) {
[cacheCollection update];
} else if (err != klUserCanceledErr) {
[ErrorAlert 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 == dockMenu) {
offset += [dockMenu indexOfItem: dockSeparatorItem] + 2; // include header item
}
if ([cacheCollection numberOfCaches] > 0) {
Cache *cache = [cacheCollection cacheAtIndex: ([menu indexOfItem: item] - offset)];
if (![cache isDefault]) {
KLStatus err = [[cache principal] setDefault];
if (err == klNoErr) {
[cacheCollection update];
} else if (err != klUserCanceledErr) {
[ErrorAlert alertForError: err
action: KerberosChangeActiveUserAction];
}
}
}
}
}
// ---------------------------------------------------------------------------
- (IBAction) showTicketInfo: (id) sender
{
if ([self haveTicketListWindow]) {
[ticketListController showTicketInfo: self];
}
}
// ---------------------------------------------------------------------------
- (IBAction) showPreferences: (id) sender
{
if (preferencesController == NULL) {
preferencesController = [[PreferencesController alloc] init];
}
[preferencesController showWindow: self];
}
// ---------------------------------------------------------------------------
- (IBAction) showAboutBox: (id) sender
{
[aboutWindow center];
[aboutWindow makeKeyAndOrderFront: self];
}
// ---------------------------------------------------------------------------
- (IBAction) showTicketList: (id) sender
{
if (ticketListController == NULL) {
ticketListController = [[TicketListController alloc] init];
}
if (ticketListController != NULL) {
[ticketListController showWindow: self];
[self menuNeedsUpdate: ticketsMenu];
}
}
// ---------------------------------------------------------------------------
- (IBAction) editRealms: (id) sender
{
if (realmsEditorController == NULL) {
realmsEditorController = [[RealmsEditorController alloc] init];
}
[realmsEditorController showWindow: self];
}
#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);
// Fill in the version field in the about dialog
NSString *name = [[NSBundle mainBundle] objectForInfoDictionaryKey: @"CFBundleName"];
NSString *version = [[NSBundle mainBundle] objectForInfoDictionaryKey: @"KfMDisplayVersion"];
NSString *copyright = [[NSBundle mainBundle] objectForInfoDictionaryKey: @"KfMDisplayCopyright"];
[aboutVersionTextField setStringValue: [NSString stringWithFormat: @" (name != NULL) ? name : @"", (version != NULL) ? version : @""]];
[aboutCopyrightTextField setStringValue: (copyright != NULL) ? copyright : @""];
// Set up timers
minuteTimer = [[NSTimer scheduledTimerWithTimeInterval: 30 // controls the minute counter in the dock
target: self
selector: @selector(minuteTimer:)
userInfo: NULL
repeats: YES] retain];
// Enable and disable menus in these lists manually
// since it's easier to manually enable and disable the active user list
[ticketsMenu setAutoenablesItems: NO];
[activeUserMenu setAutoenablesItems: NO];
[dockMenu setAutoenablesItems: NO];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: @selector (preferencesDidChange:)
name: PreferencesDidChangeNotification
object: nil];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: @selector (cacheCollectionDidChange:)
name: CacheCollectionDidChangeNotification
object: nil];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: @selector (ticketListDidChange:)
name: TicketListDidChangeNotification
object: nil];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: @selector (listSelectionDidChange:)
name: CacheSelectionDidChangeNotification
object: nil];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: @selector (windowWillClose:)
name: NSWindowWillCloseNotification
object: nil];
// disable items before ticket list opens to set up default state
[self ticketListDidChange: NULL];
// Open ticket list, if needed (will send notification if opened)
if (([[Preferences sharedPreferences] launchAction] == LaunchActionAlwaysOpenTicketWindow) ||
(([[Preferences sharedPreferences] launchAction] == LaunchActionRememberOpenTicketWindow) &&
([[Preferences sharedPreferences] ticketWindowLastOpen]))) {
[self showTicketList: self];
}
// 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];
}
// ---------------------------------------------------------------------------
- (NSMenu *) applicationDockMenu: (NSApplication *) sender
{
return dockMenu;
}
// ---------------------------------------------------------------------------
- (void) menuNeedsUpdate: (NSMenu *) menu
{
if (menu == ticketsMenu) {
dprintf ("Kerberos Controller tickets menu updating...");
BOOL ticketListWindowHasSelectedCache = [self ticketListWindowHasSelectedCache];
[renewTicketsMenuItem setEnabled: ticketListWindowHasSelectedCache];
[validateTicketsMenuItem setEnabled: [self ticketListWindowSelectedCacheNeedsValidation]];
[destroyTicketsMenuItem setEnabled: ticketListWindowHasSelectedCache];
[changePasswordMenuItem setEnabled: ticketListWindowHasSelectedCache];
[showTicketInfoMenuItem setEnabled: [self ticketListWindowHasSelectedCredential]];
} else if (menu == activeUserMenu) {
dprintf ("Kerberos Controller active user menu updating...");
[Utilities synchronizeCacheMenu: menu
fontSize: 0 // default size
staticPrefixItemsCount: 0
headerItem: NO
checkDefaultCache: YES
defaultCacheIndex: NULL
selector: @selector (changeActiveUser:)
sender: self];
} else if (menu == dockMenu) {
dprintf ("Kerberos Controller dock menu updating...");
BOOL haveDefaultCache = [self haveDefaultCache];
[dockRenewTicketsMenuItem setEnabled: haveDefaultCache];
[dockValidateTicketsMenuItem setEnabled: [self defaultCacheNeedsValidation]];
[dockDestroyTicketsMenuItem setEnabled: haveDefaultCache];
[dockChangePasswordMenuItem setEnabled: haveDefaultCache];
[Utilities synchronizeCacheMenu: menu
fontSize: 0 // default size
staticPrefixItemsCount: [dockMenu indexOfItem: dockSeparatorItem] + 1
headerItem: YES
checkDefaultCache: YES
defaultCacheIndex: NULL
selector: @selector (changeActiveUser:)
sender: self];
}
dprintf ("Kerberos Controller done updating menu...");
}
#pragma mark --- Notifications --
// ---------------------------------------------------------------------------
- (void) applicationDidBecomeActive: (NSNotification *) notification
{
static BOOL firstTime = YES;
// We would like to present the ticket list window whenever the application
// comes to the front, except on app launch (because there's a preference for
// that behavior). So we let awakeFromNib do the work of looking at the
// preferenceand displaying the window on app launch. However, applications
// get an activate event when launched, so we ignore the first call.
if (!firstTime && ((ticketListController == NULL) || ![[ticketListController window] isVisible])) {
[self showTicketList: self];
}
firstTime = NO;
}
// ---------------------------------------------------------------------------
- (void) applicationWillTerminate: (NSNotification *) notification
{
// Put the dock icon back
NSImage *applicationIconImage = [NSImage imageNamed: @"NSApplicationIcon"];
if (applicationIconImage != NULL) {
[NSApp setApplicationIconImage: applicationIconImage];
}
// Remember the ticket window state on quit.
[[Preferences sharedPreferences] setTicketWindowLastOpen: [self haveTicketListWindow]];
[minuteTimer invalidate];
}
// ---------------------------------------------------------------------------
- (void) preferencesDidChange: (NSNotification *) notification
{
dprintf ("Kerberos Controller got PreferencesDidChangeNotification");
[self synchronizeDockIcon];
[self menuNeedsUpdate: dockMenu];
dprintf ("Kerberos Controller done with PreferencesDidChangeNotification");
}
// ---------------------------------------------------------------------------
- (void) cacheCollectionDidChange: (NSNotification *) notification
{
dprintf ("Kerberos Controller got CacheCollectionDidChangeNotification");
// Note: only put things here that do not depend on the ticket list.
// If it depends on the ticket list, use ticketListDidChange: instead.
[self synchronizeDockIcon];
[self menuNeedsUpdate: dockMenu];
[self menuNeedsUpdate: activeUserMenu];
dprintf ("Kerberos Controller done with CacheCollectionDidChangeNotification");
}
// ---------------------------------------------------------------------------
- (void) ticketListDidChange: (NSNotification *) notification
{
dprintf ("Kerberos Controller got TicketListDidChangeNotification");
[self menuNeedsUpdate: ticketsMenu];
dprintf ("Kerberos Controller done with TicketListDidChangeNotification");
}
// ---------------------------------------------------------------------------
- (void) listSelectionDidChange: (NSNotification *) notification
{
if (ticketListController == [notification object]) {
dprintf ("Kerberos Controller noticed Ticket List selection change...");
[self menuNeedsUpdate: ticketsMenu];
}
}
// ---------------------------------------------------------------------------
- (void) windowWillClose: (NSNotification *) notification
{
if ((ticketListController != NULL) && ([ticketListController window] == [notification object])) {
// Okay, this is totally gross. Since there isn't an NSWindowDidCloseNotification,
// we have to do this by hand because the handy code in menuNeedsUpdate will call
// isVisible on the window and it's still visible when this gets called.
[renewTicketsMenuItem setEnabled: NO];
[validateTicketsMenuItem setEnabled: NO];
[destroyTicketsMenuItem setEnabled: NO];
[changePasswordMenuItem setEnabled: NO];
[showTicketInfoMenuItem setEnabled: NO];
dprintf ("Kerberos Controller noticed Ticket List Window closing...");
}
}
#pragma mark -- Callbacks --
// ---------------------------------------------------------------------------
- (void) minuteTimer: (NSTimer *) timer
{
[self synchronizeDockIcon];
}
#pragma mark -- Ticket List State --
// ---------------------------------------------------------------------------
- (BOOL) haveTicketListWindow
{
return ((ticketListController != NULL) && ([[ticketListController window] isVisible]));
}
// ---------------------------------------------------------------------------
- (BOOL) ticketListWindowHasSelectedCache
{
return ([self haveTicketListWindow] && [ticketListController haveSelectedCache]);
}
// ---------------------------------------------------------------------------
- (BOOL) ticketListWindowSelectedCacheNeedsValidation
{
return ([self haveTicketListWindow] && [ticketListController selectedCacheNeedsValidation]);
}
// ---------------------------------------------------------------------------
- (BOOL) ticketListWindowHasSelectedCredential
{
return ([self haveTicketListWindow] && [ticketListController haveSelectedCredential]);
}
// ---------------------------------------------------------------------------
- (BOOL) haveDefaultCache
{
return ([cacheCollection defaultCache] != NULL);
}
// ---------------------------------------------------------------------------
- (BOOL) defaultCacheNeedsValidation
{
Cache *defaultCache = [cacheCollection defaultCache];
return ((defaultCache != NULL) && [defaultCache needsValidation]);
}
#pragma mark -- Dock Icon --
// ---------------------------------------------------------------------------
- (void) synchronizeDockIcon
{
static BOOL sDockIconIsDynamic = NO; // Use so we don't refresh icon when not dynamic
Cache *defaultCache = [cacheCollection defaultCache];
if ([[Preferences sharedPreferences] showTimeInDockIcon]) {
NSImage *iconImage = [self dockIconImageForCache: defaultCache];
if (iconImage != NULL) {
[NSApp setApplicationIconImage: iconImage];
}
sDockIconIsDynamic = YES;
} else {
if (sDockIconIsDynamic) {
NSImage *applicationIconImage = [NSImage imageNamed: @"NSApplicationIcon"];
if (applicationIconImage != NULL) {
[NSApp setApplicationIconImage: applicationIconImage];
sDockIconIsDynamic = NO; // remember so we don't keep doing this over and over
}
}
}
}
// ---------------------------------------------------------------------------
- (NSImage *) ticketsKerberosIconImage
{
if (ticketsKerberosIconImage == NULL) {
NSString *iconPathString = [[NSBundle mainBundle] pathForResource: @"DockHasTickets"
ofType: @"tiff"];
if (iconPathString != NULL) {
ticketsKerberosIconImage = [[NSImage alloc] initWithContentsOfFile: iconPathString];
}
}
return ticketsKerberosIconImage;
}
// ---------------------------------------------------------------------------
- (NSImage *) warningKerberosIconImage
{
if (warningKerberosIconImage == NULL) {
NSString *iconPathString = [[NSBundle mainBundle] pathForResource: @"DockTicketsWarning"
ofType: @"tiff"];
if (iconPathString != NULL) {
warningKerberosIconImage = [[NSImage alloc] initWithContentsOfFile: iconPathString];
}
}
return warningKerberosIconImage;
}
// ---------------------------------------------------------------------------
- (NSImage *) noTicketsKerberosIconImage
{
if (noTicketsKerberosIconImage == NULL) {
NSString *iconPathString = [[NSBundle mainBundle] pathForResource: @"DockNoTickets"
ofType: @"tiff"];
if (iconPathString != NULL) {
noTicketsKerberosIconImage = [[NSImage alloc] initWithContentsOfFile: iconPathString];
}
}
return noTicketsKerberosIconImage;
}
// ---------------------------------------------------------------------------
- (NSImage *) kerberosAppIconImage
{
if (kerberosAppIconImage == NULL) {
NSString *iconPathString = [[NSBundle mainBundle] pathForResource: @"KerberosApp"
ofType: @"icns"];
if (iconPathString != NULL) {
kerberosAppIconImage = [[NSImage alloc] initWithContentsOfFile: iconPathString];
}
}
return kerberosAppIconImage;
}
// ---------------------------------------------------------------------------
- (NSImage *) dockIconImageForCache: (Cache *) cache
{
NSImage *newDockIconImage = NULL;
NSImage *baseImage = [self kerberosAppIconImage];
NSString *string = NULL; // NSLocalizedStringFromTable (@"LifetimeStringExpired", @"LifetimeFormatter", NULL);
if (cache != NULL) {
time_t now = time (NULL);
if ([cache stateAtTime: now] == CredentialValid) {
string = [cache stringValueForDockIcon];
if ([cache timeRemainingAtTime: now] > kFiveMinutes) {
baseImage = [self ticketsKerberosIconImage];
} else {
baseImage = [self warningKerberosIconImage];
}
}
}
if (baseImage != NULL) {
NSSize iconSize = [[self kerberosAppIconImage] size];
newDockIconImage = [[[NSImage alloc] initWithSize: iconSize] autorelease];
if (newDockIconImage != NULL) {
[newDockIconImage lockFocus];
[baseImage setScalesWhenResized: YES];
[baseImage setSize: [newDockIconImage size]];
[baseImage compositeToPoint: NSMakePoint (0.0, 0.0) operation: NSCompositeSourceOver];
if (string != NULL) {
NSDictionary *attributes = [Utilities attributesForDockIcon];
NSSize textSize = [string sizeWithAttributes: attributes];
// Add the time remaining string
[string drawAtPoint: NSMakePoint (((iconSize.width - textSize.width) / 2) + 19,
19.0 - (textSize.height / 2))
withAttributes: attributes];
[newDockIconImage unlockFocus];
}
}
}
return newDockIconImage;
}
@end