1 /*- 2 * SPDX-License-Identifier: BSD-3-Clause 3 * 4 * Copyright 1998 Juniper Networks, Inc. 5 * All rights reserved. 6 * Copyright (c) 2002-2003 Networks Associates Technology, Inc. 7 * All rights reserved. 8 * 9 * Portions of this software was developed for the FreeBSD Project by 10 * ThinkSec AS and NAI Labs, the Security Research Division of Network 11 * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035 12 * ("CBOSS"), as part of the DARPA CHATS research program. 13 * 14 * Redistribution and use in source and binary forms, with or without 15 * modification, are permitted provided that the following conditions 16 * are met: 17 * 1. Redistributions of source code must retain the above copyright 18 * notice, this list of conditions and the following disclaimer. 19 * 2. Redistributions in binary form must reproduce the above copyright 20 * notice, this list of conditions and the following disclaimer in the 21 * documentation and/or other materials provided with the distribution. 22 * 3. The name of the author may not be used to endorse or promote 23 * products derived from this software without specific prior written 24 * permission. 25 * 26 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 27 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 28 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 29 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 30 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 31 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 32 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 33 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 34 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 35 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 36 * SUCH DAMAGE. 37 */ 38 39 #include <sys/cdefs.h> 40 #include <sys/param.h> 41 #include <sys/socket.h> 42 #include <sys/time.h> 43 #include <netinet/in.h> 44 #include <arpa/inet.h> 45 46 #include <login_cap.h> 47 #include <netdb.h> 48 #include <pwd.h> 49 #include <stdlib.h> 50 #include <string.h> 51 #include <stdio.h> 52 #include <syslog.h> 53 #include <time.h> 54 #include <unistd.h> 55 56 #include <libutil.h> 57 58 #ifdef YP 59 #include <ypclnt.h> 60 #endif 61 62 #define PAM_SM_AUTH 63 #define PAM_SM_ACCOUNT 64 #define PAM_SM_PASSWORD 65 66 #include <security/pam_appl.h> 67 #include <security/pam_modules.h> 68 #include <security/pam_mod_misc.h> 69 70 #define PASSWORD_HASH "md5" 71 #define DEFAULT_WARN (2L * 7L * 86400L) /* Two weeks */ 72 #define SALTSIZE 32 73 74 #define LOCKED_PREFIX "*LOCKED*" 75 #define LOCKED_PREFIX_LEN (sizeof(LOCKED_PREFIX) - 1) 76 77 static void makesalt(char [SALTSIZE + 1]); 78 79 static char password_hash[] = PASSWORD_HASH; 80 81 #define PAM_OPT_LOCAL_PASS "local_pass" 82 #define PAM_OPT_NIS_PASS "nis_pass" 83 84 /* 85 * authentication management 86 */ 87 PAM_EXTERN int 88 pam_sm_authenticate(pam_handle_t *pamh, int flags, 89 int argc __unused, const char *argv[] __unused) 90 { 91 login_cap_t *lc; 92 struct passwd *pwd; 93 int retval; 94 const char *pass, *user, *realpw, *prompt; 95 const char *emptypasswd = ""; 96 97 if (openpam_get_option(pamh, PAM_OPT_AUTH_AS_SELF)) { 98 user = getlogin(); 99 } else { 100 retval = pam_get_user(pamh, &user, NULL); 101 if (retval != PAM_SUCCESS) 102 return (retval); 103 } 104 pwd = getpwnam(user); 105 106 PAM_LOG("Got user: %s", user); 107 108 if (pwd != NULL) { 109 PAM_LOG("Doing real authentication"); 110 realpw = pwd->pw_passwd; 111 if (realpw[0] == '\0') { 112 if (!(flags & PAM_DISALLOW_NULL_AUTHTOK) && 113 openpam_get_option(pamh, PAM_OPT_NULLOK)) 114 return (PAM_SUCCESS); 115 PAM_LOG("Password is empty, using fake password"); 116 realpw = "*"; 117 } 118 /* 119 * Check whether the saved password hash matches the one 120 * generated from an empty password - as opposed to empty 121 * saved password hash, which is handled above. 122 */ 123 if (!(flags & PAM_DISALLOW_NULL_AUTHTOK) && 124 openpam_get_option(pamh, PAM_OPT_EMPTYOK) && 125 strcmp(crypt(emptypasswd, realpw), realpw) == 0) 126 return (PAM_SUCCESS); 127 lc = login_getpwclass(pwd); 128 } else { 129 PAM_LOG("Doing dummy authentication"); 130 realpw = "*"; 131 lc = login_getclass(NULL); 132 } 133 prompt = login_getcapstr(lc, "passwd_prompt", NULL, NULL); 134 retval = pam_get_authtok(pamh, PAM_AUTHTOK, &pass, prompt); 135 login_close(lc); 136 if (retval != PAM_SUCCESS) 137 return (retval); 138 PAM_LOG("Got password"); 139 if (strnlen(pass, _PASSWORD_LEN + 1) > _PASSWORD_LEN) { 140 PAM_LOG("Password is too long, using fake password"); 141 realpw = "*"; 142 } 143 if (strcmp(crypt(pass, realpw), realpw) == 0) 144 return (PAM_SUCCESS); 145 146 PAM_VERBOSE_ERROR("UNIX authentication refused"); 147 return (PAM_AUTH_ERR); 148 } 149 150 PAM_EXTERN int 151 pam_sm_setcred(pam_handle_t *pamh __unused, int flags __unused, 152 int argc __unused, const char *argv[] __unused) 153 { 154 155 return (PAM_SUCCESS); 156 } 157 158 /* 159 * account management 160 */ 161 PAM_EXTERN int 162 pam_sm_acct_mgmt(pam_handle_t *pamh, int flags __unused, 163 int argc __unused, const char *argv[] __unused) 164 { 165 struct addrinfo hints, *res; 166 struct passwd *pwd; 167 struct timeval tp; 168 login_cap_t *lc; 169 time_t warntime; 170 int retval; 171 const char *user; 172 const void *rhost, *tty; 173 char rhostip[MAXHOSTNAMELEN] = ""; 174 175 retval = pam_get_user(pamh, &user, NULL); 176 if (retval != PAM_SUCCESS) 177 return (retval); 178 179 if (user == NULL || (pwd = getpwnam(user)) == NULL) 180 return (PAM_SERVICE_ERR); 181 182 PAM_LOG("Got user: %s", user); 183 184 retval = pam_get_item(pamh, PAM_RHOST, &rhost); 185 if (retval != PAM_SUCCESS) 186 return (retval); 187 188 retval = pam_get_item(pamh, PAM_TTY, &tty); 189 if (retval != PAM_SUCCESS) 190 return (retval); 191 192 if (*pwd->pw_passwd == '\0' && 193 (flags & PAM_DISALLOW_NULL_AUTHTOK) != 0) 194 return (PAM_NEW_AUTHTOK_REQD); 195 196 if (strncmp(pwd->pw_passwd, LOCKED_PREFIX, LOCKED_PREFIX_LEN) == 0) 197 return (PAM_AUTH_ERR); 198 199 lc = login_getpwclass(pwd); 200 if (lc == NULL) { 201 PAM_LOG("Unable to get login class for user %s", user); 202 return (PAM_SERVICE_ERR); 203 } 204 205 PAM_LOG("Got login_cap"); 206 207 if (pwd->pw_change || pwd->pw_expire) 208 gettimeofday(&tp, NULL); 209 210 /* 211 * Check pw_expire before pw_change - no point in letting the 212 * user change the password on an expired account. 213 */ 214 215 if (pwd->pw_expire) { 216 warntime = login_getcaptime(lc, "warnexpire", 217 DEFAULT_WARN, DEFAULT_WARN); 218 if (tp.tv_sec >= pwd->pw_expire) { 219 login_close(lc); 220 return (PAM_ACCT_EXPIRED); 221 } else if (pwd->pw_expire - tp.tv_sec < warntime && 222 (flags & PAM_SILENT) == 0) { 223 pam_error(pamh, "Warning: your account expires on %s", 224 ctime(&pwd->pw_expire)); 225 } 226 } 227 228 retval = PAM_SUCCESS; 229 if (pwd->pw_change) { 230 warntime = login_getcaptime(lc, "warnpassword", 231 DEFAULT_WARN, DEFAULT_WARN); 232 if (tp.tv_sec >= pwd->pw_change) { 233 retval = PAM_NEW_AUTHTOK_REQD; 234 } else if (pwd->pw_change - tp.tv_sec < warntime && 235 (flags & PAM_SILENT) == 0) { 236 pam_error(pamh, "Warning: your password expires on %s", 237 ctime(&pwd->pw_change)); 238 } 239 } 240 241 /* 242 * From here on, we must leave retval untouched (unless we 243 * know we're going to fail), because we need to remember 244 * whether we're supposed to return PAM_SUCCESS or 245 * PAM_NEW_AUTHTOK_REQD. 246 */ 247 248 if (rhost && *(const char *)rhost != '\0') { 249 memset(&hints, 0, sizeof(hints)); 250 hints.ai_family = AF_UNSPEC; 251 if (getaddrinfo(rhost, NULL, &hints, &res) == 0) { 252 getnameinfo(res->ai_addr, res->ai_addrlen, 253 rhostip, sizeof(rhostip), NULL, 0, 254 NI_NUMERICHOST); 255 } 256 if (res != NULL) 257 freeaddrinfo(res); 258 } 259 260 /* 261 * Check host / tty / time-of-day restrictions 262 */ 263 264 if (!auth_hostok(lc, rhost, rhostip) || 265 !auth_ttyok(lc, tty) || 266 !auth_timeok(lc, time(NULL))) 267 retval = PAM_AUTH_ERR; 268 269 login_close(lc); 270 271 return (retval); 272 } 273 274 /* 275 * password management 276 * 277 * standard Unix and NIS password changing 278 */ 279 PAM_EXTERN int 280 pam_sm_chauthtok(pam_handle_t *pamh, int flags, 281 int argc __unused, const char *argv[] __unused) 282 { 283 #ifdef YP 284 struct ypclnt *ypclnt; 285 const void *yp_domain, *yp_server; 286 #endif 287 char salt[SALTSIZE + 1]; 288 login_cap_t *lc; 289 struct passwd *pwd, *old_pwd; 290 const char *user, *old_pass, *new_pass; 291 char *encrypted; 292 time_t passwordtime; 293 int pfd, tfd, retval; 294 295 if (openpam_get_option(pamh, PAM_OPT_AUTH_AS_SELF)) 296 user = getlogin(); 297 else { 298 retval = pam_get_user(pamh, &user, NULL); 299 if (retval != PAM_SUCCESS) 300 return (retval); 301 } 302 pwd = getpwnam(user); 303 304 if (pwd == NULL) 305 return (PAM_AUTHTOK_RECOVERY_ERR); 306 307 PAM_LOG("Got user: %s", user); 308 309 if (flags & PAM_PRELIM_CHECK) { 310 311 PAM_LOG("PRELIM round"); 312 313 if (getuid() == 0 && 314 (pwd->pw_fields & _PWF_SOURCE) == _PWF_FILES) 315 /* root doesn't need the old password */ 316 return (pam_set_item(pamh, PAM_OLDAUTHTOK, "")); 317 #ifdef YP 318 if (getuid() == 0 && 319 (pwd->pw_fields & _PWF_SOURCE) == _PWF_NIS) { 320 321 yp_domain = yp_server = NULL; 322 (void)pam_get_data(pamh, "yp_domain", &yp_domain); 323 (void)pam_get_data(pamh, "yp_server", &yp_server); 324 325 ypclnt = ypclnt_new(yp_domain, "passwd.byname", yp_server); 326 if (ypclnt == NULL) 327 return (PAM_BUF_ERR); 328 329 if (ypclnt_connect(ypclnt) == -1) { 330 ypclnt_free(ypclnt); 331 return (PAM_SERVICE_ERR); 332 } 333 334 retval = ypclnt_havepasswdd(ypclnt); 335 ypclnt_free(ypclnt); 336 if (retval == 1) 337 return (pam_set_item(pamh, PAM_OLDAUTHTOK, "")); 338 else if (retval == -1) 339 return (PAM_SERVICE_ERR); 340 } 341 #endif 342 if (pwd->pw_passwd[0] == '\0' 343 && openpam_get_option(pamh, PAM_OPT_NULLOK)) { 344 /* 345 * No password case. XXX Are we giving too much away 346 * by not prompting for a password? 347 * XXX check PAM_DISALLOW_NULL_AUTHTOK 348 */ 349 old_pass = ""; 350 retval = PAM_SUCCESS; 351 } else { 352 retval = pam_get_authtok(pamh, 353 PAM_OLDAUTHTOK, &old_pass, NULL); 354 if (retval != PAM_SUCCESS) 355 return (retval); 356 } 357 PAM_LOG("Got old password"); 358 /* always encrypt first */ 359 encrypted = crypt(old_pass, pwd->pw_passwd); 360 if (old_pass[0] == '\0' && 361 !openpam_get_option(pamh, PAM_OPT_NULLOK)) 362 return (PAM_PERM_DENIED); 363 if (strcmp(encrypted, pwd->pw_passwd) != 0) 364 return (PAM_PERM_DENIED); 365 } 366 else if (flags & PAM_UPDATE_AUTHTOK) { 367 PAM_LOG("UPDATE round"); 368 369 retval = pam_get_authtok(pamh, 370 PAM_OLDAUTHTOK, &old_pass, NULL); 371 if (retval != PAM_SUCCESS) 372 return (retval); 373 PAM_LOG("Got old password"); 374 375 /* get new password */ 376 for (;;) { 377 retval = pam_get_authtok(pamh, 378 PAM_AUTHTOK, &new_pass, NULL); 379 if (retval != PAM_TRY_AGAIN) 380 break; 381 pam_error(pamh, "Mismatch; try again, EOF to quit."); 382 } 383 PAM_LOG("Got new password"); 384 if (retval != PAM_SUCCESS) { 385 PAM_VERBOSE_ERROR("Unable to get new password"); 386 return (retval); 387 } 388 389 if (getuid() != 0 && new_pass[0] == '\0' && 390 !openpam_get_option(pamh, PAM_OPT_NULLOK)) 391 return (PAM_PERM_DENIED); 392 393 if ((old_pwd = pw_dup(pwd)) == NULL) 394 return (PAM_BUF_ERR); 395 396 lc = login_getclass(pwd->pw_class); 397 if (login_setcryptfmt(lc, password_hash, NULL) == NULL) 398 openpam_log(PAM_LOG_ERROR, 399 "can't set password cipher, relying on default"); 400 401 /* set password expiry date */ 402 pwd->pw_change = 0; 403 passwordtime = login_getcaptime(lc, "passwordtime", 0, 0); 404 if (passwordtime > 0) 405 pwd->pw_change = time(NULL) + passwordtime; 406 407 login_close(lc); 408 makesalt(salt); 409 pwd->pw_passwd = crypt(new_pass, salt); 410 #ifdef YP 411 switch (old_pwd->pw_fields & _PWF_SOURCE) { 412 case _PWF_FILES: 413 #endif 414 retval = PAM_SERVICE_ERR; 415 if (pw_init(NULL, NULL)) 416 openpam_log(PAM_LOG_ERROR, "pw_init() failed"); 417 else if ((pfd = pw_lock()) == -1) 418 openpam_log(PAM_LOG_ERROR, "pw_lock() failed"); 419 else if ((tfd = pw_tmp(-1)) == -1) 420 openpam_log(PAM_LOG_ERROR, "pw_tmp() failed"); 421 else if (pw_copy(pfd, tfd, pwd, old_pwd) == -1) 422 openpam_log(PAM_LOG_ERROR, "pw_copy() failed"); 423 else if (pw_mkdb(pwd->pw_name) == -1) 424 openpam_log(PAM_LOG_ERROR, "pw_mkdb() failed"); 425 else 426 retval = PAM_SUCCESS; 427 pw_fini(); 428 #ifdef YP 429 break; 430 case _PWF_NIS: 431 yp_domain = yp_server = NULL; 432 (void)pam_get_data(pamh, "yp_domain", &yp_domain); 433 (void)pam_get_data(pamh, "yp_server", &yp_server); 434 ypclnt = ypclnt_new(yp_domain, 435 "passwd.byname", yp_server); 436 if (ypclnt == NULL) { 437 retval = PAM_BUF_ERR; 438 } else if (ypclnt_connect(ypclnt) == -1 || 439 ypclnt_passwd(ypclnt, pwd, old_pass) == -1) { 440 openpam_log(PAM_LOG_ERROR, "%s", ypclnt->error); 441 retval = PAM_SERVICE_ERR; 442 } else { 443 retval = PAM_SUCCESS; 444 } 445 ypclnt_free(ypclnt); 446 break; 447 default: 448 openpam_log(PAM_LOG_ERROR, "unsupported source 0x%x", 449 pwd->pw_fields & _PWF_SOURCE); 450 retval = PAM_SERVICE_ERR; 451 } 452 #endif 453 free(old_pwd); 454 } 455 else { 456 /* Very bad juju */ 457 retval = PAM_ABORT; 458 PAM_LOG("Illegal 'flags'"); 459 } 460 461 return (retval); 462 } 463 464 /* Mostly stolen from passwd(1)'s local_passwd.c - markm */ 465 466 static unsigned char itoa64[] = /* 0 ... 63 => ascii - 64 */ 467 "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 468 469 static void 470 to64(char *s, long v, int n) 471 { 472 while (--n >= 0) { 473 *s++ = itoa64[v&0x3f]; 474 v >>= 6; 475 } 476 } 477 478 /* Salt suitable for traditional DES and MD5 */ 479 static void 480 makesalt(char salt[SALTSIZE + 1]) 481 { 482 int i; 483 484 /* These are not really random numbers, they are just 485 * numbers that change to thwart construction of a 486 * dictionary. 487 */ 488 for (i = 0; i < SALTSIZE; i += 4) 489 to64(&salt[i], arc4random(), 4); 490 salt[SALTSIZE] = '\0'; 491 } 492 493 PAM_MODULE_ENTRY("pam_unix"); 494