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