credential.c   [plain text]


/* Copyright (c) 2012 Apple Inc. All rights reserved. */

#include "credential.h"
#include "authutilities.h"
#include "debugging.h"
#include "crc.h"

#include <pwd.h>
#include <membership.h>
#include <membershipPriv.h>

struct _credential_s {
    __AUTH_BASE_STRUCT_HEADER__;

    bool right;   // is least-privileged credential
    
    uid_t uid;
    char * name;
    char * realName;
    
    CFAbsoluteTime creationTime;
    bool valid;
    bool shared;
    
    CFMutableSetRef cachedGroups;
};

static void
_credential_finalize(CFTypeRef value)
{
    credential_t cred = (credential_t)value;
    
    free_safe(cred->name);
    free_safe(cred->realName);
    CFReleaseSafe(cred->cachedGroups);
}

static CFStringRef
_credential_copy_description(CFTypeRef value)
{
    credential_t cred = (credential_t)value;
    CFStringRef str = NULL;
    CFTimeZoneRef sys_tz = CFTimeZoneCopySystem();
    CFGregorianDate date = CFAbsoluteTimeGetGregorianDate(cred->creationTime, sys_tz);
    CFReleaseSafe(sys_tz);
    if (cred->right) {
        str = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("credential: right=%s, shared=%i, creation=%01i:%01i:%01i, valid=%i"), cred->name, cred->shared, date.hour,date.minute,(int32_t)date.second, cred->valid);
    } else {
        str = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("credential: uid=%i, name=%s, shared=%i, creation=%01i:%01i:%01i valid=%i"), cred->uid, cred->name, cred->shared, date.hour,date.minute,(int32_t)date.second, cred->valid);
    }
    return str;
}

static CFHashCode
_credential_hash(CFTypeRef value)
{
    credential_t cred = (credential_t)value;
    uint64_t crc = crc64_init();
    if (cred->right) {
        crc = crc64_update(crc, cred->name, strlen(cred->name));
    } else {
        crc = crc64_update(crc, &cred->uid, sizeof(cred->uid));
    }
    crc = crc64_update(crc, &cred->shared, sizeof(cred->shared));
    crc = crc64_final(crc);
    
    return crc;
}

static Boolean
_credential_equal(CFTypeRef value1, CFTypeRef value2)
{
    credential_t cred1 = (credential_t)value1;
    credential_t cred2 = (credential_t)value2;
    
    return _credential_hash(cred1) == _credential_hash(cred2);
}

AUTH_TYPE_INSTANCE(credential,
                   .init = NULL,
                   .copy = NULL,
                   .finalize = _credential_finalize,
                   .equal = _credential_equal,
                   .hash = _credential_hash,
                   .copyFormattingDesc = NULL,
                   .copyDebugDesc = _credential_copy_description
                   );

static CFTypeID credential_get_type_id() {
    static CFTypeID type_id = _kCFRuntimeNotATypeID;
    static dispatch_once_t onceToken;
    
    dispatch_once(&onceToken, ^{
        type_id = _CFRuntimeRegisterClass(&_auth_type_credential);
    });
    
    return type_id;
}

static credential_t
_credential_create()
{
    credential_t cred = NULL;
    
    cred = (credential_t)_CFRuntimeCreateInstance(kCFAllocatorDefault, credential_get_type_id(), AUTH_CLASS_SIZE(credential), NULL);
    require(cred != NULL, done);
    
    cred->creationTime = CFAbsoluteTimeGetCurrent();
    cred->cachedGroups = CFSetCreateMutable(kCFAllocatorDefault, 0, &kCFTypeSetCallBacks);;
    
done:
    return cred;
}

credential_t
credential_create(uid_t uid)
{
    credential_t cred = NULL;
    
    cred = _credential_create();
    require(cred != NULL, done);

    struct passwd *pw = getpwuid(uid);
	if (pw != NULL) {
        // avoid hinting a locked account
		if ( (pw->pw_passwd == NULL) || strcmp(pw->pw_passwd, "*") ) {
            cred->uid = pw->pw_uid;
            cred->name = _copy_string(pw->pw_name);
            cred->realName = _copy_string(pw->pw_gecos);
            cred->valid = true;
        } else {
            cred->uid = (uid_t)-2;
            cred->valid = false;
        }
        endpwent();
    }
    
done:
    return cred;
}

credential_t
credential_create_with_credential(credential_t srcCred, bool shared)
{
    credential_t cred = NULL;
    
    cred = _credential_create();
    require(cred != NULL, done);
    
    cred->uid = srcCred->uid;
    cred->name = _copy_string(srcCred->name);
    cred->realName = _copy_string(srcCred->realName);
    cred->valid = srcCred->valid;
    cred->right = srcCred->right;
    cred->shared = shared;
    
done:
    return cred;
}

credential_t
credential_create_with_right(const char * right)
{
    credential_t cred = NULL;
    
    cred = _credential_create();
    require(cred != NULL, done);
    
    cred->right = true;
    cred->name = _copy_string(right);
    cred->uid = (uid_t)-2;
    cred->valid = true;
    
done:
    return cred;
}

uid_t
credential_get_uid(credential_t cred)
{
    return cred->uid;
}

const char *
credential_get_name(credential_t cred)
{
    return cred->name;
}

const char *
credential_get_realname(credential_t cred)
{
    return cred->realName;
}

CFAbsoluteTime
credential_get_creation_time(credential_t cred)
{
    return cred->creationTime;
}

bool
credential_get_valid(credential_t cred)
{
    return cred->valid;
}

bool
credential_get_shared(credential_t cred)
{
    return cred->shared;
}

bool
credential_is_right(credential_t cred)
{
    return cred->right;
}

bool
credential_check_membership(credential_t cred,const char* group)
{
    bool result = false;
    CFStringRef cachedGroup = NULL;
    require(group != NULL, done);
    require(cred->uid != 0 || cred->uid != (uid_t)-2, done);
    require(cred->right != true, done);
    
    cachedGroup = CFStringCreateWithCString(kCFAllocatorDefault, group, kCFStringEncodingUTF8);
    require(cachedGroup != NULL, done);
    
    if (CFSetGetValue(cred->cachedGroups, cachedGroup) != NULL) {
        result = true;
        goto done;
    }
    
    int rc, ismember;
    uuid_t group_uuid, user_uuid;
    rc = mbr_group_name_to_uuid(group, group_uuid);
    require_noerr(rc, done);
    
    rc = mbr_uid_to_uuid(cred->uid, user_uuid);
    require_noerr(rc, done);
    
    rc = mbr_check_membership(user_uuid, group_uuid, &ismember);
    require_noerr(rc, done);
    
    result = ismember;
    
    if (ismember) {
        CFSetSetValue(cred->cachedGroups, cachedGroup);
    }

done:
    CFReleaseSafe(cachedGroup);
    return result;
}

void
credential_invalidate(credential_t cred)
{
    cred->valid = false;
}