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