1/*
2 * Copyright (c) 2000 Apple Computer, Inc. All rights reserved.
3 * Portions Copyright (c) 2001 PADL Software Pty Ltd. All rights reserved.
4 *
5 * @APPLE_LICENSE_HEADER_START@
6 *
7 * Portions Copyright (c) 2000 Apple Computer, Inc.  All Rights
8 * Reserved.  This file contains Original Code and/or Modifications of
9 * Original Code as defined in and that are subject to the Apple Public
10 * Source License Version 1.1 (the "License").  You may not use this file
11 * except in compliance with the License.  Please obtain a copy of the
12 * License at http://www.apple.com/publicsource and read it before using
13 * this file.
14 *
15 * The Original Code and all software distributed under the License are
16 * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
17 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
18 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE OR NON- INFRINGEMENT.  Please see the
20 * License for the specific language governing rights and limitations
21 * under the License.
22 *
23 * @APPLE_LICENSE_HEADER_END@
24 */
25
26/******************************************************************
27 * The purpose of this module is to provide a basic password
28 * authentication module for Mac OS X.
29 ******************************************************************/
30
31#include <pwd.h>
32#include <stdio.h>
33#include <unistd.h>
34#include <membership.h>
35#include <membershipPriv.h>
36#include <CoreFoundation/CoreFoundation.h>
37#include <OpenDirectory/OpenDirectory.h>
38#include <DirectoryService/DirectoryService.h>
39
40#define PAM_SM_AUTH
41#define PAM_SM_ACCOUNT
42
43#include <security/pam_modules.h>
44#include <security/pam_appl.h>
45
46/* legacy definition */
47#ifndef kDSValueAuthAuthorityDisabledUser
48#define kDSValueAuthAuthorityDisabledUser ";DisabledUser;"
49#endif
50
51#include "Common.h"
52
53#define PM_DISPLAY_NAME "OpenDirectory"
54#define PAM_OD_PW_EXP "ODPasswordExpire"
55
56PAM_EXTERN int
57pam_sm_acct_mgmt(pam_handle_t * pamh, int flags, int argc, const char **argv)
58{
59	int retval = PAM_PERM_DENIED;
60	const char *user = NULL;
61	char *homedir = NULL;
62	ODRecordRef cfRecord = NULL;
63	struct passwd *pwd = NULL;
64	struct passwd pwdbuf;
65	char pwbuffer[2 * PATH_MAX];
66	const char *ttl_str = NULL;
67	int ttl = 30 * 60;
68
69	/* get the username */
70	retval = pam_get_user(pamh, &user, NULL);
71	if (PAM_SUCCESS != retval) {
72		goto cleanup;
73	}
74	if (user == NULL || *user == '\0') {
75		retval = PAM_PERM_DENIED;
76		goto cleanup;
77	}
78
79	/* refresh the membership */
80	if (0 != getpwnam_r(user, &pwdbuf, pwbuffer, sizeof(pwbuffer), &pwd) || NULL == pwd) {
81		openpam_log(PAM_LOG_ERROR, "%s - Unable to get pwd record.", PM_DISPLAY_NAME);
82		retval = PAM_USER_UNKNOWN;
83		goto cleanup;
84	}
85	if (NULL != (ttl_str = openpam_get_option(pamh, "refresh"))) {
86		ttl = strtol(ttl_str, NULL, 10) * 60;
87	}
88	mbr_set_identifier_ttl(ID_TYPE_UID, &pwd->pw_uid, sizeof(pwd->pw_uid), ttl);
89	openpam_log(PAM_LOG_DEBUG, "%s - Membership cache TTL set to %d.", PM_DISPLAY_NAME, ttl);
90
91	/* Get user record from OD */
92	retval = od_record_create_cstring(pamh, &cfRecord, (const char*)user);
93	if (PAM_SUCCESS != retval) {
94		openpam_log(PAM_LOG_ERROR, "%s - Unable to get user record: %d.", PM_DISPLAY_NAME, retval);
95		goto cleanup;
96	}
97
98	/* check if authentication returned password expired */
99	if (pam_getenv(pamh, PAM_OD_PW_EXP) != NULL) {
100		openpam_log(PAM_LOG_DEBUG, "%s - Password expired.", PM_DISPLAY_NAME);
101		retval = PAM_NEW_AUTHTOK_REQD;
102		goto cleanup;
103	}
104
105	/* check user password policy */
106	retval = od_record_check_pwpolicy(cfRecord);
107	if (PAM_SUCCESS != retval) {
108		goto cleanup;
109	}
110
111	/* check user authentication authority */
112	retval = od_record_check_authauthority(cfRecord);
113	if (PAM_SUCCESS != retval) {
114		goto cleanup;
115	}
116
117	/* check user home directory */
118	if (!openpam_get_option(pamh, "no_check_home")) {
119		retval = od_record_check_homedir(cfRecord);
120		if (PAM_SUCCESS != retval) {
121			goto cleanup;
122		}
123	}
124
125	/* check user shell */
126	if (!openpam_get_option(pamh, "no_check_shell")) {
127		retval = od_record_check_shell(cfRecord);
128		if (PAM_SUCCESS != retval) {
129			goto cleanup;
130		}
131	}
132
133cleanup:
134	if (NULL != cfRecord) {
135		CFRelease(cfRecord);
136	}
137	free(homedir);
138	pam_unsetenv(pamh, PAM_OD_PW_EXP);
139
140
141	return retval;
142}
143
144
145PAM_EXTERN int
146pam_sm_authenticate(pam_handle_t * pamh, int flags, int argc, const char **argv)
147{
148	static const char password_prompt[] = "Password:";
149	int retval = PAM_SUCCESS;
150	const char *user = NULL;
151	const char *password = NULL;
152	CFStringRef cfPassword = NULL;
153	CFErrorRef odErr = NULL;
154	ODRecordRef cfRecord = NULL;
155
156	if (PAM_SUCCESS != (retval = pam_get_user(pamh, &user, NULL))) {
157		openpam_log(PAM_LOG_DEBUG, "%s - Unable to obtain the username.", PM_DISPLAY_NAME);
158		goto cleanup;
159	}
160	if (PAM_SUCCESS != (retval = pam_get_authtok(pamh, PAM_AUTHTOK, &password, password_prompt))) {
161		openpam_log(PAM_LOG_DEBUG, "%s - Error obtaining the authtok.", PM_DISPLAY_NAME);
162		retval = PAM_AUTH_ERR;
163		goto cleanup;
164	}
165	if ((password[0] == '\0') && ((NULL == openpam_get_option(pamh, "nullok")) || (flags & PAM_DISALLOW_NULL_AUTHTOK))) {
166		openpam_log(PAM_LOG_DEBUG, "%s - NULL passwords are not allowed.", PM_DISPLAY_NAME);
167		retval = PAM_AUTH_ERR;
168		goto cleanup;
169	}
170
171	/* Get user record from OD */
172	retval = od_record_create_cstring(pamh, &cfRecord, (const char*)user);
173	if (PAM_SUCCESS != retval) {
174		openpam_log(PAM_LOG_ERROR, "%s - Unable to get user record.", PM_DISPLAY_NAME);
175		goto cleanup;
176	}
177
178	if (NULL == cfRecord) {
179		openpam_log(PAM_LOG_ERROR, "%s - User record NULL.", PM_DISPLAY_NAME);
180		return PAM_USER_UNKNOWN;
181	}
182
183	/* verify the user's password */
184	cfPassword = CFStringCreateWithCString(kCFAllocatorDefault, password, kCFStringEncodingUTF8);
185	retval = PAM_USER_UNKNOWN;
186	if (!ODRecordVerifyPassword(cfRecord, cfPassword, &odErr)) {
187		switch (CFErrorGetCode(odErr)) {
188			case kODErrorCredentialsAccountNotFound:
189				retval = PAM_USER_UNKNOWN;
190				openpam_log(PAM_LOG_DEBUG, "%s - Account not found or invalid.", PM_DISPLAY_NAME);
191				break;
192			case kODErrorCredentialsAccountDisabled:
193			case kODErrorCredentialsAccountInactive:
194				openpam_log(PAM_LOG_DEBUG, "%s - The account is disabled or inactive.", PM_DISPLAY_NAME);
195				retval = PAM_PERM_DENIED;
196				break;
197			case kODErrorCredentialsPasswordExpired:
198			case kODErrorCredentialsPasswordChangeRequired:
199				openpam_log(PAM_LOG_DEBUG, "%s - The authtok is expired or requires updating.", PM_DISPLAY_NAME);
200				pam_setenv(pamh, PAM_OD_PW_EXP, "yes", 1);
201				retval = PAM_SUCCESS;
202				break;
203			case kODErrorCredentialsInvalid:
204				openpam_log(PAM_LOG_DEBUG, "%s - The authtok is incorrect.", PM_DISPLAY_NAME);
205				retval = PAM_AUTH_ERR;
206				break;
207			default:
208				openpam_log(PAM_LOG_DEBUG, "%s  Unexpected error code from ODRecordVerifyPassword(): %ld.",
209					    PM_DISPLAY_NAME, CFErrorGetCode(odErr));
210				retval = PAM_AUTH_ERR;
211				break;
212		}
213	} else {
214		retval = PAM_SUCCESS;
215	}
216
217cleanup:
218	if (NULL != cfRecord) {
219		CFRelease(cfRecord);
220	}
221
222	if (NULL != cfPassword) {
223		CFRelease(cfPassword);
224	}
225
226	if (NULL != odErr) {
227		CFRelease(odErr);
228	}
229
230	return retval;
231}
232
233
234PAM_EXTERN int
235pam_sm_setcred(pam_handle_t * pamh, int flags, int argc, const char **argv)
236{
237	return PAM_SUCCESS;
238}
239
240
241PAM_EXTERN int
242pam_sm_chauthtok(pam_handle_t * pamh, int flags, int argc, const char **argv)
243{
244	static const char old_password_prompt[] = "Old Password:";
245	static const char new_password_prompt[] = "New Password:";
246	int retval = PAM_SUCCESS;
247	const char *user = NULL;
248	const char *new_password = NULL;
249	const char *old_password = NULL;
250	CFErrorRef odErr = NULL;
251	ODRecordRef cfRecord = NULL;
252	CFStringRef cfOldPassword = NULL;
253	CFStringRef cfNewPassword = NULL;
254
255	if (flags & PAM_PRELIM_CHECK) {
256		retval = PAM_SUCCESS;
257		goto cleanup;
258	}
259
260	if ((retval = pam_get_user(pamh, &user, NULL)) != PAM_SUCCESS) {
261		openpam_log(PAM_LOG_DEBUG, "%s - Error obtaining the username.", PM_DISPLAY_NAME);
262		goto cleanup;
263	}
264
265	if (PAM_SUCCESS != (retval = pam_get_authtok(pamh, PAM_OLDAUTHTOK, &old_password, old_password_prompt))) {
266		openpam_log(PAM_LOG_DEBUG, "%s - Error obtaining the old password.", PM_DISPLAY_NAME);
267		goto cleanup;
268	}
269	if (PAM_SUCCESS != (retval = pam_get_authtok(pamh, PAM_AUTHTOK, &new_password, new_password_prompt))) {
270		openpam_log(PAM_LOG_DEBUG, "%s - Error obtaining the new password.", PM_DISPLAY_NAME);
271		goto cleanup;
272	}
273
274	/* Get user record from OD */
275	retval = od_record_create_cstring(pamh, &cfRecord, (const char*)user);
276	if (PAM_SUCCESS != retval) {
277		openpam_log(PAM_LOG_ERROR, "%s - Unable to get user record.", PM_DISPLAY_NAME);
278		goto cleanup;
279	}
280
281	/* reset the user's password */
282	cfOldPassword = CFStringCreateWithCString(kCFAllocatorDefault, old_password, kCFStringEncodingUTF8);
283	cfNewPassword = CFStringCreateWithCString(kCFAllocatorDefault, new_password, kCFStringEncodingUTF8);
284
285	retval = PAM_SYSTEM_ERR;
286	if (!ODRecordChangePassword(cfRecord, cfOldPassword, cfNewPassword, &odErr)) {
287		switch (CFErrorGetCode(odErr)) {
288			case kODErrorCredentialsInvalid:
289			case kODErrorCredentialsPasswordQualityFailed:
290				openpam_log(PAM_LOG_DEBUG, "%s - The authtok is invaild or of low quality.", PM_DISPLAY_NAME);
291				retval = PAM_AUTHTOK_ERR;
292				break;
293			case kODErrorCredentialsNotAuthorized:
294			case kODErrorCredentialsAccountDisabled:
295			case kODErrorCredentialsAccountInactive:
296				openpam_log(PAM_LOG_DEBUG, "%s - The account not authorized, disabled or inactive.", PM_DISPLAY_NAME);
297				retval = PAM_PERM_DENIED;
298				break;
299			case kODErrorCredentialsPasswordUnrecoverable:
300				openpam_log(PAM_LOG_DEBUG, "%s - The authtok us unrecoverable.", PM_DISPLAY_NAME);
301				retval = PAM_AUTHTOK_RECOVERY_ERR;
302				break;
303			default:
304				openpam_log(PAM_LOG_DEBUG, "%s - There was an unexpected error while changing the password.", PM_DISPLAY_NAME);
305				retval = PAM_ABORT;
306				break;
307		}
308	} else {
309		retval = PAM_SUCCESS;
310	}
311
312cleanup:
313	if (NULL != odErr) {
314		CFRelease(odErr);
315	}
316
317	if (NULL != cfRecord) {
318		CFRelease(cfRecord);
319	}
320
321	if (NULL != cfOldPassword) {
322		CFRelease(cfOldPassword);
323	}
324
325	if (NULL != cfNewPassword) {
326		CFRelease(cfNewPassword);
327	}
328
329	return retval;
330}
331