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