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