xref: /illumos-gate/usr/src/lib/pam_modules/ldap/ldap_acct_mgmt.c (revision cbea7aca3fd7787405cbdbd93752998f03dfc25f)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  *
26  * Copyright 2023 OmniOS Community Edition (OmniOSce) Association.
27  */
28 
29 #include "ldap_headers.h"
30 
31 /*ARGSUSED*/
32 static void
ldap_cleanup(pam_handle_t * pamh,void * data,int pam_status)33 ldap_cleanup(
34 	pam_handle_t *pamh,
35 	void *data,
36 	int pam_status)
37 {
38 	free((ldap_authtok_data *)data);
39 }
40 
41 /*
42  * warn_user_passwd_will_expire	- warn the user when the password will
43  *					  expire.
44  */
45 
46 static void
warn_user_passwd_will_expire(pam_handle_t * pamh,int sec_until_expired)47 warn_user_passwd_will_expire(
48 	pam_handle_t *pamh,
49 	int sec_until_expired)
50 {
51 	char	messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
52 	int	days = 0, hours = 0;
53 	int	seconds_d = 0, seconds_h = 0;
54 
55 	days = sec_until_expired / 86400;
56 	seconds_d = sec_until_expired % 86400;
57 	hours = (days * 24) + seconds_d / 3600;
58 	seconds_h = seconds_d % 3600;
59 
60 	if (sec_until_expired <= (86400 * 2)) {
61 		if (seconds_d <= 3600 && days == 0)
62 			(void) snprintf(messages[0], sizeof (messages[0]),
63 			    dgettext(TEXT_DOMAIN,
64 			    "Your password will expire within one hour."));
65 		else
66 			(void) snprintf(messages[0], sizeof (messages[0]),
67 			    dgettext(TEXT_DOMAIN,
68 				"Your password will expire in %d hours."),
69 				(seconds_h == 0) ? hours : hours + 1);
70 	} else {
71 			(void) snprintf(messages[0], sizeof (messages[0]),
72 			    dgettext(TEXT_DOMAIN,
73 				"Your password will expire in %d days."),
74 				(seconds_d == 0) ? days : days + 1);
75 	}
76 
77 	(void) __pam_display_msg(pamh, PAM_TEXT_INFO, 1, messages, NULL);
78 }
79 
80 /*
81  * display_acct_unlock_time - Display the time left for the account to
82  * get auto unlocked after the maximum login failures has reached.
83  */
84 static void
display_acct_unlock_time(pam_handle_t * pamh,int sec_b4_unlock)85 display_acct_unlock_time(pam_handle_t *pamh, int sec_b4_unlock)
86 {
87 	char	messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
88 	int	days = 0, hours = 0;
89 	int	seconds_d = 0, seconds_h = 0;
90 
91 	/* Account is locked forever */
92 	if (sec_b4_unlock == -1) {
93 		(void) snprintf(messages[0], sizeof (messages[0]),
94 		dgettext(TEXT_DOMAIN,
95 		"Your account is locked, please contact administrator."));
96 		(void) __pam_display_msg(pamh, PAM_TEXT_INFO, 1,
97 			messages, NULL);
98 		return;
99 	}
100 
101 	days = sec_b4_unlock / 86400;
102 	seconds_d = sec_b4_unlock % 86400;
103 	hours = (days * 24) + seconds_d / 3600;
104 	seconds_h = seconds_d % 3600;
105 
106 	if (sec_b4_unlock <= (86400 * 2)) {
107 		if (seconds_d <= 3600 && days == 0)
108 			(void) snprintf(messages[0], sizeof (messages[0]),
109 				dgettext(TEXT_DOMAIN,
110 				"Your account is locked and will be unlocked"
111 				" within one hour."));
112 		else
113 			(void) snprintf(messages[0], sizeof (messages[0]),
114 				dgettext(TEXT_DOMAIN,
115 				"Your account is locked and will be unlocked"
116 				" in %d hours."),
117 				(seconds_h == 0) ? hours : hours + 1);
118 	} else {
119 		(void) snprintf(messages[0], sizeof (messages[0]),
120 			dgettext(TEXT_DOMAIN,
121 			"Your account is locked and will be unlocked"
122 			" in %d days."),
123 			(seconds_d == 0) ? days : days + 1);
124 	}
125 
126 	(void) __pam_display_msg(pamh, PAM_TEXT_INFO, 1, messages, NULL);
127 }
128 
129 /*
130  * warn_user_passwd_expired - warn the user that the password has expired
131  */
132 static void
warn_user_passwd_expired(pam_handle_t * pamh,int grace)133 warn_user_passwd_expired(pam_handle_t *pamh, int grace)
134 {
135 	char	messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
136 
137 	if (grace)
138 		(void) snprintf(messages[0], sizeof (messages[0]),
139 			dgettext(TEXT_DOMAIN,
140 			"Your password has expired. "
141 			"Number of grace logins allowed are %d."),
142 			grace);
143 	else
144 		(void) snprintf(messages[0], sizeof (messages[0]),
145 			dgettext(TEXT_DOMAIN,
146 			"Your password has expired."));
147 
148 	(void) __pam_display_msg(pamh, PAM_TEXT_INFO, 1, messages, NULL);
149 }
150 
151 /*
152  * display_passwd_reset_msg - tell user that password has been reset by
153  * administrator
154  */
155 static void
display_passwd_reset_msg(pam_handle_t * pamh)156 display_passwd_reset_msg(pam_handle_t *pamh)
157 {
158 	char	messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
159 
160 	(void) snprintf(messages[0], sizeof (messages[0]),
161 			dgettext(TEXT_DOMAIN,
162 			"Your password has been reset by administrator."));
163 
164 	(void) __pam_display_msg(pamh, PAM_TEXT_INFO, 1, messages, NULL);
165 }
166 
167 /*
168  * Retreives account management related attributes for the user using
169  * default binding and does local account checks .
170  *
171  * Return Value: PAM_SUCCESS - If account is valid, seconds param will have
172  *				seconds left for password to expire
173  *		 PAM_ACCT_EXPIRED - If account is inactive
174  *		 PAM_NEW_AUTHTOK_REQD - Password is reset by admin
175  *		 PAM_AUTHTOK_EXPIRED - User password has expired, grace
176  *				param will have no. of grace logins allowed
177  *		 PAM_MAXTRIES - If maximum failure of wrong password has reached
178  *				seconds param will have no. of seconds for the
179  *				account to get unlocked
180  *		 PAM_AUTH_ERR - Failure return code
181  */
182 static int
get_account_mgmt(const char * user,int * seconds,int * grace)183 get_account_mgmt(const char *user, int *seconds, int *grace)
184 {
185 	int rc	= PAM_AUTH_ERR;
186 	AcctUsableResponse_t	acctResp;
187 
188 	(void *)memset((void*)&acctResp, 0, sizeof (acctResp));
189 	/* get the values for local account checking */
190 	if ((rc = __ns_ldap_getAcctMgmt(user, &acctResp))
191 		!= NS_LDAP_SUCCESS) {
192 		syslog(LOG_DEBUG,
193 			"__ns_ldap_getAcctMgmt() failed for %s with error %d",
194 				user, rc);
195 		return (PAM_AUTH_ERR);
196 	}
197 
198 	if (acctResp.choice == 0) {
199 		/* should be able to login */
200 		*seconds =
201 			acctResp.AcctUsableResp.seconds_before_expiry;
202 		return (PAM_SUCCESS);
203 	} else if (acctResp.choice == 1) {
204 		/* cannot login */
205 		if (acctResp.AcctUsableResp.more_info.inactive)
206 			/* entry inactive */
207 			return (PAM_ACCT_EXPIRED);
208 		if (acctResp.AcctUsableResp.more_info.reset)
209 			/* password reset by administrator */
210 			return (PAM_NEW_AUTHTOK_REQD);
211 		if (acctResp.AcctUsableResp.more_info.expired) {
212 			/*
213 			 * password expired, check for grace logins.
214 			 */
215 			*grace =
216 				acctResp.AcctUsableResp.more_info.rem_grace;
217 			return (PAM_AUTHTOK_EXPIRED);
218 		}
219 		if (acctResp.AcctUsableResp.more_info.sec_b4_unlock) {
220 			/* max failures reached, seconds before unlock */
221 			*seconds =
222 				acctResp.AcctUsableResp.more_info.sec_b4_unlock;
223 			return (PAM_MAXTRIES);
224 		}
225 	}
226 	return (PAM_AUTH_ERR);
227 }
228 
229 /*
230  * pam_sm_acct_mgmt	main account managment routine.
231  *			This routine relies on the LDAP
232  *			directory server to provide the
233  * 			password aging and account lockout
234  * 			information. This is done by first
235  *			trying to authenticate the user and
236  *			then checking the password status
237  *			returned.
238  *
239  *			Returns: module error or specific
240  *			error on failure.
241  */
242 
243 int
pam_sm_acct_mgmt(pam_handle_t * pamh,int flags,int argc,const char ** argv)244 pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv)
245 {
246 
247 	const char *user = NULL;
248 	int result = PAM_AUTH_ERR;
249 	int debug = 0;
250 	int i;
251 	const char *password = NULL;
252 	ns_cred_t *credp = NULL;
253 	int nowarn = 0;
254 	int seconds = 0, grace = 0;
255 	ldap_authtok_data *status;
256 
257 	for (i = 0; i < argc; i++) {
258 		if (strcmp(argv[i], "debug") == 0)
259 			debug = 1;
260 		else if (strcasecmp(argv[i], "nowarn") == 0) {
261 			nowarn = 1;
262 			flags = flags | PAM_SILENT;
263 		}
264 		else
265 			syslog(LOG_DEBUG,
266 				"pam_ldap pam_sm_acct_mgmt: "
267 				"illegal option %s",
268 				argv[i]);
269 	}
270 
271 	if ((result = pam_get_item(pamh, PAM_USER, (const void **)&user)) !=
272 	    PAM_SUCCESS) {
273 		goto out;
274 	}
275 
276 	if (debug)
277 		syslog(LOG_DEBUG,
278 			"ldap pam_sm_acct_mgmt(%s), flags = %x %s",
279 			(user)?user:"no-user", flags,
280 			(nowarn)? ", nowarn": "");
281 
282 	if (user == NULL) {
283 		result = PAM_USER_UNKNOWN;
284 		goto out;
285 	}
286 
287 	/* retrieve the password from the PAM handle */
288 	result = pam_get_item(pamh, PAM_AUTHTOK, (const void **)&password);
289 	if (password == NULL) {
290 		if (debug)
291 			syslog(LOG_DEBUG, "ldap pam_sm_acct_mgmt: "
292 			    "no password for user %s", user);
293 		/* Do local account checking */
294 		result = get_account_mgmt(user, &seconds, &grace);
295 	} else {
296 		/* Try to authenticate to get password management info */
297 		result = authenticate(&credp, user,
298 				password, &seconds);
299 	}
300 
301 	/*
302 	 * process the password management info.
303 	 * If user needs to change the password immediately,
304 	 * just return the rc.
305 	 * Otherwise, reset rc to the appropriate PAM error or
306 	 * warn the user about password expiration.
307 	 */
308 	if (result == PAM_MAXTRIES) {
309 		/* exceed retry limit, denied access to account */
310 		if (!(flags & PAM_SILENT))
311 			display_acct_unlock_time(pamh, seconds);
312 		result = PAM_PERM_DENIED;
313 	} else if (result == PAM_ACCT_EXPIRED)
314 		/* account is inactivated */
315 		result = PAM_ACCT_EXPIRED;
316 	else if (result == PAM_AUTHTOK_EXPIRED) {
317 		if (!(flags & PAM_SILENT))
318 			warn_user_passwd_expired(pamh, grace);
319 		/* password expired, check for grace logins */
320 		if (grace > 0)
321 			result = PAM_SUCCESS;
322 		else
323 			result = PAM_AUTHTOK_EXPIRED;
324 	} else if (result == PAM_NEW_AUTHTOK_REQD) {
325 		/* password has been reset by administrator */
326 		if (!(flags & PAM_SILENT))
327 			display_passwd_reset_msg(pamh);
328 		result = PAM_NEW_AUTHTOK_REQD;
329 	} else if (result == PAM_SUCCESS) {
330 		/*
331 		 * warn the user if the password
332 		 * is about to expire.
333 		 */
334 		if (!(flags & PAM_SILENT) &&
335 			seconds > 0)
336 			warn_user_passwd_will_expire(pamh,
337 				seconds);
338 
339 	}
340 
341 out:
342 	if (credp != NULL)
343 		(void) __ns_ldap_freeCred(&credp);
344 
345 	/* store the password aging status in the pam handle */
346 	if (result != PAM_SUCCESS) {
347 		int pam_res;
348 		ldap_authtok_data *authtok_data;
349 
350 		pam_res = pam_get_data(
351 			pamh, LDAP_AUTHTOK_DATA, (const void **)&authtok_data);
352 
353 		if ((status = (ldap_authtok_data *)calloc
354 			(1, sizeof (ldap_authtok_data))) == NULL) {
355 			return (PAM_BUF_ERR);
356 		}
357 
358 		if (pam_res == PAM_SUCCESS)
359 			(void) memcpy(status, authtok_data,
360 				sizeof (ldap_authtok_data));
361 
362 		status->age_status = result;
363 		if (pam_set_data(pamh, LDAP_AUTHTOK_DATA, status, ldap_cleanup)
364 							!= PAM_SUCCESS) {
365 			free(status);
366 			return (PAM_SERVICE_ERR);
367 		}
368 	}
369 
370 	return (result);
371 }
372