[PATCH] PAM chauthtok + Privsep

Darren Tucker dtucker at zip.com.au
Sat Jan 4 23:34:38 EST 2003


Darren Tucker wrote:
> Kevin Steves wrote:
> > this should be separated out as it's addressing a different problem:
> > running session modules as root.
>
> I'll remove those bits and re-post the patch in a day or two.

Here it is.  Tested on Solaris 8 and Redhat 8.

Changes relative to previous patch:
* removed all HP-UX PAM changes
* doesn't rely on the monitor's child getting a controlling tty
* sends a SIGUSR1 to parent to restore port forwarding flags

Notes:
The controlling tty stuff proved unreliable on Linux.  Not sure why, but
it seems that when the monitor child exits the pty closes (ie generates
a zero-length read from the pty).

I think using a signal to restore the port forward flags should be
safe.  In the non-privsep case, both parent and child are running as
root.  In the privsep case, they're both running as the user, but the
user can't fake the signal unless they're already logged on.

It might make sense to have do_pam_chauthtok return an int and not throw
a fatal.  It would mean a little less reliance on globals and make
things a little tidier.  You could also allow multiple attempts at
password change with something like:

while (!do_pam_chauthtok());

-- 
Darren Tucker (dtucker at zip.com.au)
GPG key 8FF4FA69 / D9A3 86E9 7EEE AF4B B2D4  37C9 C982 80C7 8FF4 FA69
    Good judgement comes with experience. Unfortunately, the experience
usually comes from bad judgement.
-------------- next part --------------
Index: auth-pam.c
===================================================================
RCS file: /cvs/openssh/auth-pam.c,v
retrieving revision 1.54
diff -u -r1.54 auth-pam.c
--- auth-pam.c	28 Jul 2002 20:24:08 -0000	1.54
+++ auth-pam.c	4 Jan 2003 12:11:31 -0000
@@ -33,17 +33,18 @@
 #include "servconf.h"
 #include "canohost.h"
 #include "readpass.h"
+#include "channels.h"
+#include "misc.h"
 
 extern char *__progname;
 
 extern int use_privsep;
+extern ServerOptions options;
 
 RCSID("$Id: auth-pam.c,v 1.54 2002/07/28 20:24:08 stevesk Exp $");
 
 #define NEW_AUTHTOK_MSG \
 	"Warning: Your password has expired, please change it now."
-#define NEW_AUTHTOK_MSG_PRIVSEP \
-	"Your password has expired, the session cannot proceed."
 
 static int do_pam_conversation(int num_msg, const struct pam_message **msg,
 	struct pam_response **resp, void *appdata_ptr);
@@ -204,7 +205,6 @@
 /* Attempt password authentation using PAM */
 int auth_pam_password(Authctxt *authctxt, const char *password)
 {
-	extern ServerOptions options;
 	int pam_retval;
 	struct passwd *pw = authctxt->pw;
 
@@ -256,18 +256,17 @@
 		case PAM_SUCCESS:
 			/* This is what we want */
 			break;
-#if 0
 		case PAM_NEW_AUTHTOK_REQD:
-			message_cat(&__pam_msg, use_privsep ?
-			    NEW_AUTHTOK_MSG_PRIVSEP : NEW_AUTHTOK_MSG);
+			message_cat(&__pam_msg, NEW_AUTHTOK_MSG);
 			/* flag that password change is necessary */
 			password_change_required = 1;
 			/* disallow other functionality for now */
 			no_port_forwarding_flag |= 2;
 			no_agent_forwarding_flag |= 2;
 			no_x11_forwarding_flag |= 2;
+			/* set signal handler to restore flags */
+			mysignal(SIGUSR1, password_change_successful_handler);
 			break;
-#endif
 		default:
 			log("PAM rejected by account configuration[%d]: "
 			    "%.200s", pam_retval, PAM_STRERROR(__pamh, 
@@ -344,26 +343,32 @@
 	do_pam_set_conv(&conv);
 
 	if (password_change_required) {
-		if (use_privsep)
-			fatal("Password changing is currently unsupported"
-			    " with privilege separation");
 		pamstate = OTHER;
 		pam_retval = pam_chauthtok(__pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
 		if (pam_retval != PAM_SUCCESS)
 			fatal("PAM pam_chauthtok failed[%d]: %.200s",
 			    pam_retval, PAM_STRERROR(__pamh, pam_retval));
-#if 0
-		/* XXX: This would need to be done in the parent process,
-		 * but there's currently no way to pass such request. */
-		no_port_forwarding_flag &= ~2;
-		no_agent_forwarding_flag &= ~2;
-		no_x11_forwarding_flag &= ~2;
-		if (!no_port_forwarding_flag && options.allow_tcp_forwarding)
-			channel_permit_all_opens();
-#endif
+		password_change_required = 0;
 	}
 }
 
+/*
+ * Because do_pam_chauthtok is called after forking to exec the user's
+ * shell, restoring the port forwarding flags is done by sending a
+ * USR1 signal after the password is changed successfully.
+ */
+void password_change_successful_handler(int sig)
+{
+	debug("%s: restoring port forwarding flags", __func__);
+	mysignal(SIGUSR1, SIG_DFL);	/* unset handler */
+	password_change_required = 0;
+	no_port_forwarding_flag &= ~2;
+	no_agent_forwarding_flag &= ~2;
+	no_x11_forwarding_flag &= ~2;
+	if (!no_port_forwarding_flag && options.allow_tcp_forwarding)
+		channel_permit_all_opens();
+}
+
 /* Cleanly shutdown PAM */
 void finish_pam(void)
 {
@@ -375,7 +380,6 @@
 void start_pam(const char *user)
 {
 	int pam_retval;
-	extern ServerOptions options;
 	extern u_int utmp_len;
 	const char *rhost;
 
Index: auth-pam.h
===================================================================
RCS file: /cvs/openssh/auth-pam.h,v
retrieving revision 1.16
diff -u -r1.16 auth-pam.h
--- auth-pam.h	23 Jul 2002 00:44:07 -0000	1.16
+++ auth-pam.h	4 Jan 2003 12:11:31 -0000
@@ -43,6 +43,7 @@
 void print_pam_messages(void);
 int is_pam_password_change_required(void);
 void do_pam_chauthtok(void);
+void password_change_successful_handler(int);
 void do_pam_set_conv(struct pam_conv *);
 void message_cat(char **p, const char *a);
 
Index: monitor.c
===================================================================
RCS file: /cvs/openssh/monitor.c,v
retrieving revision 1.33
diff -u -r1.33 monitor.c
--- monitor.c	9 Nov 2002 15:47:49 -0000	1.33
+++ monitor.c	4 Jan 2003 12:11:32 -0000
@@ -118,6 +118,7 @@
 
 #ifdef USE_PAM
 int mm_answer_pam_start(int, Buffer *);
+int mm_answer_pam_chauthtok(int, Buffer *);
 #endif
 
 #ifdef KRB4
@@ -183,6 +184,9 @@
     {MONITOR_REQ_PTY, 0, mm_answer_pty},
     {MONITOR_REQ_PTYCLEANUP, 0, mm_answer_pty_cleanup},
     {MONITOR_REQ_TERM, 0, mm_answer_term},
+#ifdef USE_PAM
+    {MONITOR_REQ_PAM_CHAUTHTOK, 0, mm_answer_pam_chauthtok},
+#endif
     {0, 0, NULL}
 };
 
@@ -219,6 +223,9 @@
     {MONITOR_REQ_PTY, MON_ONCE, mm_answer_pty},
     {MONITOR_REQ_PTYCLEANUP, MON_ONCE, mm_answer_pty_cleanup},
     {MONITOR_REQ_TERM, 0, mm_answer_term},
+#ifdef USE_PAM
+    {MONITOR_REQ_PAM_CHAUTHTOK, 0, mm_answer_pam_chauthtok},
+#endif
     {0, 0, NULL}
 };
 
@@ -328,6 +335,7 @@
 	if (!no_pty_flag) {
 		monitor_permit(mon_dispatch, MONITOR_REQ_PTY, 1);
 		monitor_permit(mon_dispatch, MONITOR_REQ_PTYCLEANUP, 1);
+		monitor_permit(mon_dispatch, MONITOR_REQ_PAM_CHAUTHTOK, 1);
 	}
 
 	for (;;)
@@ -746,6 +754,56 @@
 	xfree(user);
 
 	return (0);
+}
+
+int
+mm_answer_pam_chauthtok(int socket, Buffer *m)
+{
+	pid_t pid;
+	int ttyfd, status;
+	mysig_t old_signal;
+
+	old_signal = mysignal(SIGCHLD, SIG_DFL);
+
+	ttyfd = mm_receive_fd(socket);
+	debug3("%s: ttyfd %d, ttyname %s", __func__, ttyfd, ttyname(ttyfd));
+
+	if ((pid = fork()) == 0) {
+		fatal_remove_all_cleanups();
+
+		/* set up stdin, stdout and stderr */
+		if (dup2(ttyfd, 0) < 0)
+			error("dup2 stdin: %s", strerror(errno));
+		if (dup2(ttyfd, 1) < 0)
+			error("dup2 stdout: %s", strerror(errno));
+		if (dup2(ttyfd, 2) < 0)
+			error("dup2 stderr: %s", strerror(errno));
+
+		/* call PAM chauthtok and return status to parent */
+		do_pam_chauthtok();
+
+		if(is_pam_password_change_required())
+			exit(1); 	/* failed */
+		else
+			exit(0);	/* success */
+	}
+
+	close(ttyfd);
+
+	if (waitpid(pid, &status, 0) == -1)
+		fatal("Couldn't wait for child: %s", strerror(errno));
+	debug("chauthtok child returned %d", status);
+	 
+	if (WIFEXITED(status) && (WEXITSTATUS(status) == 0))
+		debug("password changed sucessfully");
+	else
+		fatal("do_pam_chauthtok() failed, child returned %d", status);
+
+	mysignal(SIGCHLD, old_signal);
+
+	mm_request_send(socket, MONITOR_ANS_PAM_CHAUTHTOK, m);
+
+	return 1;
 }
 #endif
 
Index: monitor.h
===================================================================
RCS file: /cvs/openssh/monitor.h,v
retrieving revision 1.10
diff -u -r1.10 monitor.h
--- monitor.h	27 Sep 2002 03:26:02 -0000	1.10
+++ monitor.h	4 Jan 2003 12:11:32 -0000
@@ -52,6 +52,7 @@
 	MONITOR_REQ_KRB4, MONITOR_ANS_KRB4,
  	MONITOR_REQ_KRB5, MONITOR_ANS_KRB5,
 	MONITOR_REQ_PAM_START,
+	MONITOR_REQ_PAM_CHAUTHTOK, MONITOR_ANS_PAM_CHAUTHTOK,
 	MONITOR_REQ_TERM
 };
 
Index: monitor_wrap.c
===================================================================
RCS file: /cvs/openssh/monitor_wrap.c,v
retrieving revision 1.21
diff -u -r1.21 monitor_wrap.c
--- monitor_wrap.c	23 Dec 2002 02:06:20 -0000	1.21
+++ monitor_wrap.c	4 Jan 2003 12:11:33 -0000
@@ -663,6 +663,27 @@
 
 	buffer_free(&m);
 }
+
+/*
+ * Privsep chauthtok works by passing a descriptor to the session's
+ * stdin/stdout to the monitor, which then sets up a child with this
+ * descriptor as stdin, stdout then calls do_pam_chauthtok
+ */
+
+void
+mm_do_pam_chauthtok(void)
+{
+	Buffer m;
+
+	debug3("%s entering", __func__);
+	
+	buffer_init(&m);
+	mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_PAM_CHAUTHTOK, &m);
+	mm_send_fd(pmonitor->m_recvfd, STDIN_FILENO);
+	mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_PAM_CHAUTHTOK, &m);
+
+	buffer_free(&m);
+}
 #endif /* USE_PAM */
 
 /* Request process termination */
Index: monitor_wrap.h
===================================================================
RCS file: /cvs/openssh/monitor_wrap.h,v
retrieving revision 1.9
diff -u -r1.9 monitor_wrap.h
--- monitor_wrap.h	27 Sep 2002 03:26:04 -0000	1.9
+++ monitor_wrap.h	4 Jan 2003 12:11:33 -0000
@@ -57,6 +57,7 @@
 
 #ifdef USE_PAM
 void mm_start_pam(char *);
+void mm_do_pam_chauthtok(void);
 #endif
 
 void mm_terminate(void);
Index: session.c
===================================================================
RCS file: /cvs/openssh/session.c,v
retrieving revision 1.226
diff -u -r1.226 session.c
--- session.c	3 Jan 2003 03:52:54 -0000	1.226
+++ session.c	4 Jan 2003 12:11:34 -0000
@@ -753,7 +753,10 @@
 	 */
 	if (is_pam_password_change_required()) {
 		print_pam_messages();
-		do_pam_chauthtok();
+		PRIVSEP(do_pam_chauthtok());
+		/* change successful, tell parent to restore port
+		 * forwarding flags */
+		kill(getppid(), SIGUSR1);
 	}
 #endif
 
Index: openbsd-compat/readpassphrase.c
===================================================================
RCS file: /cvs/openssh/openbsd-compat/readpassphrase.c,v
retrieving revision 1.9
diff -u -r1.9 readpassphrase.c
--- openbsd-compat/readpassphrase.c	11 Sep 2002 00:29:13 -0000	1.9
+++ openbsd-compat/readpassphrase.c	4 Jan 2003 12:11:36 -0000
@@ -85,6 +85,15 @@
 		output = STDERR_FILENO;
 	}
 
+        /*
+	 * Odd case where stdin is a tty but /dev/tty is not
+	 * available. Used for passed file descriptor during privsep.
+	 */
+       if (isatty(STDIN_FILENO)) {
+               input = dup(STDIN_FILENO);
+               output = dup(STDERR_FILENO);
+       }
+
 	/*
 	 * Catch signals that would otherwise cause the user to end
 	 * up with echo turned off in the shell.  Don't worry about


More information about the openssh-unix-dev mailing list