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 2009 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 
27 #include <sys/types.h>
28 #include <sys/varargs.h>
29 #include <string.h>
30 #include <syslog.h>
31 #include <stdlib.h>
32 
33 #include <security/pam_appl.h>
34 #include <security/pam_modules.h>
35 #include <security/pam_impl.h>
36 
37 #include <libintl.h>
38 
39 #include <passwdutil.h>
40 #include <shadow.h>
41 
42 /*PRINTFLIKE3*/
43 static void
error(int nowarn,pam_handle_t * pamh,char * fmt,...)44 error(int nowarn, pam_handle_t *pamh, char *fmt, ...)
45 {
46 	va_list ap;
47 	char messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
48 
49 	va_start(ap, fmt);
50 	(void) vsnprintf(messages[0], sizeof (messages[0]), fmt, ap);
51 	if (nowarn == 0)
52 		(void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1, messages,
53 		    NULL);
54 	va_end(ap);
55 }
56 
57 /*PRINTFLIKE3*/
58 static void
info(int nowarn,pam_handle_t * pamh,char * fmt,...)59 info(int nowarn, pam_handle_t *pamh, char *fmt, ...)
60 {
61 	va_list ap;
62 	char messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
63 
64 	va_start(ap, fmt);
65 	(void) vsnprintf(messages[0], sizeof (messages[0]), fmt, ap);
66 	if (nowarn == 0)
67 		(void) __pam_display_msg(pamh, PAM_TEXT_INFO, 1, messages,
68 		    NULL);
69 	va_end(ap);
70 }
71 
72 #if defined(ENABLE_AGING)
73 /*
74  * test if authtok is aged.
75  * returns 1 if it is, 0 otherwise
76  */
77 static int
authtok_is_aged(pam_handle_t * pamh)78 authtok_is_aged(pam_handle_t *pamh)
79 {
80 	unix_authtok_data *status;
81 
82 	if (pam_get_data(pamh, UNIX_AUTHTOK_DATA,
83 	    (const void **)status) != PAM_SUCCESS)
84 		return (0);
85 
86 	return (status->age_status == PAM_NEW_AUTHTOK_REQD)
87 }
88 #endif
89 
90 int
91 pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv)
92 {
93 	int i;
94 	int debug = 0;
95 	int nowarn = 0;
96 	attrlist l;
97 	pwu_repository_t *pwu_rep;
98 	char *user;
99 	char *oldpw;
100 	char *newpw;
101 	char *service;
102 	struct pam_repository *auth_rep;
103 	int res;
104 	char msg[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
105 	int updated_reps = 0;
106 	int server_policy = 0;
107 
108 	for (i = 0; i < argc; i++) {
109 		if (strcmp(argv[i], "debug") == 0)
110 			debug = 1;
111 		else if (strcmp(argv[i], "nowarn") == 0)
112 			nowarn = 1;
113 		else if (strcmp(argv[i], "server_policy") == 0)
114 			server_policy = 1;
115 	}
116 
117 	if ((flags & PAM_PRELIM_CHECK) != 0)
118 		return (PAM_IGNORE);
119 
120 	if ((flags & PAM_UPDATE_AUTHTOK) == 0)
121 		return (PAM_SYSTEM_ERR);
122 
123 	if ((flags & PAM_SILENT) != 0)
124 		nowarn = 1;
125 
126 	if (debug)
127 		syslog(LOG_DEBUG, "pam_authtok_store: storing authtok");
128 
129 #if defined(ENABLE_AGING)
130 	if ((flags & PAM_CHANGE_EXPIRED_AUTHTOK) && !authtok_is_aged(pamh)) {
131 		syslog(LOG_DEBUG, "pam_authtok_store: System password young");
132 		return (PAM_IGNORE);
133 	}
134 #endif
135 
136 	res = pam_get_item(pamh, PAM_SERVICE, (void **)&service);
137 	if (res != PAM_SUCCESS) {
138 		syslog(LOG_ERR, "pam_authtok_store: error getting SERVICE");
139 		return (PAM_SYSTEM_ERR);
140 	}
141 
142 	res = pam_get_item(pamh, PAM_USER, (void **)&user);
143 	if (res != PAM_SUCCESS) {
144 		syslog(LOG_ERR, "pam_authtok_store: error getting USER");
145 		return (PAM_SYSTEM_ERR);
146 	}
147 
148 	if (user == NULL || *user == '\0') {
149 		syslog(LOG_ERR, "pam_authtok_store: username is empty");
150 		return (PAM_USER_UNKNOWN);
151 	}
152 
153 	res = pam_get_item(pamh, PAM_OLDAUTHTOK, (void **)&oldpw);
154 	if (res != PAM_SUCCESS) {
155 		syslog(LOG_ERR, "pam_authtok_store: error getting OLDAUTHTOK");
156 		return (PAM_SYSTEM_ERR);
157 	}
158 
159 	res = pam_get_item(pamh, PAM_AUTHTOK, (void **)&newpw);
160 	if (res != PAM_SUCCESS || newpw == NULL) {
161 		/*
162 		 * A module on the stack has removed PAM_AUTHTOK. We fail
163 		 */
164 		return (PAM_SYSTEM_ERR);
165 	}
166 
167 	l.data.val_s = newpw;
168 	/*
169 	 * If the server_policy option is specified,
170 	 * use the special attribute, ATTR_PASSWD_SERVER_POLICY,
171 	 * to tell the update routine for each repository
172 	 * to perform the necessary special operations.
173 	 * For now, only the LDAP routine treats this attribute
174 	 * differently that ATTR_PASSWD. It will skip the
175 	 * crypting of the password before storing it in the LDAP
176 	 * server. NIS, and FILES will handle ATTR_PASSWD_SERVER_POLICY
177 	 * the same as ATTR_PASSWD.
178 	 */
179 	if (server_policy)
180 		l.type = ATTR_PASSWD_SERVER_POLICY;
181 	else
182 		l.type = ATTR_PASSWD;
183 	l.next = NULL;
184 
185 	res = pam_get_item(pamh, PAM_REPOSITORY, (void **)&auth_rep);
186 	if (res != PAM_SUCCESS) {
187 		syslog(LOG_ERR, "pam_authtok_store: error getting repository");
188 		return (PAM_SYSTEM_ERR);
189 	}
190 
191 	if (auth_rep == NULL) {
192 		pwu_rep = PWU_DEFAULT_REP;
193 	} else {
194 		if ((pwu_rep = calloc(1, sizeof (*pwu_rep))) == NULL)
195 			return (PAM_BUF_ERR);
196 		pwu_rep->type = auth_rep->type;
197 		pwu_rep->scope = auth_rep->scope;
198 		pwu_rep->scope_len = auth_rep->scope_len;
199 	}
200 
201 	res = __set_authtoken_attr(user, oldpw, pwu_rep, &l, &updated_reps);
202 
203 	if (pwu_rep != PWU_DEFAULT_REP)
204 		free(pwu_rep);
205 	/*
206 	 * now map the various passwdutil return states to user messages
207 	 * and PAM return codes.
208 	 */
209 	switch (res) {
210 	case PWU_SUCCESS:
211 		for (i = 1; i <= REP_LAST; i <<= 1) {
212 			if ((updated_reps & i) == 0)
213 				continue;
214 			info(nowarn, pamh, dgettext(TEXT_DOMAIN,
215 			    "%s: password successfully changed for %s"),
216 			    service, user);
217 		}
218 		res = PAM_SUCCESS;
219 		break;
220 	case PWU_BUSY:
221 		error(nowarn, pamh, dgettext(TEXT_DOMAIN,
222 		    "%s: Password database busy. Try again later."),
223 		    service);
224 		res = PAM_AUTHTOK_LOCK_BUSY;
225 		break;
226 	case PWU_STAT_FAILED:
227 		syslog(LOG_ERR, "%s: stat of password file failed", service);
228 		res = PAM_AUTHTOK_ERR;
229 		break;
230 	case PWU_OPEN_FAILED:
231 	case PWU_WRITE_FAILED:
232 	case PWU_CLOSE_FAILED:
233 	case PWU_UPDATE_FAILED:
234 		error(nowarn, pamh, dgettext(TEXT_DOMAIN,
235 		    "%s: Unexpected failure. Password database unchanged."),
236 		    service);
237 		res = PAM_SYSTEM_ERR;
238 		break;
239 	case PWU_NOT_FOUND:
240 		/* Different error if repository was explicitly specified */
241 		if (auth_rep != NULL) {
242 			error(nowarn, pamh, dgettext(TEXT_DOMAIN,
243 			    "%s: System error: no %s password for %s."),
244 			    service, auth_rep->type, user);
245 		} else {
246 			error(nowarn, pamh, dgettext(TEXT_DOMAIN,
247 			    "%s: %s does not exist."), service, user);
248 		}
249 		res = PAM_USER_UNKNOWN;
250 		break;
251 	case PWU_NOMEM:
252 		error(nowarn, pamh, dgettext(TEXT_DOMAIN,
253 		    "%s: Internal memory allocation failure."), service);
254 		res = PAM_BUF_ERR;
255 		break;
256 	case PWU_SERVER_ERROR:
257 		res = PAM_SYSTEM_ERR;
258 		break;
259 	case PWU_SYSTEM_ERROR:
260 		res = PAM_SYSTEM_ERR;
261 		break;
262 	case PWU_DENIED:
263 		res = PAM_PERM_DENIED;
264 		break;
265 	case PWU_NO_CHANGE:
266 		/*
267 		 * yppasswdd detected that we're not changing anything.
268 		 */
269 		info(nowarn, pamh, dgettext(TEXT_DOMAIN,
270 		    "%s: Password information unchanged."), service);
271 		res = PAM_SUCCESS;
272 		break;
273 	case PWU_REPOSITORY_ERROR:
274 		syslog(LOG_NOTICE, "pam_authtok_store: detected "
275 		    "unsupported configuration in /etc/nsswitch.conf.");
276 		error(nowarn, pamh, dgettext(TEXT_DOMAIN,
277 		    "%s: System error: repository out of range."), service);
278 		res = PAM_SYSTEM_ERR;
279 		break;
280 	case PWU_PWD_TOO_SHORT:
281 		(void) snprintf(msg[0], sizeof (msg[0]),
282 		    dgettext(TEXT_DOMAIN, "%s: Password too short."), service);
283 		(void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1, msg, NULL);
284 		res = PAM_AUTHTOK_ERR;
285 		break;
286 	case PWU_PWD_INVALID:
287 		(void) snprintf(msg[0], sizeof (msg[0]),
288 		    dgettext(TEXT_DOMAIN, "%s: Invalid password syntax."),
289 		    service);
290 		(void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1, msg, NULL);
291 		res = PAM_AUTHTOK_ERR;
292 		break;
293 	case PWU_PWD_IN_HISTORY:
294 		(void) snprintf(msg[0], sizeof (msg[0]),
295 		    dgettext(TEXT_DOMAIN, "%s: Reuse of old passwords not "
296 		    "allowed, the new password is in the history list."),
297 		    service);
298 		(void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1, msg, NULL);
299 		res = PAM_AUTHTOK_ERR;
300 		break;
301 	case PWU_CHANGE_NOT_ALLOWED:
302 		(void) snprintf(msg[0], sizeof (msg[0]),
303 		    dgettext(TEXT_DOMAIN, "%s: You may not change "
304 		    "this password."), service);
305 		(void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1, msg, NULL);
306 		res = PAM_PERM_DENIED;
307 		break;
308 	case PWU_WITHIN_MIN_AGE:
309 		(void) snprintf(msg[0], sizeof (msg[0]),
310 		    dgettext(TEXT_DOMAIN,
311 		    "%s: Password can not be changed yet, "
312 		    "not enough time has passed."), service);
313 		(void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1, msg, NULL);
314 		res = PAM_PERM_DENIED;
315 		break;
316 	default:
317 		res = PAM_SYSTEM_ERR;
318 		break;
319 	}
320 
321 	return (res);
322 }
323