3 * authentication: Apple Open Directory authentication
4 * authorization: enforces ACLs
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License version 2 only, as published by
10 * the Free Software Foundation.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License version 2
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 * Copyright 2007 Apple Inc.
25 * For a typical Makefile, add linker flag like this:
26 * LDFLAGS = -framework DirectoryService
29 #include <freeradius-devel/radiusd.h>
30 #include <freeradius-devel/modules.h>
37 #include <sys/types.h>
40 #include <DirectoryService/DirectoryService.h>
41 #include <membership.h>
44 #include <membershipPriv.h>
46 int mbr_check_service_membership(const uuid_t user, const char *servicename, int *ismember);
47 int mbr_check_membership_refresh(const uuid_t user, uuid_t group, int *ismember);
50 /* RADIUS service ACL constants */
51 #define kRadiusSACLName "com.apple.access_radius"
52 #define kRadiusServiceName "radius"
54 #define kAuthType "opendirectory"
62 static long od_check_passwd(const char *uname, const char *password)
64 long result = eDSAuthFailed;
65 tDirReference dsRef = 0;
66 tDataBuffer *tDataBuff = NULL;
67 tDirNodeReference nodeRef = 0;
68 long status = eDSNoErr;
69 tContextData context = 0;
70 unsigned long nodeCount = 0;
71 uint32_t attrIndex = 0;
72 tDataList *nodeName = NULL;
73 tAttributeEntryPtr pAttrEntry = NULL;
74 tDataList *pRecName = NULL;
75 tDataList *pRecType = NULL;
76 tDataList *pAttrType = NULL;
77 unsigned long recCount = 0;
78 tRecordEntry *pRecEntry = NULL;
79 tAttributeListRef attrListRef = 0;
80 char *pUserLocation = NULL;
81 char *pUserName = NULL;
82 tAttributeValueListRef valueRef = 0;
83 tAttributeValueEntry *pValueEntry = NULL;
84 tDataList *pUserNode = NULL;
85 tDirNodeReference userNodeRef = 0;
86 tDataBuffer *pStepBuff = NULL;
87 tDataNode *pAuthType = NULL;
88 tAttributeValueEntry *pRecordType = NULL;
93 if (uname == NULL || password == NULL)
98 status = dsOpenDirService( &dsRef );
99 if ( status != eDSNoErr )
102 tDataBuff = dsDataBufferAllocate( dsRef, 4096 );
103 if (tDataBuff == NULL)
106 /* find user on search node */
107 status = dsFindDirNodes( dsRef, tDataBuff, NULL, eDSSearchNodeName, &nodeCount, &context );
108 if (status != eDSNoErr || nodeCount < 1)
111 status = dsGetDirNodeName( dsRef, tDataBuff, 1, &nodeName );
112 if (status != eDSNoErr)
115 status = dsOpenDirNode( dsRef, nodeName, &nodeRef );
116 dsDataListDeallocate( dsRef, nodeName );
119 if (status != eDSNoErr)
122 pRecName = dsBuildListFromStrings( dsRef, uname, NULL );
123 pRecType = dsBuildListFromStrings( dsRef, kDSStdRecordTypeUsers, kDSStdRecordTypeComputers, kDSStdRecordTypeMachines, NULL );
124 pAttrType = dsBuildListFromStrings( dsRef, kDSNAttrMetaNodeLocation, kDSNAttrRecordName, kDSNAttrRecordType, NULL );
127 status = dsGetRecordList( nodeRef, tDataBuff, pRecName, eDSExact, pRecType,
128 pAttrType, 0, &recCount, &context );
129 if ( status != eDSNoErr || recCount == 0 )
132 status = dsGetRecordEntry( nodeRef, tDataBuff, 1, &attrListRef, &pRecEntry );
133 if ( status != eDSNoErr )
136 for ( attrIndex = 1; (attrIndex <= pRecEntry->fRecordAttributeCount) && (status == eDSNoErr); attrIndex++ )
138 status = dsGetAttributeEntry( nodeRef, tDataBuff, attrListRef, attrIndex, &valueRef, &pAttrEntry );
139 if ( status == eDSNoErr && pAttrEntry != NULL )
141 if ( strcmp( pAttrEntry->fAttributeSignature.fBufferData, kDSNAttrMetaNodeLocation ) == 0 )
143 status = dsGetAttributeValue( nodeRef, tDataBuff, 1, valueRef, &pValueEntry );
144 if ( status == eDSNoErr && pValueEntry != NULL )
146 pUserLocation = (char *) calloc( pValueEntry->fAttributeValueData.fBufferLength + 1, sizeof(char) );
147 memcpy( pUserLocation, pValueEntry->fAttributeValueData.fBufferData, pValueEntry->fAttributeValueData.fBufferLength );
151 if ( strcmp( pAttrEntry->fAttributeSignature.fBufferData, kDSNAttrRecordName ) == 0 )
153 status = dsGetAttributeValue( nodeRef, tDataBuff, 1, valueRef, &pValueEntry );
154 if ( status == eDSNoErr && pValueEntry != NULL )
156 pUserName = (char *) calloc( pValueEntry->fAttributeValueData.fBufferLength + 1, sizeof(char) );
157 memcpy( pUserName, pValueEntry->fAttributeValueData.fBufferData, pValueEntry->fAttributeValueData.fBufferLength );
161 if ( strcmp( pAttrEntry->fAttributeSignature.fBufferData, kDSNAttrRecordType ) == 0 )
163 status = dsGetAttributeValue( nodeRef, tDataBuff, 1, valueRef, &pValueEntry );
164 if ( status == eDSNoErr && pValueEntry != NULL )
166 pRecordType = pValueEntry;
171 if ( pValueEntry != NULL ) {
172 dsDeallocAttributeValueEntry( dsRef, pValueEntry );
175 if ( pAttrEntry != NULL ) {
176 dsDeallocAttributeEntry( dsRef, pAttrEntry );
179 dsCloseAttributeValueList( valueRef );
184 pUserNode = dsBuildFromPath( dsRef, pUserLocation, "/" );
185 status = dsOpenDirNode( dsRef, pUserNode, &userNodeRef );
186 dsDataListDeallocate( dsRef, pUserNode );
189 if ( status != eDSNoErr )
192 pStepBuff = dsDataBufferAllocate( dsRef, 128 );
194 pAuthType = dsDataNodeAllocateString( dsRef, kDSStdAuthNodeNativeClearTextOK );
198 uiLen = (uint32_t)strlen( pUserName );
199 memcpy( &(tDataBuff->fBufferData[ uiCurr ]), &uiLen, sizeof(uiLen) );
200 uiCurr += (uint32_t)sizeof( uiLen );
201 memcpy( &(tDataBuff->fBufferData[ uiCurr ]), pUserName, uiLen );
205 pwLen = (uint32_t)strlen( password );
206 memcpy( &(tDataBuff->fBufferData[ uiCurr ]), &pwLen, sizeof(pwLen) );
207 uiCurr += (uint32_t)sizeof( pwLen );
208 memcpy( &(tDataBuff->fBufferData[ uiCurr ]), password, pwLen );
211 tDataBuff->fBufferLength = uiCurr;
213 result = dsDoDirNodeAuthOnRecordType( userNodeRef, pAuthType, 1, tDataBuff, pStepBuff, NULL, &pRecordType->fAttributeValueData );
218 if (pAuthType != NULL) {
219 dsDataNodeDeAllocate( dsRef, pAuthType );
222 if (pRecordType != NULL) {
223 dsDeallocAttributeValueEntry( dsRef, pRecordType );
226 if (tDataBuff != NULL) {
227 bzero( tDataBuff, tDataBuff->fBufferSize );
228 dsDataBufferDeAllocate( dsRef, tDataBuff );
231 if (pStepBuff != NULL) {
232 dsDataBufferDeAllocate( dsRef, pStepBuff );
235 if (pUserLocation != NULL) {
237 pUserLocation = NULL;
239 if (pRecName != NULL) {
240 dsDataListDeallocate( dsRef, pRecName );
244 if (pRecType != NULL) {
245 dsDataListDeallocate( dsRef, pRecType );
249 if (pAttrType != NULL) {
250 dsDataListDeallocate( dsRef, pAttrType );
255 dsCloseDirNode(nodeRef);
259 dsCloseDirService(dsRef);
268 * Check the users password against the standard UNIX
271 int od_authenticate(void *instance, REQUEST *request)
275 long odResult = eDSAuthFailed;
278 * We can only authenticate user requests which HAVE
279 * a User-Name attribute.
281 if (!request->username) {
282 radlog(L_AUTH, "rlm_opendirectory: Attribute \"User-Name\" is required for authentication.");
283 return RLM_MODULE_INVALID;
287 * If the User-Password attribute is absent, is it MS-CHAPv2?
289 if (!request->password) {
290 radlog(L_AUTH, "rlm_opendirectory: Attribute \"User-Password\" is required for authentication.");
291 return RLM_MODULE_INVALID;
295 * Ensure that we're being passed a plain-text password,
296 * and not anything else.
298 if (request->password->attribute != PW_PASSWORD) {
299 radlog(L_AUTH, "rlm_opendirectory: Attribute \"User-Password\" is required for authentication. Cannot use \"%s\".",
300 request->password->name);
301 return RLM_MODULE_INVALID;
304 name = (char *)request->username->vp_strvalue;
305 passwd = (char *)request->password->vp_strvalue;
307 odResult = od_check_passwd(name, passwd);
314 case eDSAuthUnknownUser:
315 case eDSAuthInvalidUserName:
316 case eDSAuthNewPasswordRequired:
317 case eDSAuthPasswordExpired:
318 case eDSAuthAccountDisabled:
319 case eDSAuthAccountExpired:
320 case eDSAuthAccountInactive:
321 case eDSAuthInvalidLogonHours:
322 case eDSAuthInvalidComputer:
323 ret = RLM_MODULE_USERLOCK;
327 ret = RLM_MODULE_REJECT;
331 if (ret != RLM_MODULE_OK) {
332 radlog(L_AUTH, "rlm_opendirectory: [%s]: invalid password", name);
336 return RLM_MODULE_OK;
341 * member of the radius group?
343 int od_authorize(void *instance, REQUEST *request)
346 struct passwd *userdata = NULL;
347 struct group *groupdata = NULL;
349 RADCLIENT *rad_client = NULL;
352 uuid_t guid_nasgroup;
354 char host_ipaddr[128] = {0};
356 if (request == NULL || request->username == NULL) {
357 radlog(L_AUTH, "rlm_opendirectory: Attribute \"User-Name\" is required for authorization.");
358 return RLM_MODULE_INVALID;
362 uuid_clear(guid_sacl);
363 groupdata = getgrnam(kRadiusSACLName);
364 if (groupdata != NULL) {
365 err = mbr_gid_to_uuid(groupdata->gr_gid, guid_sacl);
367 radlog(L_ERR, "rlm_opendirectory: The group \"%s\" does not have a GUID.", kRadiusSACLName);
368 return RLM_MODULE_FAIL;
372 radlog(L_DBG, "rlm_opendirectory: The SACL group \"%s\" does not exist on this system.", kRadiusSACLName);
375 /* resolve client access list */
376 uuid_clear(guid_nasgroup);
378 rad_client = request->client;
380 if (rad_client->community[0] != '\0' )
383 * The "community" can be a GUID (Globally Unique ID) or
386 if (uuid_parse(rad_client->community, guid_nasgroup) != 0) {
387 /* attempt to resolve the name */
388 groupdata = getgrnam(rad_client->community);
389 if (groupdata == NULL) {
390 radlog(L_AUTH, "rlm_opendirectory: The group \"%s\" does not exist on this system.", rad_client->community);
391 return RLM_MODULE_FAIL;
393 err = mbr_gid_to_uuid(groupdata->gr_gid, guid_nasgroup);
395 radlog(L_AUTH, "rlm_opendirectory: The group \"%s\" does not have a GUID.", rad_client->community);
396 return RLM_MODULE_FAIL;
403 if (rad_client == NULL) {
404 radlog(L_DBG, "rlm_opendirectory: The client record could not be found for host %s.",
405 ip_ntoh(&request->packet->src_ipaddr,
406 host_ipaddr, sizeof(host_ipaddr)));
409 radlog(L_DBG, "rlm_opendirectory: The host %s does not have an access group.",
410 ip_ntoh(&request->packet->src_ipaddr,
411 host_ipaddr, sizeof(host_ipaddr)));
415 if (uuid_is_null(guid_sacl) && uuid_is_null(guid_nasgroup)) {
416 radlog(L_DBG, "rlm_opendirectory: no access control groups, all users allowed.");
417 if (pairfind(request->config_items, PW_AUTH_TYPE, 0) == NULL) {
418 pairadd(&request->config_items, pairmake("Auth-Type", kAuthType, T_OP_EQ));
419 radlog(L_DBG, "rlm_opendirectory: Setting Auth-Type = %s", kAuthType);
421 return RLM_MODULE_OK;
426 name = (char *)request->username->vp_strvalue;
428 userdata = getpwnam(name);
429 if (userdata != NULL) {
430 err = mbr_uid_to_uuid(userdata->pw_uid, uuid);
436 if (uuid_is_null(uuid)) {
437 radlog(L_AUTH, "rlm_opendirectory: Could not get the user's uuid.");
438 return RLM_MODULE_NOTFOUND;
441 if (!uuid_is_null(guid_sacl)) {
442 err = mbr_check_service_membership(uuid, kRadiusServiceName, &ismember);
444 radlog(L_AUTH, "rlm_opendirectory: Failed to check group membership.");
445 return RLM_MODULE_FAIL;
449 radlog(L_AUTH, "rlm_opendirectory: User <%s> is not authorized.", name ? name : "unknown");
450 return RLM_MODULE_USERLOCK;
454 if (!uuid_is_null(guid_nasgroup)) {
455 err = mbr_check_membership_refresh(uuid, guid_nasgroup, &ismember);
457 radlog(L_AUTH, "rlm_opendirectory: Failed to check group membership.");
458 return RLM_MODULE_FAIL;
462 radlog(L_AUTH, "rlm_opendirectory: User <%s> is not authorized.", name ? name : "unknown");
463 return RLM_MODULE_USERLOCK;
467 radlog(L_AUTH, "rlm_opendirectory: User <%s> is authorized.", name ? name : "unknown");
468 if (pairfind(request->config_items, PW_AUTH_TYPE, 0) == NULL) {
469 pairadd(&request->config_items, pairmake("Auth-Type", kAuthType, T_OP_EQ));
470 radlog(L_DBG, "rlm_opendirectory: Setting Auth-Type = %s", kAuthType);
472 return RLM_MODULE_OK;
476 /* globally exported name */
477 module_t rlm_opendirectory = {
480 RLM_TYPE_THREAD_SAFE, /* type */
481 NULL, /* instantiation */
484 od_authenticate, /* authentication */
485 od_authorize, /* authorization */
486 NULL, /* preaccounting */
487 NULL, /* accounting */
488 NULL, /* checksimul */
489 NULL, /* pre-proxy */
490 NULL, /* post-proxy */