rlm_opendirectory.c [plain text]
#include <freeradius-devel/radiusd.h>
#include <freeradius-devel/modules.h>
#include <freeradius-devel/rad_assert.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <grp.h>
#include <pwd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <uuid/uuid.h>
#include <OpenDirectory/OpenDirectory.h>
#include <membership.h>
#if HAVE_APPLE_SPI
#include <membershipPriv.h>
#else
int mbr_check_service_membership(const uuid_t user, const char *servicename, int *ismember);
int mbr_check_membership_refresh(const uuid_t user, uuid_t group, int *ismember);
#endif
#define kRadiusSACLName "com.apple.access_radius"
#define kRadiusServiceName "radius"
#define kAuthType "opendirectory"
static ODRecordRef od_find_rec(REQUEST* request, ODNodeRef node, CFStringRef recName, const char* recNameStr)
{
if (!node || !recName) return NULL;
ODRecordRef rec = NULL;
ODQueryRef query = ODQueryCreateWithNode(kCFAllocatorDefault,
node,
kODRecordTypeUsers,
kODAttributeTypeRecordName,
kODMatchEqualTo,
recName,
NULL,
0,
NULL);
if (!query) {
RDEBUG2("Unable to create OD query for %s", recNameStr);
} else {
CFArrayRef queryResults = ODQueryCopyResults(query, false, NULL);
if (queryResults == NULL || CFArrayGetCount(queryResults) == 0) {
RDEBUG2("Unable to find record '%s' in OD", recNameStr);
} else {
rec = (ODRecordRef)CFArrayGetValueAtIndex(queryResults, 0);
CFRetain(rec);
CFRelease(queryResults);
}
CFRelease(query);
}
return rec;
}
static int od_authenticate(UNUSED void *instance, REQUEST *request)
{
if (!request->username) {
RDEBUG("ERROR: Request does not contain a User-Name attribute!");
return RLM_MODULE_INVALID;
}
if (!request->password ||
(request->password->attribute != PW_PASSWORD)) {
RDEBUG("ERROR: Request does not contain a User-Password attribute!");
return RLM_MODULE_INVALID;
}
ODNodeRef searchNode = ODNodeCreateWithName(kCFAllocatorDefault, kODSessionDefault, CFSTR("/Search"), NULL);
if (!searchNode) {
RDEBUG2("Unable to open OD search node");
return RLM_MODULE_FAIL;
}
CFStringRef username = CFStringCreateWithCString(kCFAllocatorDefault,
request->username->vp_strvalue,
kCFStringEncodingUTF8);
CFStringRef password = CFStringCreateWithCString(kCFAllocatorDefault,
request->password->vp_strvalue,
kCFStringEncodingUTF8);
int status = RLM_MODULE_REJECT;
CFErrorRef error = NULL;
ODRecordRef rec = od_find_rec(request, searchNode, username, request->username->vp_strvalue);
if (rec) {
if (ODRecordVerifyPassword(rec, password, &error)) {
status = RLM_MODULE_OK;
} else {
if (error == NULL) {
RDEBUG2("Authentication failed for %s", request->username->vp_strvalue);
} else {
char* desc_str = NULL;
CFStringRef desc = CFErrorCopyDescription(error);
if (desc) {
size_t desc_str_size = CFStringGetLength(desc) + 1;
desc_str = malloc(desc_str_size);
if (desc_str) {
CFStringGetCString(desc,
desc_str,
desc_str_size,
kCFStringEncodingUTF8);
}
CFRelease(desc);
}
RDEBUG2("Authentication failed for %s: error %d: %s",
request->username->vp_strvalue, CFErrorGetCode(error),
desc_str ? desc_str : "unknown error");
if (desc_str) free(desc_str);
CFRelease(error);
}
}
CFRelease(rec);
}
if (username) CFRelease(username);
if (password) CFRelease(password);
CFRelease(searchNode);
return status;
}
static int od_authorize(UNUSED void *instance, REQUEST *request)
{
struct group *groupdata = NULL;
int ismember = 0;
RADCLIENT *rad_client = NULL;
uuid_t uuid;
uuid_t guid_sacl;
uuid_t guid_nasgroup;
int err;
char host_ipaddr[128] = {0};
if (!request || !request->username) {
RDEBUG("OpenDirectory requires a User-Name attribute.");
return RLM_MODULE_NOOP;
}
uuid_clear(guid_sacl);
groupdata = getgrnam(kRadiusSACLName);
if (groupdata != NULL) {
err = mbr_gid_to_uuid(groupdata->gr_gid, guid_sacl);
if (err != 0) {
radlog(L_ERR, "rlm_opendirectory: The group \"%s\" does not have a GUID.", kRadiusSACLName);
return RLM_MODULE_FAIL;
}
}
else {
RDEBUG("The SACL group \"%s\" does not exist on this system.", kRadiusSACLName);
}
uuid_clear(guid_nasgroup);
rad_client = request->client;
#if 0
if (rad_client->community[0] != '\0' )
{
if (uuid_parse(rad_client->community, guid_nasgroup) != 0) {
groupdata = getgrnam(rad_client->community);
if (groupdata == NULL) {
radlog(L_AUTH, "rlm_opendirectory: The group \"%s\" does not exist on this system.", rad_client->community);
return RLM_MODULE_FAIL;
}
err = mbr_gid_to_uuid(groupdata->gr_gid, guid_nasgroup);
if (err != 0) {
radlog(L_AUTH, "rlm_opendirectory: The group \"%s\" does not have a GUID.", rad_client->community);
return RLM_MODULE_FAIL;
}
}
}
else
#endif
{
if (rad_client == NULL) {
RDEBUG("The client record could not be found for host %s.",
ip_ntoh(&request->packet->src_ipaddr,
host_ipaddr, sizeof(host_ipaddr)));
}
else {
RDEBUG("The host %s does not have an access group.",
ip_ntoh(&request->packet->src_ipaddr,
host_ipaddr, sizeof(host_ipaddr)));
}
}
uuid_clear(uuid);
ODNodeRef searchNode = ODNodeCreateWithName(kCFAllocatorDefault, kODSessionDefault, CFSTR("/Search"), NULL);
if (!searchNode) {
RDEBUG2("Unable to open OD search node");
return RLM_MODULE_FAIL;
}
CFStringRef username = CFStringCreateWithCString(kCFAllocatorDefault,
request->username->vp_strvalue,
kCFStringEncodingUTF8);
ODRecordRef rec = od_find_rec(request, searchNode, username, request->username->vp_strvalue);
if (!rec) {
RDEBUG("User %s does not exist in OD", request->username->vp_strvalue);
} else {
RDEBUG("User %s exists in OD", request->username->vp_strvalue);
CFArrayRef vals = ODRecordCopyValues(rec, kODAttributeTypeGUID, NULL);
if (!vals || CFArrayGetCount(vals) == 0) {
RDEBUG("Could not find GUID for user %s", request->username->vp_strvalue);
} else {
CFTypeRef user_guid = CFArrayGetValueAtIndex(vals, 0);
if (CFGetTypeID(user_guid) == CFStringGetTypeID()) {
size_t len = CFStringGetLength(user_guid) + 1;
char* user_guid_str = malloc(len);
if (user_guid_str) {
CFStringGetCString(user_guid, user_guid_str, len, kCFStringEncodingUTF8);
uuid_parse(user_guid_str, uuid);
free(user_guid_str);
}
}
}
if (vals) CFRelease(vals);
CFRelease(rec);
}
if (username) CFRelease(username);
CFRelease(searchNode);
if (uuid_is_null(uuid)) {
radius_pairmake(request, &request->packet->vps,
"Module-Failure-Message", "Could not get the user's uuid", T_OP_EQ);
return RLM_MODULE_NOTFOUND;
}
if (!uuid_is_null(guid_sacl)) {
err = mbr_check_service_membership(uuid, kRadiusServiceName, &ismember);
if (err != 0) {
radius_pairmake(request, &request->packet->vps,
"Module-Failure-Message", "Failed to check group membership", T_OP_EQ);
return RLM_MODULE_FAIL;
}
if (ismember == 0) {
RDEBUG("User %s is not a member of the RADUIS SACL", request->username->vp_strvalue);
radius_pairmake(request, &request->packet->vps,
"Module-Failure-Message", "User is not authorized", T_OP_EQ);
return RLM_MODULE_REJECT;
}
RDEBUG("User %s is a member of the RADUIS SACL", request->username->vp_strvalue);
}
if (!uuid_is_null(guid_nasgroup)) {
err = mbr_check_membership_refresh(uuid, guid_nasgroup, &ismember);
if (err != 0) {
radius_pairmake(request, &request->packet->vps,
"Module-Failure-Message", "Failed to check group membership", T_OP_EQ);
return RLM_MODULE_FAIL;
}
if (ismember == 0) {
RDEBUG("User %s is not a member of the host access group", request->username->vp_strvalue);
radius_pairmake(request, &request->packet->vps,
"Module-Failure-Message", "User is not authorized", T_OP_EQ);
return RLM_MODULE_REJECT;
}
RDEBUG("User %s is a member of the hostaccess group", request->username->vp_strvalue);
}
if (uuid_is_null(guid_sacl) && uuid_is_null(guid_nasgroup)) {
RDEBUG("no access control groups, all OD users allowed.");
}
if (pairfind(request->config_items, PW_AUTH_TYPE) == NULL) {
pairadd(&request->config_items, pairmake("Auth-Type", kAuthType, T_OP_EQ));
RDEBUG("Setting Auth-Type = %s", kAuthType);
}
return RLM_MODULE_OK;
}
module_t rlm_opendirectory = {
RLM_MODULE_INIT,
"opendirectory",
RLM_TYPE_THREAD_SAFE,
NULL,
NULL,
{
od_authenticate,
od_authorize,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL
},
};