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 * $FreeBSD: head/lib/libpam/modules/pam_unix/pam_unix.c 326219 2017-11-26 02:00:33Z pfg $ 39 */ 40 41 #include <sys/param.h> 42 #include <sys/socket.h> 43 #include <sys/time.h> 44 #include <netinet/in.h> 45 #include <arpa/inet.h> 46 47 #include <login_cap.h> 48 #include <netdb.h> 49 #include <pwd.h> 50 #include <stdlib.h> 51 #include <string.h> 52 #include <stdio.h> 53 #include <syslog.h> 54 #include <time.h> 55 #include <unistd.h> 56 57 #include <libutil.h> 58 59 #ifdef YP 60 #include <ypclnt.h> 61 #endif 62 63 #define PAM_SM_AUTH 64 #define PAM_SM_ACCOUNT 65 #define PAM_SM_PASSWORD 66 67 #include <security/pam_appl.h> 68 #include <security/pam_modules.h> 69 #include <security/pam_mod_misc.h> 70 71 #define PASSWORD_HASH "sha512" 72 #define DEFAULT_WARN (2L * 7L * 86400L) /* Two weeks */ 73 #define SALTSIZE 32 74 75 #define LOCKED_PREFIX "*LOCKED*" 76 #define LOCKED_PREFIX_LEN (sizeof(LOCKED_PREFIX) - 1) 77 78 static void makesalt(char []); 79 80 static char password_hash[] = PASSWORD_HASH; 81 82 #define PAM_OPT_LOCAL_PASS "local_pass" 83 #define PAM_OPT_NIS_PASS "nis_pass" 84 85 /* 86 * authentication management 87 */ 88 PAM_EXTERN int 89 pam_sm_authenticate(pam_handle_t *pamh, int flags __unused, 90 int argc __unused, const char *argv[] __unused) 91 { 92 login_cap_t *lc; 93 struct passwd *pwd; 94 int retval; 95 const char *pass, *user, *realpw, *prompt; 96 char *cryptpw; 97 98 if (openpam_get_option(pamh, PAM_OPT_AUTH_AS_SELF)) { 99 user = getlogin(); 100 } else { 101 retval = pam_get_user(pamh, &user, NULL); 102 if (retval != PAM_SUCCESS) 103 return (retval); 104 } 105 pwd = getpwnam(user); 106 107 PAM_LOG("Got user: %s", user); 108 109 if (pwd != NULL) { 110 PAM_LOG("Doing real authentication"); 111 realpw = pwd->pw_passwd; 112 if (realpw[0] == '\0') { 113 if (!(flags & PAM_DISALLOW_NULL_AUTHTOK) && 114 openpam_get_option(pamh, PAM_OPT_NULLOK)) 115 return (PAM_SUCCESS); 116 PAM_LOG("Password is empty, using fake password"); 117 realpw = "*"; 118 } 119 lc = login_getpwclass(pwd); 120 } else { 121 PAM_LOG("Doing dummy authentication"); 122 realpw = "*"; 123 lc = login_getclass(NULL); 124 } 125 prompt = login_getcapstr(lc, "passwd_prompt", NULL, NULL); 126 retval = pam_get_authtok(pamh, PAM_AUTHTOK, &pass, prompt); 127 login_close(lc); 128 if (retval != PAM_SUCCESS) 129 return (retval); 130 PAM_LOG("Got password"); 131 if (strnlen(pass, _PASSWORD_LEN + 1) > _PASSWORD_LEN) { 132 PAM_LOG("Password is too long, using fake password"); 133 realpw = "*"; 134 } 135 cryptpw = crypt(pass, realpw); 136 if (cryptpw != NULL && strcmp(cryptpw, realpw) == 0) 137 return (PAM_SUCCESS); 138 139 PAM_VERBOSE_ERROR("UNIX authentication refused"); 140 return (PAM_AUTH_ERR); 141 } 142 143 PAM_EXTERN int 144 pam_sm_setcred(pam_handle_t *pamh __unused, int flags __unused, 145 int argc __unused, const char *argv[] __unused) 146 { 147 148 return (PAM_SUCCESS); 149 } 150 151 /* 152 * account management 153 */ 154 PAM_EXTERN int 155 pam_sm_acct_mgmt(pam_handle_t *pamh, int flags __unused, 156 int argc __unused, const char *argv[] __unused) 157 { 158 struct addrinfo hints, *res; 159 struct passwd *pwd; 160 struct timeval tp; 161 login_cap_t *lc; 162 time_t warntime; 163 int retval; 164 const char *user; 165 const void *rhost, *tty; 166 char rhostip[MAXHOSTNAMELEN] = ""; 167 168 retval = pam_get_user(pamh, &user, NULL); 169 if (retval != PAM_SUCCESS) 170 return (retval); 171 172 if (user == NULL || (pwd = getpwnam(user)) == NULL) 173 return (PAM_SERVICE_ERR); 174 175 PAM_LOG("Got user: %s", user); 176 177 retval = pam_get_item(pamh, PAM_RHOST, &rhost); 178 if (retval != PAM_SUCCESS) 179 return (retval); 180 181 retval = pam_get_item(pamh, PAM_TTY, &tty); 182 if (retval != PAM_SUCCESS) 183 return (retval); 184 185 if (*pwd->pw_passwd == '\0' && 186 (flags & PAM_DISALLOW_NULL_AUTHTOK) != 0) 187 return (PAM_NEW_AUTHTOK_REQD); 188 189 if (strncmp(pwd->pw_passwd, LOCKED_PREFIX, LOCKED_PREFIX_LEN) == 0) 190 return (PAM_AUTH_ERR); 191 192 lc = login_getpwclass(pwd); 193 if (lc == NULL) { 194 PAM_LOG("Unable to get login class for user %s", user); 195 return (PAM_SERVICE_ERR); 196 } 197 198 PAM_LOG("Got login_cap"); 199 200 if (pwd->pw_change || pwd->pw_expire) 201 gettimeofday(&tp, NULL); 202 203 /* 204 * Check pw_expire before pw_change - no point in letting the 205 * user change the password on an expired account. 206 */ 207 208 if (pwd->pw_expire) { 209 warntime = login_getcaptime(lc, "warnexpire", 210 DEFAULT_WARN, DEFAULT_WARN); 211 if (tp.tv_sec >= pwd->pw_expire) { 212 login_close(lc); 213 return (PAM_ACCT_EXPIRED); 214 } else if (pwd->pw_expire - tp.tv_sec < warntime && 215 (flags & PAM_SILENT) == 0) { 216 pam_error(pamh, "Warning: your account expires on %s", 217 ctime(&pwd->pw_expire)); 218 } 219 } 220 221 retval = PAM_SUCCESS; 222 if (pwd->pw_change) { 223 warntime = login_getcaptime(lc, "warnpassword", 224 DEFAULT_WARN, DEFAULT_WARN); 225 if (tp.tv_sec >= pwd->pw_change) { 226 retval = PAM_NEW_AUTHTOK_REQD; 227 } else if (pwd->pw_change - tp.tv_sec < warntime && 228 (flags & PAM_SILENT) == 0) { 229 pam_error(pamh, "Warning: your password expires on %s", 230 ctime(&pwd->pw_change)); 231 } 232 } 233 234 /* 235 * From here on, we must leave retval untouched (unless we 236 * know we're going to fail), because we need to remember 237 * whether we're supposed to return PAM_SUCCESS or 238 * PAM_NEW_AUTHTOK_REQD. 239 */ 240 241 if (rhost && *(const char *)rhost != '\0') { 242 memset(&hints, 0, sizeof(hints)); 243 hints.ai_family = AF_UNSPEC; 244 if (getaddrinfo(rhost, NULL, &hints, &res) == 0) { 245 getnameinfo(res->ai_addr, res->ai_addrlen, 246 rhostip, sizeof(rhostip), NULL, 0, 247 NI_NUMERICHOST); 248 } 249 if (res != NULL) 250 freeaddrinfo(res); 251 } 252 253 /* 254 * Check host / tty / time-of-day restrictions 255 */ 256 257 if (!auth_hostok(lc, rhost, rhostip) || 258 !auth_ttyok(lc, tty) || 259 !auth_timeok(lc, time(NULL))) 260 retval = PAM_AUTH_ERR; 261 262 login_close(lc); 263 264 return (retval); 265 } 266 267 /* 268 * password management 269 * 270 * standard Unix and NIS password changing 271 */ 272 PAM_EXTERN int 273 pam_sm_chauthtok(pam_handle_t *pamh, int flags, 274 int argc __unused, const char *argv[] __unused) 275 { 276 #ifdef YP 277 struct ypclnt *ypclnt; 278 const void *yp_domain, *yp_server; 279 #endif 280 char salt[SALTSIZE + 1]; 281 login_cap_t *lc; 282 struct passwd *pwd, *old_pwd; 283 const char *user, *old_pass, *new_pass; 284 char *encrypted; 285 time_t passwordtime; 286 int pfd, tfd, retval; 287 288 if (openpam_get_option(pamh, PAM_OPT_AUTH_AS_SELF)) 289 user = getlogin(); 290 else { 291 retval = pam_get_user(pamh, &user, NULL); 292 if (retval != PAM_SUCCESS) 293 return (retval); 294 } 295 pwd = getpwnam(user); 296 297 if (pwd == NULL) 298 return (PAM_AUTHTOK_RECOVERY_ERR); 299 300 PAM_LOG("Got user: %s", user); 301 302 if (flags & PAM_PRELIM_CHECK) { 303 304 PAM_LOG("PRELIM round"); 305 306 if (getuid() == 0 && 307 (pwd->pw_fields & _PWF_SOURCE) == _PWF_FILES) 308 /* root doesn't need the old password */ 309 return (pam_set_item(pamh, PAM_OLDAUTHTOK, "")); 310 #ifdef YP 311 if (getuid() == 0 && 312 (pwd->pw_fields & _PWF_SOURCE) == _PWF_NIS) { 313 314 yp_domain = yp_server = NULL; 315 pam_get_data(pamh, "yp_domain", &yp_domain); 316 pam_get_data(pamh, "yp_server", &yp_server); 317 318 ypclnt = ypclnt_new(yp_domain, "passwd.byname", yp_server); 319 if (ypclnt == NULL) 320 return (PAM_BUF_ERR); 321 322 if (ypclnt_connect(ypclnt) == -1) { 323 ypclnt_free(ypclnt); 324 return (PAM_SERVICE_ERR); 325 } 326 327 retval = ypclnt_havepasswdd(ypclnt); 328 ypclnt_free(ypclnt); 329 if (retval == 1) 330 return (pam_set_item(pamh, PAM_OLDAUTHTOK, "")); 331 else if (retval == -1) 332 return (PAM_SERVICE_ERR); 333 } 334 #endif 335 if (pwd->pw_passwd[0] == '\0' 336 && openpam_get_option(pamh, PAM_OPT_NULLOK)) { 337 /* 338 * No password case. XXX Are we giving too much away 339 * by not prompting for a password? 340 * XXX check PAM_DISALLOW_NULL_AUTHTOK 341 */ 342 old_pass = ""; 343 retval = PAM_SUCCESS; 344 } else { 345 retval = pam_get_authtok(pamh, 346 PAM_OLDAUTHTOK, &old_pass, NULL); 347 if (retval != PAM_SUCCESS) 348 return (retval); 349 } 350 PAM_LOG("Got old password"); 351 /* always encrypt first */ 352 encrypted = crypt(old_pass, pwd->pw_passwd); 353 if (old_pass[0] == '\0' && 354 !openpam_get_option(pamh, PAM_OPT_NULLOK)) 355 return (PAM_PERM_DENIED); 356 if (encrypted == NULL || strcmp(encrypted, pwd->pw_passwd) != 0) 357 return (PAM_PERM_DENIED); 358 } 359 else if (flags & PAM_UPDATE_AUTHTOK) { 360 PAM_LOG("UPDATE round"); 361 362 retval = pam_get_authtok(pamh, 363 PAM_OLDAUTHTOK, &old_pass, NULL); 364 if (retval != PAM_SUCCESS) 365 return (retval); 366 PAM_LOG("Got old password"); 367 368 /* get new password */ 369 for (;;) { 370 retval = pam_get_authtok(pamh, 371 PAM_AUTHTOK, &new_pass, NULL); 372 if (retval != PAM_TRY_AGAIN) 373 break; 374 pam_error(pamh, "Mismatch; try again, EOF to quit."); 375 } 376 PAM_LOG("Got new password"); 377 if (retval != PAM_SUCCESS) { 378 PAM_VERBOSE_ERROR("Unable to get new password"); 379 return (retval); 380 } 381 382 if (getuid() != 0 && new_pass[0] == '\0' && 383 !openpam_get_option(pamh, PAM_OPT_NULLOK)) 384 return (PAM_PERM_DENIED); 385 386 if ((old_pwd = pw_dup(pwd)) == NULL) 387 return (PAM_BUF_ERR); 388 389 lc = login_getclass(pwd->pw_class); 390 if (login_setcryptfmt(lc, password_hash, NULL) == NULL) 391 openpam_log(PAM_LOG_ERROR, 392 "can't set password cipher, relying on default"); 393 394 /* set password expiry date */ 395 pwd->pw_change = 0; 396 passwordtime = login_getcaptime(lc, "passwordtime", 0, 0); 397 if (passwordtime > 0) 398 pwd->pw_change = time(NULL) + passwordtime; 399 400 login_close(lc); 401 makesalt(salt); 402 pwd->pw_passwd = crypt(new_pass, salt); 403 #ifdef YP 404 switch (old_pwd->pw_fields & _PWF_SOURCE) { 405 case _PWF_FILES: 406 #endif 407 retval = PAM_SERVICE_ERR; 408 if (pw_init(NULL, NULL)) 409 openpam_log(PAM_LOG_ERROR, "pw_init() failed"); 410 else if ((pfd = pw_lock()) == -1) 411 openpam_log(PAM_LOG_ERROR, "pw_lock() failed"); 412 else if ((tfd = pw_tmp(-1)) == -1) 413 openpam_log(PAM_LOG_ERROR, "pw_tmp() failed"); 414 else if (pw_copy(pfd, tfd, pwd, old_pwd) == -1) 415 openpam_log(PAM_LOG_ERROR, "pw_copy() failed"); 416 else if (pw_mkdb(pwd->pw_name) == -1) 417 openpam_log(PAM_LOG_ERROR, "pw_mkdb() failed"); 418 else 419 retval = PAM_SUCCESS; 420 pw_fini(); 421 #ifdef YP 422 break; 423 case _PWF_NIS: 424 yp_domain = yp_server = NULL; 425 pam_get_data(pamh, "yp_domain", &yp_domain); 426 pam_get_data(pamh, "yp_server", &yp_server); 427 ypclnt = ypclnt_new(yp_domain, 428 "passwd.byname", yp_server); 429 if (ypclnt == NULL) { 430 retval = PAM_BUF_ERR; 431 } else if (ypclnt_connect(ypclnt) == -1 || 432 ypclnt_passwd(ypclnt, pwd, old_pass) == -1) { 433 openpam_log(PAM_LOG_ERROR, "%s", ypclnt->error); 434 retval = PAM_SERVICE_ERR; 435 } else { 436 retval = PAM_SUCCESS; 437 } 438 ypclnt_free(ypclnt); 439 break; 440 default: 441 openpam_log(PAM_LOG_ERROR, "unsupported source 0x%x", 442 pwd->pw_fields & _PWF_SOURCE); 443 retval = PAM_SERVICE_ERR; 444 } 445 #endif 446 free(old_pwd); 447 } 448 else { 449 /* Very bad juju */ 450 retval = PAM_ABORT; 451 PAM_LOG("Illegal 'flags'"); 452 } 453 454 return (retval); 455 } 456 457 /* Mostly stolen from passwd(1)'s local_passwd.c - markm */ 458 459 static unsigned char itoa64[] = /* 0 ... 63 => ascii - 64 */ 460 "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 461 462 static void 463 to64(char *s, long v, int n) 464 { 465 while (--n >= 0) { 466 *s++ = itoa64[v&0x3f]; 467 v >>= 6; 468 } 469 } 470 471 /* Salt suitable for traditional DES and MD5 */ 472 static void 473 makesalt(char salt[SALTSIZE + 1]) 474 { 475 int i; 476 477 /* These are not really random numbers, they are just 478 * numbers that change to thwart construction of a 479 * dictionary. 480 */ 481 for (i = 0; i < SALTSIZE; i += 4) 482 to64(&salt[i], arc4random(), 4); 483 salt[SALTSIZE] = '\0'; 484 } 485 486 PAM_MODULE_ENTRY("pam_unix"); 487