auth-pam.c support for pam_chauthtok()

Steve VanDevender stevev at darkwing.uoregon.edu
Thu Sep 14 05:05:29 EST 2000


When we installed OpenSSH 2.1.1p4 on our Solaris systems, our users
noticed that it did not honor password expiration consistently with
other Solaris login services.

The patch below is against OpenSSH 2.2.0p1 and adds support for PAM
password changes on expiration via pam_chauthtok().  A brief summary of
changes:

auth-pam.c:
* change declaration of pamh to "static pam_handle_t *pamh", remove
unnecessary casts "(pam_handle_t *)"
* fix typo in NEW_AUTHTOK_MSG
* extend pamconv() to support real interactive prompting and display, in
addition to the kludge to feed the user's password into PAM during
initial login
* add function do_pam_chauthtok() to call pam_chauthtok() if needed,
once interactive session has been established

auth-pam.h:
* add prototype for do_pam_chauthtok()

session.c:
* add call to do_pam_chauthtok() after print_pam_messages()

I am subscribed to openssh-unix-dev, so you do not have to copy any list
discussion to me personally.

===================================================================
RCS file: RCS/auth-pam.c,v
retrieving revision 1.1
diff -u -r1.1 auth-pam.c
--- auth-pam.c	2000/09/06 22:29:58	1.1
+++ auth-pam.c	2000/09/12 19:30:24
@@ -37,7 +37,7 @@
 RCSID("$Id: auth-pam.c,v 1.1 2000/09/06 22:29:58 stevev Exp stevev $");
 
 #define NEW_AUTHTOK_MSG \
-	"Warning: You password has expired, please change it now"
+	"Warning: Your password has expired, please change it now"
 
 /* Callbacks */
 static int pamconv(int num_msg, const struct pam_message **msg,
@@ -50,40 +50,72 @@
 	pamconv,
 	NULL
 };
-static struct pam_handle_t *pamh = NULL;
+static pam_handle_t *pamh = NULL;
 static const char *pampasswd = NULL;
 static char *pam_msg = NULL;
 
-/* PAM conversation function. This is really a kludge to get the password */
-/* into PAM and to pick up any messages generated by PAM into pamconv_msg */
+/* states for pamconv() */
+typedef enum { INITIAL_LOGIN, OTHER } pamstates;
+static pamstates pamstate = INITIAL_LOGIN;
+/* remember whether pam_acct_mgmt() returned PAM_NEWAUTHTOK_REQD */
+static int password_change_required = 0;
+
+/*
+ * PAM conversation function.
+ * There are two states this can run in.
+ *
+ * INITIAL_LOGIN mode simply feeds the password from the client into
+ * PAM in response to PAM_PROMPT_ECHO_OFF, and collects output
+ * messages with pam_msg_cat().  This is used during initial
+ * authentication to bypass the normal PAM password prompt.
+ *
+ * OTHER mode handles PAM_PROMPT_ECHO_OFF with read_passphrase(prompt, 1)
+ * and outputs messages to stderr. This mode is used if pam_chauthtok()
+ * is called to update expired passwords.
+ */
 static int pamconv(int num_msg, const struct pam_message **msg,
 	struct pam_response **resp, void *appdata_ptr)
 {
 	struct pam_response *reply;
 	int count;
+	char buf[1024];
 
 	/* PAM will free this later */
 	reply = malloc(num_msg * sizeof(*reply));
 	if (reply == NULL)
 		return PAM_CONV_ERR; 
 
-	for(count = 0; count < num_msg; count++) {
-		switch (msg[count]->msg_style) {
+	for (count = 0; count < num_msg; count++) {
+		switch ((*msg)[count].msg_style) {
+			case PAM_PROMPT_ECHO_ON:
+				fputs((*msg)[count].msg, stderr);
+				fgets(buf, sizeof(buf), stdin);
+				reply[count].resp = xstrdup(buf);
+				reply[count].resp_retcode = PAM_SUCCESS;
+				break;
 			case PAM_PROMPT_ECHO_OFF:
-				if (pampasswd == NULL) {
-					free(reply);
-					return PAM_CONV_ERR;
-				}
+				if (pamstate == INITIAL_LOGIN) {
+					if (pampasswd == NULL) {
+						free(reply);
+						return PAM_CONV_ERR;
+					}
+					reply[count].resp = xstrdup(pampasswd);
+				} else
+					reply[count].resp = xstrdup(read_passphrase((*msg)[count].msg, 1));
 				reply[count].resp_retcode = PAM_SUCCESS;
-				reply[count].resp = xstrdup(pampasswd);
 				break;
+			case PAM_ERROR_MSG:
 			case PAM_TEXT_INFO:
-				reply[count].resp_retcode = PAM_SUCCESS;
+				if ((*msg)[count].msg != NULL) {
+					if (pamstate == INITIAL_LOGIN)
+						pam_msg_cat((*msg)[count].msg);
+					else {
+						fputs((*msg)[count].msg, stderr);
+						fputs("\n", stderr);
+					}
+				}
 				reply[count].resp = xstrdup("");
-
-				if (msg[count]->msg != NULL)
-					pam_msg_cat(msg[count]->msg);
-
+				reply[count].resp_retcode = PAM_SUCCESS;
 				break;
 			default:
 				free(reply);
@@ -103,22 +135,22 @@
 
 	if (pamh != NULL)
 	{
-		pam_retval = pam_close_session((pam_handle_t *)pamh, 0);
+		pam_retval = pam_close_session(pamh, 0);
 		if (pam_retval != PAM_SUCCESS) {
 			log("Cannot close PAM session: %.200s", 
-				PAM_STRERROR((pam_handle_t *)pamh, pam_retval));
+				PAM_STRERROR(pamh, pam_retval));
 		}
 
-		pam_retval = pam_setcred((pam_handle_t *)pamh, PAM_DELETE_CRED);
+		pam_retval = pam_setcred(pamh, PAM_DELETE_CRED);
 		if (pam_retval != PAM_SUCCESS) {
 			debug("Cannot delete credentials: %.200s", 
-				PAM_STRERROR((pam_handle_t *)pamh, pam_retval));
+				PAM_STRERROR(pamh, pam_retval));
 		}
 
-		pam_retval = pam_end((pam_handle_t *)pamh, pam_retval);
+		pam_retval = pam_end(pamh, pam_retval);
 		if (pam_retval != PAM_SUCCESS) {
 			log("Cannot release PAM authentication: %.200s", 
-				PAM_STRERROR((pam_handle_t *)pamh, pam_retval));
+				PAM_STRERROR(pamh, pam_retval));
 		}
 	}
 }
@@ -139,14 +171,15 @@
 
 	pampasswd = password;
 	
-	pam_retval = pam_authenticate((pam_handle_t *)pamh, 0);
+	pamstate = INITIAL_LOGIN;
+	pam_retval = pam_authenticate(pamh, 0);
 	if (pam_retval == PAM_SUCCESS) {
 		debug("PAM Password authentication accepted for user \"%.100s\"", 
 			pw->pw_name);
 		return 1;
 	} else {
 		debug("PAM Password authentication for \"%.100s\" failed: %s", 
-			pw->pw_name, PAM_STRERROR((pam_handle_t *)pamh, pam_retval));
+			pw->pw_name, PAM_STRERROR(pamh, pam_retval));
 		return 0;
 	}
 }
@@ -157,33 +190,35 @@
 	int pam_retval;
 	
 	debug("PAM setting rhost to \"%.200s\"", get_canonical_hostname());
-	pam_retval = pam_set_item((pam_handle_t *)pamh, PAM_RHOST, 
+	pam_retval = pam_set_item(pamh, PAM_RHOST, 
 		get_canonical_hostname());
 	if (pam_retval != PAM_SUCCESS) {
 		fatal("PAM set rhost failed: %.200s", 
-			PAM_STRERROR((pam_handle_t *)pamh, pam_retval));
+			PAM_STRERROR(pamh, pam_retval));
 	}
 
 	if (remote_user != NULL) {
 		debug("PAM setting ruser to \"%.200s\"", remote_user);
-		pam_retval = pam_set_item((pam_handle_t *)pamh, PAM_RUSER, remote_user);
+		pam_retval = pam_set_item(pamh, PAM_RUSER, remote_user);
 		if (pam_retval != PAM_SUCCESS) {
 			fatal("PAM set ruser failed: %.200s", 
-				PAM_STRERROR((pam_handle_t *)pamh, pam_retval));
+				PAM_STRERROR(pamh, pam_retval));
 		}
 	}
 
-	pam_retval = pam_acct_mgmt((pam_handle_t *)pamh, 0);
+	pam_retval = pam_acct_mgmt(pamh, 0);
 	switch (pam_retval) {
 		case PAM_SUCCESS:
 			/* This is what we want */
 			break;
 		case PAM_NEW_AUTHTOK_REQD:
 			pam_msg_cat(NEW_AUTHTOK_MSG);
+			/* flag that password change is necessary */
+			password_change_required = 1;
 			break;
 		default:
 			log("PAM rejected by account configuration: %.200s", 
-				PAM_STRERROR((pam_handle_t *)pamh, pam_retval));
+				PAM_STRERROR(pamh, pam_retval));
 			return(0);
 	}
 	
@@ -197,17 +232,17 @@
 
 	if (ttyname != NULL) {
 		debug("PAM setting tty to \"%.200s\"", ttyname);
-		pam_retval = pam_set_item((pam_handle_t *)pamh, PAM_TTY, ttyname);
+		pam_retval = pam_set_item(pamh, PAM_TTY, ttyname);
 		if (pam_retval != PAM_SUCCESS) {
 			fatal("PAM set tty failed: %.200s", 
-				PAM_STRERROR((pam_handle_t *)pamh, pam_retval));
+				PAM_STRERROR(pamh, pam_retval));
 		}
 	}
 
-	pam_retval = pam_open_session((pam_handle_t *)pamh, 0);
+	pam_retval = pam_open_session(pamh, 0);
 	if (pam_retval != PAM_SUCCESS) {
 		fatal("PAM session setup failed: %.200s", 
-			PAM_STRERROR((pam_handle_t *)pamh, pam_retval));
+			PAM_STRERROR(pamh, pam_retval));
 	}
 }
 
@@ -217,10 +252,28 @@
 	int pam_retval;
  
 	debug("PAM establishing creds");
-	pam_retval = pam_setcred((pam_handle_t *)pamh, PAM_ESTABLISH_CRED);
+	pam_retval = pam_setcred(pamh, PAM_ESTABLISH_CRED);
 	if (pam_retval != PAM_SUCCESS) {
 		fatal("PAM setcred failed: %.200s", 
-			PAM_STRERROR((pam_handle_t *)pamh, pam_retval));
+			PAM_STRERROR(pamh, pam_retval));
+	}
+}
+
+/* 
+ * Have user change authentication token if pam_acct_mgmt() indicated
+ * it was expired.  This needs to be called after an interactive
+ * session is established and the user's pty is connected to
+ * stdin/stout/stderr.
+ */
+void do_pam_chauthtok()
+{
+	int pam_retval;
+
+	if (password_change_required) {
+		pamstate = OTHER;
+		do {
+			pam_retval = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
+		} while (pam_retval != PAM_SUCCESS);
 	}
 }
 
@@ -238,12 +291,11 @@
 
 	debug("Starting up PAM with username \"%.200s\"", pw->pw_name);
 
-	pam_retval = pam_start(SSHD_PAM_SERVICE, pw->pw_name, &conv, 
-		(pam_handle_t**)&pamh);
+	pam_retval = pam_start(SSHD_PAM_SERVICE, pw->pw_name, &conv, &pamh);
 
 	if (pam_retval != PAM_SUCCESS) {
 		fatal("PAM initialisation failed: %.200s", 
-			PAM_STRERROR((pam_handle_t *)pamh, pam_retval));
+			PAM_STRERROR(pamh, pam_retval));
 	}
 
 #ifdef PAM_TTY_KLUDGE
@@ -254,10 +306,10 @@
 	 * not even need one (for tty-less connections)
 	 * Kludge: Set a fake PAM_TTY 
 	 */
-	pam_retval = pam_set_item((pam_handle_t *)pamh, PAM_TTY, "ssh");
+	pam_retval = pam_set_item(pamh, PAM_TTY, "ssh");
 	if (pam_retval != PAM_SUCCESS) {
 		fatal("PAM set tty failed: %.200s", 
-			PAM_STRERROR((pam_handle_t *)pamh, pam_retval));
+			PAM_STRERROR(pamh, pam_retval));
 	}
 #endif /* PAM_TTY_KLUDGE */
 
@@ -268,7 +320,7 @@
 char **fetch_pam_environment(void)
 {
 #ifdef HAVE_PAM_GETENVLIST
-	return(pam_getenvlist((pam_handle_t *)pamh));
+	return(pam_getenvlist(pamh));
 #else /* HAVE_PAM_GETENVLIST */
 	return(NULL);
 #endif /* HAVE_PAM_GETENVLIST */
===================================================================
RCS file: RCS/auth-pam.h,v
retrieving revision 1.1
diff -u -r1.1 auth-pam.h
--- auth-pam.h	2000/09/12 02:02:26	1.1
+++ auth-pam.h	2000/09/12 02:02:41
@@ -11,5 +11,6 @@
 void do_pam_session(char *username, const char *ttyname);
 void do_pam_setcred();
 void print_pam_messages(void);
+void do_pam_chauthtok();
 
 #endif /* USE_PAM */
===================================================================
RCS file: RCS/session.c,v
retrieving revision 1.1
diff -u -r1.1 session.c
--- session.c	2000/09/12 00:43:22	1.1
+++ session.c	2000/09/12 23:58:44
@@ -674,6 +674,8 @@
 
 #ifdef USE_PAM
 	print_pam_messages();
+	/* If password change is needed, do it now. */
+	do_pam_chauthtok();
 #endif /* USE_PAM */
 #ifdef WITH_AIXAUTHENTICATE
 	if (aixloginmsg && *aixloginmsg)





More information about the openssh-unix-dev mailing list