Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 17 Jan 2001 16:47:35 -0500 (EST)
From:      "David J. MacKenzie" <djm@web.us.uu.net>
To:        freebsd-security@freebsd.org
Cc:        djm@web.us.uu.net
Subject:   full PAM support for login, rshd, and su
Message-ID:  <20010117214735.E7DAD46BC@dagger.web.us.uu.net>

next in thread | raw e-mail | index | archive | help
The FreeBSD (4.2-STABLE) login has only partial PAM support; it
supports PAM authentication, but not account management or sessions.
I want to use a locally written PAM module that restricts logins based on
a DB file lookup, but the account management function is necessary for
that.  The FreeBSD rshd and su don't have any PAM support.

Below are patches to add full PAM support to those programs.  I
haven't tackled adding PAM to the FreeBSD ftpd so far, because I use
proftpd which already has it.

I haven't looked at Heimdal or krb4, as the relevant utilities from
them don't seem to be installed on FreeBSD, and my company has
standardized on MIT krb5.

The PAM_FAIL_CHECK and PAM_END macros in su.c came from the util-linux
package's PAM patches to the BSD login.c, which are covered by the BSD
copyright (no GPL).  If you don't like that for some reason, it
would be straightforward for someone to rewrite that error checking.
I simplified the "cleanenv" code in su.c based on BSDI's version,
which is covered by the same copyright as the FreeBSD version.

I've also added PAM support (for account management and sessions) to
the MIT krb5 ksu, login.krb5, and kshd, which I'll be submitting to
krbdev@mit.edu soon.

I think if you're going to ship PAM, you should actually use it.
The OpenSSH shipped with FreeBSD (as of 4.2-STABLE) is also missing
the USE_PAM support that's in the portable OpenSSH release.  I highly
recommend importing that code into your source tree.  I'm going to
have to do so in my tree.

--- ./usr.bin/login/login.c	2000/08/08 03:12:59	1.1
+++ ./usr.bin/login/login.c	2001/01/16 23:38:50
@@ -81,6 +81,7 @@
 #ifndef NO_PAM
 #include <security/pam_appl.h>
 #include <security/pam_misc.h>
+#include <sys/wait.h>
 #endif
 
 #include "pathnames.h"
@@ -106,6 +107,7 @@
 
 #ifndef NO_PAM
 static int auth_pam __P((void));
+pam_handle_t *pamh = NULL;
 #endif
 static int auth_traditional __P((void));
 extern void login __P((struct utmp *));
@@ -150,6 +152,10 @@
 	char tname[sizeof(_PATH_TTY) + 10];
 	char *shell = NULL;
 	login_cap_t *lc = NULL;
+#ifndef NO_PAM
+	pid_t pid;
+	int e;
+#endif /* NO_PAM */
 
 	(void)signal(SIGQUIT, SIG_IGN);
 	(void)signal(SIGINT, SIG_IGN);
@@ -548,6 +554,35 @@
 	if (!pflag)
 		environ = envinit;
 
+#ifndef NO_PAM
+	if (pamh) {
+		/*
+		 * We must fork() before setuid() because we need to call
+		 * pam_close_session() as root.
+		 */
+		pid = fork();
+		if (pid < 0) {
+			err(1, "fork");
+			if ((e = pam_close_session(pamh,0)) != PAM_SUCCESS)
+				syslog(LOG_ERR, "pam_close_session: %s", pam_strerror(pamh, e));
+			if ((e = pam_end(pamh, e)) != PAM_SUCCESS)
+				syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e));
+			exit(0);
+		} else if (pid) {
+			/* parent - wait for child to finish, then cleanup session */
+			wait(NULL);
+			if ((e = pam_close_session(pamh,0)) != PAM_SUCCESS)
+				syslog(LOG_ERR, "pam_close_session: %s", pam_strerror(pamh, e));
+			if ((e = pam_end(pamh, e)) != PAM_SUCCESS)
+				syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e));
+			exit(0);
+		} else {
+			if ((e = pam_end(pamh, e)) != PAM_SUCCESS)
+				syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e));
+		}
+	}
+#endif /* NO_PAM */
+
 	/*
 	 * We don't need to be root anymore, so
 	 * set the user and session context
@@ -562,6 +597,17 @@
 		exit(1);
 	}
 
+#ifndef NO_PAM
+	if (pamh) {
+		const char * const *env = (const char * const *)pam_getenvlist(pamh);
+		int i;
+		if (env != NULL) {
+			for (i=0; env[i]; i++)
+				putenv(env[i]);
+		}
+	}
+#endif /* NO_PAM */
+
 	(void)setenv("SHELL", pwd->pw_shell, 1);
 	(void)setenv("HOME", pwd->pw_dir, 1);
 	if (term != NULL && *term != '\0')
@@ -663,7 +709,6 @@
 static int
 auth_pam()
 {
-	pam_handle_t *pamh = NULL;
 	const char *tmpl_user;
 	const void *item;
 	int rval;
@@ -724,13 +769,36 @@
 		break;
 
 	default:
-		syslog(LOG_ERR, "auth_pam: %s", pam_strerror(pamh, e));
+		syslog(LOG_ERR, "pam_authenticate: %s", pam_strerror(pamh, e));
 		rval = -1;
 		break;
 	}
-	if ((e = pam_end(pamh, e)) != PAM_SUCCESS) {
-		syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e));
-		rval = -1;
+
+	if (rval != -1) {
+		e = pam_acct_mgmt(pamh, 0);
+		if (e == PAM_NEW_AUTHTOK_REQD) {
+			e = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
+			if (e != PAM_SUCCESS) {
+				syslog(LOG_ERR, "pam_chauthtok: %s", pam_strerror(pamh, e));
+				rval = -1;
+			}
+		} else if (e != PAM_SUCCESS) {
+			rval = 1;
+		} else if ((e = pam_open_session(pamh, 0)) != PAM_SUCCESS) {
+			syslog(LOG_ERR, "pam_open_session: %s", pam_strerror(pamh, e));
+			rval = -1;
+		} else if ((e = pam_setcred(pamh, PAM_ESTABLISH_CRED)) != PAM_SUCCESS) {
+			syslog(LOG_ERR, "pam_setcred: %s", pam_strerror(pamh, e));
+			rval = -1;
+			pam_close_session(pamh, 0);
+		}
+	}
+
+	if (rval == -1) {
+		if ((e = pam_end(pamh, e)) != PAM_SUCCESS) {
+			syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e));
+		}
+		pamh = NULL;
 	}
 	return rval;
 }
@@ -745,7 +813,7 @@
 
 /*
  * Allow for authentication style and/or kerberos instance
- * */
+ */
 
 #define	NBUFSIZ		UT_NAMESIZE + 64
 
--- ./usr.bin/su/Makefile	2001/01/16 21:33:47	1.1
+++ ./usr.bin/su/Makefile	2001/01/16 21:41:43
@@ -4,9 +4,9 @@
 PROG=	su
 SRCS=	su.c
 
-COPTS+=	-DLOGIN_CAP -DSKEY
+COPTS+=	-DLOGIN_CAP -DSKEY -DUSE_PAM
 DPADD=	${LIBUTIL} ${LIBSKEY} ${LIBMD} ${LIBCRYPT}
-LDADD=	-lutil -lskey -lmd -lcrypt
+LDADD=	-lutil -lskey -lmd -lcrypt -lpam
 
 .if defined(WHEELSU)
 COPTS+=	-DWHEELSU
--- ./usr.bin/su/su.c	2000/02/24 21:06:21	1.1
+++ ./usr.bin/su/su.c	2001/01/16 23:29:48
@@ -65,6 +65,20 @@
 #include <login_cap.h>
 #endif
 
+#ifdef USE_PAM
+#include <security/pam_appl.h>
+#include <security/pam_misc.h>
+#include <signal.h>
+#include <sys/wait.h>
+#define PAM_FAIL_CHECK if (retcode != PAM_SUCCESS) { \
+       fprintf(stderr,"su: PAM error: %s\n",pam_strerror(pamh, retcode)); \
+       syslog(LOG_ERR,"PAM error: %s",pam_strerror(pamh, retcode)); \
+       pam_end(pamh, retcode); exit(1); \
+   }
+#define PAM_END { retcode = pam_close_session(pamh,0); \
+		    pam_end(pamh,retcode); }
+#endif /* USE_PAM */
+
 #ifdef	SKEY
 #include <skey.h>
 #endif
@@ -107,7 +121,7 @@
 	char *targetpass;
 	int iswheelsu;
 #endif /* WHEELSU */
-	char *p, **g, *user, *shell=NULL, *username, **cleanenv, **nargv, **np;
+	char *p, **g, *user, *shell=NULL, *username, *cleanenv = NULL, **nargv, **np;
 	struct group *gr;
 	uid_t ruid;
 	gid_t gid;
@@ -118,6 +132,15 @@
 	char *class=NULL;
 	int setwhat;
 #endif
+#ifdef USE_PAM
+	int retcode;
+	pam_handle_t *pamh = NULL;
+	struct pam_conv conv = { misc_conv, NULL };
+	char myhost[MAXHOSTNAMELEN + 1], *mytty;
+	int statusp=0;
+	int child_pid, child_pgrp, ret_pid;
+	const char * const *env;
+#endif /* USE_PAM */
 #ifdef KERBEROS
 	char *k;
 #endif
@@ -230,11 +253,24 @@
 	}
 #endif
 
+#ifdef USE_PAM
+	retcode = pam_start("su", user, &conv, &pamh);
+	PAM_FAIL_CHECK;
+#else /* !USE_PAM */
 #ifdef WHEELSU
 	targetpass = strdup(pwd->pw_passwd);
 #endif /* WHEELSU */
+#endif /* USE_PAM */
 
 	if (ruid) {
+#ifdef USE_PAM
+		retcode = pam_authenticate(pamh, 0);
+		PAM_FAIL_CHECK;
+		retcode = pam_acct_mgmt(pamh, 0);
+		if (retcode == PAM_NEW_AUTHTOK_REQD)
+			retcode = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
+		PAM_FAIL_CHECK;
+#else /* !USE_PAM */
 #ifdef KERBEROS
 		if (use_kerberos && koktologin(username, user)
 		    && !pwd->pw_uid) {
@@ -280,11 +316,12 @@
 #ifdef WHEELSU
 			      || (iswheelsu && !strcmp(targetpass, crypt(p,targetpass)))
 #endif /* WHEELSU */
-			      )) {
-#else
+			      ))
+#else /* !SKEY */
 			p = getpass("Password:");
-			if (strcmp(pwd->pw_passwd, crypt(p, pwd->pw_passwd))) {
-#endif
+			if (strcmp(pwd->pw_passwd, crypt(p, pwd->pw_passwd)))
+#endif /* SKEY */
+				{
 #ifdef KERBEROS
 	    			if (!use_kerberos || (use_kerberos && kerberos(username, user, pwd->pw_uid, p)))
 #endif
@@ -307,6 +344,7 @@
 				user, ontty());
 			exit(1);
 		}
+#endif /* USE_PAM */
 	}
 
 	if (asme) {
@@ -334,6 +372,60 @@
 
 	(void)setpriority(PRIO_PROCESS, 0, prio);
 
+#ifdef USE_PAM
+	gethostname(myhost, sizeof(myhost));
+	retcode = pam_set_item(pamh, PAM_RHOST, myhost);
+	PAM_FAIL_CHECK;
+
+	mytty = ttyname(STDERR_FILENO);
+	if (!mytty)
+		mytty = "tty";
+	retcode = pam_set_item(pamh, PAM_TTY, mytty);
+	PAM_FAIL_CHECK;
+
+	retcode = pam_open_session(pamh, 0);
+	PAM_FAIL_CHECK;
+
+	retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED);
+	PAM_FAIL_CHECK;
+
+	env = (const char * const *)pam_getenvlist(pamh);
+	if (env != NULL) {
+		for (i=0; env[i]; i++)
+			putenv(env[i]);
+	}
+
+	/*
+	 * We must fork() before setuid() because we need to call
+	 * pam_close_session() as root.
+	 */
+
+	statusp = 1;
+	switch ((child_pid = fork())) {
+	default:
+            while ((ret_pid = waitpid(child_pid, &statusp, WUNTRACED)) != -1) {
+		if (WIFSTOPPED(statusp)) {
+		    child_pgrp = tcgetpgrp(1);
+		    kill(getpid(), SIGSTOP);
+		    tcsetpgrp(1, child_pgrp);
+		    kill(child_pid, SIGCONT); 
+		    statusp = 1;
+		    continue;
+		}
+		break;
+            }
+	    if (ret_pid == -1)
+		err(1, "waitpid");
+	    PAM_END;
+	    exit(statusp);
+	case -1:
+	    err(1, "fork");
+	    PAM_END;
+	    exit (1);
+	case 0:
+	    pam_end(pamh, retcode);
+#endif /* USE_PAM */
+
 #ifdef LOGIN_CAP
 	/* Set everything now except the environment & umask */
 	setwhat = LOGIN_SETUSER|LOGIN_SETGROUP|LOGIN_SETRESOURCES|LOGIN_SETPRIORITY;
@@ -361,10 +453,7 @@
 #ifdef KERBEROS
 			k = getenv("KRBTKFILE");
 #endif
-			if ((cleanenv = calloc(20, sizeof(char*))) == NULL)
-				errx(1, "calloc");
-			cleanenv[0] = NULL;
-			environ = cleanenv;
+			environ = &cleanenv;
 #ifdef LOGIN_CAP
 			/* set the su'd user's environment & umask */
 			setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETPATH|LOGIN_SETUMASK|LOGIN_SETENV);
@@ -403,6 +492,9 @@
 
 	execv(shell, np);
 	err(1, "%s", shell);
+#ifdef USE_PAM
+	}
+#endif /* USE_PAM */
 }
 
 static void
--- ./libexec/rshd/Makefile	2001/01/17 00:04:57	1.1
+++ ./libexec/rshd/Makefile	2001/01/17 00:05:11
@@ -8,9 +8,9 @@
 #CFLAGS+= -DCRYPT
 
 # For login_cap handling
-CFLAGS+=-DLOGIN_CAP -Wall
+CFLAGS+=-DLOGIN_CAP -DUSE_PAM -Wall
 DPADD+=	${LIBUTIL}
-LDADD+= -lutil
+LDADD+= -lutil -lpam
 
 # IPv6 support
 CFLAGS+= -DINET6
--- ./libexec/rshd/rshd.c	2000/11/12 07:00:38	1.1
+++ ./libexec/rshd/rshd.c	2001/01/17 00:40:07
@@ -80,6 +80,12 @@
 #include <login_cap.h>
 #endif
 
+#ifdef USE_PAM
+#include <security/pam_appl.h>
+#include <security/pam_misc.h>
+static pam_handle_t *pamh;
+#endif /* USE_PAM */
+
 /* wrapper for KAME-special getnameinfo() */
 #ifndef NI_WITHSCOPEID
 #define	NI_WITHSCOPEID	0
@@ -219,6 +225,10 @@
 #ifdef	LOGIN_CAP
 	login_cap_t *lc;
 #endif
+#ifdef USE_PAM
+	static struct pam_conv conv = { misc_conv, NULL };
+	int retcode;
+#endif /* USE_PAM */
 
 	(void) signal(SIGINT, SIG_DFL);
 	(void) signal(SIGQUIT, SIG_DFL);
@@ -349,7 +359,8 @@
 		    remuser, fromhost, locuser, cmdbuf);
 		if (errorstr == NULL)
 			errorstr = "Login incorrect.\n";
-		goto fail;
+		error(errorstr, fromhost);
+		exit(1);
 	}
 #ifdef	LOGIN_CAP
 	lc = login_getpwclass(pwd);
@@ -377,6 +388,36 @@
 		pwd->pw_dir = "/";
 	}
 
+#ifdef USE_PAM
+	retcode = pam_start("rsh", locuser, &conv, &pamh);
+	if (retcode != PAM_SUCCESS) {
+		syslog(LOG_ERR, "pam_start: %s\n", pam_strerror(pamh, retcode));
+		exit(1);
+	}
+	pam_set_item (pamh, PAM_RUSER, remuser);
+	pam_set_item (pamh, PAM_RHOST, fromhost);
+	pam_set_item (pamh, PAM_TTY, "tty");
+
+	retcode = pam_authenticate(pamh, 0);
+	if (retcode == PAM_SUCCESS) {
+		retcode = pam_acct_mgmt(pamh, 0);
+	}
+	if (retcode == PAM_SUCCESS) {
+		retcode = pam_open_session(pamh,0);
+	}
+	if (retcode == PAM_SUCCESS) {
+		retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED);
+		if (retcode != PAM_SUCCESS)
+			pam_close_session(pamh, 0);
+	}
+	if (retcode != PAM_SUCCESS) {
+		pam_end(pamh, retcode);
+		syslog(LOG_INFO|LOG_AUTH, "%s@%s as %s: permission denied (%s). cmd='%.80s'",
+		       remuser, fromhost, locuser, pam_strerror(pamh, retcode), cmdbuf);
+		error("Login incorrect.\n");
+		exit(1);
+	}
+#else /* !USE_PAM */
 		if (errorstr ||
 		    (pwd->pw_expire && time(NULL) >= pwd->pw_expire) ||
 		    iruserok_sa(fromp, fromp->su_len, pwd->pw_uid == 0,
@@ -390,7 +431,6 @@
 				syslog(LOG_INFO|LOG_AUTH,
 			    "%s@%s as %s: permission denied. cmd='%.80s'",
 				    remuser, fromhost, locuser, cmdbuf);
-fail:
 			if (errorstr == NULL)
 				errorstr = "Login incorrect.\n";
 			error(errorstr, fromhost);
@@ -401,6 +441,8 @@
 		error("Logins currently disabled.\n");
 		exit(1);
 	}
+#endif /* USE_PAM */
+
 #ifdef	LOGIN_CAP
 	if (lc != NULL && fromp->su_family == AF_INET) {	/*XXX*/
 		char	remote_ip[MAXHOSTNAMELEN];
@@ -569,6 +611,10 @@
 			    (doencrypt && FD_ISSET(pv1[0], &readfrom)) ||
 #endif
 			    FD_ISSET(pv[0], &readfrom));
+#ifdef USE_PAM
+			pam_close_session(pamh, 0);
+			pam_end(pamh, PAM_SUCCESS);
+#endif /* USE_PAM */
 			exit(0);
 		}
 		setpgrp(0, getpid());



To Unsubscribe: send mail to majordomo@FreeBSD.org
with "unsubscribe freebsd-security" in the body of the message




Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20010117214735.E7DAD46BC>