diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 14:11:00 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 14:11:00 +0000 |
commit | af754e596a8dbb05ed8580c342e7fe02e08b28e0 (patch) | |
tree | b2f334c2b55ede42081aa6710a72da784547d8ea /src/modules/rlm_opendirectory/rlm_opendirectory.c | |
parent | Initial commit. (diff) | |
download | freeradius-b95e0cd7685e6bbc2465b1f4efe1884df2f4ef01.tar.xz freeradius-b95e0cd7685e6bbc2465b1f4efe1884df2f4ef01.zip |
Adding upstream version 3.2.3+dfsg.upstream/3.2.3+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/modules/rlm_opendirectory/rlm_opendirectory.c')
-rw-r--r-- | src/modules/rlm_opendirectory/rlm_opendirectory.c | 483 |
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 + }, +}; |