/*
* Credential.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 "CacheCollection.h"
#import "Credential.h"
#import "Utilities.h"
#import "TicketInfoController.h"
#import "Preferences.h"
@implementation Credential
// ---------------------------------------------------------------------------
- (id) initWithCredentials: (cc_credentials_t) creds
{
if ((self = [super init])) {
dprintf ("Entering initWithCredentials: ...");
credentials = NULL;
ticketFlags = 0;
clientPrincipal = NULL;
servicePrincipal = NULL;
addressesArray = NULL;
infoWindowController = NULL;
credentialTimer = NULL;
[[NSNotificationCenter defaultCenter] addObserver: self
selector: @selector (windowWillClose:)
name: @"NSWindowWillCloseNotification"
object: nil];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: @selector (wakeFromSleep:)
name: WakeFromSleepNotification
object: nil];
if ([self synchronizeWithCredentials: creds] != ccNoError) {
[self release];
self = NULL;
}
dprintf ("Credential for }
return self;
}
// ---------------------------------------------------------------------------
- (void) dealloc
{
dprintf ("Credential '%s' for '%s' releasing",
[[self servicePrincipalString] UTF8String],
[[self clientPrincipalString] UTF8String]);
[[NSNotificationCenter defaultCenter] removeObserver: self];
if (credentialTimer != NULL) {
// invalidate a TargetOwnedTimer before releasing it
[credentialTimer invalidate];
[credentialTimer release];
}
if (infoWindowController != NULL) { [infoWindowController release]; }
if (credentials != NULL) { cc_credentials_release (credentials); }
if (clientPrincipal != NULL) { [clientPrincipal release]; }
if (servicePrincipal != NULL) { [servicePrincipal release]; }
if (addressesArray != NULL) { [addressesArray release]; }
[super dealloc];
}
// ---------------------------------------------------------------------------
- (int) synchronizeWithCredentials: (cc_credentials_t) creds
{
cc_int32 err = ccNoError;
Principal *newClientPrincipal = NULL;
Principal *newServicePrincipal = NULL;
NSMutableArray *newAddressesArray = NULL;
dprintf ("Entering synchronizeWithCredentials: ...");
if (err == ccNoError) {
newClientPrincipal = [[Principal alloc] initWithClientPrincipalFromCredentials: creds];
if (newClientPrincipal == NULL) { err = ccErrNoMem; }
}
if (err == ccNoError) {
newServicePrincipal = [[Principal alloc] initWithServicePrincipalFromCredentials: creds];
if (newServicePrincipal == NULL) { err = ccErrNoMem; }
}
if (err == ccNoError) {
newAddressesArray = [[NSMutableArray alloc] init];
if (newAddressesArray == NULL) { err = ccErrNoMem; }
}
if (err == ccNoError) {
if (creds->data->version == cc_credentials_v5) {
cc_data **cbase = creds->data->credentials.credentials_v5->addresses;
if (cbase != NULL) {
for (; *cbase != NULL; *cbase++) {
cc_data *ccAdr = *cbase;
Address *address = NULL;
if (err == ccNoError) {
address = [[[Address alloc] initWithType: ccAdr->type
length: ccAdr->length
contents: ccAdr->data] autorelease];
if (address == NULL) { err = ccErrNoMem; }
}
if (err == ccNoError) {
[newAddressesArray addObject: address];
}
}
}
} else if (creds->data->version == cc_credentials_v4) {
krb5_octet *data = (krb5_octet *) &creds->data->credentials.credentials_v4->address;
Address *address = [[[Address alloc] initWithType: ADDRTYPE_INET
length: INADDRSZ
contents: data] autorelease];
if (address == NULL) { err = ccErrNoMem; }
if (err == ccNoError) {
[newAddressesArray addObject: address];
}
} else {
err = ccErrBadCredentialsVersion;
}
}
if (err == ccNoError) {
if (clientPrincipal != NULL) { [clientPrincipal release]; }
clientPrincipal = newClientPrincipal;
newClientPrincipal = NULL; // don't free
if (servicePrincipal != NULL) { [servicePrincipal release]; }
servicePrincipal = newServicePrincipal;
newServicePrincipal = NULL; // don't free
if (addressesArray != NULL) { [addressesArray release]; }
addressesArray = newAddressesArray;
newAddressesArray = NULL; // don't free
if (credentials != NULL) { cc_credentials_release (credentials); }
credentials = creds;
// for convenience
if (creds->data->version == cc_credentials_v5) {
ticketFlags = credentials->data->credentials.credentials_v5->ticket_flags;
} else {
ticketFlags = 0;
}
// Set up the timer now that the credentials are set up
[self setupTimerLastAttemptFailed: NO];
} else {
dprintf ("synchronizeWithCCache:defaultCCache: returning error }
if (newClientPrincipal != NULL) { [newClientPrincipal release]; }
if (newServicePrincipal != NULL) { [newServicePrincipal release]; }
return err;
}
// ---------------------------------------------------------------------------
- (void) setupTimerLastAttemptFailed: (BOOL) lastAttemptFailed
{
if ([self renewable] && [self initial]) {
time_t now = time (NULL);
time_t timeRemaining = [self timeRemainingAtTime: now];
if (timeRemaining > 0) {
time_t lifetime = [self expirationTime] - [self startTime];
NSDate *halfTimeRemainingDate = [NSDate dateWithTimeIntervalSince1970: now + (timeRemaining / 2)];
NSDate *halfExpiredDate = [NSDate dateWithTimeIntervalSince1970: [self startTime] + (lifetime / 2)];
NSDate *asapDate = [NSDate dateWithTimeIntervalSinceNow: 1];
NSDate *newFireDate = NULL;
dprintf ("halfTimeRemainingDate is dprintf ("halfExpiredDate is dprintf ("asapDate is
if ([halfExpiredDate timeIntervalSinceNow] <= 0) {
// If the tickets are more than half expired then fire immediately unless we are
// rescheduling from an failed attempt because then this one will probably fail too
// If that happens use halfTimeRemainingDate to exponentially back off
dprintf ("Tickets more than half expired");
newFireDate = lastAttemptFailed ? halfTimeRemainingDate : asapDate;
} else {
// Credentials are not yet half expired so try to renew them then
dprintf ("Tickets not yet half expired");
newFireDate = halfExpiredDate;
}
dprintf ("Setting timer to renew credentials for [[self clientPrincipalString] UTF8String], [[newFireDate description] UTF8String]);
if (credentialTimer != NULL) {
[credentialTimer invalidate];
[credentialTimer release];
}
credentialTimer = [[TargetOwnedTimer scheduledTimerWithFireDate: newFireDate
interval: 0
target: self
selector: @selector (credentialTimer:)
userInfo: NULL
repeats: NO] retain];
}
}
}
// ---------------------------------------------------------------------------
- (void) credentialTimer: (TargetOwnedTimer *) timer
{
BOOL renewed = NO;
dprintf ("CredentialTimer firing... (credential '%s' for '%s')",
[[self servicePrincipalString] UTF8String],
[[self clientPrincipalString] UTF8String]);
if ([[Preferences sharedPreferences] autoRenewTickets]) {
renewed = [clientPrincipal renewTicketsIfPossibleInBackground];
}
// Credential timer was invalidated when it fired (it doesn't repeat)
[credentialTimer release];
credentialTimer = NULL;
// If the tickets got renewed then the credential will get invalidated
// and will get a new timer when it gets rescheduled. So only reschedule
// the timer if we didn't renew the tickets
if (renewed) {
[[CacheCollection sharedCacheCollection] update];
} else {
[self setupTimerLastAttemptFailed: YES];
}
}
// ---------------------------------------------------------------------------
- (BOOL) isEqualToCredentials: (cc_credentials_t) compareCredentials
{
cc_uint32 equal = NO;
cc_int32 err = ccNoError;
if (credentials == NULL) { err = ccErrInvalidCredentials; }
if (compareCredentials == NULL) { err = ccErrInvalidCredentials; }
if (err == ccNoError) {
err = cc_credentials_compare (credentials, compareCredentials, &equal);
}
if (err == ccNoError) {
return equal;
} else {
// errors such as ccErrInvalidCCache mean they are not equal!
return NO;
}
}
// ---------------------------------------------------------------------------
- (time_t) issueTime
{
switch (credentials->data->version) {
case cc_credentials_v5:
return credentials->data->credentials.credentials_v5->authtime;
case cc_credentials_v4:
return credentials->data->credentials.credentials_v4->issue_date;
default:
return 0;
}
}
// ---------------------------------------------------------------------------
- (time_t) startTime
{
switch (credentials->data->version) {
case cc_credentials_v5:
return credentials->data->credentials.credentials_v5->starttime;
case cc_credentials_v4:
return credentials->data->credentials.credentials_v4->issue_date;
default:
return 0;
}
}
// ---------------------------------------------------------------------------
- (time_t) expirationTime
{
switch (credentials->data->version) {
case cc_credentials_v5:
return credentials->data->credentials.credentials_v5->endtime;
case cc_credentials_v4:
return (credentials->data->credentials.credentials_v4->issue_date +
credentials->data->credentials.credentials_v4->lifetime);
default:
return 0;
}
}
// ---------------------------------------------------------------------------
- (time_t) renewUntilTime
{
switch (credentials->data->version) {
case cc_credentials_v5:
return credentials->data->credentials.credentials_v5->renew_till;
case cc_credentials_v4:
default:
return 0;
}
}
// ---------------------------------------------------------------------------
- (BOOL) hasValidAddress
{
BOOL foundValidAddress = NO;
if ([addressesArray count] == 0) {
// ticket has no addresses
foundValidAddress = YES;
} else {
// ticket has addresses
krb5_context context = NULL;
if (krb5_init_context (&context) == 0) {
krb5_address **localAddresses = NULL;
if (krb5_os_localaddr (context, &localAddresses) == 0) {
// Machine has addresses. Check each ticket address
unsigned int i;
for (i = 0; i < [addressesArray count]; i++) {
krb5_address *ticketAddress = [[addressesArray objectAtIndex: i] krb5_address];
if (krb5_address_search (context, ticketAddress, localAddresses) == TRUE) {
foundValidAddress = YES;
break;
}
}
krb5_free_addresses (context, localAddresses);
} else {
// Machine has no addresses (off net) so we assume ticket addresses are valid
foundValidAddress = YES;
}
krb5_free_context (context);
}
}
return foundValidAddress;
}
// ---------------------------------------------------------------------------
- (BOOL) isTGT
{
if ([self version] == cc_credentials_v5) {
return [self initial];
} else {
return [servicePrincipal isTicketGrantingServiceForCCVersion: credentials->data->version];
}
}
// ---------------------------------------------------------------------------
- (BOOL) needsValidation
{
return ([self postdated] && [self invalid]);
}
// ---------------------------------------------------------------------------
- (cc_uint32) version
{
return (credentials->data->version);
}
// ---------------------------------------------------------------------------
- (BOOL) forwardable
{
return ((ticketFlags & TKT_FLG_FORWARDABLE) != 0);
}
// ---------------------------------------------------------------------------
- (BOOL) forwarded
{
return ((ticketFlags & TKT_FLG_FORWARDED) != 0);
}
// ---------------------------------------------------------------------------
- (BOOL) proxiable
{
return ((ticketFlags & TKT_FLG_PROXIABLE) != 0);
}
// ---------------------------------------------------------------------------
- (BOOL) proxied
{
return ((ticketFlags & TKT_FLG_PROXY) != 0);
}
// ---------------------------------------------------------------------------
- (BOOL) postdatable
{
return ((ticketFlags & TKT_FLG_MAY_POSTDATE) != 0);
}
// ---------------------------------------------------------------------------
- (BOOL) postdated
{
return ((ticketFlags & TKT_FLG_POSTDATED) != 0);
}
// ---------------------------------------------------------------------------
- (BOOL) invalid
{
return ((ticketFlags & TKT_FLG_INVALID) != 0);
}
// ---------------------------------------------------------------------------
- (BOOL) renewable
{
return ((ticketFlags & TKT_FLG_RENEWABLE) != 0);
}
// ---------------------------------------------------------------------------
- (BOOL) initial
{
return ((ticketFlags & TKT_FLG_INITIAL) != 0);
}
// ---------------------------------------------------------------------------
- (BOOL) preauthenticated
{
return ((ticketFlags & TKT_FLG_PRE_AUTH) != 0);
}
// ---------------------------------------------------------------------------
- (BOOL) hardwareAuthenticated
{
return ((ticketFlags & TKT_FLG_HW_AUTH) != 0);
}
// ---------------------------------------------------------------------------
- (BOOL) isSKey
{
if (credentials->data->version == cc_credentials_v5) {
return (credentials->data->credentials.credentials_v5->is_skey);
} else {
return NO;
}
}
// ---------------------------------------------------------------------------
- (int) stateAtTime: (time_t) atTime
{
int state = CredentialValid; // no bad states
// account for 5 minute offset so we don't get confused by brand new tickets
if ([self needsValidation]) {
state |= CredentialNeedsValidation;
if (atTime < [self startTime]) { state |= CredentialBeforeStartTime; }
} else {
// account for 5 minute offset so we don't get confused by brand new tickets
if ((atTime + 5*60) < [self startTime]) { state |= CredentialBeforeStartTime; }
}
if (![self hasValidAddress]) { state |= CredentialBadAddress; }
if (atTime >= [self expirationTime]) { state |= CredentialExpired; }
return state;
}
// ---------------------------------------------------------------------------
- (cc_time_t) timeRemainingAtTime: (time_t) atTime
{
cc_time_t expirationTime = [self expirationTime];
return ((atTime < 0) || (expirationTime > (unsigned) atTime)) ? (expirationTime - atTime) : 0;
}
// ---------------------------------------------------------------------------
- (NSArray *) addresses
{
return addressesArray;
}
// ---------------------------------------------------------------------------
- (NSString *) stringToKeyTypeString
{
NSString *string = NULL;
switch (credentials->data->credentials.credentials_v4->string_to_key_type) {
case cc_v4_stk_afs:
string = NSLocalizedString (@"KAppStringAFSEnctype", NULL);
break;
case cc_v4_stk_des:
string = NSLocalizedString (@"KAppStringDESEnctype", NULL);
break;
case cc_v4_stk_columbia_special:
string = NSLocalizedString (@"KAppStringColumbiaEnctype", NULL);
break;
case cc_v4_stk_unknown:
// we autodetect, and always fill string_to_key in as unknown
// but we'll label it "Automatic" to make users feel better
string = NSLocalizedString (@"KAppStringAutomaticEnctype", NULL);
break;
}
return (string != NULL) ? string : NSLocalizedString (@"KAppStringUnknownEnctype", NULL);
}
// ---------------------------------------------------------------------------
- (NSString *) sessionKeyEnctypeString
{
NSString *string = NULL;
if (credentials->data->version == cc_credentials_v5) {
krb5_error_code err = 0;
char enctypeString[BUFSIZ];
krb5_enctype enctype = credentials->data->credentials.credentials_v5->keyblock.type;
err = krb5_enctype_to_string (enctype, enctypeString, sizeof (enctypeString));
if (err == 0) {
string = [NSString stringWithUTF8String: enctypeString];
}
}
return (string != NULL) ? string : NSLocalizedString (@"KAppStringUnknownEnctype", NULL);
}
// ---------------------------------------------------------------------------
- (NSString *) servicePrincipalKeyEnctypeString
{
NSString *string = NULL;
if (credentials->data->version == cc_credentials_v5) {
krb5_error_code err = 0;
krb5_context context = NULL;
krb5_ticket *decodedTicket = NULL;
krb5_data ticketData = {
0, /* magic */
credentials->data->credentials.credentials_v5->ticket.length,
credentials->data->credentials.credentials_v5->ticket.data
};
char encTypeString[BUFSIZ];
err = krb5_init_context (&context);
if (err == 0) {
err = krb5_decode_ticket (&ticketData, &decodedTicket);
}
if (err == 0) {
err = krb5_enctype_to_string (decodedTicket->enc_part.enctype, encTypeString, sizeof (encTypeString));
}
if (err == 0) {
string = [NSString stringWithUTF8String: encTypeString];
}
if (decodedTicket != NULL) { krb5_free_ticket (context, decodedTicket); }
if (context != NULL) { krb5_free_context (context); }
}
return (string != NULL) ? string : NSLocalizedString (@"KAppStringUnknownEnctype", NULL);
}
// ---------------------------------------------------------------------------
- (NSString *) clientPrincipalString
{
return [clientPrincipal displayStringForCCVersion: credentials->data->version];
}
// ---------------------------------------------------------------------------
- (NSString *) servicePrincipalString
{
return [servicePrincipal displayStringForCCVersion: credentials->data->version];
}
// ---------------------------------------------------------------------------
- (NSString *) versionString
{
return [Utilities stringForCCVersion: credentials->data->version];
}
// ---------------------------------------------------------------------------
- (NSAttributedString *) stringValueForTicketInfoWindow
{
time_t now = time (NULL);
int state = [self stateAtTime: now];
NSString *string = [Utilities stringForCredentialState: state
format: kLongFormat];
NSDictionary *attributes = [Utilities attributesForInfoWindowWithTicketState: state];
return [[NSAttributedString alloc] initWithString: string attributes: attributes];
}
// ---------------------------------------------------------------------------
- (NSAttributedString *) stringValueForTicketColumn
{
NSString *string = [self servicePrincipalString];
NSDictionary *attributes = [Utilities attributesForTicketColumnCellOfControlSize: NSSmallControlSize
bold: NO
italic: ([self stateAtTime: time (NULL)] != CredentialValid)];
return [[[NSAttributedString alloc] initWithString: string attributes: attributes] autorelease];
}
// ---------------------------------------------------------------------------
- (NSAttributedString *) stringValueForLifetimeColumn
{
time_t now = time (NULL);
int state = [self stateAtTime: now];
cc_time_t timeRemaining = [self timeRemainingAtTime: now];
NSString *string = [Utilities stringForTimeRemaining: timeRemaining
state: state
format: kShortFormat];
NSDictionary *attributes = [Utilities attributesForLifetimeColumnCellOfControlSize: NSSmallControlSize
bold: NO
state: state
timeRemaining: timeRemaining];
return [[[NSAttributedString alloc] initWithString: string attributes: attributes] autorelease];
}
// ---------------------------------------------------------------------------
- (int) numberOfChildren
{
return 0;
}
// ---------------------------------------------------------------------------
- (id) childAtIndex: (int) rowIndex
{
return NULL;
}
// ---------------------------------------------------------------------------
- (NSPoint) showInfoWindowCascadingFromPoint: (NSPoint) cascadePoint
{
if (infoWindowController == NULL) {
infoWindowController = [[TicketInfoController alloc] initWithCredential: self];
}
NSPoint newCascadePoint = [[infoWindowController window] cascadeTopLeftFromPoint: cascadePoint];
[infoWindowController showWindow: self];
return newCascadePoint;
}
// ---------------------------------------------------------------------------
- (void) windowWillClose: (NSNotification *) notification
{
if ([infoWindowController window] == [notification object]) {
dprintf ("Credential noticed info Window closing...");
[infoWindowController release];
infoWindowController = NULL;
}
}
// ---------------------------------------------------------------------------
- (void) wakeFromSleep: (NSNotification *) notification
{
dprintf ("Credential '%s' for '%s' noticed wake from sleep... rescheduling timer",
[[self servicePrincipalString] UTF8String],
[[self clientPrincipalString] UTF8String]);
[self setupTimerLastAttemptFailed: NO];
}
@end