xref: /illumos-gate/usr/src/lib/pam_modules/unix_auth/unix_auth.c (revision 516fc7f3e48e25d0375ea3f93ead33ec38bc597b)
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 2005 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 <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
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
70  get_max_failed(char *user)
71  {
72  	char *val = NULL;
73  	userattr_t *uattr;
74  	int do_lock = 0;
75  	int retval = 0;
76  	char *p;
77  
78  	if ((uattr = getusernam(user)) != NULL)
79  		val = kva_match(uattr->attr, USERATTR_LOCK_AFTER_RETRIES_KW);
80  
81  	if (val != NULL)
82  		do_lock = (strcasecmp(val, "yes") == 0);
83  	else if (defopen(AUTH_POLICY) == 0) {
84  		int flags;
85  		flags = defcntl(DC_GETFLAGS, 0);
86  		TURNOFF(flags, DC_CASE);
87  		(void) defcntl(DC_SETFLAGS, flags);
88  		if ((p = defread("LOCK_AFTER_RETRIES=")) != NULL)
89  			do_lock = (strcasecmp(p, "yes") == 0);
90  		(void) defopen(NULL);
91  	}
92  
93  	if (uattr != NULL)
94  		free_userattr(uattr);
95  
96  	if (do_lock) {
97  		retval = MAXTRYS;
98  		if (defopen(LOGINADMIN) == 0) {
99  			if ((p = defread("RETRIES=")) != NULL)
100  				retval = atoi(p);
101  			(void) defopen(NULL);
102  		}
103  	}
104  
105  	return (retval);
106  }
107  
108  static void
109  display_warning(pam_handle_t *pamh, int failures, char *homedir)
110  {
111  	char hushpath[MAXPATHLEN];
112  	struct stat buf;
113  
114  	(void) snprintf(hushpath, sizeof (hushpath), "%s/.hushlogin", homedir);
115  	if (stat(hushpath, &buf) == 0)
116  		return;
117  
118  	if (failures == 1)
119  		error(pamh, "Warning: 1 failed login attempt since last "
120  		    "successful login.");
121  	else if (failures < FAILCOUNT_MASK)
122  		error(pamh, "Warning: %d failed login attempts since last "
123  		    "successful login.", failures);
124  	else
125  		error(pamh, "Warning: at least %d failed login attempts since "
126  		    "last successful login.", failures);
127  }
128  
129  /*
130   * int pam_sm_authenticate(pamh, flags, arc, argv)
131   *
132   * This routine verifies that the password as stored in the
133   * PAM_AUTHTOK item is indeed the password that belongs to the user
134   * as stored in PAM_USER.
135   *
136   * This routine will not establish Secure RPC Credentials. If these
137   * credentials are needed to obtain the password from the NIS+ service,
138   * the pam_dhkeys module should be stacked before us!
139   */
140  int
141  pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv)
142  {
143  	int	i;
144  	int	debug = 0;
145  	int	nowarn = (flags & PAM_SILENT) != 0;
146  	char	messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
147  	char	*user;
148  	char	*passwd;
149  	char	*rep_passwd;
150  	char	*repository_name;
151  	struct pam_repository *auth_rep;
152  	pwu_repository_t *pwu_rep;
153  	attrlist attr_pw[4];
154  	int	result;
155  	int	server_policy = 0;
156  	int	old_failed_count;
157  	char	*homedir = NULL;
158  	int	dolock = 1;
159  
160  	for (i = 0; i < argc; i++) {
161  		if (strcmp(argv[i], "debug") == 0)
162  			debug = 1;
163  		else if (strcmp(argv[i], "nowarn") == 0)
164  			nowarn = 1;
165  		else if (strcmp(argv[i], "server_policy") == 0)
166  			server_policy = 1;
167  		else if (strcmp(argv[i], "nolock") == 0)
168  			dolock = 0;
169  	}
170  
171  	if (debug)
172  		syslog(LOG_DEBUG,
173  		    "pam_unix_auth: entering pam_sm_authenticate()");
174  
175  	if (pam_get_item(pamh, PAM_USER, (void **)&user) != PAM_SUCCESS) {
176  		syslog(LOG_DEBUG, "pam_unix_auth: USER not set");
177  		return (PAM_SYSTEM_ERR);
178  	}
179  
180  	if (user == NULL || *user == '\0') {
181  		syslog(LOG_DEBUG, "pam_unix_auth: USER NULL or empty!\n");
182  		return (PAM_USER_UNKNOWN);
183  	}
184  
185  	if (pam_get_item(pamh, PAM_AUTHTOK, (void **)&passwd) != PAM_SUCCESS) {
186  		syslog(LOG_DEBUG, "pam_unix_auth: AUTHTOK not set!\n");
187  		return (PAM_SYSTEM_ERR);
188  	}
189  
190  	result = pam_get_item(pamh, PAM_REPOSITORY, (void **)&auth_rep);
191  	if (result == PAM_SUCCESS && auth_rep != NULL) {
192  		if ((pwu_rep = calloc(1, sizeof (*pwu_rep))) == NULL)
193  			return (PAM_BUF_ERR);
194  		pwu_rep->type = auth_rep->type;
195  		pwu_rep->scope = auth_rep->scope;
196  		pwu_rep->scope_len = auth_rep->scope_len;
197  	} else {
198  		pwu_rep = PWU_DEFAULT_REP;
199  	}
200  
201  	/*
202  	 * Get password and the name of the repository where the
203  	 * password resides.
204  	 */
205  	attr_pw[0].type = ATTR_PASSWD;		attr_pw[0].next = &attr_pw[1];
206  	attr_pw[1].type = ATTR_REP_NAME;	attr_pw[1].next = &attr_pw[2];
207  	/*
208  	 * Also get the current number of failed logins; we use
209  	 * this later to determine whether we need to reset the count
210  	 * on a succesful authentication. We use the home-directory
211  	 * to look for .hushlogin in order to optionaly surpress the
212  	 * "failed attempts" message.
213  	 */
214  	attr_pw[2].type = ATTR_FAILED_LOGINS;	attr_pw[2].next = &attr_pw[3];
215  	attr_pw[3].type = ATTR_HOMEDIR;		attr_pw[3].next = NULL;
216  
217  	result = __get_authtoken_attr(user, pwu_rep, attr_pw);
218  
219  	if (pwu_rep != PWU_DEFAULT_REP)
220  		free(pwu_rep);
221  
222  	if (result == PWU_NOT_FOUND) {
223  		syslog(LOG_DEBUG, "pam_unix_auth: user %s not found\n",
224  		    user);
225  		return (PAM_USER_UNKNOWN);
226  	}
227  
228  	if (result == PWU_DENIED) {
229  		syslog(LOG_DEBUG, "pam_unix_auth: failed to obtain attributes");
230  		return (PAM_PERM_DENIED);
231  	}
232  
233  	if (result != PWU_SUCCESS)
234  		return (PAM_SYSTEM_ERR);
235  
236  	rep_passwd = attr_pw[0].data.val_s;
237  	repository_name = attr_pw[1].data.val_s;
238  	old_failed_count = attr_pw[2].data.val_i;
239  	homedir = attr_pw[3].data.val_s;
240  
241  	/*
242  	 * Chop off old SunOS-style password aging information.
243  	 *
244  	 * Note: old style password aging is only defined for UNIX-style
245  	 *	 crypt strings, hence the comma will always be at position 14.
246  	 * Note: This code is here because some other vendors might still
247  	 *	 support this style of password aging. If we don't remove
248  	 *	 the age field, no one will be able to login.
249  	 * XXX   yank this code when we're certain this "compatibility"
250  	 *	 isn't needed anymore.
251  	 */
252  	if (rep_passwd != NULL && rep_passwd[0] != '$' &&
253  	    strlen(rep_passwd) > 13 && rep_passwd[13] == ',')
254  		rep_passwd[13] = '\0';
255  
256  	/* Is a password check required? */
257  	if (rep_passwd == NULL || *rep_passwd == '\0') {
258  		if (flags & PAM_DISALLOW_NULL_AUTHTOK) {
259  			result = PAM_AUTH_ERR;
260  			goto out;
261  		} else {
262  			result = PAM_SUCCESS;
263  			goto out;
264  		}
265  	}
266  
267  	/*
268  	 * Password check *is* required. Make sure we have a valid
269  	 * pointer in PAM_AUTHTOK
270  	 */
271  	if (passwd == NULL) {
272  		result = PAM_AUTH_ERR;
273  		goto out;
274  	}
275  
276  	/*
277  	 * "rep_passwd" holds the encrypted password.
278  	 * If, however, we detect that the password equals "*NP*",
279  	 * while we've obtained it from NIS+, it
280  	 * means that the permissions on the NIS+ table are too tight
281  	 * for us to get the password without having Secure RPC
282  	 * Credentials. In that case, we syslog an error stating that
283  	 * the Secure RPC credential Module should be on the PAM stack
284  	 * before the unix_auth module. We also tell the user to go
285  	 * and inform the administrator of this error.
286  	 */
287  	if (strcmp(repository_name, "nisplus") == 0 &&
288  	    strcmp(rep_passwd, "*NP*") == 0) {
289  		syslog(LOG_ERR, "pam_unix_auth: NIS+ permissions require that"
290  			"the pam_dhkeys module is on the PAM stack before "
291  			"pam_unix_auth");
292  		if (nowarn == 0) {
293  			(void) snprintf(messages[0], sizeof (messages[0]),
294  			    dgettext(TEXT_DOMAIN,
295  				"NIS+ permissions are too tight. "
296  				"Please inform your administrator."));
297  			(void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1,
298  						messages, NULL);
299  		}
300  		result = PAM_USER_UNKNOWN;
301  		goto out;
302  	}
303  
304  	if (server_policy &&
305  	    strcmp(repository_name, "files") != 0 &&
306  	    strcmp(repository_name, "nis") != 0 &&
307  	    strcmp(repository_name, "nisplus") != 0) {
308  		result = PAM_IGNORE;
309  		goto out;
310  	}
311  
312  	/* Now check the entered password */
313  	if (strcmp(crypt(passwd, rep_passwd), rep_passwd) == 0)
314  		result = PAM_SUCCESS;
315  	else
316  		result = PAM_AUTH_ERR;
317  
318  	/* Clear or increment failed failed count */
319  	if (dolock && (result == PAM_SUCCESS && old_failed_count > 0)) {
320  		old_failed_count = __rst_failed_count(user, repository_name);
321  		if (nowarn == 0 && old_failed_count > 0)
322  			display_warning(pamh, old_failed_count, homedir);
323  	} else if (dolock && result == PAM_AUTH_ERR) {
324  		int max_failed = get_max_failed(user);
325  		if (max_failed != 0)
326  			(void) __incr_failed_count(user, repository_name,
327  			    max_failed);
328  	}
329  out:
330  	if (rep_passwd)
331  		free(rep_passwd);
332  	if (repository_name)
333  		free(repository_name);
334  	if (homedir)
335  		free(homedir);
336  	return (result);
337  }
338  
339  /*ARGSUSED*/
340  int
341  pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv)
342  {
343  	return (PAM_IGNORE);
344  }
345