pam_unix.c revision 93984
1/*- 2 * Copyright 1998 Juniper Networks, Inc. 3 * All rights reserved. 4 * Copyright (c) 2002 Networks Associates Technology, Inc. 5 * All rights reserved. 6 * 7 * Portions of this software was developed for the FreeBSD Project by 8 * ThinkSec AS and NAI Labs, the Security Research Division of Network 9 * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035 10 * ("CBOSS"), as part of the DARPA CHATS research program. 11 * 12 * Redistribution and use in source and binary forms, with or without 13 * modification, are permitted provided that the following conditions 14 * are met: 15 * 1. Redistributions of source code must retain the above copyright 16 * notice, this list of conditions and the following disclaimer. 17 * 2. Redistributions in binary form must reproduce the above copyright 18 * notice, this list of conditions and the following disclaimer in the 19 * documentation and/or other materials provided with the distribution. 20 * 3. The name of the author may not be used to endorse or promote 21 * products derived from this software without specific prior written 22 * permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 34 * SUCH DAMAGE. 35 */ 36 37#include <sys/cdefs.h> 38__FBSDID("$FreeBSD: head/lib/libpam/modules/pam_unix/pam_unix.c 93984 2002-04-06 19:30:04Z des $"); 39 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#ifdef YP 47#include <rpc/rpc.h> 48#include <rpcsvc/yp_prot.h> 49#include <rpcsvc/ypclnt.h> 50#include <rpcsvc/yppasswd.h> 51#endif 52 53#include <login_cap.h> 54#include <netdb.h> 55#include <pwd.h> 56#include <stdlib.h> 57#include <string.h> 58#include <stdio.h> 59#include <syslog.h> 60#include <unistd.h> 61 62#include <pw_copy.h> 63#include <pw_util.h> 64 65#ifdef YP 66#include <pw_yp.h> 67#include "yppasswd_private.h" 68#endif 69 70#define PAM_SM_AUTH 71#define PAM_SM_ACCOUNT 72#define PAM_SM_SESSION 73#define PAM_SM_PASSWORD 74 75#include <security/pam_appl.h> 76#include <security/pam_modules.h> 77#include <security/pam_mod_misc.h> 78 79#define PASSWORD_HASH "md5" 80#define DEFAULT_WARN (2L * 7L * 86400L) /* Two weeks */ 81#define SALTSIZE 32 82 83static void makesalt(char []); 84 85static char password_hash[] = PASSWORD_HASH; 86static char colon[] = ":"; 87 88enum { 89 PAM_OPT_AUTH_AS_SELF = PAM_OPT_STD_MAX, 90 PAM_OPT_NULLOK, 91 PAM_OPT_LOCAL_PASS, 92 PAM_OPT_NIS_PASS 93}; 94 95static struct opttab other_options[] = { 96 { "auth_as_self", PAM_OPT_AUTH_AS_SELF }, 97 { "nullok", PAM_OPT_NULLOK }, 98 { "local_pass", PAM_OPT_LOCAL_PASS }, 99 { "nis_pass", PAM_OPT_NIS_PASS }, 100 { NULL, 0 } 101}; 102 103#ifdef YP 104int pam_use_yp = 0; 105int yp_errno = YP_TRUE; 106#endif 107 108char *tempname = NULL; 109static int local_passwd(const char *user, const char *pass); 110#ifdef YP 111static int yp_passwd(const char *user, const char *pass); 112#endif 113 114/* 115 * authentication management 116 */ 117PAM_EXTERN int 118pam_sm_authenticate(pam_handle_t *pamh, int flags __unused, int argc, const char **argv) 119{ 120 login_cap_t *lc; 121 struct options options; 122 struct passwd *pwd; 123 int retval; 124 const char *pass, *user; 125 char *encrypted, *password_prompt; 126 127 pam_std_option(&options, other_options, argc, argv); 128 129 PAM_LOG("Options processed"); 130 131 if (pam_test_option(&options, PAM_OPT_AUTH_AS_SELF, NULL)) 132 pwd = getpwnam(getlogin()); 133 else { 134 retval = pam_get_user(pamh, &user, NULL); 135 if (retval != PAM_SUCCESS) 136 PAM_RETURN(retval); 137 pwd = getpwnam(user); 138 } 139 140 PAM_LOG("Got user: %s", user); 141 142 lc = login_getclass(NULL); 143 password_prompt = login_getcapstr(lc, "passwd_prompt", 144 password_prompt, NULL); 145 login_close(lc); 146 lc = NULL; 147 148 if (pwd != NULL) { 149 150 PAM_LOG("Doing real authentication"); 151 152 if (pwd->pw_passwd[0] == '\0' 153 && pam_test_option(&options, PAM_OPT_NULLOK, NULL)) { 154 /* 155 * No password case. XXX Are we giving too much away 156 * by not prompting for a password? 157 */ 158 PAM_LOG("No password, and null password OK"); 159 PAM_RETURN(PAM_SUCCESS); 160 } 161 else { 162 retval = pam_get_authtok(pamh, PAM_AUTHTOK, 163 &pass, password_prompt); 164 if (retval != PAM_SUCCESS) 165 PAM_RETURN(retval); 166 PAM_LOG("Got password"); 167 } 168 encrypted = crypt(pass, pwd->pw_passwd); 169 if (pass[0] == '\0' && pwd->pw_passwd[0] != '\0') 170 encrypted = colon; 171 172 PAM_LOG("Encrypted password 1 is: %s", encrypted); 173 PAM_LOG("Encrypted password 2 is: %s", pwd->pw_passwd); 174 175 retval = strcmp(encrypted, pwd->pw_passwd) == 0 ? 176 PAM_SUCCESS : PAM_AUTH_ERR; 177 } 178 else { 179 180 PAM_LOG("Doing dummy authentication"); 181 182 /* 183 * User unknown. 184 * Encrypt a dummy password so as to not give away too much. 185 */ 186 retval = pam_get_authtok(pamh, 187 PAM_AUTHTOK, &pass, password_prompt); 188 if (retval != PAM_SUCCESS) 189 PAM_RETURN(retval); 190 PAM_LOG("Got password"); 191 crypt(pass, "xx"); 192 retval = PAM_AUTH_ERR; 193 } 194 195 /* 196 * The PAM infrastructure will obliterate the cleartext 197 * password before returning to the application. 198 */ 199 if (retval != PAM_SUCCESS) 200 PAM_VERBOSE_ERROR("UNIX authentication refused"); 201 202 PAM_RETURN(retval); 203} 204 205PAM_EXTERN int 206pam_sm_setcred(pam_handle_t *pamh __unused, int flags __unused, int argc, const char **argv) 207{ 208 struct options options; 209 210 pam_std_option(&options, other_options, argc, argv); 211 212 PAM_LOG("Options processed"); 213 214 PAM_RETURN(PAM_SUCCESS); 215} 216 217/* 218 * account management 219 */ 220PAM_EXTERN int 221pam_sm_acct_mgmt(pam_handle_t *pamh, int flags __unused, int argc, const char **argv) 222{ 223 struct addrinfo hints, *res; 224 struct options options; 225 struct passwd *pwd; 226 struct timeval tp; 227 login_cap_t *lc; 228 time_t warntime; 229 int retval; 230 const char *rhost, *tty, *user; 231 char rhostip[MAXHOSTNAMELEN]; 232 233 pam_std_option(&options, other_options, argc, argv); 234 235 PAM_LOG("Options processed"); 236 237 retval = pam_get_user(pamh, &user, NULL); 238 if (retval != PAM_SUCCESS) 239 PAM_RETURN(retval); 240 241 if (user == NULL || (pwd = getpwnam(user)) == NULL) 242 PAM_RETURN(PAM_SERVICE_ERR); 243 244 PAM_LOG("Got user: %s", user); 245 246 retval = pam_get_item(pamh, PAM_RHOST, (const void **)&rhost); 247 if (retval != PAM_SUCCESS) 248 PAM_RETURN(retval); 249 250 retval = pam_get_item(pamh, PAM_TTY, (const void **)&tty); 251 if (retval != PAM_SUCCESS) 252 PAM_RETURN(retval); 253 254 if (*pwd->pw_passwd == '\0' && 255 (flags & PAM_DISALLOW_NULL_AUTHTOK) != 0) 256 return (PAM_NEW_AUTHTOK_REQD); 257 258 lc = login_getpwclass(pwd); 259 if (lc == NULL) { 260 PAM_LOG("Unable to get login class for user %s", user); 261 return (PAM_SERVICE_ERR); 262 } 263 264 PAM_LOG("Got login_cap"); 265 266 if (pwd->pw_change || pwd->pw_expire) 267 gettimeofday(&tp, NULL); 268 269 /* 270 * Check pw_expire before pw_change - no point in letting the 271 * user change the password on an expired account. 272 */ 273 274 if (pwd->pw_expire) { 275 warntime = login_getcaptime(lc, "warnexpire", 276 DEFAULT_WARN, DEFAULT_WARN); 277 if (tp.tv_sec >= pwd->pw_expire) { 278 login_close(lc); 279 PAM_RETURN(PAM_ACCT_EXPIRED); 280 } else if (pwd->pw_expire - tp.tv_sec < warntime && 281 (flags & PAM_SILENT) == 0) { 282 pam_error(pamh, "Warning: your account expires on %s", 283 ctime(&pwd->pw_expire)); 284 } 285 } 286 287 retval = PAM_SUCCESS; 288 if (pwd->pw_change) { 289 warntime = login_getcaptime(lc, "warnpassword", 290 DEFAULT_WARN, DEFAULT_WARN); 291 if (tp.tv_sec >= pwd->pw_change) { 292 retval = PAM_NEW_AUTHTOK_REQD; 293 } else if (pwd->pw_change - tp.tv_sec < warntime && 294 (flags & PAM_SILENT) == 0) { 295 pam_error(pamh, "Warning: your password expires on %s", 296 ctime(&pwd->pw_change)); 297 } 298 } 299 300 /* 301 * From here on, we must leave retval untouched (unless we 302 * know we're going to fail), because we need to remember 303 * whether we're supposed to return PAM_SUCCESS or 304 * PAM_NEW_AUTHTOK_REQD. 305 */ 306 307 if (rhost) { 308 memset(&hints, 0, sizeof(hints)); 309 hints.ai_family = AF_UNSPEC; 310 if (getaddrinfo(rhost, NULL, &hints, &res) == 0) { 311 getnameinfo(res->ai_addr, res->ai_addrlen, 312 rhostip, sizeof(rhostip), NULL, 0, 313 NI_NUMERICHOST|NI_WITHSCOPEID); 314 } 315 if (res != NULL) 316 freeaddrinfo(res); 317 } 318 319 /* 320 * Check host / tty / time-of-day restrictions 321 */ 322 323 if (!auth_hostok(lc, rhost, rhostip) || 324 !auth_ttyok(lc, tty) || 325 !auth_timeok(lc, time(NULL))) 326 retval = PAM_AUTH_ERR; 327 328 login_close(lc); 329 330 PAM_RETURN(retval); 331} 332 333/* 334 * session management 335 * 336 * logging only 337 */ 338PAM_EXTERN int 339pam_sm_open_session(pam_handle_t *pamh __unused, int flags __unused, int argc, const char **argv) 340{ 341 struct options options; 342 343 pam_std_option(&options, other_options, argc, argv); 344 345 PAM_LOG("Options processed"); 346 347 PAM_RETURN(PAM_SUCCESS); 348} 349 350PAM_EXTERN int 351pam_sm_close_session(pam_handle_t *pamh __unused, int flags __unused, int argc, const char **argv) 352{ 353 struct options options; 354 355 pam_std_option(&options, other_options, argc, argv); 356 357 PAM_LOG("Options processed"); 358 359 PAM_RETURN(PAM_SUCCESS); 360} 361 362/* 363 * password management 364 * 365 * standard Unix and NIS password changing 366 */ 367PAM_EXTERN int 368pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv) 369{ 370 struct options options; 371 struct passwd *pwd; 372 const char *user, *pass, *new_pass; 373 char *encrypted, *usrdup; 374 int retval, res; 375 376 pam_std_option(&options, other_options, argc, argv); 377 378 PAM_LOG("Options processed"); 379 380 if (pam_test_option(&options, PAM_OPT_AUTH_AS_SELF, NULL)) 381 pwd = getpwnam(getlogin()); 382 else { 383 retval = pam_get_user(pamh, &user, NULL); 384 if (retval != PAM_SUCCESS) 385 PAM_RETURN(retval); 386 pwd = getpwnam(user); 387 } 388 389 PAM_LOG("Got user: %s", user); 390 391 if (flags & PAM_PRELIM_CHECK) { 392 393 PAM_LOG("PRELIM round; checking user password"); 394 395 if (pwd->pw_passwd[0] == '\0' 396 && pam_test_option(&options, PAM_OPT_NULLOK, NULL)) { 397 /* 398 * No password case. XXX Are we giving too much away 399 * by not prompting for a password? 400 * XXX check PAM_DISALLOW_NULL_AUTHTOK 401 */ 402 PAM_LOG("Got password"); 403 PAM_RETURN(PAM_SUCCESS); 404 } 405 else { 406 retval = pam_get_authtok(pamh, 407 PAM_OLDAUTHTOK, &pass, NULL); 408 if (retval != PAM_SUCCESS) 409 PAM_RETURN(retval); 410 PAM_LOG("Got password"); 411 } 412 encrypted = crypt(pass, pwd->pw_passwd); 413 if (pass[0] == '\0' && pwd->pw_passwd[0] != '\0') 414 encrypted = colon; 415 416 if (strcmp(encrypted, pwd->pw_passwd) != 0) { 417 pam_set_item(pamh, PAM_OLDAUTHTOK, NULL); 418 PAM_RETURN(PAM_AUTH_ERR); 419 } 420 421 PAM_RETURN(PAM_SUCCESS); 422 } 423 else if (flags & PAM_UPDATE_AUTHTOK) { 424 PAM_LOG("UPDATE round; checking user password"); 425 426 retval = pam_get_authtok(pamh, PAM_OLDAUTHTOK, &pass, NULL); 427 if (retval != PAM_SUCCESS) 428 PAM_RETURN(retval); 429 430 PAM_LOG("Got old password"); 431 432 for (;;) { 433 retval = pam_get_authtok(pamh, 434 PAM_AUTHTOK, &new_pass, NULL); 435 if (retval != PAM_TRY_AGAIN) 436 break; 437 pam_error(pamh, "Mismatch; try again, EOF to quit."); 438 } 439 440 if (retval != PAM_SUCCESS) { 441 PAM_VERBOSE_ERROR("Unable to get new password"); 442 PAM_RETURN(PAM_PERM_DENIED); 443 } 444 445 PAM_LOG("Got new password: %s", new_pass); 446 447#ifdef YP 448 /* If NIS is set in the passwd database, use it */ 449 if ((usrdup = strdup(user)) == NULL) 450 PAM_RETURN(PAM_BUF_ERR); 451 res = use_yp(usrdup, 0, 0); 452 free(usrdup); 453 if (res == USER_YP_ONLY) { 454 if (!pam_test_option(&options, PAM_OPT_LOCAL_PASS, 455 NULL)) 456 retval = yp_passwd(user, new_pass); 457 else { 458 /* Reject 'local' flag if NIS is on and the user 459 * is not local 460 */ 461 retval = PAM_PERM_DENIED; 462 PAM_LOG("Unknown local user: %s", user); 463 } 464 } 465 else if (res == USER_LOCAL_ONLY) { 466 if (!pam_test_option(&options, PAM_OPT_NIS_PASS, NULL)) 467 retval = local_passwd(user, new_pass); 468 else { 469 /* Reject 'nis' flag if user is only local */ 470 retval = PAM_PERM_DENIED; 471 PAM_LOG("Unknown NIS user: %s", user); 472 } 473 } 474 else if (res == USER_YP_AND_LOCAL) { 475 if (pam_test_option(&options, PAM_OPT_NIS_PASS, NULL)) 476 retval = yp_passwd(user, new_pass); 477 else 478 retval = local_passwd(user, new_pass); 479 } 480 else 481 retval = PAM_SERVICE_ERR; /* Bad juju */ 482#else 483 retval = local_passwd(user, new_pass); 484#endif 485 } 486 else { 487 /* Very bad juju */ 488 retval = PAM_ABORT; 489 PAM_LOG("Illegal 'flags'"); 490 } 491 492 PAM_RETURN(retval); 493} 494 495/* Mostly stolen from passwd(1)'s local_passwd.c - markm */ 496 497static unsigned char itoa64[] = /* 0 ... 63 => ascii - 64 */ 498 "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 499 500static void 501to64(char *s, long v, int n) 502{ 503 while (--n >= 0) { 504 *s++ = itoa64[v&0x3f]; 505 v >>= 6; 506 } 507} 508 509static int 510local_passwd(const char *user, const char *pass) 511{ 512 login_cap_t * lc; 513 struct passwd *pwd; 514 int pfd, tfd; 515 char *crypt_type, salt[SALTSIZE + 1]; 516 517 pwd = getpwnam(user); 518 if (pwd == NULL) 519 return(PAM_SERVICE_ERR); /* Really bad things */ 520 521#ifdef YP 522 pwd = (struct passwd *)&local_password; 523#endif 524 pw_init(); 525 526 pwd->pw_change = 0; 527 lc = login_getclass(NULL); 528 crypt_type = login_getcapstr(lc, "passwd_format", 529 password_hash, password_hash); 530 if (login_setcryptfmt(lc, crypt_type, NULL) == NULL) 531 syslog(LOG_ERR, "cannot set password cipher"); 532 login_close(lc); 533 makesalt(salt); 534 pwd->pw_passwd = crypt(pass, salt); 535 536 pfd = pw_lock(); 537 tfd = pw_tmp(); 538 pw_copy(pfd, tfd, pwd, NULL); 539 540 if (!pw_mkdb(user)) 541 pw_error((char *)NULL, 0, 1); 542 543 return (PAM_SUCCESS); 544} 545 546#ifdef YP 547/* Stolen from src/usr.bin/passwd/yp_passwd.c, carrying copyrights of: 548 * Copyright (c) 1992/3 Theo de Raadt <deraadt@fsa.ca> 549 * Copyright (c) 1994 Olaf Kirch <okir@monad.swb.de> 550 * Copyright (c) 1995 Bill Paul <wpaul@ctr.columbia.edu> 551 */ 552int 553yp_passwd(const char *user __unused, const char *pass) 554{ 555 struct yppasswd yppwd; 556 struct master_yppasswd master_yppwd; 557 struct passwd *pwd; 558 struct rpc_err err; 559 CLIENT *clnt; 560 login_cap_t *lc; 561 int *status; 562 uid_t uid; 563 char *master, sockname[] = YP_SOCKNAME, salt[SALTSIZE + 1]; 564 565 _use_yp = 1; 566 567 uid = getuid(); 568 569 master = get_yp_master(1); 570 if (master == NULL) 571 return (PAM_SERVICE_ERR); /* Major disaster */ 572 573 /* 574 * It is presumed that by the time we get here, use_yp() 575 * has been called and that we have verified that the user 576 * actually exists. This being the case, the yp_password 577 * stucture has already been filled in for us. 578 */ 579 580 /* Use the correct password */ 581 pwd = (struct passwd *)&yp_password; 582 583 pwd->pw_change = 0; 584 585 /* Initialize password information */ 586 if (suser_override) { 587 master_yppwd.newpw.pw_passwd = strdup(pwd->pw_passwd); 588 master_yppwd.newpw.pw_name = strdup(pwd->pw_name); 589 master_yppwd.newpw.pw_uid = pwd->pw_uid; 590 master_yppwd.newpw.pw_gid = pwd->pw_gid; 591 master_yppwd.newpw.pw_expire = pwd->pw_expire; 592 master_yppwd.newpw.pw_change = pwd->pw_change; 593 master_yppwd.newpw.pw_fields = pwd->pw_fields; 594 master_yppwd.newpw.pw_gecos = strdup(pwd->pw_gecos); 595 master_yppwd.newpw.pw_dir = strdup(pwd->pw_dir); 596 master_yppwd.newpw.pw_shell = strdup(pwd->pw_shell); 597 master_yppwd.newpw.pw_class = pwd->pw_class != NULL ? 598 strdup(pwd->pw_class) : strdup(""); 599 master_yppwd.oldpass = strdup(""); 600 master_yppwd.domain = yp_domain; 601 } else { 602 yppwd.newpw.pw_passwd = strdup(pwd->pw_passwd); 603 yppwd.newpw.pw_name = strdup(pwd->pw_name); 604 yppwd.newpw.pw_uid = pwd->pw_uid; 605 yppwd.newpw.pw_gid = pwd->pw_gid; 606 yppwd.newpw.pw_gecos = strdup(pwd->pw_gecos); 607 yppwd.newpw.pw_dir = strdup(pwd->pw_dir); 608 yppwd.newpw.pw_shell = strdup(pwd->pw_shell); 609 yppwd.oldpass = strdup(""); 610 } 611 612 if (login_setcryptfmt(lc, "md5", NULL) == NULL) 613 syslog(LOG_ERR, "cannot set password cipher"); 614 login_close(lc); 615 616 makesalt(salt); 617 if (suser_override) 618 master_yppwd.newpw.pw_passwd = crypt(pass, salt); 619 else 620 yppwd.newpw.pw_passwd = crypt(pass, salt); 621 622 if (suser_override) { 623 if ((clnt = clnt_create(sockname, MASTER_YPPASSWDPROG, 624 MASTER_YPPASSWDVERS, "unix")) == NULL) { 625 syslog(LOG_ERR, 626 "Cannot contact rpc.yppasswdd on host %s: %s", 627 master, clnt_spcreateerror("")); 628 return (PAM_SERVICE_ERR); 629 } 630 } 631 else { 632 if ((clnt = clnt_create(master, YPPASSWDPROG, 633 YPPASSWDVERS, "udp")) == NULL) { 634 syslog(LOG_ERR, 635 "Cannot contact rpc.yppasswdd on host %s: %s", 636 master, clnt_spcreateerror("")); 637 return (PAM_SERVICE_ERR); 638 } 639 } 640 /* 641 * The yppasswd.x file said `unix authentication required', 642 * so I added it. This is the only reason it is in here. 643 * My yppasswdd doesn't use it, but maybe some others out there 644 * do. --okir 645 */ 646 clnt->cl_auth = authunix_create_default(); 647 648 if (suser_override) 649 status = yppasswdproc_update_master_1(&master_yppwd, clnt); 650 else 651 status = yppasswdproc_update_1(&yppwd, clnt); 652 653 clnt_geterr(clnt, &err); 654 655 auth_destroy(clnt->cl_auth); 656 clnt_destroy(clnt); 657 658 if (err.re_status != RPC_SUCCESS || status == NULL || *status) 659 return (PAM_SERVICE_ERR); 660 661 if (err.re_status || status == NULL || *status) 662 return (PAM_SERVICE_ERR); 663 return (PAM_SUCCESS); 664} 665#endif /* YP */ 666 667/* Salt suitable for traditional DES and MD5 */ 668void 669makesalt(char salt[SALTSIZE]) 670{ 671 int i; 672 673 /* These are not really random numbers, they are just 674 * numbers that change to thwart construction of a 675 * dictionary. This is exposed to the public. 676 */ 677 for (i = 0; i < SALTSIZE; i += 4) 678 to64(&salt[i], arc4random(), 4); 679 salt[SALTSIZE] = '\0'; 680} 681 682PAM_MODULE_ENTRY("pam_unix"); 683