xref: /illumos-gate/usr/src/lib/pam_modules/unix_auth/unix_auth.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 <stdlib.h>
30 #include <pwd.h>
31 #include <shadow.h>
32 #include <syslog.h>
33 #include <errno.h>
34 #include <string.h>
35 #include <crypt.h>
36 #include <unistd.h>
37 #include <user_attr.h>
38 #include <auth_attr.h>
39 #include <userdefs.h>
40 #include <deflt.h>
41 #include <sys/stat.h>
42 #include <sys/param.h>
43 #include <stdarg.h>
44 
45 #include <security/pam_appl.h>
46 #include <security/pam_modules.h>
47 #include <security/pam_impl.h>
48 
49 #include <libintl.h>
50 
51 #include <passwdutil.h>
52 
53 #define	LOGINADMIN	"/etc/default/login"
54 #define	MAXTRYS		5
55 
56 /*PRINTFLIKE2*/
57 void
error(pam_handle_t * pamh,char * fmt,...)58 error(pam_handle_t *pamh, char *fmt, ...)
59 {
60 	va_list ap;
61 	char messages[1][PAM_MAX_MSG_SIZE];
62 
63 	va_start(ap, fmt);
64 	(void) vsnprintf(messages[0], sizeof (messages[0]), fmt, ap);
65 	(void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1, messages, NULL);
66 	va_end(ap);
67 }
68 
69 static int
get_max_failed(const char * user)70 get_max_failed(const char *user)
71 {
72 	char *val = NULL;
73 	userattr_t *uattr;
74 	int do_lock = 0;
75 	int retval = 0;
76 	char *p;
77 	void	*defp;
78 
79 	if ((uattr = getusernam(user)) != NULL)
80 		val = kva_match(uattr->attr, USERATTR_LOCK_AFTER_RETRIES_KW);
81 
82 	if (val != NULL) {
83 		do_lock = (strcasecmp(val, "yes") == 0);
84 	} else if ((defp = defopen_r(AUTH_POLICY)) != NULL) {
85 		int flags;
86 		flags = defcntl_r(DC_GETFLAGS, 0, defp);
87 		TURNOFF(flags, DC_CASE);
88 		(void) defcntl_r(DC_SETFLAGS, flags, defp);
89 		if ((p = defread_r("LOCK_AFTER_RETRIES=", defp)) != NULL)
90 			do_lock = (strcasecmp(p, "yes") == 0);
91 		defclose_r(defp);
92 	}
93 
94 	if (uattr != NULL)
95 		free_userattr(uattr);
96 
97 	if (do_lock) {
98 		retval = MAXTRYS;
99 		if ((defp = defopen_r(LOGINADMIN)) != NULL) {
100 			if ((p = defread_r("RETRIES=", defp)) != NULL)
101 				retval = atoi(p);
102 			defclose_r(defp);
103 		}
104 	}
105 
106 	return (retval);
107 }
108 
109 static void
display_warning(pam_handle_t * pamh,int failures,char * homedir)110 display_warning(pam_handle_t *pamh, int failures, char *homedir)
111 {
112 	char hushpath[MAXPATHLEN];
113 	struct stat buf;
114 
115 	(void) snprintf(hushpath, sizeof (hushpath), "%s/.hushlogin", homedir);
116 	if (stat(hushpath, &buf) == 0)
117 		return;
118 
119 	if (failures == 1)
120 		error(pamh, "Warning: 1 failed login attempt since last "
121 		    "successful login.");
122 	else if (failures < FAILCOUNT_MASK)
123 		error(pamh, "Warning: %d failed login attempts since last "
124 		    "successful login.", failures);
125 	else
126 		error(pamh, "Warning: at least %d failed login attempts since "
127 		    "last successful login.", failures);
128 }
129 
130 /*
131  * int pam_sm_authenticate(pamh, flags, arc, argv)
132  *
133  * This routine verifies that the password as stored in the
134  * PAM_AUTHTOK item is indeed the password that belongs to the user
135  * as stored in PAM_USER.
136  *
137  * This routine will not establish Secure RPC Credentials, the pam_dhkeys
138  * module should be stacked before us if Secure RPC Credentials are needed
139  * to obtain passwords.
140  */
141 int
pam_sm_authenticate(pam_handle_t * pamh,int flags,int argc,const char ** argv)142 pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv)
143 {
144 	int i;
145 	int debug = 0;
146 	int nowarn = (flags & PAM_SILENT) != 0;
147 	const char *user;
148 	const char *passwd;
149 	char *rep_passwd;
150 	char *crypt_passwd;
151 	char *repository_name;
152 	const struct pam_repository *auth_rep;
153 	pwu_repository_t *pwu_rep;
154 	attrlist attr_pw[4];
155 	int result;
156 	int server_policy = 0;
157 	int old_failed_count;
158 	char *homedir = NULL;
159 	int dolock = 1;
160 
161 	for (i = 0; i < argc; i++) {
162 		if (strcmp(argv[i], "debug") == 0)
163 			debug = 1;
164 		else if (strcmp(argv[i], "nowarn") == 0)
165 			nowarn = 1;
166 		else if (strcmp(argv[i], "server_policy") == 0)
167 			server_policy = 1;
168 		else if (strcmp(argv[i], "nolock") == 0)
169 			dolock = 0;
170 	}
171 
172 	if (debug)
173 		__pam_log(LOG_AUTH | LOG_DEBUG,
174 		    "pam_unix_auth: entering pam_sm_authenticate()");
175 
176 	if (pam_get_item(pamh, PAM_USER, (const void **)&user) != PAM_SUCCESS) {
177 		__pam_log(LOG_AUTH | LOG_DEBUG, "pam_unix_auth: USER not set");
178 		return (PAM_SYSTEM_ERR);
179 	}
180 
181 	if (user == NULL || *user == '\0') {
182 		__pam_log(LOG_AUTH | LOG_DEBUG,
183 		    "pam_unix_auth: USER NULL or empty!\n");
184 		return (PAM_USER_UNKNOWN);
185 	}
186 
187 	if (pam_get_item(pamh, PAM_AUTHTOK, (const void **)&passwd) !=
188 	    PAM_SUCCESS) {
189 		__pam_log(LOG_AUTH | LOG_DEBUG,
190 		    "pam_unix_auth: AUTHTOK not set!\n");
191 		return (PAM_SYSTEM_ERR);
192 	}
193 
194 	result = pam_get_item(pamh, PAM_REPOSITORY, (const void **)&auth_rep);
195 	if (result == PAM_SUCCESS && auth_rep != NULL) {
196 		if ((pwu_rep = calloc(1, sizeof (*pwu_rep))) == NULL)
197 			return (PAM_BUF_ERR);
198 		pwu_rep->type = auth_rep->type;
199 		pwu_rep->scope = auth_rep->scope;
200 		pwu_rep->scope_len = auth_rep->scope_len;
201 	} else {
202 		pwu_rep = PWU_DEFAULT_REP;
203 	}
204 
205 	/*
206 	 * Get password and the name of the repository where the
207 	 * password resides.
208 	 */
209 	attr_pw[0].type = ATTR_PASSWD;		attr_pw[0].next = &attr_pw[1];
210 	attr_pw[1].type = ATTR_REP_NAME;	attr_pw[1].next = &attr_pw[2];
211 	/*
212 	 * Also get the current number of failed logins; we use
213 	 * this later to determine whether we need to reset the count
214 	 * on a succesful authentication. We use the home-directory
215 	 * to look for .hushlogin in order to optionaly surpress the
216 	 * "failed attempts" message.
217 	 */
218 	attr_pw[2].type = ATTR_FAILED_LOGINS;	attr_pw[2].next = &attr_pw[3];
219 	attr_pw[3].type = ATTR_HOMEDIR;		attr_pw[3].next = NULL;
220 
221 	result = __get_authtoken_attr(user, pwu_rep, attr_pw);
222 
223 	if (pwu_rep != PWU_DEFAULT_REP)
224 		free(pwu_rep);
225 
226 	if (result == PWU_NOT_FOUND) {
227 		__pam_log(LOG_AUTH | LOG_DEBUG,
228 		    "pam_unix_auth: user %s not found\n", user);
229 		return (PAM_USER_UNKNOWN);
230 	}
231 
232 	if (result == PWU_DENIED) {
233 		__pam_log(LOG_AUTH | LOG_DEBUG,
234 		    "pam_unix_auth: failed to obtain attributes");
235 		return (PAM_PERM_DENIED);
236 	}
237 
238 	if (result != PWU_SUCCESS)
239 		return (PAM_SYSTEM_ERR);
240 
241 	rep_passwd = attr_pw[0].data.val_s;
242 	repository_name = attr_pw[1].data.val_s;
243 	old_failed_count = attr_pw[2].data.val_i;
244 	homedir = attr_pw[3].data.val_s;
245 
246 	/*
247 	 * Chop off old SunOS-style password aging information.
248 	 *
249 	 * Note: old style password aging is only defined for UNIX-style
250 	 *	 crypt strings, hence the comma will always be at position 14.
251 	 * Note: This code is here because some other vendors might still
252 	 *	 support this style of password aging. If we don't remove
253 	 *	 the age field, no one will be able to login.
254 	 * XXX   yank this code when we're certain this "compatibility"
255 	 *	 isn't needed anymore.
256 	 */
257 	if (rep_passwd != NULL && rep_passwd[0] != '$' &&
258 	    strlen(rep_passwd) > 13 && rep_passwd[13] == ',')
259 		rep_passwd[13] = '\0';
260 
261 	/* Is a password check required? */
262 	if (rep_passwd == NULL || *rep_passwd == '\0') {
263 		if (flags & PAM_DISALLOW_NULL_AUTHTOK) {
264 			result = PAM_AUTH_ERR;
265 			__pam_log(LOG_AUTH | LOG_NOTICE,
266 			    "pam_unix_auth: empty password for %s not allowed.",
267 			    user);
268 			goto out;
269 		} else {
270 			result = PAM_SUCCESS;
271 			goto out;
272 		}
273 	}
274 
275 	/*
276 	 * Password check *is* required. Make sure we have a valid
277 	 * pointer in PAM_AUTHTOK
278 	 */
279 	if (passwd == NULL) {
280 		result = PAM_AUTH_ERR;
281 		goto out;
282 	}
283 
284 	if (server_policy &&
285 	    strcmp(repository_name, "files") != 0 &&
286 	    strcmp(repository_name, "nis") != 0) {
287 		result = PAM_IGNORE;
288 		goto out;
289 	}
290 
291 	/* Now check the entered password */
292 	if ((crypt_passwd = crypt(passwd, rep_passwd)) == NULL) {
293 		switch (errno) {
294 		case ENOMEM:
295 			result = PAM_BUF_ERR;
296 			break;
297 		case ELIBACC:
298 			result = PAM_OPEN_ERR;
299 			break;
300 		default:
301 			result = PAM_SYSTEM_ERR;
302 		}
303 		goto out;
304 	}
305 
306 	if (strcmp(crypt_passwd, rep_passwd) == 0)
307 		result = PAM_SUCCESS;
308 	else
309 		result = PAM_AUTH_ERR;
310 
311 	/* Clear or increment failed failed count */
312 	if (dolock && (result == PAM_SUCCESS && old_failed_count > 0)) {
313 		old_failed_count = __rst_failed_count(user, repository_name);
314 		if (nowarn == 0 && old_failed_count > 0)
315 			display_warning(pamh, old_failed_count, homedir);
316 	} else if (dolock && result == PAM_AUTH_ERR) {
317 		int max_failed = get_max_failed(user);
318 		if (max_failed != 0) {
319 			if (__incr_failed_count(user, repository_name,
320 			    max_failed) == PWU_ACCOUNT_LOCKED)
321 				result = PAM_MAXTRIES;
322 		}
323 	}
324 out:
325 	if (rep_passwd)
326 		free(rep_passwd);
327 	if (repository_name)
328 		free(repository_name);
329 	if (homedir)
330 		free(homedir);
331 	return (result);
332 }
333 
334 /*ARGSUSED*/
335 int
pam_sm_setcred(pam_handle_t * pamh,int flags,int argc,const char ** argv)336 pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv)
337 {
338 	return (PAM_IGNORE);
339 }
340