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