summaryrefslogtreecommitdiffstats
path: root/src/modules/rlm_opendirectory/rlm_opendirectory.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/modules/rlm_opendirectory/rlm_opendirectory.c')
-rw-r--r--src/modules/rlm_opendirectory/rlm_opendirectory.c483
1 files changed, 483 insertions, 0 deletions
diff --git a/src/modules/rlm_opendirectory/rlm_opendirectory.c b/src/modules/rlm_opendirectory/rlm_opendirectory.c
new file mode 100644
index 0000000..580c62b
--- /dev/null
+++ b/src/modules/rlm_opendirectory/rlm_opendirectory.c
@@ -0,0 +1,483 @@
+/*
+ * This program is is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 2 of the
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ * @file rlm_opendirectory.c
+ * @brief Allows authentication against OpenDirectory and enforces ACLS.
+ *
+ * authentication: Apple Open Directory authentication
+ * authorization: enforces ACLs
+ *
+ * @copyright 2007 Apple Inc.
+ */
+
+/*
+ * For a typical Makefile, add linker flag like this:
+ * LDFLAGS = -framework DirectoryService
+ */
+USES_APPLE_DEPRECATED_API
+#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 <DirectoryService/DirectoryService.h>
+#include <membership.h>
+
+#ifndef HAVE_DECL_MBR_CHECK_SERVICE_MEMBERSHIP
+int mbr_check_service_membership(uuid_t const user, char const *servicename, int *ismember);
+#endif
+#ifndef HAVE_DECL_MBR_CHECK_MEMBERSHIP_REFRESH
+int mbr_check_membership_refresh(uuid_t const user, uuid_t group, int *ismember);
+#endif
+
+/* RADIUS service ACL constants */
+#define kRadiusSACLName "com.apple.access_radius"
+#define kRadiusServiceName "radius"
+
+#define kAuthType "opendirectory"
+
+/*
+ * od_check_passwd
+ *
+ * Returns: ds err
+ */
+
+static long od_check_passwd(REQUEST *request, char const *uname, char const *password)
+{
+ long result = eDSAuthFailed;
+ tDirReference dsRef = 0;
+ tDataBuffer *tDataBuff;
+ tDirNodeReference nodeRef = 0;
+ long status = eDSNoErr;
+ tContextData context = 0;
+ uint32_t nodeCount = 0;
+ uint32_t attrIndex = 0;
+ tDataList *nodeName = NULL;
+ tAttributeEntryPtr pAttrEntry = NULL;
+ tDataList *pRecName = NULL;
+ tDataList *pRecType = NULL;
+ tDataList *pAttrType = NULL;
+ uint32_t recCount = 0;
+ tRecordEntry *pRecEntry = NULL;
+ tAttributeListRef attrListRef = 0;
+ char *pUserLocation = NULL;
+ char *pUserName = NULL;
+ tAttributeValueListRef valueRef = 0;
+ tAttributeValueEntry *pValueEntry = NULL;
+ tDataList *pUserNode = NULL;
+ tDirNodeReference userNodeRef = 0;
+ tDataBuffer *pStepBuff = NULL;
+ tDataNode *pAuthType = NULL;
+ tAttributeValueEntry *pRecordType = NULL;
+ uint32_t uiCurr = 0;
+ uint32_t uiLen = 0;
+ uint32_t pwLen = 0;
+
+ if (!uname || !password)
+ return result;
+
+ do
+ {
+ status = dsOpenDirService( &dsRef );
+ if ( status != eDSNoErr )
+ return result;
+
+ tDataBuff = dsDataBufferAllocate( dsRef, 4096 );
+ if (!tDataBuff)
+ break;
+
+ /* find user on search node */
+ status = dsFindDirNodes( dsRef, tDataBuff, NULL, eDSSearchNodeName, &nodeCount, &context );
+ if (status != eDSNoErr || nodeCount < 1)
+ break;
+
+ status = dsGetDirNodeName( dsRef, tDataBuff, 1, &nodeName );
+ if (status != eDSNoErr)
+ break;
+
+ status = dsOpenDirNode( dsRef, nodeName, &nodeRef );
+ dsDataListDeallocate( dsRef, nodeName );
+ free( nodeName );
+ nodeName = NULL;
+ if (status != eDSNoErr)
+ break;
+
+ pRecName = dsBuildListFromStrings( dsRef, uname, NULL );
+ pRecType = dsBuildListFromStrings( dsRef, kDSStdRecordTypeUsers, kDSStdRecordTypeComputers, kDSStdRecordTypeMachines, NULL );
+ pAttrType = dsBuildListFromStrings( dsRef, kDSNAttrMetaNodeLocation, kDSNAttrRecordName, kDSNAttrRecordType, NULL );
+
+ recCount = 1;
+ status = dsGetRecordList( nodeRef, tDataBuff, pRecName, eDSExact, pRecType,
+ pAttrType, 0, &recCount, &context );
+ if ( status != eDSNoErr || recCount == 0 )
+ break;
+
+ status = dsGetRecordEntry( nodeRef, tDataBuff, 1, &attrListRef, &pRecEntry );
+ if ( status != eDSNoErr )
+ break;
+
+ for ( attrIndex = 1; (attrIndex <= pRecEntry->fRecordAttributeCount) && (status == eDSNoErr); attrIndex++ )
+ {
+ status = dsGetAttributeEntry( nodeRef, tDataBuff, attrListRef, attrIndex, &valueRef, &pAttrEntry );
+ if ( status == eDSNoErr && pAttrEntry != NULL )
+ {
+ if ( strcmp( pAttrEntry->fAttributeSignature.fBufferData, kDSNAttrMetaNodeLocation ) == 0 )
+ {
+ status = dsGetAttributeValue( nodeRef, tDataBuff, 1, valueRef, &pValueEntry );
+ if ( status == eDSNoErr && pValueEntry != NULL )
+ {
+ pUserLocation = talloc_zero_array(request, char, pValueEntry->fAttributeValueData.fBufferLength + 1);
+ memcpy( pUserLocation, pValueEntry->fAttributeValueData.fBufferData, pValueEntry->fAttributeValueData.fBufferLength );
+ }
+ }
+ else
+ if ( strcmp( pAttrEntry->fAttributeSignature.fBufferData, kDSNAttrRecordName ) == 0 )
+ {
+ status = dsGetAttributeValue( nodeRef, tDataBuff, 1, valueRef, &pValueEntry );
+ if ( status == eDSNoErr && pValueEntry != NULL )
+ {
+ pUserName = talloc_zero_array(request, char, pValueEntry->fAttributeValueData.fBufferLength + 1);
+ memcpy( pUserName, pValueEntry->fAttributeValueData.fBufferData, pValueEntry->fAttributeValueData.fBufferLength );
+ }
+ }
+ else
+ if ( strcmp( pAttrEntry->fAttributeSignature.fBufferData, kDSNAttrRecordType ) == 0 )
+ {
+ status = dsGetAttributeValue( nodeRef, tDataBuff, 1, valueRef, &pValueEntry );
+ if ( status == eDSNoErr && pValueEntry != NULL )
+ {
+ pRecordType = pValueEntry;
+ pValueEntry = NULL;
+ }
+ }
+
+ if ( pValueEntry != NULL ) {
+ dsDeallocAttributeValueEntry( dsRef, pValueEntry );
+ pValueEntry = NULL;
+ }
+ if ( pAttrEntry != NULL ) {
+ dsDeallocAttributeEntry( dsRef, pAttrEntry );
+ pAttrEntry = NULL;
+ }
+ dsCloseAttributeValueList( valueRef );
+ valueRef = 0;
+ }
+ }
+
+ pUserNode = dsBuildFromPath( dsRef, pUserLocation, "/" );
+ status = dsOpenDirNode( dsRef, pUserNode, &userNodeRef );
+ dsDataListDeallocate( dsRef, pUserNode );
+ free( pUserNode );
+ pUserNode = NULL;
+ if ( status != eDSNoErr )
+ break;
+
+ pStepBuff = dsDataBufferAllocate( dsRef, 128 );
+
+ pAuthType = dsDataNodeAllocateString( dsRef, kDSStdAuthNodeNativeClearTextOK );
+ uiCurr = 0;
+
+ if (!pUserName) {
+ RDEBUG("Failed to find user name");
+ break;
+ }
+
+ /* User name */
+ uiLen = (uint32_t)strlen( pUserName );
+ memcpy( &(tDataBuff->fBufferData[ uiCurr ]), &uiLen, sizeof(uiLen) );
+ uiCurr += (uint32_t)sizeof( uiLen );
+ memcpy( &(tDataBuff->fBufferData[ uiCurr ]), pUserName, uiLen );
+ uiCurr += uiLen;
+
+ /* pw */
+ pwLen = (uint32_t)strlen( password );
+ memcpy( &(tDataBuff->fBufferData[ uiCurr ]), &pwLen, sizeof(pwLen) );
+ uiCurr += (uint32_t)sizeof( pwLen );
+ memcpy( &(tDataBuff->fBufferData[ uiCurr ]), password, pwLen );
+ uiCurr += pwLen;
+
+ tDataBuff->fBufferLength = uiCurr;
+
+ result = dsDoDirNodeAuthOnRecordType( userNodeRef, pAuthType, 1, tDataBuff, pStepBuff, NULL, &pRecordType->fAttributeValueData );
+ }
+ while ( 0 );
+
+ /* clean up */
+ if (pAuthType != NULL) {
+ dsDataNodeDeAllocate( dsRef, pAuthType );
+ pAuthType = NULL;
+ }
+ if (pRecordType != NULL) {
+ dsDeallocAttributeValueEntry( dsRef, pRecordType );
+ pRecordType = NULL;
+ }
+ if (tDataBuff != NULL) {
+ bzero( tDataBuff, tDataBuff->fBufferSize );
+ dsDataBufferDeAllocate( dsRef, tDataBuff );
+ tDataBuff = NULL;
+ }
+ if (pStepBuff != NULL) {
+ dsDataBufferDeAllocate( dsRef, pStepBuff );
+ pStepBuff = NULL;
+ }
+ if (pUserLocation != NULL) {
+ talloc_free(pUserLocation);
+ pUserLocation = NULL;
+ }
+ if (pUserName != NULL) {
+ talloc_free(pUserName);
+ pUserName = NULL;
+ }
+ if (pRecName != NULL) {
+ dsDataListDeallocate( dsRef, pRecName );
+ free( pRecName );
+ pRecName = NULL;
+ }
+ if (pRecType != NULL) {
+ dsDataListDeallocate( dsRef, pRecType );
+ free( pRecType );
+ pRecType = NULL;
+ }
+ if (pAttrType != NULL) {
+ dsDataListDeallocate( dsRef, pAttrType );
+ free( pAttrType );
+ pAttrType = NULL;
+ }
+ if (nodeRef != 0) {
+ dsCloseDirNode(nodeRef);
+ nodeRef = 0;
+ }
+ if (dsRef != 0) {
+ dsCloseDirService(dsRef);
+ dsRef = 0;
+ }
+
+ return result;
+}
+
+
+/*
+ * Check the users password against the standard UNIX
+ * password table.
+ */
+static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(UNUSED void *instance, REQUEST *request)
+{
+ int ret;
+ long odResult = eDSAuthFailed;
+
+ /*
+ * We can only authenticate user requests which HAVE
+ * a User-Name attribute.
+ */
+ if (!request->username) {
+ REDEBUG("You set 'Auth-Type = OpenDirectory' for a request that does not contain a User-Name attribute!");
+ return RLM_MODULE_INVALID;
+ }
+
+ /*
+ * Can't do OpenDirectory if there's no password.
+ */
+ if (!request->password ||
+ (request->password->da->attr != PW_USER_PASSWORD)) {
+ REDEBUG("You set 'Auth-Type = OpenDirectory' for a request that does not contain a User-Password attribute!");
+ return RLM_MODULE_INVALID;
+ }
+
+ odResult = od_check_passwd(request, request->username->vp_strvalue,
+ request->password->vp_strvalue);
+ switch (odResult) {
+ case eDSNoErr:
+ ret = RLM_MODULE_OK;
+ break;
+
+ case eDSAuthUnknownUser:
+ case eDSAuthInvalidUserName:
+ case eDSAuthNewPasswordRequired:
+ case eDSAuthPasswordExpired:
+ case eDSAuthAccountDisabled:
+ case eDSAuthAccountExpired:
+ case eDSAuthAccountInactive:
+ case eDSAuthInvalidLogonHours:
+ case eDSAuthInvalidComputer:
+ ret = RLM_MODULE_USERLOCK;
+ break;
+
+ default:
+ ret = RLM_MODULE_REJECT;
+ break;
+ }
+
+ if (ret != RLM_MODULE_OK) {
+ RDEBUG("[%s]: Invalid password", request->username->vp_strvalue);
+ return ret;
+ }
+
+ return RLM_MODULE_OK;
+}
+
+
+/*
+ * member of the radius group?
+ */
+static rlm_rcode_t CC_HINT(nonnull) mod_authorize(UNUSED void *instance, REQUEST *request)
+{
+ struct passwd *userdata = 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};
+ gid_t gid;
+
+ if (!request->username) {
+ RDEBUG("OpenDirectory requires a User-Name attribute");
+ return RLM_MODULE_NOOP;
+ }
+
+ /* resolve SACL */
+ uuid_clear(guid_sacl);
+
+ if (rad_getgid(request, &gid, kRadiusSACLName) < 0) {
+ RDEBUG("The SACL group \"%s\" does not exist on this system.", kRadiusSACLName);
+ } else {
+ err = mbr_gid_to_uuid(gid, guid_sacl);
+ if (err != 0) {
+ ERROR("rlm_opendirectory: The group \"%s\" does not have a GUID.", kRadiusSACLName);
+ return RLM_MODULE_FAIL;
+ }
+ }
+
+ /* resolve client access list */
+ uuid_clear(guid_nasgroup);
+
+ rad_client = request->client;
+#if 0
+ if (rad_client->community[0] != '\0' )
+ {
+ /*
+ * The "community" can be a GUID (Globally Unique ID) or
+ * a group name
+ */
+ if (uuid_parse(rad_client->community, guid_nasgroup) != 0) {
+ /* attempt to resolve the name */
+ groupdata = getgrnam(rad_client->community);
+ if (!groupdata) {
+ 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) {
+ AUTH("rlm_opendirectory: The group \"%s\" does not have a GUID.", rad_client->community);
+ return RLM_MODULE_FAIL;
+ }
+ }
+ }
+ else
+#endif
+ {
+ if (!rad_client) {
+ 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)));
+ }
+ }
+
+ if (uuid_is_null(guid_sacl) && uuid_is_null(guid_nasgroup)) {
+ RDEBUG("no access control groups, all users allowed");
+ if (fr_pair_find_by_num(request->config, PW_AUTH_TYPE, 0, TAG_ANY) == NULL) {
+ pair_make_config("Auth-Type", kAuthType, T_OP_EQ);
+ RDEBUG("Setting Auth-Type = %s", kAuthType);
+ }
+ return RLM_MODULE_OK;
+ }
+
+ /* resolve user */
+ uuid_clear(uuid);
+
+ rad_getpwnam(request, &userdata, request->username->vp_strvalue);
+ if (userdata != NULL) {
+ err = mbr_uid_to_uuid(userdata->pw_uid, uuid);
+ if (err != 0)
+ uuid_clear(uuid);
+ }
+ talloc_free(userdata);
+
+ if (uuid_is_null(uuid)) {
+ REDEBUG("Could not get the user's uuid");
+ return RLM_MODULE_NOTFOUND;
+ }
+
+ if (!uuid_is_null(guid_sacl)) {
+ err = mbr_check_service_membership(uuid, kRadiusServiceName, &ismember);
+ if (err != 0) {
+ REDEBUG("Failed to check group membership");
+ return RLM_MODULE_FAIL;
+ }
+
+ if (ismember == 0) {
+ REDEBUG("User is not authorized");
+ return RLM_MODULE_USERLOCK;
+ }
+ }
+
+ if (!uuid_is_null(guid_nasgroup)) {
+ err = mbr_check_membership_refresh(uuid, guid_nasgroup, &ismember);
+ if (err != 0) {
+ REDEBUG("Failed to check group membership");
+ return RLM_MODULE_FAIL;
+ }
+
+ if (ismember == 0) {
+ REDEBUG("User is not authorized");
+ return RLM_MODULE_USERLOCK;
+ }
+ }
+
+ if (fr_pair_find_by_num(request->config, PW_AUTH_TYPE, 0, TAG_ANY) == NULL) {
+ pair_make_config("Auth-Type", kAuthType, T_OP_EQ);
+ RDEBUG("Setting Auth-Type = %s", kAuthType);
+ }
+
+ return RLM_MODULE_OK;
+}
+
+
+/* globally exported name */
+extern module_t rlm_opendirectory;
+module_t rlm_opendirectory = {
+ .magic = RLM_MODULE_INIT,
+ .name = "opendirectory",
+ .type = RLM_TYPE_THREAD_SAFE,
+ .methods = {
+ [MOD_AUTHENTICATE] = mod_authenticate,
+ [MOD_AUTHORIZE] = mod_authorize
+ },
+};