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