xref: /illumos-gate/usr/src/lib/pam_modules/authtok_store/authtok_store.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 (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  * Copyright 2023 OmniOS Community Edition (OmniOSce) Association.
26  */
27 
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 #include <shadow.h>
43 
44 /*PRINTFLIKE3*/
45 static void
error(int nowarn,pam_handle_t * pamh,char * fmt,...)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
info(int nowarn,pam_handle_t * pamh,char * fmt,...)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
authtok_is_aged(pam_handle_t * pamh)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 	const char *user;
101 	const char *oldpw;
102 	const char *newpw;
103 	const char *service;
104 	const struct pam_repository *auth_rep;
105 	int res;
106 	char msg[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
107 	int updated_reps = 0;
108 	int server_policy = 0;
109 
110 	for (i = 0; i < argc; i++) {
111 		if (strcmp(argv[i], "debug") == 0)
112 			debug = 1;
113 		else if (strcmp(argv[i], "nowarn") == 0)
114 			nowarn = 1;
115 		else if (strcmp(argv[i], "server_policy") == 0)
116 			server_policy = 1;
117 	}
118 
119 	if ((flags & PAM_PRELIM_CHECK) != 0)
120 		return (PAM_IGNORE);
121 
122 	if ((flags & PAM_UPDATE_AUTHTOK) == 0)
123 		return (PAM_SYSTEM_ERR);
124 
125 	if ((flags & PAM_SILENT) != 0)
126 		nowarn = 1;
127 
128 	if (debug)
129 		syslog(LOG_DEBUG, "pam_authtok_store: storing authtok");
130 
131 #if defined(ENABLE_AGING)
132 	if ((flags & PAM_CHANGE_EXPIRED_AUTHTOK) && !authtok_is_aged(pamh)) {
133 		syslog(LOG_DEBUG, "pam_authtok_store: System password young");
134 		return (PAM_IGNORE);
135 	}
136 #endif
137 
138 	res = pam_get_item(pamh, PAM_SERVICE, (const void **)&service);
139 	if (res != PAM_SUCCESS) {
140 		syslog(LOG_ERR, "pam_authtok_store: error getting SERVICE");
141 		return (PAM_SYSTEM_ERR);
142 	}
143 
144 	res = pam_get_item(pamh, PAM_USER, (const void **)&user);
145 	if (res != PAM_SUCCESS) {
146 		syslog(LOG_ERR, "pam_authtok_store: error getting USER");
147 		return (PAM_SYSTEM_ERR);
148 	}
149 
150 	if (user == NULL || *user == '\0') {
151 		syslog(LOG_ERR, "pam_authtok_store: username is empty");
152 		return (PAM_USER_UNKNOWN);
153 	}
154 
155 	res = pam_get_item(pamh, PAM_OLDAUTHTOK, (const void **)&oldpw);
156 	if (res != PAM_SUCCESS) {
157 		syslog(LOG_ERR, "pam_authtok_store: error getting OLDAUTHTOK");
158 		return (PAM_SYSTEM_ERR);
159 	}
160 
161 	res = pam_get_item(pamh, PAM_AUTHTOK, (const void **)&newpw);
162 	if (res != PAM_SUCCESS || newpw == NULL) {
163 		/*
164 		 * A module on the stack has removed PAM_AUTHTOK. We fail
165 		 */
166 		return (PAM_SYSTEM_ERR);
167 	}
168 
169 	l.data.val_s = strdup(newpw);
170 	if (l.data.val_s == NULL)
171 		return (PAM_BUF_ERR);
172 	/*
173 	 * If the server_policy option is specified,
174 	 * use the special attribute, ATTR_PASSWD_SERVER_POLICY,
175 	 * to tell the update routine for each repository
176 	 * to perform the necessary special operations.
177 	 * For now, only the LDAP routine treats this attribute
178 	 * differently that ATTR_PASSWD. It will skip the
179 	 * crypting of the password before storing it in the LDAP
180 	 * server. NIS, and FILES will handle ATTR_PASSWD_SERVER_POLICY
181 	 * the same as ATTR_PASSWD.
182 	 */
183 	if (server_policy)
184 		l.type = ATTR_PASSWD_SERVER_POLICY;
185 	else
186 		l.type = ATTR_PASSWD;
187 	l.next = NULL;
188 
189 	res = pam_get_item(pamh, PAM_REPOSITORY, (const void **)&auth_rep);
190 	if (res != PAM_SUCCESS) {
191 		free(l.data.val_s);
192 		syslog(LOG_ERR, "pam_authtok_store: error getting repository");
193 		return (PAM_SYSTEM_ERR);
194 	}
195 
196 	if (auth_rep == NULL) {
197 		pwu_rep = PWU_DEFAULT_REP;
198 	} else {
199 		if ((pwu_rep = calloc(1, sizeof (*pwu_rep))) == NULL) {
200 			free(l.data.val_s);
201 			return (PAM_BUF_ERR);
202 		}
203 		pwu_rep->type = auth_rep->type;
204 		pwu_rep->scope = auth_rep->scope;
205 		pwu_rep->scope_len = auth_rep->scope_len;
206 	}
207 
208 	res = __set_authtoken_attr(user, oldpw, pwu_rep, &l, &updated_reps);
209 	free(l.data.val_s);
210 
211 	if (pwu_rep != PWU_DEFAULT_REP)
212 		free(pwu_rep);
213 	/*
214 	 * now map the various passwdutil return states to user messages
215 	 * and PAM return codes.
216 	 */
217 	switch (res) {
218 	case PWU_SUCCESS:
219 		for (i = 1; i <= REP_LAST; i <<= 1) {
220 			if ((updated_reps & i) == 0)
221 				continue;
222 			info(nowarn, pamh, dgettext(TEXT_DOMAIN,
223 			    "%s: password successfully changed for %s"),
224 			    service, user);
225 		}
226 		res = PAM_SUCCESS;
227 		break;
228 	case PWU_BUSY:
229 		error(nowarn, pamh, dgettext(TEXT_DOMAIN,
230 		    "%s: Password database busy. Try again later."),
231 		    service);
232 		res = PAM_AUTHTOK_LOCK_BUSY;
233 		break;
234 	case PWU_STAT_FAILED:
235 		syslog(LOG_ERR, "%s: stat of password file failed", service);
236 		res = PAM_AUTHTOK_ERR;
237 		break;
238 	case PWU_OPEN_FAILED:
239 	case PWU_WRITE_FAILED:
240 	case PWU_CLOSE_FAILED:
241 	case PWU_UPDATE_FAILED:
242 		error(nowarn, pamh, dgettext(TEXT_DOMAIN,
243 		    "%s: Unexpected failure. Password database unchanged."),
244 		    service);
245 		res = PAM_SYSTEM_ERR;
246 		break;
247 	case PWU_NOT_FOUND:
248 		/* Different error if repository was explicitly specified */
249 		if (auth_rep != NULL) {
250 			error(nowarn, pamh, dgettext(TEXT_DOMAIN,
251 			    "%s: System error: no %s password for %s."),
252 			    service, auth_rep->type, user);
253 		} else {
254 			error(nowarn, pamh, dgettext(TEXT_DOMAIN,
255 			    "%s: %s does not exist."), service, user);
256 		}
257 		res = PAM_USER_UNKNOWN;
258 		break;
259 	case PWU_NOMEM:
260 		error(nowarn, pamh, dgettext(TEXT_DOMAIN,
261 		    "%s: Internal memory allocation failure."), service);
262 		res = PAM_BUF_ERR;
263 		break;
264 	case PWU_SERVER_ERROR:
265 		res = PAM_SYSTEM_ERR;
266 		break;
267 	case PWU_SYSTEM_ERROR:
268 		res = PAM_SYSTEM_ERR;
269 		break;
270 	case PWU_DENIED:
271 		res = PAM_PERM_DENIED;
272 		break;
273 	case PWU_NO_CHANGE:
274 		/*
275 		 * yppasswdd detected that we're not changing anything.
276 		 */
277 		info(nowarn, pamh, dgettext(TEXT_DOMAIN,
278 		    "%s: Password information unchanged."), service);
279 		res = PAM_SUCCESS;
280 		break;
281 	case PWU_REPOSITORY_ERROR:
282 		syslog(LOG_NOTICE, "pam_authtok_store: detected "
283 		    "unsupported configuration in /etc/nsswitch.conf.");
284 		error(nowarn, pamh, dgettext(TEXT_DOMAIN,
285 		    "%s: System error: repository out of range."), service);
286 		res = PAM_SYSTEM_ERR;
287 		break;
288 	case PWU_PWD_TOO_SHORT:
289 		(void) snprintf(msg[0], sizeof (msg[0]),
290 		    dgettext(TEXT_DOMAIN, "%s: Password too short."), service);
291 		(void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1, msg, NULL);
292 		res = PAM_AUTHTOK_ERR;
293 		break;
294 	case PWU_PWD_INVALID:
295 		(void) snprintf(msg[0], sizeof (msg[0]),
296 		    dgettext(TEXT_DOMAIN, "%s: Invalid password syntax."),
297 		    service);
298 		(void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1, msg, NULL);
299 		res = PAM_AUTHTOK_ERR;
300 		break;
301 	case PWU_PWD_IN_HISTORY:
302 		(void) snprintf(msg[0], sizeof (msg[0]),
303 		    dgettext(TEXT_DOMAIN, "%s: Reuse of old passwords not "
304 		    "allowed, the new password is in the history list."),
305 		    service);
306 		(void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1, msg, NULL);
307 		res = PAM_AUTHTOK_ERR;
308 		break;
309 	case PWU_CHANGE_NOT_ALLOWED:
310 		(void) snprintf(msg[0], sizeof (msg[0]),
311 		    dgettext(TEXT_DOMAIN, "%s: You may not change "
312 		    "this password."), service);
313 		(void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1, msg, NULL);
314 		res = PAM_PERM_DENIED;
315 		break;
316 	case PWU_WITHIN_MIN_AGE:
317 		(void) snprintf(msg[0], sizeof (msg[0]),
318 		    dgettext(TEXT_DOMAIN,
319 		    "%s: Password can not be changed yet, "
320 		    "not enough time has passed."), service);
321 		(void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1, msg, NULL);
322 		res = PAM_PERM_DENIED;
323 		break;
324 	default:
325 		res = PAM_SYSTEM_ERR;
326 		break;
327 	}
328 
329 	return (res);
330 }
331