The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
rlm_opendirectory.c
Go to the documentation of this file.
1/*
2 * This program is is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License, version 2 of the
4 * License as published by the Free Software Foundation.
5 *
6 * This program is distributed in the hope that it will be useful,
7 * but WITHOUT ANY WARRANTY; without even the implied warranty of
8 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 * GNU General Public License for more details.
10 *
11 * You should have received a copy of the GNU General Public License
12 * along with this program; if not, write to the Free Software
13 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
14 */
15
16/**
17 * $Id: f81079236c32d5264a78c884dbebe196a38b84a0 $
18 * @file rlm_opendirectory.c
19 * @brief Allows authentication against OpenDirectory and enforces ACLS.
20 *
21 * authentication: Apple Open Directory authentication
22 * authorization: enforces ACLs
23 *
24 * @copyright 2007 Apple Inc.
25 */
26
27/*
28 * For a typical Makefile, add linker flag like this:
29 * LDFLAGS = -framework DirectoryService
30 */
32#include <freeradius-devel/server/base.h>
33#include <freeradius-devel/server/module_rlm.h>
34#include <freeradius-devel/util/debug.h>
35#include <freeradius-devel/util/perm.h>
36
37#include <stdlib.h>
38#include <string.h>
39
40#include <DirectoryService/DirectoryService.h>
41#include <membership.h>
42
46
47#ifndef HAVE_DECL_MBR_CHECK_SERVICE_MEMBERSHIP
48int mbr_check_service_membership(uuid_t const user, char const *servicename, int *ismember);
49#endif
50#ifndef HAVE_DECL_MBR_CHECK_MEMBERSHIP_REFRESH
51int mbr_check_membership_refresh(uuid_t const user, uuid_t group, int *ismember);
52#endif
53
54/* RADIUS service ACL constants */
55#define kRadiusSACLName "com.apple.access_radius"
56#define kRadiusServiceName "radius"
57
59static fr_dict_t const *dict_radius;
60
63 { .out = &dict_freeradius, .proto = "freeradius" },
64 { .out = &dict_radius, .proto = "radius" },
66};
67
71
74 { .out = &attr_auth_type, .name = "Auth-Type", .type = FR_TYPE_UINT32, .dict = &dict_freeradius },
75 { .out = &attr_user_name, .name = "User-Name", .type = FR_TYPE_STRING, .dict = &dict_radius },
76 { .out = &attr_user_password, .name = "User-Password", .type = FR_TYPE_STRING, .dict = &dict_radius },
78};
79
80/*
81 * od_check_passwd
82 *
83 * Returns: ds err
84 */
85
86static long od_check_passwd(request_t *request, char const *uname, char const *password)
87{
88 long result = eDSAuthFailed;
89 tDirReference dsRef = 0;
90 tDataBuffer *tDataBuff;
91 tDirNodeReference nodeRef = 0;
92 long status = eDSNoErr;
93 tContextData context = 0;
94 uint32_t nodeCount = 0;
95 uint32_t attrIndex = 0;
96 tDataList *nodeName = NULL;
97 tAttributeEntryPtr pAttrEntry = NULL;
98 tDataList *pRecName = NULL;
99 tDataList *pRecType = NULL;
100 tDataList *pAttrType = NULL;
101 uint32_t recCount = 0;
102 tRecordEntry *pRecEntry = NULL;
103 tAttributeListRef attrListRef = 0;
104 char *pUserLocation = NULL;
105 char *pUserName = NULL;
106 tAttributeValueListRef valueRef = 0;
107 tAttributeValueEntry *pValueEntry = NULL;
108 tDataList *pUserNode = NULL;
109 tDirNodeReference userNodeRef = 0;
110 tDataBuffer *pStepBuff = NULL;
111 tDataNode *pAuthType = NULL;
112 tAttributeValueEntry *pRecordType = NULL;
113 uint32_t uiCurr = 0;
114 uint32_t uiLen = 0;
115 uint32_t pwLen = 0;
116
117 if (!uname || !password)
118 return result;
119
120 do
121 {
122 status = dsOpenDirService( &dsRef );
123 if ( status != eDSNoErr )
124 return result;
125
126 tDataBuff = dsDataBufferAllocate( dsRef, 4096 );
127 if (!tDataBuff)
128 break;
129
130 /* find user on search node */
131 status = dsFindDirNodes( dsRef, tDataBuff, NULL, eDSSearchNodeName, &nodeCount, &context );
132 if (status != eDSNoErr || nodeCount < 1)
133 break;
134
135 status = dsGetDirNodeName( dsRef, tDataBuff, 1, &nodeName );
136 if (status != eDSNoErr)
137 break;
138
139 status = dsOpenDirNode( dsRef, nodeName, &nodeRef );
140 dsDataListDeallocate( dsRef, nodeName );
141 free( nodeName );
142 nodeName = NULL;
143 if (status != eDSNoErr)
144 break;
145
146 pRecName = dsBuildListFromStrings( dsRef, uname, NULL );
147 pRecType = dsBuildListFromStrings( dsRef, kDSStdRecordTypeUsers, kDSStdRecordTypeComputers, kDSStdRecordTypeMachines, NULL );
148 pAttrType = dsBuildListFromStrings( dsRef, kDSNAttrMetaNodeLocation, kDSNAttrRecordName, kDSNAttrRecordType, NULL );
149
150 recCount = 1;
151 status = dsGetRecordList( nodeRef, tDataBuff, pRecName, eDSExact, pRecType,
152 pAttrType, 0, &recCount, &context );
153 if ( status != eDSNoErr || recCount == 0 )
154 break;
155
156 status = dsGetRecordEntry( nodeRef, tDataBuff, 1, &attrListRef, &pRecEntry );
157 if ( status != eDSNoErr )
158 break;
159
160 for ( attrIndex = 1; (attrIndex <= pRecEntry->fRecordAttributeCount) && (status == eDSNoErr); attrIndex++ )
161 {
162 status = dsGetAttributeEntry( nodeRef, tDataBuff, attrListRef, attrIndex, &valueRef, &pAttrEntry );
163 if ( status == eDSNoErr && pAttrEntry != NULL )
164 {
165 if ( strcmp( pAttrEntry->fAttributeSignature.fBufferData, kDSNAttrMetaNodeLocation ) == 0 )
166 {
167 status = dsGetAttributeValue( nodeRef, tDataBuff, 1, valueRef, &pValueEntry );
168 if ( status == eDSNoErr && pValueEntry != NULL )
169 {
170 pUserLocation = talloc_zero_array(request, char, pValueEntry->fAttributeValueData.fBufferLength + 1);
171 memcpy( pUserLocation, pValueEntry->fAttributeValueData.fBufferData, pValueEntry->fAttributeValueData.fBufferLength );
172 }
173 }
174 else
175 if ( strcmp( pAttrEntry->fAttributeSignature.fBufferData, kDSNAttrRecordName ) == 0 )
176 {
177 status = dsGetAttributeValue( nodeRef, tDataBuff, 1, valueRef, &pValueEntry );
178 if ( status == eDSNoErr && pValueEntry != NULL )
179 {
180 pUserName = talloc_zero_array(request, char, pValueEntry->fAttributeValueData.fBufferLength + 1);
181 memcpy( pUserName, pValueEntry->fAttributeValueData.fBufferData, pValueEntry->fAttributeValueData.fBufferLength );
182 }
183 }
184 else
185 if ( strcmp( pAttrEntry->fAttributeSignature.fBufferData, kDSNAttrRecordType ) == 0 )
186 {
187 status = dsGetAttributeValue( nodeRef, tDataBuff, 1, valueRef, &pValueEntry );
188 if ( status == eDSNoErr && pValueEntry != NULL )
189 {
190 pRecordType = pValueEntry;
191 pValueEntry = NULL;
192 }
193 }
194
195 if ( pValueEntry != NULL ) {
196 dsDeallocAttributeValueEntry( dsRef, pValueEntry );
197 pValueEntry = NULL;
198 }
199 if ( pAttrEntry != NULL ) {
200 dsDeallocAttributeEntry( dsRef, pAttrEntry );
201 pAttrEntry = NULL;
202 }
203 dsCloseAttributeValueList( valueRef );
204 valueRef = 0;
205 }
206 }
207
208 pUserNode = dsBuildFromPath( dsRef, pUserLocation, "/" );
209 status = dsOpenDirNode( dsRef, pUserNode, &userNodeRef );
210 dsDataListDeallocate( dsRef, pUserNode );
211 free( pUserNode );
212 pUserNode = NULL;
213 if ( status != eDSNoErr )
214 break;
215
216 pStepBuff = dsDataBufferAllocate( dsRef, 128 );
217
218 pAuthType = dsDataNodeAllocateString( dsRef, kDSStdAuthNodeNativeClearTextOK );
219 uiCurr = 0;
220
221 if (!pUserName) {
222 RDEBUG2("Failed to find user name");
223 break;
224 }
225
226 /* User name */
227 uiLen = (uint32_t)strlen( pUserName );
228 memcpy( &(tDataBuff->fBufferData[ uiCurr ]), &uiLen, sizeof(uiLen) );
229 uiCurr += (uint32_t)sizeof( uiLen );
230 memcpy( &(tDataBuff->fBufferData[ uiCurr ]), pUserName, uiLen );
231 uiCurr += uiLen;
232
233 /* pw */
234 pwLen = (uint32_t)strlen( password );
235 memcpy( &(tDataBuff->fBufferData[ uiCurr ]), &pwLen, sizeof(pwLen) );
236 uiCurr += (uint32_t)sizeof( pwLen );
237 memcpy( &(tDataBuff->fBufferData[ uiCurr ]), password, pwLen );
238 uiCurr += pwLen;
239
240 tDataBuff->fBufferLength = uiCurr;
241
242 result = dsDoDirNodeAuthOnRecordType( userNodeRef, pAuthType, 1, tDataBuff, pStepBuff, NULL, &pRecordType->fAttributeValueData );
243 }
244 while ( 0 );
245
246 /* clean up */
247 if (pAuthType != NULL) {
248 dsDataNodeDeAllocate( dsRef, pAuthType );
249 pAuthType = NULL;
250 }
251 if (pRecordType != NULL) {
252 dsDeallocAttributeValueEntry( dsRef, pRecordType );
253 pRecordType = NULL;
254 }
255 if (tDataBuff != NULL) {
256 bzero( tDataBuff, tDataBuff->fBufferSize );
257 dsDataBufferDeAllocate( dsRef, tDataBuff );
258 tDataBuff = NULL;
259 }
260 if (pStepBuff != NULL) {
261 dsDataBufferDeAllocate( dsRef, pStepBuff );
262 pStepBuff = NULL;
263 }
264 if (pUserLocation != NULL) {
265 talloc_free(pUserLocation);
266 pUserLocation = NULL;
267 }
268 if (pUserName != NULL) {
269 talloc_free(pUserName);
270 pUserName = NULL;
271 }
272 if (pRecName != NULL) {
273 dsDataListDeallocate( dsRef, pRecName );
274 free( pRecName );
275 pRecName = NULL;
276 }
277 if (pRecType != NULL) {
278 dsDataListDeallocate( dsRef, pRecType );
279 free( pRecType );
280 pRecType = NULL;
281 }
282 if (pAttrType != NULL) {
283 dsDataListDeallocate( dsRef, pAttrType );
284 free( pAttrType );
285 pAttrType = NULL;
286 }
287 if (nodeRef != 0) {
288 dsCloseDirNode(nodeRef);
289 nodeRef = 0;
290 }
291 if (dsRef != 0) {
292 dsCloseDirService(dsRef);
293 dsRef = 0;
294 }
295
296 return result;
297}
298
299
300/*
301 * Check the users password against the standard UNIX
302 * password table.
303 */
304static unlang_action_t CC_HINT(nonnull) mod_authenticate(unlang_result_t *p_result, UNUSED module_ctx_t const *mctx, request_t *request)
305{
306 int ret;
307 long odResult = eDSAuthFailed;
308 fr_pair_t *username, *password;
309
310 username = fr_pair_find_by_da(&request->request_pairs, NULL, attr_user_name);
311 password = fr_pair_find_by_da(&request->request_pairs, NULL, attr_user_password);
312
313 /*
314 * We can only authenticate user requests which HAVE
315 * a User-Name attribute.
316 */
317 if (!username) {
318 REDEBUG("Attribute \"User-Name\" is required for authentication");
320 }
321
322 if (!password) {
323 REDEBUG("Attribute \"User-Password\" is required for authentication");
325 }
326
327 /*
328 * Make sure the supplied password isn't empty
329 */
330 if (password->vp_length == 0) {
331 REDEBUG("User-Password must not be empty");
333 }
334
335 /*
336 * Log the password
337 */
338 if (RDEBUG_ENABLED3) {
339 RDEBUG("Login attempt with password \"%pV\"", &password->data);
340 } else {
341 RDEBUG2("Login attempt with password");
342 }
343
344 odResult = od_check_passwd(request, username->vp_strvalue,
345 password->vp_strvalue);
346 switch (odResult) {
347 case eDSNoErr:
348 ret = RLM_MODULE_OK;
349 break;
350
351 case eDSAuthUnknownUser:
352 case eDSAuthInvalidUserName:
353 case eDSAuthNewPasswordRequired:
354 case eDSAuthPasswordExpired:
355 case eDSAuthAccountDisabled:
356 case eDSAuthAccountExpired:
357 case eDSAuthAccountInactive:
358 case eDSAuthInvalidLogonHours:
359 case eDSAuthInvalidComputer:
361 break;
362
363 default:
364 ret = RLM_MODULE_REJECT;
365 break;
366 }
367
368 if (ret != RLM_MODULE_OK) {
369 RDEBUG2("Invalid password: %pV", &username->data);
371 }
372
374}
375
376
377/*
378 * member of the radius group?
379 */
380static unlang_action_t CC_HINT(nonnull) mod_authorize(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
381{
383 struct passwd *userdata = NULL;
384 int ismember = 0;
385 fr_client_t *client = NULL;
386 uuid_t uuid;
387 uuid_t guid_sacl;
388 uuid_t guid_nasgroup;
389 int err;
390 char host_ipaddr[128] = {0};
391 gid_t gid;
392 fr_pair_t *username;
393
394 /*
395 * We can only authenticate user requests which HAVE
396 * a User-Name attribute.
397 */
398 username = fr_pair_find_by_da(&request->request_pairs, NULL, attr_user_name);
399 if (!username) {
400 RDEBUG2("OpenDirectory requires a User-Name attribute");
402 }
403
404 /* resolve SACL */
405 uuid_clear(guid_sacl);
406
407 if (fr_perm_gid_from_str(request, &gid, kRadiusSACLName) < 0) {
408 RDEBUG2("The SACL group \"%s\" does not exist on this system", kRadiusSACLName);
409 } else {
410 err = mbr_gid_to_uuid(gid, guid_sacl);
411 if (err != 0) {
412 REDEBUG("The group \"%s\" does not have a GUID", kRadiusSACLName);
414 }
415 }
416
417 /* resolve client access list */
418 uuid_clear(guid_nasgroup);
419
420 client = client_from_request(request);
421#if 0
422 if (client->community[0] != '\0' ) {
423 /*
424 * The "community" can be a GUID (Globally Unique ID) or
425 * a group name
426 */
427 if (uuid_parse(client->community, guid_nasgroup) != 0) {
428 /* attempt to resolve the name */
429 groupdata = getgrnam(client->community);
430 if (!groupdata) {
431 REDEBUG("The group \"%s\" does not exist on this system", client->community);
433 }
434 err = mbr_gid_to_uuid(groupdata->gr_gid, guid_nasgroup);
435 if (err != 0) {
436 REDEBUG("The group \"%s\" does not have a GUID", client->community);
438 }
439 }
440 }
441 else
442#endif
443 {
444 if (!client) {
445 RDEBUG2("The client record could not be found for host %s",
446 fr_inet_ntoh(&request->packet->socket.inet.src_ipaddr, host_ipaddr, sizeof(host_ipaddr)));
447 } else {
448 RDEBUG2("The host %s does not have an access group",
449 fr_inet_ntoh(&request->packet->socket.inet.src_ipaddr, host_ipaddr, sizeof(host_ipaddr)));
450 }
451 }
452
453 if (uuid_is_null(guid_sacl) && uuid_is_null(guid_nasgroup)) {
454 RDEBUG2("No access control groups, all users allowed");
455 goto setup_auth_type;
456 }
457
458 /* resolve user */
459 uuid_clear(uuid);
460
461 fr_perm_getpwnam(request, &userdata, username->vp_strvalue);
462 if (userdata != NULL) {
463 err = mbr_uid_to_uuid(userdata->pw_uid, uuid);
464 if (err != 0)
465 uuid_clear(uuid);
466 }
467 talloc_free(userdata);
468
469 if (uuid_is_null(uuid)) {
470 REDEBUG("Could not get the user's uuid");
472 }
473
474 if (!uuid_is_null(guid_sacl)) {
476 if (err != 0) {
477 REDEBUG("Failed to check group membership");
479 }
480
481 if (ismember == 0) {
482 REDEBUG("User is not authorized");
484 }
485 }
486
487 if (!uuid_is_null(guid_nasgroup)) {
488 err = mbr_check_membership_refresh(uuid, guid_nasgroup, &ismember);
489 if (err != 0) {
490 REDEBUG("Failed to check group membership");
492 }
493
494 if (ismember == 0) {
495 REDEBUG("User is not authorized");
497 }
498 }
499
500setup_auth_type:
501 if (!inst->auth_type) {
502 WARN("No 'authenticate %s {...}' section or 'Auth-Type = %s' set. Cannot setup OpenDirectory authentication",
503 mctx->mi->name, mctx->mi->name);
505 }
506
508
510}
511
512static int mod_instantiate(module_inst_ctx_t const *mctx)
513{
514 rlm_opendirectory_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_opendirectory_t);
515
516 inst->auth_type = fr_dict_enum_by_name(attr_auth_type, mctx->mi->name, -1);
517 if (!inst->auth_type) {
518 WARN("Failed to find 'authenticate %s {...}' section. OpenDirectory authentication will likely not work",
519 mctx->mi->name);
520 }
521
522 return 0;
523}
524
525/* globally exported name */
528 .common = {
529 .magic = MODULE_MAGIC_INIT,
530 .name = "opendirectory",
532 .instantiate = mod_instantiate
533 },
534 .method_group = {
535 .bindings = (module_method_binding_t[]){
536 { .section = SECTION_NAME("authenticate", CF_IDENT_ANY), .method = mod_authenticate },
537 { .section = SECTION_NAME("recv", CF_IDENT_ANY), .method = mod_authorize },
539 }
540 }
541};
unlang_action_t
Returned by unlang_op_t calls, determine the next action of the interpreter.
Definition action.h:35
static int context
Definition radmin.c:69
#define USES_APPLE_DEPRECATED_API
Definition build.h:493
#define UNUSED
Definition build.h:336
#define CF_IDENT_ANY
Definition cf_util.h:75
static fr_slen_t err
Definition dict.h:882
fr_dict_attr_t const ** out
Where to write a pointer to the resolved fr_dict_attr_t.
Definition dict.h:292
fr_dict_t const ** out
Where to write a pointer to the loaded/resolved fr_dict_t.
Definition dict.h:305
#define DICT_AUTOLOAD_TERMINATOR
Definition dict.h:311
fr_dict_enum_value_t const * fr_dict_enum_by_name(fr_dict_attr_t const *da, char const *name, ssize_t len)
Definition dict_util.c:3701
Specifies an attribute which must be present for the module to function.
Definition dict.h:291
Specifies a dictionary which must be loaded/loadable for the module to function.
Definition dict.h:304
Value of an enumerated attribute.
Definition dict.h:253
#define MODULE_MAGIC_INIT
Stop people using different module/library/server versions together.
Definition dl_module.h:63
free(array)
talloc_free(hp)
char const * fr_inet_ntoh(fr_ipaddr_t const *src, char *out, size_t outlen)
Perform reverse resolution of an IP address.
Definition inet.c:356
Describes a host allowed to send packets to the server.
Definition client.h:80
#define RDEBUG_ENABLED3
True if request debug level 1-3 messages are enabled.
Definition log.h:347
@ FR_TYPE_STRING
String of printable characters.
@ FR_TYPE_UINT32
32 Bit unsigned integer.
unsigned int uint32_t
module_instance_t const * mi
Instance of the module being instantiated.
Definition module_ctx.h:42
module_instance_t * mi
Instance of the module being instantiated.
Definition module_ctx.h:51
Temporary structure to hold arguments for module calls.
Definition module_ctx.h:41
Temporary structure to hold arguments for instantiation calls.
Definition module_ctx.h:50
bool module_rlm_section_type_set(request_t *request, fr_dict_attr_t const *type_da, fr_dict_enum_value_t const *enumv)
Set the next section type if it's not already set.
Definition module_rlm.c:418
module_t common
Common fields presented by all modules.
Definition module_rlm.h:39
fr_pair_t * fr_pair_find_by_da(fr_pair_list_t const *list, fr_pair_t const *prev, fr_dict_attr_t const *da)
Find the first pair with a matching da.
Definition pair.c:707
int fr_perm_getpwnam(TALLOC_CTX *ctx, struct passwd **out, char const *name)
Resolve a username to a passwd entry.
Definition perm.c:268
int fr_perm_gid_from_str(TALLOC_CTX *ctx, gid_t *out, char const *name)
Resolve a group name to a GID.
Definition perm.c:475
#define REDEBUG(fmt,...)
#define RDEBUG2(fmt,...)
#define RDEBUG(fmt,...)
#define WARN(fmt,...)
#define RETURN_UNLANG_DISALLOW
Definition rcode.h:67
#define RETURN_UNLANG_INVALID
Definition rcode.h:66
#define RETURN_UNLANG_RCODE(_rcode)
Definition rcode.h:61
#define RETURN_UNLANG_NOTFOUND
Definition rcode.h:68
#define RETURN_UNLANG_FAIL
Definition rcode.h:63
#define RETURN_UNLANG_OK
Definition rcode.h:64
@ RLM_MODULE_OK
The module is OK, continue.
Definition rcode.h:49
@ RLM_MODULE_DISALLOW
Reject the request (user is locked out).
Definition rcode.h:52
@ RLM_MODULE_REJECT
Immediately reject the request.
Definition rcode.h:47
#define RETURN_UNLANG_NOOP
Definition rcode.h:69
static fr_dict_attr_t const * attr_user_password
static fr_dict_t const * dict_freeradius
fr_dict_enum_value_t const * auth_type
#define kRadiusSACLName
static fr_dict_t const * dict_radius
#define kRadiusServiceName
static fr_dict_attr_t const * attr_auth_type
static unlang_action_t mod_authorize(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
int mbr_check_membership_refresh(uuid_t const user, uuid_t group, int *ismember)
fr_dict_attr_autoload_t rlm_opendirectory_dict_attr[]
int mbr_check_service_membership(uuid_t const user, char const *servicename, int *ismember)
static fr_dict_attr_t const * attr_user_name
static long od_check_passwd(request_t *request, char const *uname, char const *password)
module_rlm_t rlm_opendirectory
static unlang_action_t mod_authenticate(unlang_result_t *p_result, UNUSED module_ctx_t const *mctx, request_t *request)
fr_dict_autoload_t rlm_opendirectory_dict[]
static int mod_instantiate(module_inst_ctx_t const *mctx)
#define SECTION_NAME(_name1, _name2)
Define a section name consisting of a verb and a noun.
Definition section.h:39
char const * name
Instance name e.g. user_database.
Definition module.h:357
size_t inst_size
Size of the module's instance data.
Definition module.h:212
void * data
Module's instance data.
Definition module.h:293
#define MODULE_BINDING_TERMINATOR
Terminate a module binding list.
Definition module.h:152
Named methods exported by a module.
Definition module.h:174
fr_client_t * client_from_request(request_t *request)
Search up a list of requests trying to locate one which has a client.
Definition client.c:1121
eap_aka_sim_process_conf_t * inst
Stores an attribute, a value and various bits of other data.
Definition pair.h:68
#define talloc_get_type_abort_const
Definition talloc.h:110
int nonnull(2, 5))