pam_unix.c revision 94148
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 94148 2002-04-07 20:43:27Z 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	if (pwd != NULL) {
143
144		PAM_LOG("Doing real authentication");
145
146		if (pwd->pw_passwd[0] == '\0'
147		    && pam_test_option(&options, PAM_OPT_NULLOK, NULL)) {
148			/*
149			 * No password case. XXX Are we giving too much away
150			 * by not prompting for a password?
151			 */
152			PAM_LOG("No password, and null password OK");
153			PAM_RETURN(PAM_SUCCESS);
154		}
155		else {
156			lc = login_getpwclass(pwd);
157			password_prompt = login_getcapstr(lc, "passwd_prompt",
158			    NULL, NULL);
159			retval = pam_get_authtok(pamh, PAM_AUTHTOK,
160			    &pass, password_prompt);
161			login_close(lc);
162			if (retval != PAM_SUCCESS)
163				PAM_RETURN(retval);
164			PAM_LOG("Got password");
165		}
166		encrypted = crypt(pass, pwd->pw_passwd);
167		if (pass[0] == '\0' && pwd->pw_passwd[0] != '\0')
168			encrypted = colon;
169
170		PAM_LOG("Encrypted password 1 is: %s", encrypted);
171		PAM_LOG("Encrypted password 2 is: %s", pwd->pw_passwd);
172
173		retval = strcmp(encrypted, pwd->pw_passwd) == 0 ?
174		    PAM_SUCCESS : PAM_AUTH_ERR;
175	}
176	else {
177
178		PAM_LOG("Doing dummy authentication");
179
180		/*
181		 * User unknown.
182		 * Encrypt a dummy password so as to not give away too much.
183		 */
184		lc = login_getclass(NULL);
185		password_prompt = login_getcapstr(lc, "passwd_prompt",
186		    NULL, NULL);
187		retval = pam_get_authtok(pamh,
188		    PAM_AUTHTOK, &pass, password_prompt);
189		login_close(lc);
190		if (retval != PAM_SUCCESS)
191			PAM_RETURN(retval);
192		PAM_LOG("Got password");
193		crypt(pass, "xx");
194		retval = PAM_AUTH_ERR;
195	}
196
197	/*
198	 * The PAM infrastructure will obliterate the cleartext
199	 * password before returning to the application.
200	 */
201	if (retval != PAM_SUCCESS)
202		PAM_VERBOSE_ERROR("UNIX authentication refused");
203
204	PAM_RETURN(retval);
205}
206
207PAM_EXTERN int
208pam_sm_setcred(pam_handle_t *pamh __unused, int flags __unused, int argc, const char **argv)
209{
210	struct options options;
211
212	pam_std_option(&options, other_options, argc, argv);
213
214	PAM_LOG("Options processed");
215
216	PAM_RETURN(PAM_SUCCESS);
217}
218
219/*
220 * account management
221 */
222PAM_EXTERN int
223pam_sm_acct_mgmt(pam_handle_t *pamh, int flags __unused, int argc, const char **argv)
224{
225	struct addrinfo hints, *res;
226	struct options options;
227	struct passwd *pwd;
228	struct timeval tp;
229	login_cap_t *lc;
230	time_t warntime;
231	int retval;
232	const char *rhost, *tty, *user;
233	char rhostip[MAXHOSTNAMELEN];
234
235	pam_std_option(&options, other_options, argc, argv);
236
237	PAM_LOG("Options processed");
238
239	retval = pam_get_user(pamh, &user, NULL);
240	if (retval != PAM_SUCCESS)
241		PAM_RETURN(retval);
242
243	if (user == NULL || (pwd = getpwnam(user)) == NULL)
244		PAM_RETURN(PAM_SERVICE_ERR);
245
246	PAM_LOG("Got user: %s", user);
247
248	retval = pam_get_item(pamh, PAM_RHOST, (const void **)&rhost);
249	if (retval != PAM_SUCCESS)
250		PAM_RETURN(retval);
251
252	retval = pam_get_item(pamh, PAM_TTY, (const void **)&tty);
253	if (retval != PAM_SUCCESS)
254		PAM_RETURN(retval);
255
256	if (*pwd->pw_passwd == '\0' &&
257	    (flags & PAM_DISALLOW_NULL_AUTHTOK) != 0)
258		return (PAM_NEW_AUTHTOK_REQD);
259
260	lc = login_getpwclass(pwd);
261	if (lc == NULL) {
262		PAM_LOG("Unable to get login class for user %s", user);
263		return (PAM_SERVICE_ERR);
264	}
265
266	PAM_LOG("Got login_cap");
267
268	if (pwd->pw_change || pwd->pw_expire)
269		gettimeofday(&tp, NULL);
270
271	/*
272	 * Check pw_expire before pw_change - no point in letting the
273	 * user change the password on an expired account.
274	 */
275
276	if (pwd->pw_expire) {
277		warntime = login_getcaptime(lc, "warnexpire",
278		    DEFAULT_WARN, DEFAULT_WARN);
279		if (tp.tv_sec >= pwd->pw_expire) {
280			login_close(lc);
281			PAM_RETURN(PAM_ACCT_EXPIRED);
282		} else if (pwd->pw_expire - tp.tv_sec < warntime &&
283		    (flags & PAM_SILENT) == 0) {
284			pam_error(pamh, "Warning: your account expires on %s",
285			    ctime(&pwd->pw_expire));
286		}
287	}
288
289	retval = PAM_SUCCESS;
290	if (pwd->pw_change) {
291		warntime = login_getcaptime(lc, "warnpassword",
292		    DEFAULT_WARN, DEFAULT_WARN);
293		if (tp.tv_sec >= pwd->pw_change) {
294			retval = PAM_NEW_AUTHTOK_REQD;
295		} else if (pwd->pw_change - tp.tv_sec < warntime &&
296		    (flags & PAM_SILENT) == 0) {
297			pam_error(pamh, "Warning: your password expires on %s",
298			    ctime(&pwd->pw_change));
299		}
300	}
301
302	/*
303	 * From here on, we must leave retval untouched (unless we
304	 * know we're going to fail), because we need to remember
305	 * whether we're supposed to return PAM_SUCCESS or
306	 * PAM_NEW_AUTHTOK_REQD.
307	 */
308
309	if (rhost) {
310		memset(&hints, 0, sizeof(hints));
311		hints.ai_family = AF_UNSPEC;
312		if (getaddrinfo(rhost, NULL, &hints, &res) == 0) {
313			getnameinfo(res->ai_addr, res->ai_addrlen,
314			    rhostip, sizeof(rhostip), NULL, 0,
315			    NI_NUMERICHOST|NI_WITHSCOPEID);
316		}
317		if (res != NULL)
318			freeaddrinfo(res);
319	}
320
321	/*
322	 * Check host / tty / time-of-day restrictions
323	 */
324
325	if (!auth_hostok(lc, rhost, rhostip) ||
326	    !auth_ttyok(lc, tty) ||
327	    !auth_timeok(lc, time(NULL)))
328		retval = PAM_AUTH_ERR;
329
330	login_close(lc);
331
332	PAM_RETURN(retval);
333}
334
335/*
336 * session management
337 *
338 * logging only
339 */
340PAM_EXTERN int
341pam_sm_open_session(pam_handle_t *pamh __unused, int flags __unused, int argc, const char **argv)
342{
343	struct options options;
344
345	pam_std_option(&options, other_options, argc, argv);
346
347	PAM_LOG("Options processed");
348
349	PAM_RETURN(PAM_SUCCESS);
350}
351
352PAM_EXTERN int
353pam_sm_close_session(pam_handle_t *pamh __unused, int flags __unused, int argc, const char **argv)
354{
355	struct options options;
356
357	pam_std_option(&options, other_options, argc, argv);
358
359	PAM_LOG("Options processed");
360
361	PAM_RETURN(PAM_SUCCESS);
362}
363
364/*
365 * password management
366 *
367 * standard Unix and NIS password changing
368 */
369PAM_EXTERN int
370pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv)
371{
372	struct options options;
373	struct passwd *pwd;
374	const char *user, *pass, *new_pass;
375	char *encrypted, *usrdup;
376	int retval, res;
377
378	pam_std_option(&options, other_options, argc, argv);
379
380	PAM_LOG("Options processed");
381
382	if (pam_test_option(&options, PAM_OPT_AUTH_AS_SELF, NULL))
383		pwd = getpwnam(getlogin());
384	else {
385		retval = pam_get_user(pamh, &user, NULL);
386		if (retval != PAM_SUCCESS)
387			PAM_RETURN(retval);
388		pwd = getpwnam(user);
389	}
390
391	PAM_LOG("Got user: %s", user);
392
393	if (flags & PAM_PRELIM_CHECK) {
394
395		PAM_LOG("PRELIM round; checking user password");
396
397		if (pwd->pw_passwd[0] == '\0'
398		    && pam_test_option(&options, PAM_OPT_NULLOK, NULL)) {
399			/*
400			 * No password case. XXX Are we giving too much away
401			 * by not prompting for a password?
402			 * XXX check PAM_DISALLOW_NULL_AUTHTOK
403			 */
404			PAM_LOG("Got password");
405			PAM_RETURN(PAM_SUCCESS);
406		}
407		else {
408			retval = pam_get_authtok(pamh,
409			    PAM_OLDAUTHTOK, &pass, NULL);
410			if (retval != PAM_SUCCESS)
411				PAM_RETURN(retval);
412			PAM_LOG("Got password");
413		}
414		encrypted = crypt(pass, pwd->pw_passwd);
415		if (pass[0] == '\0' && pwd->pw_passwd[0] != '\0')
416			encrypted = colon;
417
418		if (strcmp(encrypted, pwd->pw_passwd) != 0) {
419			pam_set_item(pamh, PAM_OLDAUTHTOK, NULL);
420			PAM_RETURN(PAM_AUTH_ERR);
421		}
422
423		PAM_RETURN(PAM_SUCCESS);
424	}
425	else if (flags & PAM_UPDATE_AUTHTOK) {
426		PAM_LOG("UPDATE round; checking user password");
427
428		retval = pam_get_authtok(pamh, PAM_OLDAUTHTOK, &pass, NULL);
429		if (retval != PAM_SUCCESS)
430			PAM_RETURN(retval);
431
432		PAM_LOG("Got old password");
433
434		for (;;) {
435			retval = pam_get_authtok(pamh,
436			    PAM_AUTHTOK, &new_pass, NULL);
437			if (retval != PAM_TRY_AGAIN)
438				break;
439			pam_error(pamh, "Mismatch; try again, EOF to quit.");
440		}
441
442		if (retval != PAM_SUCCESS) {
443			PAM_VERBOSE_ERROR("Unable to get new password");
444			PAM_RETURN(PAM_PERM_DENIED);
445		}
446
447		PAM_LOG("Got new password: %s", new_pass);
448
449#ifdef YP
450		/* If NIS is set in the passwd database, use it */
451		if ((usrdup = strdup(user)) == NULL)
452			PAM_RETURN(PAM_BUF_ERR);
453		res = use_yp(usrdup, 0, 0);
454		free(usrdup);
455		if (res == USER_YP_ONLY) {
456			if (!pam_test_option(&options, PAM_OPT_LOCAL_PASS,
457			    NULL))
458				retval = yp_passwd(user, new_pass);
459			else {
460				/* Reject 'local' flag if NIS is on and the user
461				 * is not local
462				 */
463				retval = PAM_PERM_DENIED;
464				PAM_LOG("Unknown local user: %s", user);
465			}
466		}
467		else if (res == USER_LOCAL_ONLY) {
468			if (!pam_test_option(&options, PAM_OPT_NIS_PASS, NULL))
469				retval = local_passwd(user, new_pass);
470			else {
471				/* Reject 'nis' flag if user is only local */
472				retval = PAM_PERM_DENIED;
473				PAM_LOG("Unknown NIS user: %s", user);
474			}
475		}
476		else if (res == USER_YP_AND_LOCAL) {
477			if (pam_test_option(&options, PAM_OPT_NIS_PASS, NULL))
478				retval = yp_passwd(user, new_pass);
479			else
480				retval = local_passwd(user, new_pass);
481		}
482		else
483			retval = PAM_SERVICE_ERR; /* Bad juju */
484#else
485		retval = local_passwd(user, new_pass);
486#endif
487	}
488	else {
489		/* Very bad juju */
490		retval = PAM_ABORT;
491		PAM_LOG("Illegal 'flags'");
492	}
493
494	PAM_RETURN(retval);
495}
496
497/* Mostly stolen from passwd(1)'s local_passwd.c - markm */
498
499static unsigned char itoa64[] =		/* 0 ... 63 => ascii - 64 */
500	"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
501
502static void
503to64(char *s, long v, int n)
504{
505	while (--n >= 0) {
506		*s++ = itoa64[v&0x3f];
507		v >>= 6;
508	}
509}
510
511static int
512local_passwd(const char *user, const char *pass)
513{
514	login_cap_t * lc;
515	struct passwd *pwd;
516	int pfd, tfd;
517	char *crypt_type, salt[SALTSIZE + 1];
518
519	pwd = getpwnam(user);
520	if (pwd == NULL)
521		return(PAM_SERVICE_ERR); /* Really bad things */
522
523#ifdef YP
524	pwd = (struct passwd *)&local_password;
525#endif
526	pw_init();
527
528	pwd->pw_change = 0;
529	lc = login_getclass(NULL);
530	crypt_type = login_getcapstr(lc, "passwd_format",
531		password_hash, password_hash);
532	if (login_setcryptfmt(lc, crypt_type, NULL) == NULL)
533		syslog(LOG_ERR, "cannot set password cipher");
534	login_close(lc);
535	makesalt(salt);
536	pwd->pw_passwd = crypt(pass, salt);
537
538	pfd = pw_lock();
539	tfd = pw_tmp();
540	pw_copy(pfd, tfd, pwd, NULL);
541
542	if (!pw_mkdb(user))
543		pw_error((char *)NULL, 0, 1);
544
545	return (PAM_SUCCESS);
546}
547
548#ifdef YP
549/* Stolen from src/usr.bin/passwd/yp_passwd.c, carrying copyrights of:
550 * Copyright (c) 1992/3 Theo de Raadt <deraadt@fsa.ca>
551 * Copyright (c) 1994 Olaf Kirch <okir@monad.swb.de>
552 * Copyright (c) 1995 Bill Paul <wpaul@ctr.columbia.edu>
553 */
554int
555yp_passwd(const char *user __unused, const char *pass)
556{
557	struct yppasswd yppwd;
558	struct master_yppasswd master_yppwd;
559	struct passwd *pwd;
560	struct rpc_err err;
561	CLIENT *clnt;
562	login_cap_t *lc;
563	int    *status;
564	uid_t uid;
565	char   *master, sockname[] = YP_SOCKNAME, salt[SALTSIZE + 1];
566
567	_use_yp = 1;
568
569	uid = getuid();
570
571	master = get_yp_master(1);
572	if (master == NULL)
573		return (PAM_SERVICE_ERR); /* Major disaster */
574
575	/*
576	 * It is presumed that by the time we get here, use_yp()
577	 * has been called and that we have verified that the user
578	 * actually exists. This being the case, the yp_password
579	 * stucture has already been filled in for us.
580	 */
581
582	/* Use the correct password */
583	pwd = (struct passwd *)&yp_password;
584
585	pwd->pw_change = 0;
586
587	/* Initialize password information */
588	if (suser_override) {
589		master_yppwd.newpw.pw_passwd = strdup(pwd->pw_passwd);
590		master_yppwd.newpw.pw_name = strdup(pwd->pw_name);
591		master_yppwd.newpw.pw_uid = pwd->pw_uid;
592		master_yppwd.newpw.pw_gid = pwd->pw_gid;
593		master_yppwd.newpw.pw_expire = pwd->pw_expire;
594		master_yppwd.newpw.pw_change = pwd->pw_change;
595		master_yppwd.newpw.pw_fields = pwd->pw_fields;
596		master_yppwd.newpw.pw_gecos = strdup(pwd->pw_gecos);
597		master_yppwd.newpw.pw_dir = strdup(pwd->pw_dir);
598		master_yppwd.newpw.pw_shell = strdup(pwd->pw_shell);
599		master_yppwd.newpw.pw_class = pwd->pw_class != NULL ?
600					strdup(pwd->pw_class) : strdup("");
601		master_yppwd.oldpass = strdup("");
602		master_yppwd.domain = yp_domain;
603	} else {
604		yppwd.newpw.pw_passwd = strdup(pwd->pw_passwd);
605		yppwd.newpw.pw_name = strdup(pwd->pw_name);
606		yppwd.newpw.pw_uid = pwd->pw_uid;
607		yppwd.newpw.pw_gid = pwd->pw_gid;
608		yppwd.newpw.pw_gecos = strdup(pwd->pw_gecos);
609		yppwd.newpw.pw_dir = strdup(pwd->pw_dir);
610		yppwd.newpw.pw_shell = strdup(pwd->pw_shell);
611		yppwd.oldpass = strdup("");
612	}
613
614	if (login_setcryptfmt(lc, "md5", NULL) == NULL)
615		syslog(LOG_ERR, "cannot set password cipher");
616	login_close(lc);
617
618	makesalt(salt);
619	if (suser_override)
620		master_yppwd.newpw.pw_passwd = crypt(pass, salt);
621	else
622		yppwd.newpw.pw_passwd = crypt(pass, salt);
623
624	if (suser_override) {
625		if ((clnt = clnt_create(sockname, MASTER_YPPASSWDPROG,
626		    MASTER_YPPASSWDVERS, "unix")) == NULL) {
627			syslog(LOG_ERR,
628			    "Cannot contact rpc.yppasswdd on host %s: %s",
629			    master, clnt_spcreateerror(""));
630			return (PAM_SERVICE_ERR);
631		}
632	}
633	else {
634		if ((clnt = clnt_create(master, YPPASSWDPROG,
635		    YPPASSWDVERS, "udp")) == NULL) {
636			syslog(LOG_ERR,
637			    "Cannot contact rpc.yppasswdd on host %s: %s",
638			    master, clnt_spcreateerror(""));
639			return (PAM_SERVICE_ERR);
640		}
641	}
642	/*
643	 * The yppasswd.x file said `unix authentication required',
644	 * so I added it. This is the only reason it is in here.
645	 * My yppasswdd doesn't use it, but maybe some others out there
646	 * do. 					--okir
647	 */
648	clnt->cl_auth = authunix_create_default();
649
650	if (suser_override)
651		status = yppasswdproc_update_master_1(&master_yppwd, clnt);
652	else
653		status = yppasswdproc_update_1(&yppwd, clnt);
654
655	clnt_geterr(clnt, &err);
656
657	auth_destroy(clnt->cl_auth);
658	clnt_destroy(clnt);
659
660	if (err.re_status != RPC_SUCCESS || status == NULL || *status)
661		return (PAM_SERVICE_ERR);
662
663	if (err.re_status || status == NULL || *status)
664		return (PAM_SERVICE_ERR);
665	return (PAM_SUCCESS);
666}
667#endif /* YP */
668
669/* Salt suitable for traditional DES and MD5 */
670void
671makesalt(char salt[SALTSIZE])
672{
673	int i;
674
675	/* These are not really random numbers, they are just
676	 * numbers that change to thwart construction of a
677	 * dictionary. This is exposed to the public.
678	 */
679	for (i = 0; i < SALTSIZE; i += 4)
680		to64(&salt[i], arc4random(), 4);
681	salt[SALTSIZE] = '\0';
682}
683
684PAM_MODULE_ENTRY("pam_unix");
685