kim_os_preferences.c   [plain text]


/*
 * $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.
 */

#include <CoreFoundation/CoreFoundation.h>

#include "kim_os_private.h"

#define kim_os_preference_any_identity "KIM_IDENTITY_ANY"

#pragma mark -

/* ------------------------------------------------------------------------ */

static CFStringRef kim_os_preferences_cfstring_for_key (kim_preference_key in_key)
{
    if (in_key == kim_preference_key_options) {
        return CFSTR ("CredentialOptions");
        
    } else if (in_key == kim_preference_key_lifetime) {
        return CFSTR ("CredentialLifetime");
        
    } else if (in_key == kim_preference_key_renewable) {
        return CFSTR ("RenewableCredentials");
        
    } else if (in_key == kim_preference_key_renewal_lifetime) {
        return CFSTR ("CredentialRenewalLifetime");
        
    } else if (in_key == kim_preference_key_forwardable) {
        return CFSTR ("ForwardableCredentials");
        
    } else if (in_key == kim_preference_key_proxiable) {
        return CFSTR ("ProxiableCredentials");
        
    } else if (in_key == kim_preference_key_addressless) {
        return CFSTR ("AddresslessCredentials");
        
    } else if (in_key == kim_preference_key_remember_options) {
        return CFSTR ("RememberCredentialAttributes");
        
    } else if (in_key == kim_preference_key_client_identity) {
        return CFSTR ("ClientIdentity");
        
    } else if (in_key == kim_preference_key_remember_client_identity) {
        return CFSTR ("RememberClientIdentity");
        
    } else if (in_key == kim_preference_key_favorites) {
        return CFSTR ("FavoriteIdentities");
        
    } else if (in_key == kim_preference_key_minimum_lifetime) {
        return CFSTR ("MinimumLifetime");
        
    } else if (in_key == kim_preference_key_maximum_lifetime) {
        return CFSTR ("MaximumLifetime");
        
    } else if (in_key == kim_preference_key_minimum_renewal_lifetime) {
        return CFSTR ("MinimumRenewalLifetime");
        
    } else if (in_key == kim_preference_key_maximum_renewal_lifetime) {
        return CFSTR ("MaximumRenewalLifetime");
        
    }
    
    return NULL; /* ignore unsupported keys */
}

/* ------------------------------------------------------------------------ */

static CFStringRef kim_os_preferences_compat_cfstring_for_key (kim_preference_key in_key)
{
    if (in_key == kim_preference_key_lifetime) {
        return CFSTR ("KLDefaultTicketLifetime");
        
    } else if (in_key == kim_preference_key_renewable) {
        return CFSTR ("KLGetRenewableTickets");
        
    } else if (in_key == kim_preference_key_renewal_lifetime) {
        return CFSTR ("KLDefaultRenewableLifetime");
        
    } else if (in_key == kim_preference_key_forwardable) {
        return CFSTR ("KLDefaultForwardableTicket");
        
    } else if (in_key == kim_preference_key_proxiable) {
        return CFSTR ("KLGetProxiableTickets");
        
    } else if (in_key == kim_preference_key_addressless) {
        return CFSTR ("KLGetAddresslessTickets");
        
    } else if (in_key == kim_preference_key_remember_options) {
        return CFSTR ("KLRememberExtras");
        
    } else if (in_key == kim_preference_key_client_identity) {
        return CFSTR ("KLName");
        
    } else if (in_key == kim_preference_key_remember_client_identity) {
        return CFSTR ("KLRememberPrincipal");
        
    } else if (in_key == kim_preference_key_favorites) {
        return CFSTR ("KLFavoriteIdentities");
        
    } else if (in_key == kim_preference_key_minimum_lifetime) {
        return CFSTR ("KLMinimumTicketLifetime");
        
    } else if (in_key == kim_preference_key_maximum_lifetime) {
        return CFSTR ("KLMaximumTicketLifetime");
        
    } else if (in_key == kim_preference_key_minimum_renewal_lifetime) {
        return CFSTR ("KLMinimumRenewableLifetime");
        
    } else if (in_key == kim_preference_key_maximum_renewal_lifetime) {
        return CFSTR ("KLMaximumRenewableLifetime");
        
    }
    
    return NULL; /* ignore unsupported keys */
}

/* ------------------------------------------------------------------------ */

static kim_error kim_os_preferences_copy_value_for_file (CFStringRef         in_key, 
                                                         CFTypeID            in_type, 
                                                         CFStringRef         in_file,
                                                         CFPropertyListRef  *out_value)
{
    
    kim_error err = KIM_NO_ERROR;
    CFPropertyListRef value = NULL;
    CFStringRef users[] = { kCFPreferencesCurrentUser, kCFPreferencesAnyUser, NULL };
    CFStringRef hosts[] = { kCFPreferencesCurrentHost, kCFPreferencesAnyHost, NULL };
    
    if (!err && !in_key   ) { err = check_error (KIM_NULL_PARAMETER_ERR); }
    if (!err && !in_file  ) { err = check_error (KIM_NULL_PARAMETER_ERR); }
    if (!err && !out_value) { err = check_error (KIM_NULL_PARAMETER_ERR); }
    
    if (!err) {
        kim_count u, h;
        
        if (!kim_library_allow_home_directory_access()) {
            users[0] = kCFPreferencesAnyUser;
            users[1] = NULL;
        }
        
        for (u = 0; !value && users[u]; u++) {
            for (h = 0; !value && hosts[h]; h++) {
                value = CFPreferencesCopyValue (in_key, in_file, users[u], hosts[h]);
            }
        }        
        
        if (value && CFGetTypeID (value) != in_type) {
            err = check_error (KIM_PREFERENCES_READ_ERR);
	    if (in_type == CFBooleanGetTypeID() && CFGetTypeID(value) == CFNumberGetTypeID()) {
		int num;
		if (CFNumberGetValue(value, kCFNumberIntType, &num)) {
		    value = num ? kCFBooleanTrue : kCFBooleanFalse;
		    err = 0;
		}
	    }
	}
    }
    
    
    if (!err) {
        *out_value = value;
        value = NULL;
    }
    
    if (value) { CFRelease (value); }
    
    return check_error (err);
}

#pragma mark -

/* ------------------------------------------------------------------------ */

static kim_error kim_os_preferences_copy_value (kim_preference_key  in_key, 
                                                CFTypeID            in_type, 
                                                CFPropertyListRef  *out_value)
{
    kim_error err = KIM_NO_ERROR;
    CFStringRef key = kim_os_preferences_cfstring_for_key (in_key);
    
    err = kim_os_preferences_copy_value_for_file (key, in_type,
                                                  KIM_PREFERENCES_FILE,
                                                  out_value);
    
    return check_error (err);
}

/* ------------------------------------------------------------------------ */

static kim_error kim_os_preferences_copy_value_compat (kim_preference_key  in_key, 
                                                       CFTypeID            in_type, 
                                                       CFPropertyListRef  *out_value)
{
    kim_error err = KIM_NO_ERROR;
    CFStringRef key = kim_os_preferences_compat_cfstring_for_key (in_key);
    
    err = kim_os_preferences_copy_value_for_file (key, in_type,
                                                  KLL_PREFERENCES_FILE,
                                                  out_value);
    
    return check_error (err);
}

/* ------------------------------------------------------------------------ */

static kim_error kim_os_preferences_set_value (kim_preference_key in_key, 
                                               CFPropertyListRef  in_value)
{
    kim_error err = KIM_NO_ERROR;
    CFStringRef key = NULL;
    
    /* in_value may be NULL if removing the key */
    
    if (!err) {
        key = kim_os_preferences_cfstring_for_key (in_key);
    }
    
    if (!err && key) {
        kim_boolean homedir_ok = kim_library_allow_home_directory_access();
        CFStringRef user = homedir_ok ? kCFPreferencesCurrentUser : kCFPreferencesAnyUser;
        CFStringRef host = homedir_ok ? kCFPreferencesAnyHost : kCFPreferencesCurrentHost;
        
        CFPreferencesSetValue (key, in_value, KIM_PREFERENCES_FILE, user, host);
        if (!CFPreferencesSynchronize (KIM_PREFERENCES_FILE, user, host)) {
            err = check_error (KIM_PREFERENCES_WRITE_ERR);
        }
    }
    
    return check_error (err);
}

#pragma mark -

/* ------------------------------------------------------------------------ */

kim_error kim_os_preferences_get_identity_for_key (kim_preference_key  in_key, 
                                                   kim_identity        in_hardcoded_default,
                                                   kim_identity       *out_identity)
{
    kim_error err = KIM_NO_ERROR;
    kim_string string = NULL;
    CFStringRef value = NULL;
    
    if (!err && !out_identity) { err = check_error (KIM_NULL_PARAMETER_ERR); }
    
    if (!err) {
        err = kim_os_preferences_copy_value (in_key, CFStringGetTypeID (), 
                                             (CFPropertyListRef *) &value);
        
    }
    
    if (!err && !value) {
        err = kim_os_preferences_copy_value_compat (in_key, CFStringGetTypeID (), 
                                                    (CFPropertyListRef *) &value);
    }
    
    if (!err) {
        if (value) {
            err = kim_os_string_create_from_cfstring (&string, value);
            
            if (!err) {
                if (!strcmp (kim_os_preference_any_identity, string)) {
                    *out_identity = KIM_IDENTITY_ANY;
                    
                } else {
                    err = kim_identity_create_from_string (out_identity, string);
                }
            }
        } else {
            err = kim_identity_copy (out_identity, in_hardcoded_default);
        }
    }
    
    kim_string_free (&string);
    if (value) { CFRelease (value); }
    
    return check_error (err);
}

/* ------------------------------------------------------------------------ */

kim_error kim_os_preferences_set_identity_for_key (kim_preference_key in_key, 
                                                   kim_identity       in_identity)
{
    kim_error err = KIM_NO_ERROR;
    CFStringRef value = NULL;
    kim_string string = NULL;
    
    /* in_identity can be KIM_IDENTITY_ANY */
    
    if (!err) {
        if (in_identity) {
            err = kim_identity_get_string (in_identity, &string);
            
        } else {
            err = kim_string_copy (&string, kim_os_preference_any_identity);
        }
    }
    
    if (!err) {
        err = kim_os_string_get_cfstring (string, &value);
    }
    
    if (!err) {
        err = kim_os_preferences_set_value (in_key, value);
    }
    
    if (value) { CFRelease (value); }
    kim_string_free (&string);
    
    return check_error (err);
}

/* ------------------------------------------------------------------------ */

kim_error kim_os_preferences_get_lifetime_for_key (kim_preference_key  in_key, 
                                                   kim_lifetime        in_hardcoded_default,
                                                   kim_lifetime       *out_lifetime)
{
    kim_error err = KIM_NO_ERROR;
    CFNumberRef value = NULL;
    
    if (!err && !out_lifetime) { err = check_error (KIM_NULL_PARAMETER_ERR); }
    
    if (!err) {
        err = kim_os_preferences_copy_value (in_key, CFNumberGetTypeID (), 
                                             (CFPropertyListRef *) &value);
    }
    
    if (!err && !value) {
        err = kim_os_preferences_copy_value_compat (in_key, CFNumberGetTypeID (), 
                                                    (CFPropertyListRef *) &value);
    }
    
    if (!err) {
        if (value) {
            SInt32 number; // CFNumbers are signed so we need to cast
            if (CFNumberGetValue (value, kCFNumberSInt32Type, &number) != TRUE) {
                err = KIM_OUT_OF_MEMORY_ERR;
            } else {
                *out_lifetime = number;
            }
        } else {
            *out_lifetime = in_hardcoded_default;
        }
    }
    
    if (value) { CFRelease (value); }
    
    return check_error (err);
}

/* ------------------------------------------------------------------------ */

kim_error kim_os_preferences_set_lifetime_for_key (kim_preference_key in_key, 
                                                   kim_lifetime       in_lifetime)
{
    kim_error err = KIM_NO_ERROR;
    CFNumberRef value = NULL;
    SInt32 number = (SInt32) in_lifetime;
    
    if (!err) {
        value = CFNumberCreate (kCFAllocatorDefault, kCFNumberSInt32Type, &number);
        if (!value) { err = KIM_OUT_OF_MEMORY_ERR; }
    }
    
    if (!err) {
        err = kim_os_preferences_set_value (in_key, value);
    }
    
    if (value) { CFRelease (value); }
    
    return check_error (err);
}

/* ------------------------------------------------------------------------ */

kim_error kim_os_preferences_get_boolean_for_key (kim_preference_key  in_key, 
                                                  kim_boolean         in_hardcoded_default,
                                                  kim_boolean        *out_boolean)
{
    kim_error err = KIM_NO_ERROR;
    CFBooleanRef value = NULL;
    
    if (!err && !out_boolean) { err = check_error (KIM_NULL_PARAMETER_ERR); }
    
    if (!err) {
        err = kim_os_preferences_copy_value (in_key, CFBooleanGetTypeID (), 
                                             (CFPropertyListRef *) &value);
    }
    
    if (!err && !value) {
        err = kim_os_preferences_copy_value_compat (in_key, CFBooleanGetTypeID (), 
                                                    (CFPropertyListRef *) &value);
    }
    
    if (!err) {
        if (value) {
            *out_boolean = CFBooleanGetValue (value);
        } else {
            *out_boolean = in_hardcoded_default;
        }
    }
    
    if (value) { CFRelease (value); }
    
    return check_error (err);
}

/* ------------------------------------------------------------------------ */

kim_error kim_os_preferences_set_boolean_for_key (kim_preference_key in_key, 
                                                  kim_boolean        in_boolean)
{
    kim_error err = KIM_NO_ERROR;
    CFBooleanRef value = in_boolean ? kCFBooleanTrue : kCFBooleanFalse;
    
    if (!err) {
        err = kim_os_preferences_set_value (in_key, value);
    }
    
    return check_error (err);
}

#pragma mark -

/* ------------------------------------------------------------------------ */

static kim_error kim_os_preferences_copy_value_for_dict_key (CFDictionaryRef     in_dictionary,
                                                             kim_preference_key  in_key, 
                                                             CFTypeID            in_type, 
                                                             CFPropertyListRef  *out_value)
{
    kim_error err = KIM_NO_ERROR;
    CFPropertyListRef value = NULL;
    
    if (!err && !in_dictionary) { err = check_error (KIM_NULL_PARAMETER_ERR); }
    if (!err && !out_value    ) { err = check_error (KIM_NULL_PARAMETER_ERR); }
    
    if (!err) {
        CFStringRef key = kim_os_preferences_cfstring_for_key (in_key);
        
        value = CFDictionaryGetValue (in_dictionary, key);
        if (value && CFGetTypeID (value) != in_type) {
            err = check_error (KIM_PREFERENCES_READ_ERR);
	    if (in_type == CFBooleanGetTypeID() && CFGetTypeID(value) == CFNumberGetTypeID()) {
		int num;
		if (CFNumberGetValue(value, kCFNumberIntType, &num)) {
		    value = num ? kCFBooleanTrue : kCFBooleanFalse;
		    err = 0;
		}
	    }
        }
    }
    
    if (!err) {
        *out_value = value;
    }
    
    return check_error (err);
}

/* ------------------------------------------------------------------------ */

static kim_error kim_os_preferences_set_value_for_dict_key (CFMutableDictionaryRef in_dictionary,
                                                            kim_preference_key     in_key, 
                                                            CFPropertyListRef      in_value)
{
    kim_error err = KIM_NO_ERROR;
    
    if (!err && !in_dictionary) { err = check_error (KIM_NULL_PARAMETER_ERR); }
    if (!err && !in_value     ) { err = check_error (KIM_NULL_PARAMETER_ERR); }
    
    if (!err) {
        CFStringRef key = kim_os_preferences_cfstring_for_key (in_key);
        
        CFDictionarySetValue (in_dictionary, key, in_value);
    }
    
    return check_error (err);
}

/* ------------------------------------------------------------------------ */

static kim_error kim_os_preferences_dictionary_to_options (CFDictionaryRef  in_dictionary, 
                                                           kim_options     *out_options)
{
    kim_error err = KIM_NO_ERROR;
    kim_options options = KIM_OPTIONS_DEFAULT;
    kim_boolean found_options = 0;
    
    if (!err && !in_dictionary) { err = check_error (KIM_NULL_PARAMETER_ERR); }
    if (!err && !out_options  ) { err = check_error (KIM_NULL_PARAMETER_ERR); }
    
    if (!err) {
        err = kim_options_create_empty (&options);
    }
    
    if (!err) {
        CFBooleanRef value = NULL;
        
        err = kim_os_preferences_copy_value_for_dict_key (in_dictionary,
                                                          kim_preference_key_renewable, 
                                                          CFBooleanGetTypeID (), 
                                                          (CFPropertyListRef *) &value);
        
        if (!err && value) {
            found_options = 1;
            err = kim_options_set_renewable (options, CFBooleanGetValue (value));
        }
    }
    
    if (!err) {
        CFNumberRef value = NULL;
        SInt32 lifetime; // CFNumbers are signed so we need to cast
        
        err = kim_os_preferences_copy_value_for_dict_key (in_dictionary,
                                                          kim_preference_key_lifetime, 
                                                          CFNumberGetTypeID (), 
                                                          (CFPropertyListRef *) &value);
        
        if (!err && value && CFNumberGetValue (value, kCFNumberSInt32Type, 
                                               &lifetime)) {
            found_options = 1;
            err = kim_options_set_lifetime (options, lifetime);
        }
    }
    
    if (!err) {
        CFNumberRef value = NULL;
        SInt32 lifetime; // CFNumbers are signed so we need to cast
        
        err = kim_os_preferences_copy_value_for_dict_key (in_dictionary,
                                                          kim_preference_key_renewal_lifetime, 
                                                          CFNumberGetTypeID (), 
                                                          (CFPropertyListRef *) &value);
        
        if (!err && value && CFNumberGetValue (value, kCFNumberSInt32Type, 
                                               &lifetime)) {
            found_options = 1;
            err = kim_options_set_renewal_lifetime (options, lifetime);
        }    }
    
    if (!err) {
        CFBooleanRef value = NULL;
        
        err = kim_os_preferences_copy_value_for_dict_key (in_dictionary,
                                                          kim_preference_key_forwardable, 
                                                          CFBooleanGetTypeID (), 
                                                          (CFPropertyListRef *) &value);
        
        if (!err && value) {
            found_options = 1;
            err = kim_options_set_forwardable (options, CFBooleanGetValue (value));
        }
    }
    
    if (!err) {
        CFBooleanRef value = NULL;
        
        err = kim_os_preferences_copy_value_for_dict_key (in_dictionary,
                                                          kim_preference_key_proxiable, 
                                                          CFBooleanGetTypeID (), 
                                                          (CFPropertyListRef *) &value);
        
        if (!err && value) {
            found_options = 1;
            err = kim_options_set_proxiable (options, CFBooleanGetValue (value));
        }
    }
    
    if (!err) {
        CFBooleanRef value = NULL;
        
        err = kim_os_preferences_copy_value_for_dict_key (in_dictionary,
                                                          kim_preference_key_addressless, 
                                                          CFBooleanGetTypeID (), 
                                                          (CFPropertyListRef *) &value);
        
        if (!err && value) {
            found_options = 1;
            err = kim_options_set_addressless (options, CFBooleanGetValue (value));
        }
    }
    
    if (!err && !found_options) {
        kim_options_free (&options);
        options = KIM_OPTIONS_DEFAULT;
    }
    
    if (!err) {
        *out_options = options;
        options = NULL;
    }
    
    kim_options_free (&options);
    
    return check_error (err);
}

/* ------------------------------------------------------------------------ */

static kim_error kim_os_preferences_options_to_dictionary (kim_options            in_options,
                                                           CFMutableDictionaryRef io_dictionary)
{
    kim_error err = KIM_NO_ERROR;
    
    if (!err && !in_options   ) { err = check_error (KIM_NULL_PARAMETER_ERR); }
    if (!err && !io_dictionary) { err = check_error (KIM_NULL_PARAMETER_ERR); }
    
    if (!err) {
        CFNumberRef value = NULL;
        kim_lifetime lifetime;
        
        err = kim_options_get_lifetime (in_options, &lifetime);
        
        if (!err) {
            SInt32 number = (SInt32) lifetime;
            
            value = CFNumberCreate (kCFAllocatorDefault, 
                                    kCFNumberSInt32Type, &number);
            if (!value) { err = KIM_OUT_OF_MEMORY_ERR; }
        }
        
        if (!err) {
            err = kim_os_preferences_set_value_for_dict_key (io_dictionary, 
                                                             kim_preference_key_lifetime, 
                                                             value);
        }
        
        if (value) { CFRelease (value); }        
    }
    
    if (!err) {
        kim_boolean boolean;
        
        err = kim_options_get_renewable (in_options, &boolean);
        
        if (!err) {
            CFBooleanRef value = boolean ? kCFBooleanTrue : kCFBooleanFalse;
            
            err = kim_os_preferences_set_value_for_dict_key (io_dictionary, 
                                                             kim_preference_key_renewable, 
                                                             value);
        }
    }    
    
    if (!err) {
        CFNumberRef value = NULL;
        kim_lifetime lifetime;
        
        err = kim_options_get_renewal_lifetime (in_options, &lifetime);
        
        if (!err) {
            SInt32 number = (SInt32) lifetime;
            
            value = CFNumberCreate (kCFAllocatorDefault, 
                                    kCFNumberSInt32Type, &number);
            if (!value) { err = KIM_OUT_OF_MEMORY_ERR; }
        }
        
        if (!err) {
            err = kim_os_preferences_set_value_for_dict_key (io_dictionary, 
                                                             kim_preference_key_renewal_lifetime, 
                                                             value);
        }
        
        if (value) { CFRelease (value); }        
    }
    
    if (!err) {
        kim_boolean boolean;
        
        err = kim_options_get_forwardable (in_options, &boolean);
        
        if (!err) {
            CFBooleanRef value = boolean ? kCFBooleanTrue : kCFBooleanFalse;
            
            err = kim_os_preferences_set_value_for_dict_key (io_dictionary, 
                                                             kim_preference_key_forwardable, 
                                                             value);
        }
    }    
    
    if (!err) {
        kim_boolean boolean;
        
        err = kim_options_get_proxiable (in_options, &boolean);
        
        if (!err) {
            CFBooleanRef value = boolean ? kCFBooleanTrue : kCFBooleanFalse;
            
            err = kim_os_preferences_set_value_for_dict_key (io_dictionary, 
                                                             kim_preference_key_proxiable, 
                                                             value);
        }
    }
    
    if (!err) {
        kim_boolean boolean;
        
        err = kim_options_get_addressless (in_options, &boolean);
        
        if (!err) {
            CFBooleanRef value = boolean ? kCFBooleanTrue : kCFBooleanFalse;
            
            err = kim_os_preferences_set_value_for_dict_key (io_dictionary, 
                                                             kim_preference_key_addressless, 
                                                             value);
        }
    }    
    
    return check_error (err);
}



#pragma mark -

/* ------------------------------------------------------------------------ */

static kim_error kim_os_preferences_get_options_compat (kim_options *out_options)
{
    kim_error err = KIM_NO_ERROR;
    kim_options options = KIM_OPTIONS_DEFAULT;
    kim_boolean found_options = 0;
    
    if (!err && !out_options) { err = check_error (KIM_NULL_PARAMETER_ERR); }
    
    if (!err) {
        err = kim_options_create_empty (&options);
    }
    
    if (!err) { 
        CFNumberRef value = NULL;
        SInt32 lifetime; // CFNumbers are signed so we need to cast
        
        err = kim_os_preferences_copy_value_compat (kim_preference_key_lifetime, 
                                                    CFNumberGetTypeID (), 
                                                    (CFPropertyListRef *) &value);
        
        if (!err && value && CFNumberGetValue (value, kCFNumberSInt32Type, 
                                               &lifetime)) {
            found_options = 1;
            err = kim_options_set_lifetime (options, lifetime);
        }
        
        if (value) { CFRelease (value); }
    }
    
    if (!err) {
        CFBooleanRef value = NULL;
        
        err = kim_os_preferences_copy_value_compat (kim_preference_key_renewable, 
                                                    CFBooleanGetTypeID (), 
                                                    (CFPropertyListRef *) &value);
        
        if (!err && value) {
            found_options = 1;
            err = kim_options_set_renewable (options, CFBooleanGetValue (value));
        }
        
        if (value) { CFRelease (value); }
    }
    
    if (!err) {
        CFNumberRef value = NULL;
        SInt32 lifetime; // CFNumbers are signed so we need to cast
        
        err = kim_os_preferences_copy_value_compat (kim_preference_key_renewal_lifetime, 
                                                    CFNumberGetTypeID (), 
                                                    (CFPropertyListRef *) &value);
        
        if (!err && value && CFNumberGetValue (value, kCFNumberSInt32Type, 
                                               &lifetime)) {
            found_options = 1;
            err = kim_options_set_renewal_lifetime (options, lifetime);
        }
        
        if (value) { CFRelease (value); }
    }
    
    if (!err) {
        CFBooleanRef value = NULL;
        
        err = kim_os_preferences_copy_value_compat (kim_preference_key_forwardable, 
                                                    CFBooleanGetTypeID (), 
                                                    (CFPropertyListRef *) &value);
        
        if (!err && value) {
            found_options = 1;
            err = kim_options_set_forwardable (options, CFBooleanGetValue (value));
        }
        
        if (value) { CFRelease (value); }
    }
    
    if (!err) {
        CFBooleanRef value = NULL;
        
        err = kim_os_preferences_copy_value_compat (kim_preference_key_proxiable, 
                                                    CFBooleanGetTypeID (), 
                                                    (CFPropertyListRef *) &value);
        
        if (!err && value) {
            found_options = 1;
            err = kim_options_set_proxiable (options, CFBooleanGetValue (value));
        }
        
        if (value) { CFRelease (value); }
    }
    
    if (!err) {
        CFBooleanRef value = NULL;
        
        err = kim_os_preferences_copy_value_compat (kim_preference_key_addressless, 
                                                    CFBooleanGetTypeID (), 
                                                    (CFPropertyListRef *) &value);
        
        if (!err && value) {
            found_options = 1;
            err = kim_options_set_addressless (options, CFBooleanGetValue (value));
        }
        
        if (value) { CFRelease (value); }
    }
    
    if (!err && !found_options) {
        kim_options_free (&options);
        options = KIM_OPTIONS_DEFAULT;
    }
    
    if (!err) {
        *out_options = options;
    }
    
    return check_error (err);
}

/* ------------------------------------------------------------------------ */

kim_error kim_os_preferences_get_options_for_key (kim_preference_key  in_key, 
                                                  kim_options        *out_options)
{
    kim_error err = KIM_NO_ERROR;
    CFDictionaryRef dictionary = NULL;
    kim_options options = KIM_OPTIONS_DEFAULT;
    
    if (!err && !out_options) { err = check_error (KIM_NULL_PARAMETER_ERR); }
    
    if (!err) {
        err = kim_os_preferences_copy_value (in_key, CFDictionaryGetTypeID (), 
                                             (CFPropertyListRef *) &dictionary);
        
        if (!err && dictionary) {
            err = kim_os_preferences_dictionary_to_options (dictionary, &options);
        }
    }
    
    if (!err && !dictionary) {
        err = kim_os_preferences_get_options_compat (&options);
    }
    
    if (!err) {
        *out_options = options;
    }
    
    if (dictionary) { CFRelease (dictionary); }
    
    return check_error (err);
}

/* ------------------------------------------------------------------------ */

kim_error kim_os_preferences_set_options_for_key (kim_preference_key in_key, 
                                                  kim_options        in_options)
{
    kim_error err = KIM_NO_ERROR;
    CFMutableDictionaryRef dictionary = NULL;
    
    /* in_options may be KIM_OPTIONS_DEFAULT, in which case we empty the dict */
    
    if (!err && in_options) {
        dictionary = CFDictionaryCreateMutable (kCFAllocatorDefault, 0, 
                                                &kCFTypeDictionaryKeyCallBacks,
                                                &kCFTypeDictionaryValueCallBacks);
        if (!dictionary) { err = check_error (KIM_OUT_OF_MEMORY_ERR); }

        if (!err) {
            err = kim_os_preferences_options_to_dictionary (in_options, dictionary);
        }
    }
    
    if (!err) {
        /* NULL dictioray will remove any entry for this key */
        err = kim_os_preferences_set_value (in_key, dictionary);
    }
    
    if (dictionary) { CFRelease (dictionary); }
    
    return check_error (err);
}

#pragma mark -

/* ------------------------------------------------------------------------ */

kim_error kim_os_preferences_get_favorites_for_key (kim_preference_key in_key, 
                                                    kim_favorites      io_favorites)
{
    kim_error err = KIM_NO_ERROR;
    CFArrayRef value = NULL;
    
    if (!err && !io_favorites) { err = check_error (KIM_NULL_PARAMETER_ERR); }
    
    if (!err) {
        err = kim_os_preferences_copy_value (in_key, CFArrayGetTypeID (), 
                                             (CFPropertyListRef *) &value);
    }
    
    if (!err && value) {
        if (!value || CFArrayGetCount (value) < 1) {
            err =  kim_favorites_remove_all_identities (io_favorites);
            
        } else {
            CFIndex count = CFArrayGetCount (value);
            CFIndex i;
            
            for (i = 0; !err && i < count; i++) {
                CFDictionaryRef dictionary = NULL;
                CFStringRef cfstring = NULL;
                
                dictionary = (CFDictionaryRef) CFArrayGetValueAtIndex (value, i);
                if (!dictionary || CFGetTypeID (dictionary) != CFDictionaryGetTypeID ()) {
                    err = check_error (KIM_PREFERENCES_READ_ERR);
                }
                
                if (!err) {
                    err = kim_os_preferences_copy_value_for_dict_key (dictionary,
                                                                      kim_preference_key_client_identity,
                                                                      CFStringGetTypeID (),
                                                                      (CFPropertyListRef *) &cfstring);                    
                }
                
                if (!err && cfstring) {
                    kim_string string = NULL;
                    kim_identity identity = NULL;
                    kim_options options = KIM_OPTIONS_DEFAULT;
                    
                    err = kim_os_string_create_from_cfstring (&string, cfstring);
                    
                    if (!err) {
                        err = kim_identity_create_from_string (&identity, string);
                    }
                    
                    if (!err && (CFDictionaryGetCount (dictionary) > 1)) {
                        err = kim_os_preferences_dictionary_to_options (dictionary, 
                                                                        &options);
                    }
                    
                    if (!err) {
                        err = kim_favorites_add_identity (io_favorites, identity,
                                                          options);
                    }
                    
                    kim_string_free (&string);
                    kim_options_free (&options);
                    kim_identity_free (&identity);
                }
            }
            
            if (err) {
                kim_favorites_remove_all_identities (io_favorites);
            }
        }
    }
    
    if (value) { CFRelease (value); }
    
    return check_error (err);
}

/* ------------------------------------------------------------------------ */
kim_error kim_os_preferences_set_favorites_for_key (kim_preference_key in_key, 
                                                    kim_favorites      in_favorites)
{
    kim_error err = KIM_NO_ERROR;
    kim_count count = 0;
    CFMutableArrayRef array = NULL;
    
    if (!err && !in_favorites) { err = check_error (KIM_NULL_PARAMETER_ERR); }
    
    if (!err) {
        err = kim_favorites_get_number_of_identities (in_favorites, &count);
    }
    
    if (!err) {
        array = CFArrayCreateMutable (kCFAllocatorDefault, count, 
                                      &kCFTypeArrayCallBacks);
        if (!array) { err = KIM_OUT_OF_MEMORY_ERR; }
    }
    
    if (!err) {
        kim_count i;
        
        for (i = 0; !err && i < count; i++) {
            kim_identity identity = NULL;
            kim_options options = NULL;
            kim_string string = NULL;
            CFStringRef cfstring = NULL;
            CFMutableDictionaryRef dictionary = NULL;
            
            err = kim_favorites_get_identity_at_index (in_favorites, i, 
                                                       &identity,
                                                       &options);
            
            if (!err) {
                err = kim_identity_get_string (identity, &string);
            }
            
            if (!err) {
                err = kim_os_string_get_cfstring (string, &cfstring);
            }
            
            if (!err) {
                dictionary = CFDictionaryCreateMutable (kCFAllocatorDefault, 0, 
                                                        &kCFTypeDictionaryKeyCallBacks,
                                                        &kCFTypeDictionaryValueCallBacks);
                if (!dictionary) { err = check_error (KIM_OUT_OF_MEMORY_ERR); }
            }
            
            if (!err) {
                err = kim_os_preferences_set_value_for_dict_key (dictionary,
                                                                 kim_preference_key_client_identity,
                                                                 cfstring);                    
            }
            
            if (!err && options) {
                err = kim_os_preferences_options_to_dictionary (options, 
                                                                dictionary);
            }
            
            if (!err) {
                CFArrayAppendValue (array, dictionary);
            }
            
            if (dictionary) { CFRelease (dictionary); }
            if (cfstring  ) { CFRelease (cfstring); }
            kim_string_free (&string);
            kim_options_free (&options);
            kim_identity_free (&identity);
        }
    }
    
    if (!err) {
        err = kim_os_preferences_set_value (in_key, array);
    }
    
    if (array) { CFRelease (array); }
    
    return check_error (err);
}