pam_unix.c revision 147780
167754Smsmith/*-
267754Smsmith * Copyright 1998 Juniper Networks, Inc.
377424Smsmith * All rights reserved.
467754Smsmith * Copyright (c) 2002-2003 Networks Associates Technology, Inc.
567754Smsmith * All rights reserved.
667754Smsmith *
7217365Sjkim * Portions of this software was developed for the FreeBSD Project by
8281075Sdim * ThinkSec AS and NAI Labs, the Security Research Division of Network
970243Smsmith * Associates, Inc.  under DARPA/SPAWAR contract N66001-01-C-8035
1067754Smsmith * ("CBOSS"), as part of the DARPA CHATS research program.
11217365Sjkim *
12217365Sjkim * Redistribution and use in source and binary forms, with or without
13217365Sjkim * modification, are permitted provided that the following conditions
14217365Sjkim * are met:
15217365Sjkim * 1. Redistributions of source code must retain the above copyright
16217365Sjkim *    notice, this list of conditions and the following disclaimer.
17217365Sjkim * 2. Redistributions in binary form must reproduce the above copyright
18217365Sjkim *    notice, this list of conditions and the following disclaimer in the
19217365Sjkim *    documentation and/or other materials provided with the distribution.
20217365Sjkim * 3. The name of the author may not be used to endorse or promote
21217365Sjkim *    products derived from this software without specific prior written
22217365Sjkim *    permission.
23217365Sjkim *
24217365Sjkim * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
2567754Smsmith * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26217365Sjkim * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27217365Sjkim * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
28217365Sjkim * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2967754Smsmith * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30217365Sjkim * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31217365Sjkim * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32217365Sjkim * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33217365Sjkim * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34217365Sjkim * SUCH DAMAGE.
35217365Sjkim */
36217365Sjkim
37217365Sjkim#include <sys/cdefs.h>
38217365Sjkim__FBSDID("$FreeBSD: head/lib/libpam/modules/pam_unix/pam_unix.c 147780 2005-07-05 18:42:18Z des $");
39217365Sjkim
40217365Sjkim#include <sys/param.h>
41217365Sjkim#include <sys/socket.h>
42217365Sjkim#include <sys/time.h>
4367754Smsmith#include <netinet/in.h>
44193341Sjkim#include <arpa/inet.h>
45193341Sjkim
46193341Sjkim#include <login_cap.h>
4767754Smsmith#include <netdb.h>
4867754Smsmith#include <pwd.h>
4977424Smsmith#include <stdlib.h>
5091116Smsmith#include <string.h>
5167754Smsmith#include <stdio.h>
5267754Smsmith#include <syslog.h>
5377424Smsmith#include <unistd.h>
5467754Smsmith
55114237Snjl#include <libutil.h>
5667754Smsmith
57114237Snjl#ifdef YP
58114237Snjl#include <ypclnt.h>
59114237Snjl#endif
60114237Snjl
6167754Smsmith#define PAM_SM_AUTH
6267754Smsmith#define PAM_SM_ACCOUNT
6367754Smsmith#define	PAM_SM_PASSWORD
64114237Snjl
65197104Sjkim#include <security/pam_appl.h>
66114237Snjl#include <security/pam_modules.h>
6767754Smsmith#include <security/pam_mod_misc.h>
6867754Smsmith
6967754Smsmith#define PASSWORD_HASH		"md5"
7077424Smsmith#define DEFAULT_WARN		(2L * 7L * 86400L)  /* Two weeks */
7167754Smsmith#define	SALTSIZE		32
7267754Smsmith
73114237Snjlstatic void makesalt(char []);
74114237Snjl
75114237Snjlstatic char password_hash[] =		PASSWORD_HASH;
76114237Snjl
77114237Snjl#define PAM_OPT_LOCAL_PASS	"local_pass"
7867754Smsmith#define PAM_OPT_NIS_PASS	"nis_pass"
79167802Sjkim
8067754Smsmithchar *tempname = NULL;
81114237Snjl
8267754Smsmith/*
8367754Smsmith * authentication management
84167802Sjkim */
8567754SmsmithPAM_EXTERN int
8667754Smsmithpam_sm_authenticate(pam_handle_t *pamh, int flags __unused,
87167802Sjkim    int argc __unused, const char *argv[] __unused)
88129684Snjl{
89167802Sjkim	login_cap_t *lc;
90167802Sjkim	struct passwd *pwd;
91167802Sjkim	int retval;
92167802Sjkim	const char *pass, *user, *realpw, *prompt;
93167802Sjkim
94167802Sjkim	if (openpam_get_option(pamh, PAM_OPT_AUTH_AS_SELF)) {
95167802Sjkim		pwd = getpwnam(getlogin());
96249663Sjkim	} else {
97167802Sjkim		retval = pam_get_user(pamh, &user, NULL);
98114237Snjl		if (retval != PAM_SUCCESS)
9967754Smsmith			return (retval);
100167802Sjkim		pwd = getpwnam(user);
10167754Smsmith	}
10267754Smsmith
10367754Smsmith	PAM_LOG("Got user: %s", user);
10467754Smsmith
105114237Snjl	if (pwd != NULL) {
106123315Snjl		PAM_LOG("Doing real authentication");
10767754Smsmith		realpw = pwd->pw_passwd;
10867754Smsmith		if (realpw[0] == '\0') {
10967754Smsmith			if (!(flags & PAM_DISALLOW_NULL_AUTHTOK) &&
110167802Sjkim			    openpam_get_option(pamh, PAM_OPT_NULLOK))
111114237Snjl				return (PAM_SUCCESS);
11267754Smsmith			realpw = "*";
11367754Smsmith		}
114167802Sjkim		lc = login_getpwclass(pwd);
11567754Smsmith	} else {
11667754Smsmith		PAM_LOG("Doing dummy authentication");
11767754Smsmith		realpw = "*";
11867754Smsmith		lc = login_getclass(NULL);
119167802Sjkim	}
12067754Smsmith	prompt = login_getcapstr(lc, "passwd_prompt", NULL, NULL);
121114237Snjl	retval = pam_get_authtok(pamh, PAM_AUTHTOK, &pass, prompt);
122114237Snjl	login_close(lc);
123167802Sjkim	if (retval != PAM_SUCCESS)
124114237Snjl		return (retval);
125114237Snjl	PAM_LOG("Got password");
126167802Sjkim	if (strcmp(crypt(pass, realpw), realpw) == 0)
127114237Snjl		return (PAM_SUCCESS);
128114237Snjl
129167802Sjkim	PAM_VERBOSE_ERROR("UNIX authentication refused");
13067754Smsmith	return (PAM_AUTH_ERR);
13167754Smsmith}
132114237Snjl
13367754SmsmithPAM_EXTERN int
134193267Sjkimpam_sm_setcred(pam_handle_t *pamh __unused, int flags __unused,
13567754Smsmith    int argc __unused, const char *argv[] __unused)
136114237Snjl{
137250838Sjkim
138114237Snjl	return (PAM_SUCCESS);
139114237Snjl}
140114237Snjl
141114237Snjl/*
142250838Sjkim * account management
143114237Snjl */
144114237SnjlPAM_EXTERN int
145114237Snjlpam_sm_acct_mgmt(pam_handle_t *pamh, int flags __unused,
146114237Snjl    int argc __unused, const char *argv[] __unused)
147250838Sjkim{
148114237Snjl	struct addrinfo hints, *res;
149114237Snjl	struct passwd *pwd;
150114237Snjl	struct timeval tp;
151114237Snjl	login_cap_t *lc;
152250838Sjkim	time_t warntime;
153114237Snjl	int retval;
154114237Snjl	const char *user;
155114237Snjl	const void *rhost, *tty;
156114237Snjl	char rhostip[MAXHOSTNAMELEN] = "";
157250838Sjkim
158114237Snjl	retval = pam_get_user(pamh, &user, NULL);
159114237Snjl	if (retval != PAM_SUCCESS)
160114237Snjl		return (retval);
161114237Snjl
162151937Sjkim	if (user == NULL || (pwd = getpwnam(user)) == NULL)
163151937Sjkim		return (PAM_SERVICE_ERR);
164151937Sjkim
165151937Sjkim	PAM_LOG("Got user: %s", user);
166197104Sjkim
167151937Sjkim	retval = pam_get_item(pamh, PAM_RHOST, &rhost);
168151937Sjkim	if (retval != PAM_SUCCESS)
169151937Sjkim		return (retval);
170167802Sjkim
171167802Sjkim	retval = pam_get_item(pamh, PAM_TTY, &tty);
172151937Sjkim	if (retval != PAM_SUCCESS)
173151937Sjkim		return (retval);
174114237Snjl
175114237Snjl	if (*pwd->pw_passwd == '\0' &&
176114237Snjl	    (flags & PAM_DISALLOW_NULL_AUTHTOK) != 0)
177114237Snjl		return (PAM_NEW_AUTHTOK_REQD);
178167802Sjkim
179114237Snjl	lc = login_getpwclass(pwd);
180114237Snjl	if (lc == NULL) {
181167802Sjkim		PAM_LOG("Unable to get login class for user %s", user);
182204773Sjkim		return (PAM_SERVICE_ERR);
183167802Sjkim	}
184151937Sjkim
185114237Snjl	PAM_LOG("Got login_cap");
186114237Snjl
187114237Snjl	if (pwd->pw_change || pwd->pw_expire)
188167802Sjkim		gettimeofday(&tp, NULL);
189167802Sjkim
190167802Sjkim	/*
19167754Smsmith	 * Check pw_expire before pw_change - no point in letting the
192114237Snjl	 * user change the password on an expired account.
193114237Snjl	 */
194114237Snjl
195167802Sjkim	if (pwd->pw_expire) {
196167802Sjkim		warntime = login_getcaptime(lc, "warnexpire",
197167802Sjkim		    DEFAULT_WARN, DEFAULT_WARN);
198167802Sjkim		if (tp.tv_sec >= pwd->pw_expire) {
199167802Sjkim			login_close(lc);
200114237Snjl			return (PAM_ACCT_EXPIRED);
201114237Snjl		} else if (pwd->pw_expire - tp.tv_sec < warntime &&
202114237Snjl		    (flags & PAM_SILENT) == 0) {
203114237Snjl			pam_error(pamh, "Warning: your account expires on %s",
204114237Snjl			    ctime(&pwd->pw_expire));
205114237Snjl		}
206114237Snjl	}
207151937Sjkim
208114237Snjl	retval = PAM_SUCCESS;
209197104Sjkim	if (pwd->pw_change) {
210114237Snjl		warntime = login_getcaptime(lc, "warnpassword",
211114237Snjl		    DEFAULT_WARN, DEFAULT_WARN);
212114237Snjl		if (tp.tv_sec >= pwd->pw_change) {
213114237Snjl			retval = PAM_NEW_AUTHTOK_REQD;
214197104Sjkim		} else if (pwd->pw_change - tp.tv_sec < warntime &&
215114237Snjl		    (flags & PAM_SILENT) == 0) {
216114237Snjl			pam_error(pamh, "Warning: your password expires on %s",
217114237Snjl			    ctime(&pwd->pw_change));
218114237Snjl		}
219114237Snjl	}
220114237Snjl
221114237Snjl	/*
222114237Snjl	 * From here on, we must leave retval untouched (unless we
223114237Snjl	 * know we're going to fail), because we need to remember
224202771Sjkim	 * whether we're supposed to return PAM_SUCCESS or
225114237Snjl	 * PAM_NEW_AUTHTOK_REQD.
226114237Snjl	 */
227114237Snjl
228114237Snjl	if (rhost && *(const char *)rhost != '\0') {
229114237Snjl		memset(&hints, 0, sizeof(hints));
230167802Sjkim		hints.ai_family = AF_UNSPEC;
231114237Snjl		if (getaddrinfo(rhost, NULL, &hints, &res) == 0) {
232114237Snjl			getnameinfo(res->ai_addr, res->ai_addrlen,
233114237Snjl			    rhostip, sizeof(rhostip), NULL, 0,
234114237Snjl			    NI_NUMERICHOST);
235114237Snjl		}
23667754Smsmith		if (res != NULL)
237114237Snjl			freeaddrinfo(res);
23867754Smsmith	}
23967754Smsmith
240114237Snjl	/*
241114237Snjl	 * Check host / tty / time-of-day restrictions
242197104Sjkim	 */
243114237Snjl
24467754Smsmith	if (!auth_hostok(lc, rhost, rhostip) ||
24567754Smsmith	    !auth_ttyok(lc, tty) ||
24677424Smsmith	    !auth_timeok(lc, time(NULL)))
24767754Smsmith		retval = PAM_AUTH_ERR;
24867754Smsmith
24967754Smsmith	login_close(lc);
25067754Smsmith
25177424Smsmith	return (retval);
25267754Smsmith}
25377424Smsmith
25467754Smsmith/*
25567754Smsmith * password management
256151937Sjkim *
25767754Smsmith * standard Unix and NIS password changing
25867754Smsmith */
25967754SmsmithPAM_EXTERN int
26067754Smsmithpam_sm_chauthtok(pam_handle_t *pamh, int flags,
261252279Sjkim    int argc __unused, const char *argv[] __unused)
262252279Sjkim{
26367754Smsmith#ifdef YP
26467754Smsmith	struct ypclnt *ypclnt;
26567754Smsmith	void *yp_domain, *yp_server;
26677424Smsmith#endif
26767754Smsmith	char salt[SALTSIZE + 1];
26867754Smsmith	login_cap_t * lc;
26977424Smsmith	struct passwd *pwd, *old_pwd;
27067754Smsmith	const char *user, *old_pass, *new_pass;
27167754Smsmith	char *encrypted;
27267754Smsmith	int pfd, tfd, retval;
27367754Smsmith
27467754Smsmith	if (openpam_get_option(pamh, PAM_OPT_AUTH_AS_SELF))
27567754Smsmith		pwd = getpwnam(getlogin());
27667754Smsmith	else {
277167802Sjkim		retval = pam_get_user(pamh, &user, NULL);
27867754Smsmith		if (retval != PAM_SUCCESS)
27977424Smsmith			return (retval);
280114237Snjl		pwd = getpwnam(user);
281114237Snjl	}
282114237Snjl
28367754Smsmith	if (pwd == NULL)
284114237Snjl		return (PAM_AUTHTOK_RECOVERY_ERR);
285114237Snjl
286252279Sjkim	PAM_LOG("Got user: %s", user);
287252279Sjkim
288252279Sjkim	if (flags & PAM_PRELIM_CHECK) {
289252279Sjkim
290252279Sjkim		PAM_LOG("PRELIM round");
291114237Snjl
292114237Snjl		if (getuid() == 0 &&
293123315Snjl		    (pwd->pw_fields & _PWF_SOURCE) == _PWF_FILES)
29467754Smsmith			/* root doesn't need the old password */
295167802Sjkim			return (pam_set_item(pamh, PAM_OLDAUTHTOK, ""));
296114237Snjl#ifdef YP
297114237Snjl		if (getuid() == 0 &&
29867754Smsmith		    (pwd->pw_fields & _PWF_SOURCE) == _PWF_NIS) {
299114237Snjl
30067754Smsmith			yp_domain = yp_server = NULL;
30167754Smsmith			(void)pam_get_data(pamh, "yp_domain", &yp_domain);
302114237Snjl			(void)pam_get_data(pamh, "yp_server", &yp_server);
30367754Smsmith
304114237Snjl			ypclnt = ypclnt_new(yp_domain, "passwd.byname", yp_server);
30567754Smsmith			if (ypclnt == NULL)
306114237Snjl				return (PAM_BUF_ERR);
30767754Smsmith
308114237Snjl			if (ypclnt_connect(ypclnt) == -1) {
30967754Smsmith				ypclnt_free(ypclnt);
31067754Smsmith				return (PAM_SERVICE_ERR);
311126372Snjl			}
312126372Snjl
313126372Snjl			retval = ypclnt_havepasswdd(ypclnt);
314126372Snjl			ypclnt_free(ypclnt);
315197104Sjkim			if (retval == 1)
316126372Snjl				return (pam_set_item(pamh, PAM_OLDAUTHTOK, ""));
317126372Snjl			else if (retval == -1)
318197104Sjkim				return (PAM_SERVICE_ERR);
319197104Sjkim		}
320197104Sjkim#endif
321126372Snjl		if (pwd->pw_passwd[0] == '\0'
322197104Sjkim		    && openpam_get_option(pamh, PAM_OPT_NULLOK)) {
323126372Snjl			/*
324197104Sjkim			 * No password case. XXX Are we giving too much away
325197104Sjkim			 * by not prompting for a password?
326126372Snjl			 * XXX check PAM_DISALLOW_NULL_AUTHTOK
327126372Snjl			 */
328126372Snjl			old_pass = "";
329126372Snjl		} else {
330126372Snjl			retval = pam_get_authtok(pamh,
331126372Snjl			    PAM_OLDAUTHTOK, &old_pass, NULL);
332197104Sjkim			if (retval != PAM_SUCCESS)
333126372Snjl				return (retval);
334197104Sjkim		}
335197104Sjkim		PAM_LOG("Got old password");
336197104Sjkim		/* always encrypt first */
337126372Snjl		encrypted = crypt(old_pass, pwd->pw_passwd);
338126372Snjl		if (old_pass[0] == '\0' &&
339126372Snjl		    !openpam_get_option(pamh, PAM_OPT_NULLOK))
340197104Sjkim			return (PAM_PERM_DENIED);
341126372Snjl		if (strcmp(encrypted, pwd->pw_passwd) != 0)
342126372Snjl			return (PAM_PERM_DENIED);
343126372Snjl	}
344197104Sjkim	else if (flags & PAM_UPDATE_AUTHTOK) {
345126372Snjl		PAM_LOG("UPDATE round");
346126372Snjl
347197104Sjkim		retval = pam_get_authtok(pamh,
348126372Snjl		    PAM_OLDAUTHTOK, &old_pass, NULL);
349197104Sjkim		if (retval != PAM_SUCCESS)
350197104Sjkim			return (retval);
351197104Sjkim		PAM_LOG("Got old password");
352197104Sjkim
353126372Snjl		/* get new password */
354197104Sjkim		for (;;) {
355126372Snjl			retval = pam_get_authtok(pamh,
356197104Sjkim			    PAM_AUTHTOK, &new_pass, NULL);
357126372Snjl			if (retval != PAM_TRY_AGAIN)
358197104Sjkim				break;
359126372Snjl			pam_error(pamh, "Mismatch; try again, EOF to quit.");
360126372Snjl		}
361126372Snjl		PAM_LOG("Got new password");
362126372Snjl		if (retval != PAM_SUCCESS) {
363197104Sjkim			PAM_VERBOSE_ERROR("Unable to get new password");
364197104Sjkim			return (retval);
365126372Snjl		}
366197104Sjkim
367197104Sjkim		if (getuid() != 0 && new_pass[0] == '\0' &&
368197104Sjkim		    !openpam_get_option(pamh, PAM_OPT_NULLOK))
369197104Sjkim			return (PAM_PERM_DENIED);
370197104Sjkim
371197104Sjkim		if ((old_pwd = pw_dup(pwd)) == NULL)
372197104Sjkim			return (PAM_BUF_ERR);
373197104Sjkim
374197104Sjkim		pwd->pw_change = 0;
375197104Sjkim		lc = login_getclass(pwd->pw_class);
376126372Snjl		if (login_setcryptfmt(lc, password_hash, NULL) == NULL)
377126372Snjl			openpam_log(PAM_LOG_ERROR,
378197104Sjkim			    "can't set password cipher, relying on default");
379126372Snjl		login_close(lc);
380		makesalt(salt);
381		pwd->pw_passwd = crypt(new_pass, salt);
382#ifdef YP
383		switch (old_pwd->pw_fields & _PWF_SOURCE) {
384		case _PWF_FILES:
385#endif
386			retval = PAM_SERVICE_ERR;
387			if (pw_init(NULL, NULL))
388				openpam_log(PAM_LOG_ERROR, "pw_init() failed");
389			else if ((pfd = pw_lock()) == -1)
390				openpam_log(PAM_LOG_ERROR, "pw_lock() failed");
391			else if ((tfd = pw_tmp(-1)) == -1)
392				openpam_log(PAM_LOG_ERROR, "pw_tmp() failed");
393			else if (pw_copy(pfd, tfd, pwd, old_pwd) == -1)
394				openpam_log(PAM_LOG_ERROR, "pw_copy() failed");
395			else if (pw_mkdb(pwd->pw_name) == -1)
396				openpam_log(PAM_LOG_ERROR, "pw_mkdb() failed");
397			else
398				retval = PAM_SUCCESS;
399			pw_fini();
400#ifdef YP
401			break;
402		case _PWF_NIS:
403			yp_domain = yp_server = NULL;
404			(void)pam_get_data(pamh, "yp_domain", &yp_domain);
405			(void)pam_get_data(pamh, "yp_server", &yp_server);
406			ypclnt = ypclnt_new(yp_domain,
407			    "passwd.byname", yp_server);
408			if (ypclnt == NULL) {
409				retval = PAM_BUF_ERR;
410			} else if (ypclnt_connect(ypclnt) == -1 ||
411			    ypclnt_passwd(ypclnt, pwd, old_pass) == -1) {
412				openpam_log(PAM_LOG_ERROR, "%s", ypclnt->error);
413				retval = PAM_SERVICE_ERR;
414			} else {
415				retval = PAM_SUCCESS;
416			}
417			ypclnt_free(ypclnt);
418			break;
419		default:
420			openpam_log(PAM_LOG_ERROR, "unsupported source 0x%x",
421			    pwd->pw_fields & _PWF_SOURCE);
422			retval = PAM_SERVICE_ERR;
423		}
424#endif
425		free(old_pwd);
426	}
427	else {
428		/* Very bad juju */
429		retval = PAM_ABORT;
430		PAM_LOG("Illegal 'flags'");
431	}
432
433	return (retval);
434}
435
436/* Mostly stolen from passwd(1)'s local_passwd.c - markm */
437
438static unsigned char itoa64[] =		/* 0 ... 63 => ascii - 64 */
439	"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
440
441static void
442to64(char *s, long v, int n)
443{
444	while (--n >= 0) {
445		*s++ = itoa64[v&0x3f];
446		v >>= 6;
447	}
448}
449
450/* Salt suitable for traditional DES and MD5 */
451void
452makesalt(char salt[SALTSIZE])
453{
454	int i;
455
456	/* These are not really random numbers, they are just
457	 * numbers that change to thwart construction of a
458	 * dictionary. This is exposed to the public.
459	 */
460	for (i = 0; i < SALTSIZE; i += 4)
461		to64(&salt[i], arc4random(), 4);
462	salt[SALTSIZE] = '\0';
463}
464
465PAM_MODULE_ENTRY("pam_unix");
466