All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
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: 2494d22f29b6760cffeae2b6fbe76111e1b59ad9 $
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/radiusd.h>
33 #include <freeradius-devel/modules.h>
34 #include <freeradius-devel/rad_assert.h>
35 
36 #include <ctype.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <grp.h>
40 #include <pwd.h>
41 #include <sys/types.h>
42 #include <sys/stat.h>
43 
44 #include <DirectoryService/DirectoryService.h>
45 #include <membership.h>
46 
47 #ifndef HAVE_DECL_MBR_CHECK_SERVICE_MEMBERSHIP
48 int mbr_check_service_membership(uuid_t const user, char const *servicename, int *ismember);
49 #endif
50 #ifndef HAVE_DECL_MBR_CHECK_MEMBERSHIP_REFRESH
51 int 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 
58 #define kAuthType "opendirectory"
59 
60 /*
61  * od_check_passwd
62  *
63  * Returns: ds err
64  */
65 
66 static long od_check_passwd(REQUEST *request, char const *uname, char const *password)
67 {
68  long result = eDSAuthFailed;
69  tDirReference dsRef = 0;
70  tDataBuffer *tDataBuff;
71  tDirNodeReference nodeRef = 0;
72  long status = eDSNoErr;
73  tContextData context = 0;
74  uint32_t nodeCount = 0;
75  uint32_t attrIndex = 0;
76  tDataList *nodeName = NULL;
77  tAttributeEntryPtr pAttrEntry = NULL;
78  tDataList *pRecName = NULL;
79  tDataList *pRecType = NULL;
80  tDataList *pAttrType = NULL;
81  uint32_t recCount = 0;
82  tRecordEntry *pRecEntry = NULL;
83  tAttributeListRef attrListRef = 0;
84  char *pUserLocation = NULL;
85  char *pUserName = NULL;
86  tAttributeValueListRef valueRef = 0;
87  tAttributeValueEntry *pValueEntry = NULL;
88  tDataList *pUserNode = NULL;
89  tDirNodeReference userNodeRef = 0;
90  tDataBuffer *pStepBuff = NULL;
91  tDataNode *pAuthType = NULL;
92  tAttributeValueEntry *pRecordType = NULL;
93  uint32_t uiCurr = 0;
94  uint32_t uiLen = 0;
95  uint32_t pwLen = 0;
96 
97  if (!uname || !password)
98  return result;
99 
100  do
101  {
102  status = dsOpenDirService( &dsRef );
103  if ( status != eDSNoErr )
104  return result;
105 
106  tDataBuff = dsDataBufferAllocate( dsRef, 4096 );
107  if (!tDataBuff)
108  break;
109 
110  /* find user on search node */
111  status = dsFindDirNodes( dsRef, tDataBuff, NULL, eDSSearchNodeName, &nodeCount, &context );
112  if (status != eDSNoErr || nodeCount < 1)
113  break;
114 
115  status = dsGetDirNodeName( dsRef, tDataBuff, 1, &nodeName );
116  if (status != eDSNoErr)
117  break;
118 
119  status = dsOpenDirNode( dsRef, nodeName, &nodeRef );
120  dsDataListDeallocate( dsRef, nodeName );
121  free( nodeName );
122  nodeName = NULL;
123  if (status != eDSNoErr)
124  break;
125 
126  pRecName = dsBuildListFromStrings( dsRef, uname, NULL );
127  pRecType = dsBuildListFromStrings( dsRef, kDSStdRecordTypeUsers, kDSStdRecordTypeComputers, kDSStdRecordTypeMachines, NULL );
128  pAttrType = dsBuildListFromStrings( dsRef, kDSNAttrMetaNodeLocation, kDSNAttrRecordName, kDSNAttrRecordType, NULL );
129 
130  recCount = 1;
131  status = dsGetRecordList( nodeRef, tDataBuff, pRecName, eDSExact, pRecType,
132  pAttrType, 0, &recCount, &context );
133  if ( status != eDSNoErr || recCount == 0 )
134  break;
135 
136  status = dsGetRecordEntry( nodeRef, tDataBuff, 1, &attrListRef, &pRecEntry );
137  if ( status != eDSNoErr )
138  break;
139 
140  for ( attrIndex = 1; (attrIndex <= pRecEntry->fRecordAttributeCount) && (status == eDSNoErr); attrIndex++ )
141  {
142  status = dsGetAttributeEntry( nodeRef, tDataBuff, attrListRef, attrIndex, &valueRef, &pAttrEntry );
143  if ( status == eDSNoErr && pAttrEntry != NULL )
144  {
145  if ( strcmp( pAttrEntry->fAttributeSignature.fBufferData, kDSNAttrMetaNodeLocation ) == 0 )
146  {
147  status = dsGetAttributeValue( nodeRef, tDataBuff, 1, valueRef, &pValueEntry );
148  if ( status == eDSNoErr && pValueEntry != NULL )
149  {
150  pUserLocation = talloc_zero_array(request, char, pValueEntry->fAttributeValueData.fBufferLength + 1);
151  memcpy( pUserLocation, pValueEntry->fAttributeValueData.fBufferData, pValueEntry->fAttributeValueData.fBufferLength );
152  }
153  }
154  else
155  if ( strcmp( pAttrEntry->fAttributeSignature.fBufferData, kDSNAttrRecordName ) == 0 )
156  {
157  status = dsGetAttributeValue( nodeRef, tDataBuff, 1, valueRef, &pValueEntry );
158  if ( status == eDSNoErr && pValueEntry != NULL )
159  {
160  pUserName = talloc_zero_array(request, char, pValueEntry->fAttributeValueData.fBufferLength + 1);
161  memcpy( pUserName, pValueEntry->fAttributeValueData.fBufferData, pValueEntry->fAttributeValueData.fBufferLength );
162  }
163  }
164  else
165  if ( strcmp( pAttrEntry->fAttributeSignature.fBufferData, kDSNAttrRecordType ) == 0 )
166  {
167  status = dsGetAttributeValue( nodeRef, tDataBuff, 1, valueRef, &pValueEntry );
168  if ( status == eDSNoErr && pValueEntry != NULL )
169  {
170  pRecordType = pValueEntry;
171  pValueEntry = NULL;
172  }
173  }
174 
175  if ( pValueEntry != NULL ) {
176  dsDeallocAttributeValueEntry( dsRef, pValueEntry );
177  pValueEntry = NULL;
178  }
179  if ( pAttrEntry != NULL ) {
180  dsDeallocAttributeEntry( dsRef, pAttrEntry );
181  pAttrEntry = NULL;
182  }
183  dsCloseAttributeValueList( valueRef );
184  valueRef = 0;
185  }
186  }
187 
188  pUserNode = dsBuildFromPath( dsRef, pUserLocation, "/" );
189  status = dsOpenDirNode( dsRef, pUserNode, &userNodeRef );
190  dsDataListDeallocate( dsRef, pUserNode );
191  free( pUserNode );
192  pUserNode = NULL;
193  if ( status != eDSNoErr )
194  break;
195 
196  pStepBuff = dsDataBufferAllocate( dsRef, 128 );
197 
198  pAuthType = dsDataNodeAllocateString( dsRef, kDSStdAuthNodeNativeClearTextOK );
199  uiCurr = 0;
200 
201  if (!pUserName) {
202  RDEBUG("Failed to find user name");
203  break;
204  }
205 
206  /* User name */
207  uiLen = (uint32_t)strlen( pUserName );
208  memcpy( &(tDataBuff->fBufferData[ uiCurr ]), &uiLen, sizeof(uiLen) );
209  uiCurr += (uint32_t)sizeof( uiLen );
210  memcpy( &(tDataBuff->fBufferData[ uiCurr ]), pUserName, uiLen );
211  uiCurr += uiLen;
212 
213  /* pw */
214  pwLen = (uint32_t)strlen( password );
215  memcpy( &(tDataBuff->fBufferData[ uiCurr ]), &pwLen, sizeof(pwLen) );
216  uiCurr += (uint32_t)sizeof( pwLen );
217  memcpy( &(tDataBuff->fBufferData[ uiCurr ]), password, pwLen );
218  uiCurr += pwLen;
219 
220  tDataBuff->fBufferLength = uiCurr;
221 
222  result = dsDoDirNodeAuthOnRecordType( userNodeRef, pAuthType, 1, tDataBuff, pStepBuff, NULL, &pRecordType->fAttributeValueData );
223  }
224  while ( 0 );
225 
226  /* clean up */
227  if (pAuthType != NULL) {
228  dsDataNodeDeAllocate( dsRef, pAuthType );
229  pAuthType = NULL;
230  }
231  if (pRecordType != NULL) {
232  dsDeallocAttributeValueEntry( dsRef, pRecordType );
233  pRecordType = NULL;
234  }
235  if (tDataBuff != NULL) {
236  bzero( tDataBuff, tDataBuff->fBufferSize );
237  dsDataBufferDeAllocate( dsRef, tDataBuff );
238  tDataBuff = NULL;
239  }
240  if (pStepBuff != NULL) {
241  dsDataBufferDeAllocate( dsRef, pStepBuff );
242  pStepBuff = NULL;
243  }
244  if (pUserLocation != NULL) {
245  talloc_free(pUserLocation);
246  pUserLocation = NULL;
247  }
248  if (pUserName != NULL) {
249  talloc_free(pUserName);
250  pUserName = NULL;
251  }
252  if (pRecName != NULL) {
253  dsDataListDeallocate( dsRef, pRecName );
254  free( pRecName );
255  pRecName = NULL;
256  }
257  if (pRecType != NULL) {
258  dsDataListDeallocate( dsRef, pRecType );
259  free( pRecType );
260  pRecType = NULL;
261  }
262  if (pAttrType != NULL) {
263  dsDataListDeallocate( dsRef, pAttrType );
264  free( pAttrType );
265  pAttrType = NULL;
266  }
267  if (nodeRef != 0) {
268  dsCloseDirNode(nodeRef);
269  nodeRef = 0;
270  }
271  if (dsRef != 0) {
272  dsCloseDirService(dsRef);
273  dsRef = 0;
274  }
275 
276  return result;
277 }
278 
279 
280 /*
281  * Check the users password against the standard UNIX
282  * password table.
283  */
284 static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(UNUSED void *instance, REQUEST *request)
285 {
286  int ret;
287  long odResult = eDSAuthFailed;
288 
289  /*
290  * We can only authenticate user requests which HAVE
291  * a User-Name attribute.
292  */
293  if (!request->username) {
294  REDEBUG("You set 'Auth-Type = OpenDirectory' for a request that does not contain a User-Name attribute!");
295  return RLM_MODULE_INVALID;
296  }
297 
298  /*
299  * Can't do OpenDirectory if there's no password.
300  */
301  if (!request->password ||
302  (request->password->da->attr != PW_USER_PASSWORD)) {
303  REDEBUG("You set 'Auth-Type = OpenDirectory' for a request that does not contain a User-Password attribute!");
304  return RLM_MODULE_INVALID;
305  }
306 
307  odResult = od_check_passwd(request, request->username->vp_strvalue,
308  request->password->vp_strvalue);
309  switch (odResult) {
310  case eDSNoErr:
311  ret = RLM_MODULE_OK;
312  break;
313 
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;
324  break;
325 
326  default:
327  ret = RLM_MODULE_REJECT;
328  break;
329  }
330 
331  if (ret != RLM_MODULE_OK) {
332  RDEBUG("[%s]: Invalid password", request->username->vp_strvalue);
333  return ret;
334  }
335 
336  return RLM_MODULE_OK;
337 }
338 
339 
340 /*
341  * member of the radius group?
342  */
343 static rlm_rcode_t CC_HINT(nonnull) mod_authorize(UNUSED void *instance, REQUEST *request)
344 {
345  struct passwd *userdata = NULL;
346  int ismember = 0;
347  RADCLIENT *rad_client = NULL;
348  uuid_t uuid;
349  uuid_t guid_sacl;
350  uuid_t guid_nasgroup;
351  int err;
352  char host_ipaddr[128] = {0};
353  gid_t gid;
354 
355  if (!request->username) {
356  RDEBUG("OpenDirectory requires a User-Name attribute");
357  return RLM_MODULE_NOOP;
358  }
359 
360  /* resolve SACL */
361  uuid_clear(guid_sacl);
362 
363  if (rad_getgid(request, &gid, kRadiusSACLName) < 0) {
364  RDEBUG("The SACL group \"%s\" does not exist on this system.", kRadiusSACLName);
365  } else {
366  err = mbr_gid_to_uuid(gid, guid_sacl);
367  if (err != 0) {
368  ERROR("rlm_opendirectory: The group \"%s\" does not have a GUID.", kRadiusSACLName);
369  return RLM_MODULE_FAIL;
370  }
371  }
372 
373  /* resolve client access list */
374  uuid_clear(guid_nasgroup);
375 
376  rad_client = request->client;
377 #if 0
378  if (rad_client->community[0] != '\0' )
379  {
380  /*
381  * The "community" can be a GUID (Globally Unique ID) or
382  * a group name
383  */
384  if (uuid_parse(rad_client->community, guid_nasgroup) != 0) {
385  /* attempt to resolve the name */
386  groupdata = getgrnam(rad_client->community);
387  if (!groupdata) {
388  AUTH("rlm_opendirectory: The group \"%s\" does not exist on this system.", rad_client->community);
389  return RLM_MODULE_FAIL;
390  }
391  err = mbr_gid_to_uuid(groupdata->gr_gid, guid_nasgroup);
392  if (err != 0) {
393  AUTH("rlm_opendirectory: The group \"%s\" does not have a GUID.", rad_client->community);
394  return RLM_MODULE_FAIL;
395  }
396  }
397  }
398  else
399 #endif
400  {
401  if (!rad_client) {
402  RDEBUG("The client record could not be found for host %s.",
403  fr_inet_ntoh(&request->packet->src_ipaddr,
404  host_ipaddr, sizeof(host_ipaddr)));
405  }
406  else {
407  RDEBUG("The host %s does not have an access group.",
408  fr_inet_ntoh(&request->packet->src_ipaddr,
409  host_ipaddr, sizeof(host_ipaddr)));
410  }
411  }
412 
413  if (uuid_is_null(guid_sacl) && uuid_is_null(guid_nasgroup)) {
414  RDEBUG("no access control groups, all users allowed");
415  if (fr_pair_find_by_num(request->config, 0, PW_AUTH_TYPE, TAG_ANY) == NULL) {
416  pair_make_config("Auth-Type", kAuthType, T_OP_EQ);
417  RDEBUG("Setting Auth-Type = %s", kAuthType);
418  }
419  return RLM_MODULE_OK;
420  }
421 
422  /* resolve user */
423  uuid_clear(uuid);
424 
425  rad_getpwnam(request, &userdata, request->username->vp_strvalue);
426  if (userdata != NULL) {
427  err = mbr_uid_to_uuid(userdata->pw_uid, uuid);
428  if (err != 0)
429  uuid_clear(uuid);
430  }
431  talloc_free(userdata);
432 
433  if (uuid_is_null(uuid)) {
434  REDEBUG("Could not get the user's uuid");
435  return RLM_MODULE_NOTFOUND;
436  }
437 
438  if (!uuid_is_null(guid_sacl)) {
439  err = mbr_check_service_membership(uuid, kRadiusServiceName, &ismember);
440  if (err != 0) {
441  REDEBUG("Failed to check group membership");
442  return RLM_MODULE_FAIL;
443  }
444 
445  if (ismember == 0) {
446  REDEBUG("User is not authorized");
447  return RLM_MODULE_USERLOCK;
448  }
449  }
450 
451  if (!uuid_is_null(guid_nasgroup)) {
452  err = mbr_check_membership_refresh(uuid, guid_nasgroup, &ismember);
453  if (err != 0) {
454  REDEBUG("Failed to check group membership");
455  return RLM_MODULE_FAIL;
456  }
457 
458  if (ismember == 0) {
459  REDEBUG("User is not authorized");
460  return RLM_MODULE_USERLOCK;
461  }
462  }
463 
464  if (fr_pair_find_by_num(request->config, 0, PW_AUTH_TYPE, TAG_ANY) == NULL) {
465  pair_make_config("Auth-Type", kAuthType, T_OP_EQ);
466  RDEBUG("Setting Auth-Type = %s", kAuthType);
467  }
468 
469  return RLM_MODULE_OK;
470 }
471 
472 
473 /* globally exported name */
475 module_t rlm_opendirectory = {
477  .name = "opendirectory",
478  .type = RLM_TYPE_THREAD_SAFE,
479  .methods = {
482  },
483 };
#define kAuthType
#define AUTH(fmt,...)
Definition: log.h:139
The module is OK, continue.
Definition: radiusd.h:91
module_t rlm_opendirectory
Metadata exported by the module.
Definition: modules.h:134
static rlm_rcode_t mod_authorize(void *instance, REQUEST *request)
Handle authorization requests using Couchbase document data.
#define RLM_TYPE_THREAD_SAFE
Module is threadsafe.
Definition: modules.h:75
#define UNUSED
Definition: libradius.h:134
#define RLM_MODULE_INIT
Definition: modules.h:86
int mbr_check_membership_refresh(uuid_t const user, uuid_t group, int *ismember)
Definition: token.h:46
The module considers the request invalid.
Definition: radiusd.h:93
static rlm_rcode_t mod_authenticate(void *instance, REQUEST *request) CC_HINT(nonnull)
int rad_getgid(TALLOC_CTX *ctx, gid_t *out, char const *name)
Resolve a group name to a GID.
Definition: util.c:1248
Reject the request (user is locked out).
Definition: radiusd.h:94
#define pair_make_config(_a, _b, _c)
Definition: radiusd.h:547
Immediately reject the request.
Definition: radiusd.h:89
USES_APPLE_DEPRECATED_API int mbr_check_service_membership(uuid_t const user, char const *servicename, int *ismember)
int rad_getpwnam(TALLOC_CTX *ctx, struct passwd **out, char const *name)
Resolve a username to a passwd entry.
Definition: util.c:1051
0 methods index for authenticate section.
Definition: modules.h:41
enum rlm_rcodes rlm_rcode_t
Return codes indicating the result of the module call.
Module succeeded without doing anything.
Definition: radiusd.h:96
Describes a host allowed to send packets to the server.
Definition: clients.h:35
uint64_t magic
Used to validate module struct.
Definition: modules.h:135
Module failed, don't reply.
Definition: radiusd.h:90
#define TAG_ANY
Definition: pair.h:191
static rlm_rcode_t CC_HINT(nonnull)
#define REDEBUG(fmt,...)
Definition: log.h:254
VALUE_PAIR * fr_pair_find_by_num(VALUE_PAIR *head, unsigned int vendor, unsigned int attr, int8_t tag)
Find the pair with the matching attribute.
Definition: pair.c:639
#define kRadiusSACLName
1 methods index for authorize section.
Definition: modules.h:42
User not found.
Definition: radiusd.h:95
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:226
static long od_check_passwd(REQUEST *request, char const *uname, char const *password)
#define kRadiusServiceName
#define RDEBUG(fmt,...)
Definition: log.h:243
#define ERROR(fmt,...)
Definition: log.h:145
#define USES_APPLE_DEPRECATED_API
Definition: build.h:122