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 }