/* * 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 %s initializing", [[self servicePrincipalString] UTF8String]); } 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 %d (%s)", err, error_message (err)); } 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 %s", [[halfTimeRemainingDate description] UTF8String]); dprintf ("halfExpiredDate is %s", [[halfExpiredDate description] UTF8String]); dprintf ("asapDate is %s", [[asapDate description] UTF8String]); 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 %s at %s", [[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