xref: /illumos-gate/usr/src/lib/pam_modules/authtok_store/authtok_store.c (revision 004388ebfdfe2ed7dfd2d153a876dfcc22d2c006)
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 (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 #include <sys/types.h>
29 #include <sys/varargs.h>
30 #include <string.h>
31 #include <syslog.h>
32 #include <stdlib.h>
33 
34 #include <security/pam_appl.h>
35 #include <security/pam_modules.h>
36 #include <security/pam_impl.h>
37 
38 #include <libintl.h>
39 
40 #include <passwdutil.h>
41 
42 #define	SUNW_OLDRPCPASS "SUNW-OLD-RPC-PASSWORD"
43 
44 /*PRINTFLIKE3*/
45 static void
46 error(int nowarn, pam_handle_t *pamh, char *fmt, ...)
47 {
48 	va_list ap;
49 	char messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
50 
51 	va_start(ap, fmt);
52 	(void) vsnprintf(messages[0], sizeof (messages[0]), fmt, ap);
53 	if (nowarn == 0)
54 		(void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1, messages,
55 		    NULL);
56 	va_end(ap);
57 }
58 
59 /*PRINTFLIKE3*/
60 static void
61 info(int nowarn, pam_handle_t *pamh, char *fmt, ...)
62 {
63 	va_list ap;
64 	char messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
65 
66 	va_start(ap, fmt);
67 	(void) vsnprintf(messages[0], sizeof (messages[0]), fmt, ap);
68 	if (nowarn == 0)
69 		(void) __pam_display_msg(pamh, PAM_TEXT_INFO, 1, messages,
70 		    NULL);
71 	va_end(ap);
72 }
73 
74 #if defined(ENABLE_AGING)
75 /*
76  * test if authtok is aged.
77  * returns 1 if it is, 0 otherwise
78  */
79 static int
80 authtok_is_aged(pam_handle_t *pamh)
81 {
82 	unix_authtok_data *status;
83 
84 	if (pam_get_data(pamh, UNIX_AUTHTOK_DATA,
85 			(const void **)status) != PAM_SUCCESS)
86 		return (0);
87 
88 	return (status->age_status == PAM_NEW_AUTHTOK_REQD)
89 }
90 #endif
91 
92 int
93 pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv)
94 {
95 	int i;
96 	int debug = 0;
97 	int nowarn = 0;
98 	attrlist l;
99 	pwu_repository_t *pwu_rep;
100 	char *user;
101 	char *oldpw;
102 	char *oldrpcpw = "*NP*";
103 	char *newpw;
104 	char *service;
105 	struct pam_repository *auth_rep;
106 	int res;
107 	char msg[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
108 	int updated_reps = 0;
109 	int server_policy = 0;
110 
111 	for (i = 0; i < argc; i++) {
112 		if (strcmp(argv[i], "debug") == 0)
113 			debug = 1;
114 		else if (strcmp(argv[i], "nowarn") == 0)
115 			nowarn = 1;
116 		else if (strcmp(argv[i], "server_policy") == 0)
117 			server_policy = 1;
118 	}
119 
120 	if ((flags & PAM_PRELIM_CHECK) != 0)
121 		return (PAM_IGNORE);
122 
123 	if ((flags & PAM_UPDATE_AUTHTOK) == 0)
124 		return (PAM_SYSTEM_ERR);
125 
126 	if ((flags & PAM_SILENT) != 0)
127 		nowarn = 1;
128 
129 	if (debug)
130 		syslog(LOG_DEBUG, "pam_authtok_store: storing authtok");
131 
132 #if defined(ENABLE_AGING)
133 	if ((flags & PAM_CHANGE_EXPIRED_AUTHTOK) && !authtok_is_aged(pamh)) {
134 		syslog(LOG_DEBUG, "pam_authtok_store: System password young");
135 		return (PAM_IGNORE)
136 	}
137 #endif
138 
139 	res = pam_get_item(pamh, PAM_SERVICE, (void **)&service);
140 	if (res != PAM_SUCCESS) {
141 		syslog(LOG_ERR, "pam_authtok_store: error getting SERVICE");
142 		return (PAM_SYSTEM_ERR);
143 	}
144 
145 	res = pam_get_item(pamh, PAM_USER, (void **)&user);
146 	if (res != PAM_SUCCESS) {
147 		syslog(LOG_ERR, "pam_authtok_store: error getting USER");
148 		return (PAM_SYSTEM_ERR);
149 	}
150 
151 	if (user == NULL || *user == '\0') {
152 		syslog(LOG_ERR, "pam_authtok_store: username is empty");
153 		return (PAM_USER_UNKNOWN);
154 	}
155 
156 	res = pam_get_item(pamh, PAM_OLDAUTHTOK, (void **)&oldpw);
157 	if (res != PAM_SUCCESS) {
158 		syslog(LOG_ERR, "pam_authtok_store: error getting OLDAUTHTOK");
159 		return (PAM_SYSTEM_ERR);
160 	}
161 
162 	res = pam_get_item(pamh, PAM_AUTHTOK, (void **)&newpw);
163 	if (res != PAM_SUCCESS || newpw == NULL) {
164 		/*
165 		 * A module on the stack has removed PAM_AUTHTOK. We fail
166 		 */
167 		return (PAM_SYSTEM_ERR);
168 	}
169 
170 	l.data.val_s = newpw;
171 	/*
172 	 * If the server_policy option is specified,
173 	 * use the special attribute, ATTR_PASSWD_SERVER_POLICY,
174 	 * to tell the update routine for each repository
175 	 * to perform the necessary special operations.
176 	 * For now, only the LDAP routine treats this attribute
177 	 * differently that ATTR_PASSWD. It will skip the
178 	 * crypting of the password before storing it in the LDAP
179 	 * server. NIS, NISPLUS, and FILES will handle
180 	 * ATTR_PASSWD_SERVER_POLICY the same as ATTR_PASSWD.
181 	 */
182 	if (server_policy)
183 		l.type = ATTR_PASSWD_SERVER_POLICY;
184 	else
185 		l.type = ATTR_PASSWD;
186 	l.next = NULL;
187 
188 	res = pam_get_item(pamh, PAM_REPOSITORY, (void **)&auth_rep);
189 	if (res != PAM_SUCCESS) {
190 		syslog(LOG_ERR, "pam_authtok_store: error getting repository");
191 		return (PAM_SYSTEM_ERR);
192 	}
193 
194 	if (auth_rep == NULL) {
195 		pwu_rep = PWU_DEFAULT_REP;
196 	} else {
197 		if ((pwu_rep = calloc(1, sizeof (*pwu_rep))) == NULL)
198 			return (PAM_BUF_ERR);
199 		pwu_rep->type = auth_rep->type;
200 		pwu_rep->scope = auth_rep->scope;
201 		pwu_rep->scope_len = auth_rep->scope_len;
202 	}
203 
204 	/*
205 	 * The pam_dhkeys module might have set SUNW_OLDRPCPASS if it
206 	 * discovered that the user's old password doesn't decrypt the
207 	 * user's secure RPC credentials. In that case, the
208 	 * item SUNW_OLDRPCPASS contains the correct password to
209 	 * decrypt these credentials.
210 	 */
211 
212 	res = pam_get_data(pamh, SUNW_OLDRPCPASS, (const void **)&oldrpcpw);
213 	if (res != PAM_SUCCESS && res != PAM_NO_MODULE_DATA) {
214 		syslog(LOG_ERR, "pam_authtok_store: error getting OLDRPCPASS");
215 		return (PAM_SYSTEM_ERR);
216 	}
217 
218 	res = __set_authtoken_attr(user, oldpw, oldrpcpw, pwu_rep, &l,
219 	    &updated_reps);
220 
221 	if (pwu_rep != PWU_DEFAULT_REP)
222 		free(pwu_rep);
223 	/*
224 	 * now map the various passwdutil return states to user messages
225 	 * and PAM return codes.
226 	 */
227 	switch (res) {
228 	case PWU_SUCCESS:
229 		for (i = 1; i <= REP_LAST; i <<= 1) {
230 			if ((updated_reps & i) == 0)
231 				continue;
232 			info(nowarn, pamh, dgettext(TEXT_DOMAIN,
233 			    "%s: password successfully changed for %s"),
234 			    service, user);
235 		}
236 
237 		/*
238 		 * If we have updated NIS+, and we got SUCCESS (not one of
239 		 * the partial failures), this indicates that the credential
240 		 * update went well too... Inform the user
241 		 */
242 		if (updated_reps & REP_NISPLUS)
243 			info(nowarn, pamh, dgettext(TEXT_DOMAIN,
244 			    "%s: credential information changed for %s"),
245 			    service, user);
246 		res = PAM_SUCCESS;
247 		break;
248 	case PWU_BUSY:
249 		error(nowarn, pamh, dgettext(TEXT_DOMAIN,
250 			"%s: Password database busy. Try again later."),
251 			service);
252 		res = PAM_AUTHTOK_LOCK_BUSY;
253 		break;
254 	case PWU_STAT_FAILED:
255 		syslog(LOG_ERR, "%s: stat of password file failed", service);
256 		res = PAM_AUTHTOK_ERR;
257 		break;
258 	case PWU_OPEN_FAILED:
259 	case PWU_WRITE_FAILED:
260 	case PWU_CLOSE_FAILED:
261 	case PWU_UPDATE_FAILED:
262 		error(nowarn, pamh, dgettext(TEXT_DOMAIN,
263 		    "%s: Unexpected failure. Password database unchanged."),
264 		    service);
265 		res = PAM_SYSTEM_ERR;
266 		break;
267 	case PWU_NOT_FOUND:
268 		/* Different error if repository was explicitly specified */
269 		if (auth_rep != NULL) {
270 			error(nowarn, pamh, dgettext(TEXT_DOMAIN,
271 				"%s: System error: no %s password for %s."),
272 				service, auth_rep->type, user);
273 		} else {
274 			error(nowarn, pamh, dgettext(TEXT_DOMAIN,
275 			    "%s: %s does not exist."), service, user);
276 		}
277 		res = PAM_USER_UNKNOWN;
278 		break;
279 	case PWU_NOMEM:
280 		error(nowarn, pamh, dgettext(TEXT_DOMAIN,
281 			"%s: Internal memory allocation failure."), service);
282 		res = PAM_BUF_ERR;
283 		break;
284 	case PWU_SERVER_ERROR:
285 		res = PAM_SYSTEM_ERR;
286 		break;
287 	case PWU_SYSTEM_ERROR:
288 		res = PAM_SYSTEM_ERR;
289 		break;
290 	case PWU_DENIED:
291 		res = PAM_PERM_DENIED;
292 		break;
293 	case PWU_NO_CHANGE:
294 		/*
295 		 * yppasswdd detected that we're not changing anything.
296 		 */
297 		info(nowarn, pamh, dgettext(TEXT_DOMAIN,
298 		    "%s: Password information unchanged."), service);
299 		res = PAM_SUCCESS;
300 		break;
301 	case PWU_REPOSITORY_ERROR:
302 		syslog(LOG_NOTICE, "pam_authtok_store: detected "
303 		    "unsupported configuration in /etc/nsswitch.conf.");
304 		error(nowarn, pamh, dgettext(TEXT_DOMAIN,
305 		    "%s: System error: repository out of range."), service);
306 		res = PAM_SYSTEM_ERR;
307 		break;
308 	case PWU_RECOVERY_ERR:
309 		res = PAM_AUTHTOK_RECOVERY_ERR;
310 		break;
311 	case PWU_NO_PRIV_CRED_UPDATE:
312 		/*
313 		 * A privileged process has updated a user's password.
314 		 * In this case, the password will be updated, but the
315 		 * credentials won't. This is not a failure, but we need
316 		 * to inform the user about it, and return PAM_SUCCESS
317 		 */
318 
319 		/* First inform the user about the passsword update */
320 		info(nowarn, pamh, dgettext(TEXT_DOMAIN,
321 			"%s: password successfully changed for %s"),
322 			service, user);
323 
324 		/* and now the bad news */
325 		(void) sprintf(msg[0], " ");
326 		(void) snprintf(msg[1], sizeof (msg[1]),
327 		    dgettext(TEXT_DOMAIN,
328 			"The Secure RPC credential information for %s "
329 			"will not be changed."), user);
330 		(void) snprintf(msg[2], sizeof (msg[2]),
331 		    dgettext(TEXT_DOMAIN, "User %s must do the following to "
332 		    "update his/her"), user);
333 		(void) snprintf(msg[3], sizeof (msg[3]),
334 		    dgettext(TEXT_DOMAIN, "credential information:"));
335 		(void) snprintf(msg[4], sizeof (msg[4]),
336 		    dgettext(TEXT_DOMAIN, "Use NEW passwd for login and OLD "
337 		    "passwd for keylogin."));
338 		(void) snprintf(msg[5], sizeof (msg[5]),
339 		    dgettext(TEXT_DOMAIN, "Use \"chkey -p\" to reencrypt the "
340 		    "credentials with the"));
341 		(void) snprintf(msg[6], sizeof (msg[6]),
342 		    dgettext(TEXT_DOMAIN, "new login passwd."));
343 		(void) snprintf(msg[7], sizeof (msg[7]),
344 		    dgettext(TEXT_DOMAIN, "The user must keylogin explicitly "
345 		    "after their next login."));
346 		(void) sprintf(msg[8], " ");
347 		(void) __pam_display_msg(pamh, PAM_ERROR_MSG, 9, msg, NULL);
348 		res = PAM_SUCCESS;
349 		break;
350 	case PWU_UPDATED_SOME_CREDS:
351 		info(nowarn, pamh, dgettext(TEXT_DOMAIN,
352 			"%s: password successfully changed for %s"),
353 			service, user);
354 
355 		error(nowarn, pamh, dgettext(TEXT_DOMAIN,
356 		    "WARNING: some but not all credentials were reencrypted "
357 		    "for user %s"), user);
358 		res = PAM_SUCCESS;
359 		break;
360 	case PWU_PWD_TOO_SHORT:
361 		(void) snprintf(msg[0], sizeof (msg[0]),
362 		    dgettext(TEXT_DOMAIN, "%s: Password too short."), service);
363 		(void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1, msg, NULL);
364 		res = PAM_AUTHTOK_ERR;
365 		break;
366 	case PWU_PWD_INVALID:
367 		(void) snprintf(msg[0], sizeof (msg[0]),
368 		    dgettext(TEXT_DOMAIN, "%s: Invalid password syntax."),
369 			service);
370 		(void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1, msg, NULL);
371 		res = PAM_AUTHTOK_ERR;
372 		break;
373 	case PWU_PWD_IN_HISTORY:
374 		(void) snprintf(msg[0], sizeof (msg[0]),
375 		    dgettext(TEXT_DOMAIN, "%s: Reuse of old passwords not "
376 			"allowed, the new password is in the history list."),
377 			service);
378 		(void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1, msg, NULL);
379 		res = PAM_AUTHTOK_ERR;
380 		break;
381 	case PWU_CHANGE_NOT_ALLOWED:
382 		(void) snprintf(msg[0], sizeof (msg[0]),
383 		    dgettext(TEXT_DOMAIN, "%s: You may not change "
384 			"this password."), service);
385 		(void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1, msg, NULL);
386 		res = PAM_PERM_DENIED;
387 		break;
388 	case PWU_WITHIN_MIN_AGE:
389 		(void) snprintf(msg[0], sizeof (msg[0]),
390 		    dgettext(TEXT_DOMAIN,
391 			"%s: Password can not be changed yet, "
392 			"not enough time has passed."), service);
393 		(void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1, msg, NULL);
394 		res = PAM_PERM_DENIED;
395 		break;
396 	default:
397 		res = PAM_SYSTEM_ERR;
398 		break;
399 	}
400 
401 	return (res);
402 }
403