1#include <asl.h>
2#include <stdio.h>
3#include <unistd.h>
4#include <sys/stat.h>
5
6#include <security/pam_appl.h>
7#include <security/pam_modules.h>
8#include <security/openpam.h>
9
10
11#include <CoreFoundation/CoreFoundation.h>
12#include <DirectoryService/DirectoryService.h>
13#include <OpenDirectory/OpenDirectory.h>
14#include <OpenDirectory/OpenDirectoryPriv.h>
15#include <ServerInformation/ServerInformation.h>
16
17#include "Common.h"
18
19#if !defined(kDSValueAuthAuthorityDisabledUser)
20#define kDSValueAuthAuthorityDisabledUser ";DisabledUser;"
21#endif
22
23#define kOSInstall_mpkg "/System/Installation/Packages/OSInstall.mpkg"
24#define kOSInstall_collection "/System/Installation/Packages/OSInstall.collection"
25
26enum {
27	kWaitSeconds       =  1,
28	kMaxIterationCount = 30
29};
30
31int
32cfboolean_get_value(CFTypeRef p)
33{
34	int value = 0;
35	int retval = 0;
36
37	if (NULL == p) {
38		goto cleanup;
39	}
40
41	if (CFBooleanGetTypeID() == CFGetTypeID(p))
42		retval = CFBooleanGetValue(p);
43	else if (CFNumberGetTypeID() == CFGetTypeID(p) && CFNumberGetValue(p, kCFNumberIntType, &value))
44		retval = value;
45	else
46		retval = 0;
47
48cleanup:
49	if (PAM_SUCCESS != retval)
50		openpam_log(PAM_LOG_ERROR, "failed: %d", retval);
51
52	return retval;
53}
54
55int
56cstring_to_cfstring(const char *val, CFStringRef *buffer)
57{
58	int retval = PAM_BUF_ERR;
59
60	if (NULL == val || NULL == buffer) {
61		openpam_log(PAM_LOG_DEBUG, "NULL argument passed");
62		retval = PAM_SERVICE_ERR;
63		goto cleanup;
64	}
65
66	*buffer = CFStringCreateWithCString(kCFAllocatorDefault, val, kCFStringEncodingUTF8);
67	if (NULL == *buffer) {
68		openpam_log(PAM_LOG_DEBUG, "CFStringCreateWithCString() failed");
69		retval = PAM_BUF_ERR;
70		goto cleanup;
71	}
72
73	retval =  PAM_SUCCESS;
74
75cleanup:
76	if (PAM_SUCCESS != retval)
77		openpam_log(PAM_LOG_ERROR, "failed: %d", retval);
78
79	return retval;
80}
81
82int
83cfstring_to_cstring(const CFStringRef val, char **buffer)
84{
85	CFIndex maxlen = 0;
86	int retval = PAM_BUF_ERR;
87
88	if (NULL == val || NULL == buffer) {
89		openpam_log(PAM_LOG_DEBUG, "NULL argument passed");
90		retval = PAM_SERVICE_ERR;
91		goto cleanup;
92	}
93
94	maxlen = CFStringGetMaximumSizeForEncoding(CFStringGetLength(val), kCFStringEncodingUTF8);
95	*buffer = calloc(maxlen + 1, sizeof(char));
96	if (NULL == *buffer) {
97		openpam_log(PAM_LOG_DEBUG, "malloc() failed");
98		retval = PAM_BUF_ERR;
99		goto cleanup;
100	}
101
102	if (CFStringGetCString(val, *buffer, maxlen + 1, kCFStringEncodingUTF8)) {
103		retval =  PAM_SUCCESS;
104	} else {
105		openpam_log(PAM_LOG_DEBUG, "CFStringGetCString failed.");
106		free(*buffer);
107		*buffer = NULL;
108	}
109
110cleanup:
111	if (PAM_SUCCESS != retval)
112		openpam_log(PAM_LOG_ERROR, "failed: %d", retval);
113
114	return retval;
115}
116
117#ifdef OPENDIRECTORY_CACHE
118static void
119cleanup_cache(pam_handle_t *pamh, void *data, int pam_end_status)
120{
121    CFRelease((CFTypeRef)data);
122}
123#endif /* OPENDIRECTORY_CACHE */
124
125int
126od_record_create(pam_handle_t *pamh, ODRecordRef *record, CFStringRef cfUser)
127{
128	int retval = PAM_SERVICE_ERR;
129	const int attr_num = 5;
130
131	ODNodeRef cfNode = NULL;
132	CFErrorRef cferror = NULL;
133	CFArrayRef attrs = NULL;
134	CFTypeRef cfVals[attr_num];
135
136	if (NULL == record || NULL == cfUser) {
137		openpam_log(PAM_LOG_DEBUG, "NULL argument passed");
138		retval = PAM_SERVICE_ERR;
139		goto cleanup;
140	}
141
142#ifdef OPENDIRECTORY_CACHE
143#define CFRECORDNAME_CACHE "CFRecordName"
144#define CFRECORDNAME_NAME CFSTR("name")
145#define CFRECORDNAME_RECORD CFSTR("record")
146
147	CFDictionaryRef cfdict;
148	CFStringRef cachedUser;
149
150	if (pam_get_data(pamh, CFRECORDNAME_CACHE, (void *)&cfdict) == PAM_SUCCESS &&
151	    (CFGetTypeID(cfdict) == CFDictionaryGetTypeID()) &&
152	    (cachedUser = CFDictionaryGetValue(cfdict, CFRECORDNAME_NAME)) != NULL &&
153	    CFGetTypeID(cachedUser) == CFStringGetTypeID() &&
154	    CFStringCompare(cfUser, cachedUser, 0) == kCFCompareEqualTo &&
155	    (*record = (ODRecordRef)CFDictionaryGetValue(cfdict, CFRECORDNAME_RECORD)) != NULL)
156	{
157		CFRetain(*record);
158		return PAM_SUCCESS;
159	}
160#endif /* OPENDIRECTORY_CACHE */
161
162	int current_iterations = 0;
163
164	cfNode = ODNodeCreateWithNodeType(kCFAllocatorDefault,
165					  kODSessionDefault,
166					  eDSAuthenticationSearchNodeName,
167					  &cferror);
168	if (NULL == cfNode || NULL != cferror) {
169		openpam_log(PAM_LOG_ERROR, "ODNodeCreateWithNodeType failed.");
170		retval = PAM_SERVICE_ERR;
171		goto cleanup;
172	}
173
174	cfVals[0] = kODAttributeTypeAuthenticationAuthority;
175	cfVals[1] = kODAttributeTypeHomeDirectory;
176	cfVals[2] = kODAttributeTypeNFSHomeDirectory;
177	cfVals[3] = kODAttributeTypeUserShell;
178	cfVals[4] = kODAttributeTypeUniqueID;
179	attrs = CFArrayCreate(kCFAllocatorDefault, cfVals, (CFIndex)attr_num, &kCFTypeArrayCallBacks);
180	if (NULL == attrs) {
181		openpam_log(PAM_LOG_DEBUG, "CFArrayCreate() failed");
182		retval = PAM_BUF_ERR;
183		goto cleanup;
184	}
185
186	retval = PAM_SERVICE_ERR;
187	while (current_iterations <= kMaxIterationCount) {
188		CFIndex unreachable_count = 0;
189		CFArrayRef unreachable_nodes = ODNodeCopyUnreachableSubnodeNames(cfNode, NULL);
190		if (unreachable_nodes) {
191			unreachable_count = CFArrayGetCount(unreachable_nodes);
192			CFRelease(unreachable_nodes);
193			openpam_log(PAM_LOG_DEBUG, "%lu OD nodes unreachable.", unreachable_count);
194		}
195
196		*record = ODNodeCopyRecord(cfNode, kODRecordTypeUsers, cfUser, attrs, &cferror);
197		if (*record)
198			break;
199		if (0 == unreachable_count)
200			break;
201
202		openpam_log(PAM_LOG_DEBUG, "Waiting %d seconds for nodes to become reachable", kWaitSeconds);
203		sleep(kWaitSeconds);
204		++current_iterations;
205	}
206
207	if (*record) {
208#ifdef OPENDIRECTORY_CACHE
209		const void *keys[] = { CFRECORDNAME_NAME, CFRECORDNAME_RECORD };
210		const void *values[] = { cfUser, *record };
211		CFDictionaryRef dict;
212
213		dict = CFDictionaryCreate(NULL, keys, values, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
214		if (dict)
215			pam_set_data(pamh, CFRECORDNAME_CACHE, (void *)dict, cleanup_cache);
216#endif /* OPENDIRECTORY_CACHE */
217		retval = PAM_SUCCESS;
218	} else {
219		retval = PAM_USER_UNKNOWN;
220	}
221
222	if (current_iterations > 0) {
223		char *wt = NULL, *found = NULL;
224		int retval2;
225
226		if (*record)
227			found = "failure";
228		else
229			found = "success";
230
231		retval2 = asprintf(&wt, "%d", kWaitSeconds * current_iterations);
232		if (-1 == retval2) {
233			openpam_log(PAM_LOG_DEBUG, "Failed to convert current wait time to string.");
234			retval = PAM_BUF_ERR;
235			goto cleanup;
236		}
237
238
239		aslmsg m = asl_new(ASL_TYPE_MSG);
240		asl_set(m, "com.apple.message.domain", "com.apple.pam_modules.odAvailableWaitTime" );
241		asl_set(m, "com.apple.message.signature", "wait_time");
242		asl_set(m, "com.apple.message.value", wt);
243		asl_set(m, "com.apple.message.result", found);
244		asl_log(NULL, m, ASL_LEVEL_NOTICE, "OD nodes online delay: %ss. User record lookup: %s.", wt, found);
245		asl_free(m);
246		free(wt);
247	}
248
249cleanup:
250	if (NULL != attrs) {
251		CFRelease(attrs);
252	}
253
254	if (NULL != cferror) {
255		CFRelease(cferror);
256	}
257
258	if (NULL != cfNode) {
259		CFRelease(cfNode);
260	}
261
262	if (PAM_SUCCESS != retval) {
263		openpam_log(PAM_LOG_ERROR, "failed: %d", retval);
264		if (NULL != *record) {
265			CFRelease(*record);
266			*record = NULL;
267		}
268	}
269
270	return retval;
271}
272
273int
274od_record_create_cstring(pam_handle_t *pamh, ODRecordRef *record, const char *user)
275{
276	int retval = PAM_SUCCESS;
277	CFStringRef cfUser = NULL;
278
279	if (NULL == record || NULL == user) {
280		openpam_log(PAM_LOG_DEBUG, "NULL argument passed");
281		retval = PAM_SERVICE_ERR;
282		goto cleanup;
283	}
284
285	if (PAM_SUCCESS != (retval = cstring_to_cfstring(user, &cfUser)) ||
286	    PAM_SUCCESS != (retval = od_record_create(pamh, record, cfUser))) {
287		openpam_log(PAM_LOG_DEBUG, "od_record_create() failed");
288		goto cleanup;
289	}
290
291cleanup:
292	if (PAM_SUCCESS != retval) {
293		openpam_log(PAM_LOG_ERROR, "failed: %d", retval);
294		if (NULL != *record) {
295			CFRelease(*record);
296		}
297	}
298
299	if (NULL != cfUser) {
300		CFRelease(cfUser);
301	}
302
303	return retval;
304}
305
306/* Can return NULL */
307int
308od_record_attribute_create_cfarray(ODRecordRef record, CFStringRef attrib,  CFArrayRef *out)
309{
310	int retval = PAM_SUCCESS;
311
312	if (NULL == record || NULL == attrib || NULL == out) {
313		openpam_log(PAM_LOG_DEBUG, "NULL argument passed");
314		retval = PAM_SERVICE_ERR;
315		goto cleanup;
316	}
317
318	*out = ODRecordCopyValues(record, attrib, NULL);
319
320cleanup:
321	if (PAM_SUCCESS != retval) {
322		if (NULL != out) {
323			CFRelease(out);
324		}
325	}
326	return retval;
327}
328
329/* Can return NULL */
330int
331od_record_attribute_create_cfstring(ODRecordRef record, CFStringRef attrib,  CFStringRef *out)
332{
333	int retval = PAM_SERVICE_ERR;
334	CFTypeRef cval = NULL;
335	CFArrayRef vals = NULL;
336	CFIndex i = 0, count = 0;
337
338	if (NULL == record || NULL == attrib || NULL == out) {
339		openpam_log(PAM_LOG_DEBUG, "NULL argument passed");
340		retval = PAM_SERVICE_ERR;
341		goto cleanup;
342	}
343
344	*out = NULL;
345	retval = od_record_attribute_create_cfarray(record, attrib, &vals);
346	if (PAM_SUCCESS != retval) {
347		openpam_log(PAM_LOG_DEBUG, "od_record_attribute_create_cfarray() failed");
348		goto cleanup;
349	}
350	if (NULL == vals) {
351		retval = PAM_SUCCESS;
352		goto cleanup;
353	}
354
355	count = CFArrayGetCount(vals);
356	if (1 != count) {
357		char *attr_cstr = NULL;
358		cfstring_to_cstring(attrib, &attr_cstr);
359		openpam_log(PAM_LOG_DEBUG, "returned %lx attributes for %s", count, attr_cstr);
360		free(attr_cstr);
361	}
362
363	for (i = 0; i < count; ++i) {
364		cval = CFArrayGetValueAtIndex(vals, i);
365		if (NULL == cval) {
366			continue;
367		}
368		if (CFGetTypeID(cval) == CFStringGetTypeID()) {
369			*out = CFStringCreateCopy(kCFAllocatorDefault, cval);
370			if (NULL == *out) {
371				openpam_log(PAM_LOG_DEBUG, "CFStringCreateCopy() failed");
372				retval = PAM_BUF_ERR;
373				goto cleanup;
374			}
375			break;
376		} else {
377			openpam_log(PAM_LOG_DEBUG, "attribute is not a cfstring");
378			retval = PAM_PERM_DENIED;
379			goto cleanup;
380		}
381	}
382	retval = PAM_SUCCESS;
383
384cleanup:
385	if (PAM_SUCCESS != retval) {
386		if (NULL != out) {
387			CFRelease(out);
388		}
389	}
390	if (NULL != vals) {
391		CFRelease(vals);
392	}
393
394	return retval;
395}
396
397/* Can return NULL */
398int
399od_record_attribute_create_cstring(ODRecordRef record, CFStringRef attrib,  char **out)
400{
401	int retval = PAM_SERVICE_ERR;
402	CFStringRef val = NULL;
403
404	if (NULL == record || NULL == attrib || NULL == out) {
405		openpam_log(PAM_LOG_DEBUG, "NULL argument passed");
406		retval = PAM_SERVICE_ERR;
407		goto cleanup;
408	}
409
410	retval = od_record_attribute_create_cfstring(record, attrib, &val);
411	if (PAM_SUCCESS != retval) {
412		openpam_log(PAM_LOG_DEBUG, "od_record_attribute_create_cfstring() failed");
413		goto cleanup;
414	}
415
416	if (NULL != val) {
417		retval = cfstring_to_cstring(val, out);
418		if (PAM_SUCCESS != retval) {
419			openpam_log(PAM_LOG_DEBUG, "cfstring_to_cstring() failed");
420			goto cleanup;
421		}
422	}
423
424cleanup:
425	if (PAM_SUCCESS != retval) {
426		free(out);
427	}
428
429	if (NULL != val) {
430		CFRelease(val);
431	}
432
433	return retval;
434}
435
436int
437od_record_check_pwpolicy(ODRecordRef record)
438{
439	CFDictionaryRef policy = NULL;
440	const void *isDisabled;
441	const void *newPasswordRequired;
442	int retval = PAM_SERVICE_ERR;
443
444	if (NULL == record) {
445		openpam_log(PAM_LOG_DEBUG, "NULL argument passed");
446		retval = PAM_SERVICE_ERR;
447		goto cleanup;
448	}
449
450	if (NULL == (policy = ODRecordCopyPasswordPolicy(kCFAllocatorDefault, record, NULL)) ||
451	    NULL == (isDisabled = CFDictionaryGetValue(policy, CFSTR("isDisabled"))) ||
452	    !cfboolean_get_value(isDisabled))
453		retval = PAM_SUCCESS;
454	else
455		retval = PAM_PERM_DENIED;
456	if (NULL != policy &&
457		NULL != (newPasswordRequired = CFDictionaryGetValue(policy, CFSTR("newPasswordRequired"))) &&
458	    cfboolean_get_value(newPasswordRequired))
459		retval = PAM_NEW_AUTHTOK_REQD;
460
461	if (NULL != policy) {
462		CFRelease(policy);
463	}
464
465cleanup:
466	openpam_log(PAM_LOG_DEBUG, "retval: %d", retval);
467	return retval;
468}
469
470int
471od_record_check_authauthority(ODRecordRef record)
472{
473	int retval = PAM_PERM_DENIED;
474	CFStringRef authauth = NULL;
475
476	if (NULL == record) {
477		openpam_log(PAM_LOG_DEBUG, "NULL argument passed");
478		retval = PAM_SERVICE_ERR;
479		goto cleanup;
480	}
481
482	retval = od_record_attribute_create_cfstring(record, kODAttributeTypeAuthenticationAuthority, &authauth);
483	if (PAM_SUCCESS != retval) {
484		openpam_log(PAM_LOG_DEBUG, "od_record_attribute_create_cfstring() failed");
485		goto cleanup;
486	}
487	if (NULL == authauth) {
488		retval = PAM_SUCCESS;
489		goto cleanup;
490	}
491	if (!CFStringHasPrefix(authauth, CFSTR(kDSValueAuthAuthorityDisabledUser))) {
492		retval = PAM_SUCCESS;
493	}
494
495cleanup:
496	if (PAM_SUCCESS != retval) {
497		openpam_log(PAM_LOG_ERROR, "failed: %d", retval);
498	}
499
500	if (authauth) {
501		CFRelease(authauth);
502	}
503
504	return retval;
505}
506
507int
508od_record_check_homedir(ODRecordRef record)
509{
510	int retval = PAM_SERVICE_ERR;
511	CFStringRef tmp = NULL;
512
513	if (NULL == record) {
514		openpam_log(PAM_LOG_DEBUG, "NULL argument passed");
515		retval = PAM_SERVICE_ERR;
516		goto cleanup;
517	}
518
519	retval = od_record_attribute_create_cfstring(record, kODAttributeTypeNFSHomeDirectory, &tmp);
520	if (PAM_SUCCESS != retval) {
521		openpam_log(PAM_LOG_DEBUG, "od_record_attribute_create_cfstring() failed");
522		goto cleanup;
523	}
524
525	/* Allow NULL home directories */
526	if (NULL == tmp) {
527		retval = PAM_SUCCESS;
528		goto cleanup;
529	}
530
531	/* Do not allow login with '/dev/null' home */
532	if (kCFCompareEqualTo == CFStringCompare(tmp, CFSTR("/dev/null"), 0)) {
533		openpam_log(PAM_LOG_DEBUG, "home directory is /dev/null");
534		retval = PAM_PERM_DENIED;
535		goto cleanup;
536	}
537
538	if (kCFCompareEqualTo == CFStringCompare(tmp, CFSTR("99"), 0)) {
539		openpam_log(PAM_LOG_DEBUG, "home directory is 99");
540		retval = PAM_PERM_DENIED;
541		goto cleanup;
542	}
543
544	retval = PAM_SUCCESS;
545
546cleanup:
547	if (PAM_SUCCESS != retval)
548		openpam_log(PAM_LOG_ERROR, "failed: %d", retval);
549
550	if (NULL != tmp) {
551		CFRelease(tmp);
552	}
553
554	return retval;
555}
556
557int
558od_record_check_shell(ODRecordRef record)
559{
560	int retval = PAM_PERM_DENIED;
561	CFStringRef cfstr = NULL;
562
563	if (NULL == record) {
564		openpam_log(PAM_LOG_DEBUG, "NULL argument passed");
565		retval = PAM_SERVICE_ERR;
566		goto cleanup;
567	}
568
569	retval = od_record_attribute_create_cfstring(record, kODAttributeTypeUserShell, &cfstr);
570	if (PAM_SUCCESS != retval) {
571		openpam_log(PAM_LOG_DEBUG, "od_record_attribute_create_cfstring() failed");
572		goto cleanup;
573	}
574
575	if (NULL == cfstr) {
576		retval = PAM_SUCCESS;
577		goto cleanup;
578	}
579
580	if (CFStringCompare(cfstr, CFSTR("/usr/bin/false"), 0) == kCFCompareEqualTo) {
581		openpam_log(PAM_LOG_DEBUG, "user shell is /bin/false");
582		retval = PAM_PERM_DENIED;
583	}
584
585cleanup:
586	if (PAM_SUCCESS != retval)
587		openpam_log(PAM_LOG_ERROR, "failed: %d", retval);
588
589	if (NULL != cfstr) {
590		CFRelease(cfstr);
591	}
592
593	return retval;
594}
595
596int
597od_string_from_record(ODRecordRef record, CFStringRef attrib,  char **out)
598{
599	int retval = PAM_SERVICE_ERR;
600	CFStringRef val = NULL;
601
602	if (NULL == record) {
603		openpam_log(PAM_LOG_DEBUG, "%s - NULL ODRecord passed.", __func__);
604		goto cleanup;
605	}
606
607	retval = od_record_attribute_create_cfstring(record, attrib, &val);
608	if (PAM_SUCCESS != retval) {
609		goto cleanup;
610	}
611
612	if (val)
613		retval = cfstring_to_cstring(val, out);
614
615cleanup:
616	if (val)
617		CFRelease(val);
618
619	return retval;
620}
621
622int
623extract_homemount(char *in, char **out_url, char **out_path)
624{
625	// Directory Services people have assured me that this won't change
626	static const char URL_OPEN[] = "<url>";
627	static const char URL_CLOSE[] = "</url>";
628	static const char PATH_OPEN[] = "<path>";
629	static const char PATH_CLOSE[] = "</path>";
630
631	char *server_URL = NULL;
632	char *path = NULL;
633	char *record_start = NULL;
634	char *record_end = NULL;
635
636	int retval = PAM_SERVICE_ERR;
637
638	if (NULL == in)
639		goto fin;
640
641	record_start = in;
642	server_URL = strstr(record_start, URL_OPEN);
643	if (NULL == server_URL)
644		goto fin;
645	server_URL += sizeof(URL_OPEN)-1;
646	while ('\0' != *server_URL && isspace(*server_URL))
647		server_URL++;
648	record_end = strstr(server_URL, URL_CLOSE);
649	if (NULL == record_end)
650		goto fin;
651	while (record_end >= server_URL && '\0' != *record_end && isspace(*(record_end-1)))
652		record_end--;
653	if (NULL == record_end)
654		goto fin;
655	*record_end = '\0';
656	if (NULL == (*out_url = strdup(server_URL)))
657		goto fin;
658
659	record_start = record_end+1;
660	path = strstr(record_start, PATH_OPEN);
661	if (NULL == path)
662		goto ok;
663	path += sizeof(PATH_OPEN)-1;
664	while ('\0' != *path && isspace(*path))
665		path++;
666	record_end = strstr(path, PATH_CLOSE);
667	if (NULL == record_end)
668		goto fin;
669	while (record_end >= path && '\0' != *record_end && isspace(*(record_end-1)))
670		record_end--;
671	if (NULL == record_end)
672		goto fin;
673	*record_end = '\0';
674	if (NULL == (*out_path = strdup(path)))
675		goto fin;
676
677ok:
678	retval = PAM_SUCCESS;
679fin:
680	return retval;
681}
682
683int
684od_extract_home(pam_handle_t *pamh, const char *username, char **server_URL, char **path, char **homedir)
685{
686	int retval = PAM_SERVICE_ERR;
687	char *tmp = NULL;
688	ODRecordRef record = NULL;
689
690	retval = od_record_create_cstring(pamh, &record, username);
691	if (PAM_SUCCESS != retval) {
692		goto cleanup;
693	}
694
695	retval = od_string_from_record(record, kODAttributeTypeHomeDirectory, &tmp);
696	if (retval) {
697		openpam_log(PAM_LOG_DEBUG, "%s - get kODAttributeTypeHomeDirectory  : %d",
698			    __func__, retval);
699		goto cleanup;
700	}
701	extract_homemount(tmp, server_URL, path);
702	openpam_log(PAM_LOG_DEBUG, "%s - Server URL   : %s", __func__, *server_URL);
703	openpam_log(PAM_LOG_DEBUG, "%s - Path to mount: %s", __func__, *path);
704
705	retval = od_string_from_record(record, kODAttributeTypeNFSHomeDirectory, homedir);
706	openpam_log(PAM_LOG_DEBUG, "%s - Home dir     : %s", __func__, *homedir);
707	if (retval)
708		goto cleanup;
709
710	retval = PAM_SUCCESS;
711
712cleanup:
713	if (tmp)
714		free(tmp);
715	if (record)
716		CFRelease(record);
717
718	return retval;
719}
720
721/* extract the principal from OpenDirectory */
722int
723od_principal_for_user(pam_handle_t *pamh, const char *user, char **od_principal)
724{
725	int retval = PAM_SERVICE_ERR;
726	ODRecordRef record = NULL;
727	CFStringRef principal = NULL;
728	CFArrayRef authparts = NULL, vals = NULL;
729	CFIndex i = 0, count = 0;
730
731	if (NULL == user || NULL == od_principal) {
732		openpam_log(PAM_LOG_DEBUG, "NULL argument passed");
733		retval = PAM_SERVICE_ERR;
734		goto cleanup;
735	}
736
737	retval = od_record_create_cstring(pamh, &record, user);
738	if (PAM_SUCCESS != retval) {
739		openpam_log(PAM_LOG_DEBUG, "od_record_attribute_create_cfstring() failed");
740		goto cleanup;
741	}
742
743	retval = od_record_attribute_create_cfarray(record, kODAttributeTypeAuthenticationAuthority, &vals);
744	if (PAM_SUCCESS != retval) {
745		openpam_log(PAM_LOG_DEBUG, "od_record_attribute_create_cfarray() failed");
746		goto cleanup;
747	}
748	if (NULL == vals) {
749		openpam_log(PAM_LOG_DEBUG, "no authauth availale for user.");
750		retval = PAM_PERM_DENIED;
751		goto cleanup;
752	}
753
754	count = CFArrayGetCount(vals);
755	for (i = 0; i < count; i++)
756	{
757		const void *val = CFArrayGetValueAtIndex(vals, i);
758		if (NULL == val || CFGetTypeID(val) != CFStringGetTypeID())
759			break;
760
761		authparts = CFStringCreateArrayBySeparatingStrings(kCFAllocatorDefault, val, CFSTR(";"));
762		if (NULL == authparts)
763			continue;
764
765		if ((CFArrayGetCount(authparts) < 5) ||
766		    (CFStringCompare(CFArrayGetValueAtIndex(authparts, 1), CFSTR("Kerberosv5"), kCFCompareEqualTo)) ||
767		    (CFStringHasPrefix(CFArrayGetValueAtIndex(authparts, 4), CFSTR("LKDC:")))) {
768			if (NULL != authparts) {
769				CFRelease(authparts);
770				authparts = NULL;
771			}
772			continue;
773		} else {
774			break;
775		}
776	}
777
778	if (NULL == authparts) {
779		openpam_log(PAM_LOG_DEBUG, "No authentication authority returned");
780		retval = PAM_PERM_DENIED;
781		goto cleanup;
782	}
783
784	principal = CFArrayGetValueAtIndex(authparts, 3);
785	if (NULL == principal) {
786		openpam_log(PAM_LOG_DEBUG, "no principal found in authentication authority");
787		retval = PAM_PERM_DENIED;
788		goto cleanup;
789	}
790
791	retval = cfstring_to_cstring(principal, od_principal);
792	if (PAM_SUCCESS != retval) {
793		openpam_log(PAM_LOG_DEBUG, "cfstring_to_cstring() failed");
794		goto cleanup;
795	}
796
797
798cleanup:
799	if (PAM_SUCCESS != retval) {
800		openpam_log(PAM_LOG_DEBUG, "failed: %d", retval);
801	}
802
803	if (NULL != record) {
804		CFRelease(record);
805	}
806
807	if (NULL != authparts) {
808		CFRelease(authparts);
809	}
810
811	if (NULL != vals) {
812		CFRelease(vals);
813	}
814
815	return retval;
816}
817
818void
819pam_cf_cleanup(__unused pam_handle_t *pamh, void *data, __unused int pam_end_status)
820{
821	if (data) {
822		CFStringRef *cfstring = data;
823		CFRelease(*cfstring);
824	}
825}
826