pam_unix.c revision 116394
155682Smarkm/*-
2233294Sstas * Copyright 1998 Juniper Networks, Inc.
3233294Sstas * All rights reserved.
4233294Sstas * Copyright (c) 2002-2003 Networks Associates Technology, Inc.
555682Smarkm * All rights reserved.
6233294Sstas *
7233294Sstas * Portions of this software was developed for the FreeBSD Project by
8233294Sstas * ThinkSec AS and NAI Labs, the Security Research Division of Network
955682Smarkm * Associates, Inc.  under DARPA/SPAWAR contract N66001-01-C-8035
10233294Sstas * ("CBOSS"), as part of the DARPA CHATS research program.
11233294Sstas *
1255682Smarkm * Redistribution and use in source and binary forms, with or without
13233294Sstas * modification, are permitted provided that the following conditions
14233294Sstas * are met:
15233294Sstas * 1. Redistributions of source code must retain the above copyright
1655682Smarkm *    notice, this list of conditions and the following disclaimer.
17233294Sstas * 2. Redistributions in binary form must reproduce the above copyright
18233294Sstas *    notice, this list of conditions and the following disclaimer in the
19233294Sstas *    documentation and/or other materials provided with the distribution.
2055682Smarkm * 3. The name of the author may not be used to endorse or promote
21233294Sstas *    products derived from this software without specific prior written
22233294Sstas *    permission.
23233294Sstas *
24233294Sstas * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
25233294Sstas * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26233294Sstas * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27233294Sstas * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
28233294Sstas * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29233294Sstas * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30233294Sstas * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31233294Sstas * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
3255682Smarkm * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
3355682Smarkm * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3455682Smarkm * SUCH DAMAGE.
3555682Smarkm */
3655682Smarkm
3755682Smarkm#include <sys/cdefs.h>
3855682Smarkm__FBSDID("$FreeBSD: head/lib/libpam/modules/pam_unix/pam_unix.c 116394 2003-06-15 10:37:22Z mbr $");
3955682Smarkm
4055682Smarkm#include <sys/param.h>
4155682Smarkm#include <sys/socket.h>
4255682Smarkm#include <sys/time.h>
43233294Sstas#include <netinet/in.h>
44233294Sstas#include <arpa/inet.h>
45233294Sstas
46233294Sstas#include <login_cap.h>
47233294Sstas#include <netdb.h>
48233294Sstas#include <pwd.h>
49233294Sstas#include <stdlib.h>
5072445Sassar#include <string.h>
5172445Sassar#include <stdio.h>
52233294Sstas#include <syslog.h>
5372445Sassar#include <unistd.h>
5472445Sassar
55233294Sstas#include <libutil.h>
5655682Smarkm
5755682Smarkm#ifdef YP
5855682Smarkm#include <ypclnt.h>
5955682Smarkm#endif
6072445Sassar
6155682Smarkm#define PAM_SM_AUTH
6290926Snectar#define PAM_SM_ACCOUNT
63233294Sstas#define	PAM_SM_PASSWORD
6455682Smarkm
6590926Snectar#include <security/pam_appl.h>
6655682Smarkm#include <security/pam_modules.h>
67233294Sstas#include <security/pam_mod_misc.h>
68233294Sstas
6955682Smarkm#define PASSWORD_HASH		"md5"
7055682Smarkm#define DEFAULT_WARN		(2L * 7L * 86400L)  /* Two weeks */
7155682Smarkm#define	SALTSIZE		32
7255682Smarkm
7355682Smarkmstatic void makesalt(char []);
7455682Smarkm
75233294Sstasstatic char password_hash[] =		PASSWORD_HASH;
7655682Smarkm
7755682Smarkm#define PAM_OPT_LOCAL_PASS	"local_pass"
7855682Smarkm#define PAM_OPT_NIS_PASS	"nis_pass"
7955682Smarkm
8055682Smarkmchar *tempname = NULL;
81233294Sstas
82233294Sstas/*
83233294Sstas * authentication management
84233294Sstas */
85233294SstasPAM_EXTERN int
8655682Smarkmpam_sm_authenticate(pam_handle_t *pamh, int flags __unused,
87233294Sstas    int argc __unused, const char *argv[] __unused)
88233294Sstas{
89233294Sstas	login_cap_t *lc;
90233294Sstas	struct passwd *pwd;
9155682Smarkm	int retval;
9255682Smarkm	const char *pass, *user, *realpw, *prompt;
9355682Smarkm
94233294Sstas	if (openpam_get_option(pamh, PAM_OPT_AUTH_AS_SELF)) {
9555682Smarkm		pwd = getpwnam(getlogin());
9655682Smarkm	} else {
9755682Smarkm		retval = pam_get_user(pamh, &user, NULL);
9855682Smarkm		if (retval != PAM_SUCCESS)
9955682Smarkm			return (retval);
10055682Smarkm		pwd = getpwnam(user);
10155682Smarkm	}
102233294Sstas
10355682Smarkm	PAM_LOG("Got user: %s", user);
10455682Smarkm
10555682Smarkm	if (pwd != NULL) {
10672445Sassar		PAM_LOG("Doing real authentication");
10772445Sassar		realpw = pwd->pw_passwd;
10872445Sassar		if (realpw[0] == '\0') {
10955682Smarkm			if (!(flags & PAM_DISALLOW_NULL_AUTHTOK) &&
11055682Smarkm			    openpam_get_option(pamh, PAM_OPT_NULLOK))
11155682Smarkm				return (PAM_SUCCESS);
11255682Smarkm			realpw = "*";
113233294Sstas		}
114233294Sstas		lc = login_getpwclass(pwd);
115233294Sstas	} else {
116233294Sstas		PAM_LOG("Doing dummy authentication");
11755682Smarkm		realpw = "*";
11855682Smarkm		lc = login_getclass(NULL);
11955682Smarkm	}
12072445Sassar	prompt = login_getcapstr(lc, "passwd_prompt", NULL, NULL);
121233294Sstas	retval = pam_get_authtok(pamh, PAM_AUTHTOK, &pass, prompt);
12255682Smarkm	login_close(lc);
12355682Smarkm	if (retval != PAM_SUCCESS)
12455682Smarkm		return (retval);
12555682Smarkm	PAM_LOG("Got password");
12655682Smarkm	if (strcmp(crypt(pass, realpw), realpw) == 0)
12755682Smarkm		return (PAM_SUCCESS);
12872445Sassar
12972445Sassar	PAM_VERBOSE_ERROR("UNIX authentication refused");
13072445Sassar	return (PAM_AUTH_ERR);
13172445Sassar}
13272445Sassar
133233294SstasPAM_EXTERN int
13472445Sassarpam_sm_setcred(pam_handle_t *pamh __unused, int flags __unused,
135233294Sstas    int argc __unused, const char *argv[] __unused)
136233294Sstas{
13772445Sassar
13872445Sassar	return (PAM_SUCCESS);
139233294Sstas}
140233294Sstas
141233294Sstas/*
14272445Sassar * account management
14372445Sassar */
14472445SassarPAM_EXTERN int
145233294Sstaspam_sm_acct_mgmt(pam_handle_t *pamh, int flags __unused,
146233294Sstas    int argc __unused, const char *argv[] __unused)
147233294Sstas{
148233294Sstas	struct addrinfo hints, *res;
149233294Sstas	struct passwd *pwd;
150233294Sstas	struct timeval tp;
151233294Sstas	login_cap_t *lc;
152233294Sstas	time_t warntime;
153233294Sstas	int retval;
154233294Sstas	const char *rhost, *tty, *user;
155233294Sstas	char rhostip[MAXHOSTNAMELEN] = "";
156233294Sstas
157233294Sstas	retval = pam_get_user(pamh, &user, NULL);
15872445Sassar	if (retval != PAM_SUCCESS)
15972445Sassar		return (retval);
16072445Sassar
161233294Sstas	if (user == NULL || (pwd = getpwnam(user)) == NULL)
16272445Sassar		return (PAM_SERVICE_ERR);
163233294Sstas
164233294Sstas	PAM_LOG("Got user: %s", user);
16572445Sassar
16672445Sassar	retval = pam_get_item(pamh, PAM_RHOST, (const void **)&rhost);
16772445Sassar	if (retval != PAM_SUCCESS)
16872445Sassar		return (retval);
16972445Sassar
17072445Sassar	retval = pam_get_item(pamh, PAM_TTY, (const void **)&tty);
17172445Sassar	if (retval != PAM_SUCCESS)
172233294Sstas		return (retval);
17355682Smarkm
17455682Smarkm	if (*pwd->pw_passwd == '\0' &&
17555682Smarkm	    (flags & PAM_DISALLOW_NULL_AUTHTOK) != 0)
17655682Smarkm		return (PAM_NEW_AUTHTOK_REQD);
17755682Smarkm
17855682Smarkm	lc = login_getpwclass(pwd);
17955682Smarkm	if (lc == NULL) {
180178825Sdfr		PAM_LOG("Unable to get login class for user %s", user);
18155682Smarkm		return (PAM_SERVICE_ERR);
18255682Smarkm	}
18372445Sassar
18472445Sassar	PAM_LOG("Got login_cap");
185233294Sstas
186233294Sstas	if (pwd->pw_change || pwd->pw_expire)
187233294Sstas		gettimeofday(&tp, NULL);
18855682Smarkm
189178825Sdfr	/*
190178825Sdfr	 * Check pw_expire before pw_change - no point in letting the
191233294Sstas	 * user change the password on an expired account.
192233294Sstas	 */
193233294Sstas
194233294Sstas	if (pwd->pw_expire) {
195233294Sstas		warntime = login_getcaptime(lc, "warnexpire",
196233294Sstas		    DEFAULT_WARN, DEFAULT_WARN);
197233294Sstas		if (tp.tv_sec >= pwd->pw_expire) {
19872445Sassar			login_close(lc);
19972445Sassar			return (PAM_ACCT_EXPIRED);
20055682Smarkm		} else if (pwd->pw_expire - tp.tv_sec < warntime &&
201233294Sstas		    (flags & PAM_SILENT) == 0) {
20272445Sassar			pam_error(pamh, "Warning: your account expires on %s",
20372445Sassar			    ctime(&pwd->pw_expire));
204178825Sdfr		}
205233294Sstas	}
20672445Sassar
207233294Sstas	retval = PAM_SUCCESS;
208178825Sdfr	if (pwd->pw_change) {
20972445Sassar		warntime = login_getcaptime(lc, "warnpassword",
210178825Sdfr		    DEFAULT_WARN, DEFAULT_WARN);
211233294Sstas		if (tp.tv_sec >= pwd->pw_change) {
21272445Sassar			retval = PAM_NEW_AUTHTOK_REQD;
21372445Sassar		} else if (pwd->pw_change - tp.tv_sec < warntime &&
214233294Sstas		    (flags & PAM_SILENT) == 0) {
215233294Sstas			pam_error(pamh, "Warning: your password expires on %s",
216233294Sstas			    ctime(&pwd->pw_change));
217233294Sstas		}
218233294Sstas	}
219178825Sdfr
220178825Sdfr	/*
221178825Sdfr	 * From here on, we must leave retval untouched (unless we
222178825Sdfr	 * know we're going to fail), because we need to remember
223178825Sdfr	 * whether we're supposed to return PAM_SUCCESS or
224178825Sdfr	 * PAM_NEW_AUTHTOK_REQD.
225233294Sstas	 */
22655682Smarkm
227178825Sdfr	if (rhost && *rhost) {
228178825Sdfr		memset(&hints, 0, sizeof(hints));
22955682Smarkm		hints.ai_family = AF_UNSPEC;
23055682Smarkm		if (getaddrinfo(rhost, NULL, &hints, &res) == 0) {
231178825Sdfr			getnameinfo(res->ai_addr, res->ai_addrlen,
232178825Sdfr			    rhostip, sizeof(rhostip), NULL, 0,
23355682Smarkm			    NI_NUMERICHOST|NI_WITHSCOPEID);
234178825Sdfr		}
235178825Sdfr		if (res != NULL)
23655682Smarkm			freeaddrinfo(res);
237178825Sdfr	}
238233294Sstas
239233294Sstas	/*
24055682Smarkm	 * Check host / tty / time-of-day restrictions
24155682Smarkm	 */
24255682Smarkm
24355682Smarkm	if (!auth_hostok(lc, rhost, rhostip) ||
24455682Smarkm	    !auth_ttyok(lc, tty) ||
24555682Smarkm	    !auth_timeok(lc, time(NULL)))
246233294Sstas		retval = PAM_AUTH_ERR;
247178825Sdfr
248178825Sdfr	login_close(lc);
249233294Sstas
250233294Sstas	return (retval);
251233294Sstas}
25255682Smarkm
25355682Smarkm/*
25455682Smarkm * password management
255233294Sstas *
256233294Sstas * standard Unix and NIS password changing
257233294Sstas */
258233294SstasPAM_EXTERN int
259233294Sstaspam_sm_chauthtok(pam_handle_t *pamh, int flags,
260233294Sstas    int argc __unused, const char *argv[] __unused)
261233294Sstas{
262233294Sstas#ifdef YP
263233294Sstas	struct ypclnt *ypclnt;
264233294Sstas	const char *yp_domain, *yp_server;
265233294Sstas#endif
266233294Sstas	char salt[SALTSIZE + 1];
267233294Sstas	login_cap_t * lc;
268233294Sstas	struct passwd *pwd, *old_pwd;
269233294Sstas	const char *user, *old_pass, *new_pass;
270233294Sstas	char *encrypted;
271233294Sstas	int pfd, tfd, retval;
272233294Sstas
273233294Sstas	if (openpam_get_option(pamh, PAM_OPT_AUTH_AS_SELF))
274233294Sstas		pwd = getpwnam(getlogin());
275233294Sstas	else {
276233294Sstas		retval = pam_get_user(pamh, &user, NULL);
277233294Sstas		if (retval != PAM_SUCCESS)
278233294Sstas			return (retval);
279233294Sstas		pwd = getpwnam(user);
280233294Sstas	}
281233294Sstas
282233294Sstas	if (pwd == NULL)
283233294Sstas		return (PAM_AUTHTOK_RECOVERY_ERR);
284233294Sstas
285233294Sstas	PAM_LOG("Got user: %s", user);
286233294Sstas
287233294Sstas	if (flags & PAM_PRELIM_CHECK) {
288233294Sstas
289233294Sstas		PAM_LOG("PRELIM round");
290233294Sstas
291233294Sstas		if (getuid() == 0 &&
292233294Sstas		    (pwd->pw_fields & _PWF_SOURCE) == _PWF_FILES)
293233294Sstas			/* root doesn't need the old password */
294233294Sstas			return (pam_set_item(pamh, PAM_OLDAUTHTOK, ""));
295233294Sstas#ifdef YP
296233294Sstas		if (getuid() == 0 &&
297233294Sstas		    (pwd->pw_fields & _PWF_SOURCE) == _PWF_NIS) {
298233294Sstas
299233294Sstas			yp_domain = yp_server = NULL;
300233294Sstas			(void)pam_get_data(pamh,
301233294Sstas			    "yp_domain", (const void **)&yp_domain);
302233294Sstas			(void)pam_get_data(pamh,
303233294Sstas			    "yp_server", (const void **)&yp_server);
304233294Sstas
305233294Sstas			ypclnt = ypclnt_new(yp_domain, "passwd.byname", yp_server);
306233294Sstas			if (ypclnt == NULL)
307233294Sstas				return (PAM_BUF_ERR);
308233294Sstas
309233294Sstas			if (ypclnt_connect(ypclnt) == -1) {
310233294Sstas				ypclnt_free(ypclnt);
311233294Sstas				return (PAM_SERVICE_ERR);
312233294Sstas			}
313233294Sstas
314233294Sstas			retval = ypclnt_havepasswdd(ypclnt);
315233294Sstas			ypclnt_free(ypclnt);
316233294Sstas			if (retval == 1)
317233294Sstas				return (pam_set_item(pamh, PAM_OLDAUTHTOK, ""));
318233294Sstas			else if (retval == -1)
319233294Sstas				return (PAM_SERVICE_ERR);
320233294Sstas		}
321233294Sstas#endif
322233294Sstas		if (pwd->pw_passwd[0] == '\0'
323233294Sstas		    && openpam_get_option(pamh, PAM_OPT_NULLOK)) {
324233294Sstas			/*
325233294Sstas			 * No password case. XXX Are we giving too much away
326233294Sstas			 * by not prompting for a password?
327233294Sstas			 * XXX check PAM_DISALLOW_NULL_AUTHTOK
328233294Sstas			 */
329233294Sstas			old_pass = "";
330233294Sstas		} else {
331233294Sstas			retval = pam_get_authtok(pamh,
332233294Sstas			    PAM_OLDAUTHTOK, &old_pass, NULL);
333233294Sstas			if (retval != PAM_SUCCESS)
334233294Sstas				return (retval);
335233294Sstas		}
336233294Sstas		PAM_LOG("Got old password");
337233294Sstas		/* always encrypt first */
338233294Sstas		encrypted = crypt(old_pass, pwd->pw_passwd);
339233294Sstas		if (old_pass[0] == '\0' &&
340233294Sstas		    !openpam_get_option(pamh, PAM_OPT_NULLOK))
341233294Sstas			return (PAM_PERM_DENIED);
342233294Sstas		if (strcmp(encrypted, pwd->pw_passwd) != 0)
343233294Sstas			return (PAM_PERM_DENIED);
344233294Sstas	}
345233294Sstas	else if (flags & PAM_UPDATE_AUTHTOK) {
346233294Sstas		PAM_LOG("UPDATE round");
347233294Sstas
348233294Sstas		retval = pam_get_authtok(pamh,
349233294Sstas		    PAM_OLDAUTHTOK, &old_pass, NULL);
350233294Sstas		if (retval != PAM_SUCCESS)
351233294Sstas			return (retval);
352233294Sstas		PAM_LOG("Got old password");
353233294Sstas
354233294Sstas		/* get new password */
355233294Sstas		for (;;) {
356233294Sstas			retval = pam_get_authtok(pamh,
357233294Sstas			    PAM_AUTHTOK, &new_pass, NULL);
358233294Sstas			if (retval != PAM_TRY_AGAIN)
359233294Sstas				break;
360233294Sstas			pam_error(pamh, "Mismatch; try again, EOF to quit.");
361233294Sstas		}
362233294Sstas		PAM_LOG("Got new password");
363233294Sstas		if (retval != PAM_SUCCESS) {
364233294Sstas			PAM_VERBOSE_ERROR("Unable to get new password");
365233294Sstas			return (retval);
366233294Sstas		}
367233294Sstas
368233294Sstas		if (getuid() != 0 && new_pass[0] == '\0' &&
369233294Sstas		    !openpam_get_option(pamh, PAM_OPT_NULLOK))
370233294Sstas			return (PAM_PERM_DENIED);
371233294Sstas
372233294Sstas		if ((old_pwd = pw_dup(pwd)) == NULL)
373233294Sstas			return (PAM_BUF_ERR);
374233294Sstas
375233294Sstas		pwd->pw_change = 0;
376233294Sstas		lc = login_getclass(NULL);
377233294Sstas		if (login_setcryptfmt(lc, password_hash, NULL) == NULL)
378233294Sstas			openpam_log(PAM_LOG_ERROR,
379233294Sstas			    "can't set password cipher, relying on default");
380233294Sstas		login_close(lc);
381233294Sstas		makesalt(salt);
382233294Sstas		pwd->pw_passwd = crypt(new_pass, salt);
383233294Sstas#ifdef YP
384233294Sstas		switch (old_pwd->pw_fields & _PWF_SOURCE) {
385233294Sstas		case _PWF_FILES:
386233294Sstas#endif
387233294Sstas			retval = PAM_SERVICE_ERR;
388233294Sstas			if (pw_init(NULL, NULL))
389233294Sstas				openpam_log(PAM_LOG_ERROR, "pw_init() failed");
390233294Sstas			else if ((pfd = pw_lock()) == -1)
391233294Sstas				openpam_log(PAM_LOG_ERROR, "pw_lock() failed");
392233294Sstas			else if ((tfd = pw_tmp(-1)) == -1)
393233294Sstas				openpam_log(PAM_LOG_ERROR, "pw_tmp() failed");
394233294Sstas			else if (pw_copy(pfd, tfd, pwd, old_pwd) == -1)
395233294Sstas				openpam_log(PAM_LOG_ERROR, "pw_copy() failed");
396233294Sstas			else if (pw_mkdb(pwd->pw_name) == -1)
397233294Sstas				openpam_log(PAM_LOG_ERROR, "pw_mkdb() failed");
398233294Sstas			else
399233294Sstas				retval = PAM_SUCCESS;
400233294Sstas			pw_fini();
401233294Sstas#ifdef YP
402233294Sstas			break;
403233294Sstas		case _PWF_NIS:
404233294Sstas			yp_domain = yp_server = NULL;
405233294Sstas			(void)pam_get_data(pamh,
406233294Sstas			    "yp_domain", (const void **)&yp_domain);
407233294Sstas			(void)pam_get_data(pamh,
408233294Sstas			    "yp_server", (const void **)&yp_server);
409233294Sstas			ypclnt = ypclnt_new(yp_domain,
410233294Sstas			    "passwd.byname", yp_server);
411233294Sstas			if (ypclnt == NULL) {
41255682Smarkm				retval = PAM_BUF_ERR;
41355682Smarkm			} else if (ypclnt_connect(ypclnt) == -1 ||
41455682Smarkm			    ypclnt_passwd(ypclnt, pwd, old_pass) == -1) {
41555682Smarkm				openpam_log(PAM_LOG_ERROR, "%s", ypclnt->error);
41655682Smarkm				retval = PAM_SERVICE_ERR;
417233294Sstas			} else {
41855682Smarkm				retval = PAM_SUCCESS;
419233294Sstas			}
420233294Sstas			ypclnt_free(ypclnt);
421233294Sstas			break;
42255682Smarkm		default:
42355682Smarkm			openpam_log(PAM_LOG_ERROR, "unsupported source 0x%x",
42455682Smarkm			    pwd->pw_fields & _PWF_SOURCE);
425			retval = PAM_SERVICE_ERR;
426		}
427#endif
428		free(old_pwd);
429	}
430	else {
431		/* Very bad juju */
432		retval = PAM_ABORT;
433		PAM_LOG("Illegal 'flags'");
434	}
435
436	return (retval);
437}
438
439/* Mostly stolen from passwd(1)'s local_passwd.c - markm */
440
441static unsigned char itoa64[] =		/* 0 ... 63 => ascii - 64 */
442	"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
443
444static void
445to64(char *s, long v, int n)
446{
447	while (--n >= 0) {
448		*s++ = itoa64[v&0x3f];
449		v >>= 6;
450	}
451}
452
453/* Salt suitable for traditional DES and MD5 */
454void
455makesalt(char salt[SALTSIZE])
456{
457	int i;
458
459	/* These are not really random numbers, they are just
460	 * numbers that change to thwart construction of a
461	 * dictionary. This is exposed to the public.
462	 */
463	for (i = 0; i < SALTSIZE; i += 4)
464		to64(&salt[i], arc4random(), 4);
465	salt[SALTSIZE] = '\0';
466}
467
468PAM_MODULE_ENTRY("pam_unix");
469