db24744039c56fb7b9a16fc9eff40fc396a834dd
[freeradius.git] / src / modules / rlm_opendirectory / rlm_opendirectory.c
1 /*
2  * rlm_opendirectory.c
3  *              authentication: Apple Open Directory authentication
4  *              authorization:  enforces ACLs
5  *
6  * Version:     $Id$
7  *
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.
11  *
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.
16  *
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
20  *
21  * Copyright 2007 Apple Inc.
22  */
23
24 /*
25  *      For a typical Makefile, add linker flag like this:
26  *      LDFLAGS = -framework DirectoryService
27  */
28
29 #include <freeradius-devel/radiusd.h>
30 #include <freeradius-devel/modules.h>
31
32 #include <ctype.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <grp.h>
36 #include <pwd.h>
37 #include <sys/types.h>
38 #include <sys/stat.h>
39
40 #include <DirectoryService/DirectoryService.h>
41 #include <membership.h>
42
43 #if HAVE_APPLE_SPI
44 #include <membershipPriv.h>
45 #else
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);
48 #endif
49
50 /* RADIUS service ACL constants */
51 #define kRadiusSACLName         "com.apple.access_radius"
52 #define kRadiusServiceName      "radius"
53
54 #define kAuthType           "opendirectory"
55
56 /*
57  *      od_check_passwd
58  *
59  *  Returns: ds err
60  */
61
62 static long od_check_passwd(const char *uname, const char *password)
63 {
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;
89         uint32_t                                uiCurr                          = 0;
90         uint32_t                                uiLen                           = 0;
91         uint32_t                                pwLen                           = 0;
92         
93         if (uname == NULL || password == NULL)
94                 return result;
95         
96         do
97         {               
98                 status = dsOpenDirService( &dsRef );
99                 if ( status != eDSNoErr )
100                         return result;
101                 
102                 tDataBuff = dsDataBufferAllocate( dsRef, 4096 );
103                 if (tDataBuff == NULL)
104                         break;
105                 
106                 /* find user on search node */
107                 status = dsFindDirNodes( dsRef, tDataBuff, NULL, eDSSearchNodeName, &nodeCount, &context );
108                 if (status != eDSNoErr || nodeCount < 1)
109                         break;
110                 
111                 status = dsGetDirNodeName( dsRef, tDataBuff, 1, &nodeName );
112                 if (status != eDSNoErr)
113                         break;
114                 
115                 status = dsOpenDirNode( dsRef, nodeName, &nodeRef );
116                 dsDataListDeallocate( dsRef, nodeName );
117                 free( nodeName );
118                 nodeName = NULL;
119                 if (status != eDSNoErr)
120                         break;
121
122                 pRecName = dsBuildListFromStrings( dsRef, uname, NULL );
123                 pRecType = dsBuildListFromStrings( dsRef, kDSStdRecordTypeUsers, kDSStdRecordTypeComputers, kDSStdRecordTypeMachines, NULL );
124                 pAttrType = dsBuildListFromStrings( dsRef, kDSNAttrMetaNodeLocation, kDSNAttrRecordName, kDSNAttrRecordType, NULL );
125                 
126                 recCount = 1;
127                 status = dsGetRecordList( nodeRef, tDataBuff, pRecName, eDSExact, pRecType,
128                                                                                                         pAttrType, 0, &recCount, &context );
129                 if ( status != eDSNoErr || recCount == 0 )
130                         break;
131                                 
132                 status = dsGetRecordEntry( nodeRef, tDataBuff, 1, &attrListRef, &pRecEntry );
133                 if ( status != eDSNoErr )
134                         break;
135                 
136                 for ( attrIndex = 1; (attrIndex <= pRecEntry->fRecordAttributeCount) && (status == eDSNoErr); attrIndex++ )
137                 {
138                         status = dsGetAttributeEntry( nodeRef, tDataBuff, attrListRef, attrIndex, &valueRef, &pAttrEntry );
139                         if ( status == eDSNoErr && pAttrEntry != NULL )
140                         {
141                                 if ( strcmp( pAttrEntry->fAttributeSignature.fBufferData, kDSNAttrMetaNodeLocation ) == 0 )
142                                 {
143                                         status = dsGetAttributeValue( nodeRef, tDataBuff, 1, valueRef, &pValueEntry );
144                                         if ( status == eDSNoErr && pValueEntry != NULL )
145                                         {
146                                                 pUserLocation = (char *) calloc( pValueEntry->fAttributeValueData.fBufferLength + 1, sizeof(char) );
147                                                 memcpy( pUserLocation, pValueEntry->fAttributeValueData.fBufferData, pValueEntry->fAttributeValueData.fBufferLength );
148                                         }
149                                 }
150                                 else
151                                 if ( strcmp( pAttrEntry->fAttributeSignature.fBufferData, kDSNAttrRecordName ) == 0 )
152                                 {
153                                         status = dsGetAttributeValue( nodeRef, tDataBuff, 1, valueRef, &pValueEntry );
154                                         if ( status == eDSNoErr && pValueEntry != NULL )
155                                         {
156                                                 pUserName = (char *) calloc( pValueEntry->fAttributeValueData.fBufferLength + 1, sizeof(char) );
157                                                 memcpy( pUserName, pValueEntry->fAttributeValueData.fBufferData, pValueEntry->fAttributeValueData.fBufferLength );
158                                         }
159                                 }
160                                 else
161                                 if ( strcmp( pAttrEntry->fAttributeSignature.fBufferData, kDSNAttrRecordType ) == 0 )
162                                 {
163                                         status = dsGetAttributeValue( nodeRef, tDataBuff, 1, valueRef, &pValueEntry );
164                                         if ( status == eDSNoErr && pValueEntry != NULL )
165                                         {
166                                                 pRecordType = pValueEntry;
167                                                 pValueEntry = NULL;
168                                         }
169                                 }
170                                 
171                                 if ( pValueEntry != NULL ) {
172                                         dsDeallocAttributeValueEntry( dsRef, pValueEntry );
173                                         pValueEntry = NULL;
174                                 }
175                                 if ( pAttrEntry != NULL ) {
176                                         dsDeallocAttributeEntry( dsRef, pAttrEntry );
177                                         pAttrEntry = NULL;
178                                 }
179                                 dsCloseAttributeValueList( valueRef );
180                                 valueRef = 0;
181                         }
182                 }
183                 
184                 pUserNode = dsBuildFromPath( dsRef, pUserLocation, "/" );
185                 status = dsOpenDirNode( dsRef, pUserNode, &userNodeRef );
186                 dsDataListDeallocate( dsRef, pUserNode );
187                 free( pUserNode );
188                 pUserNode = NULL;
189                 if ( status != eDSNoErr )
190                         break;
191                 
192                 pStepBuff = dsDataBufferAllocate( dsRef, 128 );
193                 
194                 pAuthType = dsDataNodeAllocateString( dsRef, kDSStdAuthNodeNativeClearTextOK );
195                 uiCurr = 0;
196                 
197                 /* User name */
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 );
202                 uiCurr += uiLen;
203                 
204                 /* pw */
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 );
209                 uiCurr += pwLen;
210                 
211                 tDataBuff->fBufferLength = uiCurr;
212                 
213                 result = dsDoDirNodeAuthOnRecordType( userNodeRef, pAuthType, 1, tDataBuff, pStepBuff, NULL, &pRecordType->fAttributeValueData );
214         }
215         while ( 0 );
216         
217         /* clean up */
218         if (pAuthType != NULL) {
219                 dsDataNodeDeAllocate( dsRef, pAuthType );
220                 pAuthType = NULL;
221         }
222         if (pRecordType != NULL) {
223                 dsDeallocAttributeValueEntry( dsRef, pRecordType );
224                 pRecordType = NULL;
225         }
226         if (tDataBuff != NULL) {
227                 bzero( tDataBuff, tDataBuff->fBufferSize );
228                 dsDataBufferDeAllocate( dsRef, tDataBuff );
229                 tDataBuff = NULL;
230         }
231         if (pStepBuff != NULL) {
232                 dsDataBufferDeAllocate( dsRef, pStepBuff );
233                 pStepBuff = NULL;
234         }
235         if (pUserLocation != NULL) {
236                 free(pUserLocation);
237                 pUserLocation = NULL;
238         }
239         if (pRecName != NULL) {
240                 dsDataListDeallocate( dsRef, pRecName );
241                 free( pRecName );
242                 pRecName = NULL;
243         }
244         if (pRecType != NULL) {
245                 dsDataListDeallocate( dsRef, pRecType );
246                 free( pRecType );
247                 pRecType = NULL;
248         }
249         if (pAttrType != NULL) {
250                 dsDataListDeallocate( dsRef, pAttrType );
251                 free( pAttrType );
252                 pAttrType = NULL;
253         }
254         if (nodeRef != 0) {
255                 dsCloseDirNode(nodeRef);
256                 nodeRef = 0;
257         }
258         if (dsRef != 0) {
259                 dsCloseDirService(dsRef);
260                 dsRef = 0;
261         }
262         
263         return result;
264 }
265
266
267 /*
268  *      Check the users password against the standard UNIX
269  *      password table.
270  */
271 int od_authenticate(void *instance, REQUEST *request)
272 {
273         char *name, *passwd;
274         int             ret;
275         long odResult = eDSAuthFailed;
276         
277         /*
278          *      We can only authenticate user requests which HAVE
279          *      a User-Name attribute.
280          */
281         if (!request->username) {
282                 radlog(L_AUTH, "rlm_opendirectory: Attribute \"User-Name\" is required for authentication.");
283                 return RLM_MODULE_INVALID;
284         }
285
286         /*
287          *      If the User-Password attribute is absent, is it MS-CHAPv2?
288          */
289         if (!request->password) {
290                 radlog(L_AUTH, "rlm_opendirectory: Attribute \"User-Password\" is required for authentication.");
291                 return RLM_MODULE_INVALID;
292         }
293         
294         /*
295          *  Ensure that we're being passed a plain-text password,
296          *  and not anything else.
297          */
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;
302         }
303         
304         name = (char *)request->username->vp_strvalue;
305         passwd = (char *)request->password->vp_strvalue;
306         
307         odResult = od_check_passwd(name, passwd);
308         switch(odResult)
309         {
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                 radlog(L_AUTH, "rlm_opendirectory: [%s]: invalid password", name);
333                 return ret;
334         }
335                 
336         return RLM_MODULE_OK;
337 }
338
339
340 /*
341  *      member of the radius group?
342  */
343 int od_authorize(void *instance, REQUEST *request)
344 {
345         char *name = NULL;
346         struct passwd *userdata = NULL;
347         struct group *groupdata = NULL;
348         int ismember = 0;
349         RADCLIENT *rad_client = NULL;
350         uuid_t uuid;
351         uuid_t guid_sacl;
352         uuid_t guid_nasgroup;
353         int err;
354         char host_ipaddr[128] = {0};
355         
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;
359         }
360         
361         /* resolve SACL */
362         uuid_clear(guid_sacl);
363         groupdata = getgrnam(kRadiusSACLName);
364         if (groupdata != NULL) {
365                 err = mbr_gid_to_uuid(groupdata->gr_gid, guid_sacl);
366                 if (err != 0) {
367                         radlog(L_ERR, "rlm_opendirectory: The group \"%s\" does not have a GUID.", kRadiusSACLName);
368                         return RLM_MODULE_FAIL;
369                 }               
370         }
371         else {
372                 radlog(L_DBG, "rlm_opendirectory: The SACL group \"%s\" does not exist on this system.", kRadiusSACLName);
373         }
374         
375         /* resolve client access list */
376         uuid_clear(guid_nasgroup);
377
378         rad_client = request->client;
379 #if 0
380         if (rad_client->community[0] != '\0' )
381         {
382                 /*
383                  *      The "community" can be a GUID (Globally Unique ID) or
384                  *      a group name
385                  */
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;
392                         }
393                         err = mbr_gid_to_uuid(groupdata->gr_gid, guid_nasgroup);
394                         if (err != 0) {
395                                 radlog(L_AUTH, "rlm_opendirectory: The group \"%s\" does not have a GUID.", rad_client->community);
396                                 return RLM_MODULE_FAIL;
397                         }
398                 }
399         }
400         else
401 #endif
402         {
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)));
407                 }
408                 else {
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)));
412                 }
413         }
414         
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);
420                 }
421                 return RLM_MODULE_OK;
422         }
423
424         /* resolve user */
425         uuid_clear(uuid);
426         name = (char *)request->username->vp_strvalue;
427         if (name != NULL) {
428                 userdata = getpwnam(name);
429                 if (userdata != NULL) {
430                         err = mbr_uid_to_uuid(userdata->pw_uid, uuid);
431                         if (err != 0)
432                                 uuid_clear(uuid);
433                 }
434         }
435         
436         if (uuid_is_null(uuid)) {
437                 radlog(L_AUTH, "rlm_opendirectory: Could not get the user's uuid.");
438                 return RLM_MODULE_NOTFOUND;
439         }
440         
441         if (!uuid_is_null(guid_sacl)) {
442                 err = mbr_check_service_membership(uuid, kRadiusServiceName, &ismember);
443                 if (err != 0) {
444                         radlog(L_AUTH, "rlm_opendirectory: Failed to check group membership.");
445                         return RLM_MODULE_FAIL;
446                 }
447                 
448                 if (ismember == 0) {
449                         radlog(L_AUTH, "rlm_opendirectory: User <%s> is not authorized.", name ? name : "unknown");
450                         return RLM_MODULE_USERLOCK;
451                 }
452         }
453         
454         if (!uuid_is_null(guid_nasgroup)) {
455                 err = mbr_check_membership_refresh(uuid, guid_nasgroup, &ismember);
456                 if (err != 0) {
457                         radlog(L_AUTH, "rlm_opendirectory: Failed to check group membership.");
458                         return RLM_MODULE_FAIL;
459                 }
460                 
461                 if (ismember == 0) {
462                         radlog(L_AUTH, "rlm_opendirectory: User <%s> is not authorized.", name ? name : "unknown");
463                         return RLM_MODULE_USERLOCK;
464                 }
465         }
466         
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);
471         }
472         return RLM_MODULE_OK;
473 }
474
475
476 /* globally exported name */
477 module_t rlm_opendirectory = {
478         RLM_MODULE_INIT,
479         "opendirectory",
480         RLM_TYPE_THREAD_SAFE,   /* type */
481         NULL,                   /* instantiation */
482         NULL,                   /* detach */
483         {
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 */
491                 NULL            /* post-auth */
492         },
493 };