Index: TODO
===================================================================
RCS file: /cvs/openssh/TODO,v
retrieving revision 1.53
diff -u -r1.53 TODO
--- TODO	12 Jan 2003 23:00:34 -0000	1.53
+++ TODO	14 Jan 2003 11:53:15 -0000
@@ -15,8 +15,6 @@
 
 - More platforms for for setproctitle() emulation (testing needed)
 
-- Handle changing passwords for the non-PAM expired password case
-
 - Improve PAM support (a pam_lastlog module will cause sshd to exit)
   and maybe support alternate forms of authentications like OPIE via
   pam?
Index: acconfig.h
===================================================================
RCS file: /cvs/openssh/acconfig.h,v
retrieving revision 1.147
diff -u -r1.147 acconfig.h
--- acconfig.h	12 Jan 2003 23:04:59 -0000	1.147
+++ acconfig.h	14 Jan 2003 11:53:16 -0000
@@ -25,6 +25,9 @@
 /* from environment and PATH */
 #undef LOGIN_PROGRAM_FALLBACK
 
+/* Path to passwd program */
+#undef PASSWD_PROGRAM_PATH
+
 /* Define if your password has a pw_class field */
 #undef HAVE_PW_CLASS_IN_PASSWD
 
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	14 Jan 2003 11:53:17 -0000
@@ -60,7 +60,7 @@
 /* states for do_pam_conversation() */
 enum { INITIAL_LOGIN, OTHER } pamstate = INITIAL_LOGIN;
 /* remember whether pam_acct_mgmt() returned PAM_NEW_AUTHTOK_REQD */
-static int password_change_required = 0;
+extern int password_change_required;
 /* remember whether the last pam_authenticate() succeeded or not */
 static int was_authenticated = 0;
 
@@ -260,12 +260,7 @@
 		case PAM_NEW_AUTHTOK_REQD:
 			message_cat(&__pam_msg, use_privsep ?
 			    NEW_AUTHTOK_MSG_PRIVSEP : 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;
+			flag_password_change_required();
 			break;
 #endif
 		default:
@@ -353,13 +348,7 @@
 			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();
+		flag_password_change_successful();
 #endif
 	}
 }
Index: auth-passwd.c
===================================================================
RCS file: /cvs/openssh/auth-passwd.c,v
retrieving revision 1.48
diff -u -r1.48 auth-passwd.c
--- auth-passwd.c	25 Sep 2002 23:14:16 -0000	1.48
+++ auth-passwd.c	14 Jan 2003 11:53:17 -0000
@@ -42,6 +42,11 @@
 #include "log.h"
 #include "servconf.h"
 #include "auth.h"
+#include "buffer.h"
+#include "misc.h"
+#include "channels.h"
+#include "monitor_wrap.h"
+#include "auth-options.h"
 
 #if !defined(USE_PAM) && !defined(HAVE_OSF_SIA)
 /* Don't need any of these headers for the PAM or SIA cases */
@@ -81,9 +86,9 @@
 #endif /* !USE_PAM && !HAVE_OSF_SIA */
 
 extern ServerOptions options;
-#ifdef WITH_AIXAUTHENTICATE
-extern char *aixloginmsg;
-#endif
+extern Buffer login_message;
+extern int password_change_required;
+pid_t password_change_pid;	/* pid used to reset forwarding flags */
 
 /*
  * Tries to authenticate the user using password.  Returns true if
@@ -123,6 +128,7 @@
 	/* deny if no user. */
 	if (pw == NULL)
 		return 0;
+	buffer_init(&login_message);
 #ifndef HAVE_CYGWIN
        if (pw->pw_uid == 0 && options.permit_root_login != PERMIT_YES)
 		return 0;
@@ -149,13 +155,25 @@
 #endif
 #ifdef WITH_AIXAUTHENTICATE
 	authsuccess = (authenticate(pw->pw_name,password,&reenter,&authmsg) == 0);
+	aix_remove_embedded_newlines(authmsg);
+
+	if (authsuccess) {
+		char *msg;
 
-	if (authsuccess)
+		debug("authenticate() succeeded for user %s: %.100s",
+		    pw->pw_name, authmsg);
 	        /* We don't have a pty yet, so just label the line as "ssh" */
 	        if (loginsuccess(authctxt->user,
-			get_canonical_hostname(options.verify_reverse_mapping),
-			"ssh", &aixloginmsg) < 0)
-				aixloginmsg = NULL;
+		    get_canonical_hostname(options.verify_reverse_mapping),
+		    "ssh", &msg) < 0)
+			msg = NULL;
+		buffer_append(&login_message, msg, strlen(msg));
+	} else {
+		debug("AIX authenticate() failed for user %s: %.100s",
+		    pw->pw_name, authmsg);
+	}
+	if (authmsg)
+		xfree(authmsg);
 
 	return(authsuccess);
 #endif
@@ -232,4 +250,124 @@
 	/* Authentication is accepted if the encrypted passwords are identical. */
 	return (strcmp(encrypted_password, pw_password) == 0);
 #endif /* !USE_PAM && !HAVE_OSF_SIA */
+}
+
+/*
+ * Perform generic password change via tty. Like do_pam_chauthtok(),
+ * it throws a fatal error if the password can't be changed.
+ */
+int
+do_tty_change_password(struct passwd *pw)
+{
+	pid_t pid;
+	int status;
+	mysig_t old_signal;
+
+	old_signal = mysignal(SIGCHLD, SIG_DFL);
+
+	if ((pid = fork()) == -1)
+		fatal("Couldn't fork: %s", strerror(errno));
+
+	if (pid == 0) {
+		setuid(pw->pw_uid);
+		if (geteuid() == 0) 
+			execl(PASSWD_PROGRAM_PATH, "passwd", pw->pw_name,
+			    (char *)NULL);
+		else
+			execl(PASSWD_PROGRAM_PATH, "passwd", (char *)NULL);
+			
+		/* execl shouldn't return */
+		fatal("Couldn't exec %s", PASSWD_PROGRAM_PATH);
+		exit(1);
+	}
+
+	if (waitpid(pid, &status, 0) == -1)
+		fatal("Couldn't wait for child: %s", strerror(errno));
+	mysignal(SIGCHLD, old_signal);
+
+	/*
+	 * passwd sometimes returns 0 when the password has not been changed
+	 * so we must re-test if change is still required.
+	 */
+	if (WIFEXITED(status) && WEXITSTATUS(status) == 0 &&
+	    PRIVSEP(is_password_change_required(pw)) == 0) {
+		debug("%s password changed sucessfully", __func__);
+		flag_password_change_successful();
+		return 1;
+	} else {
+		fatal("Failed to change password for %s, passwd returned %d",
+		    pw->pw_name, status);
+		return 0;
+	}
+}
+
+/*
+ * Checks account requires password to be changed.
+ * 0 = no change, 1 = change reqd, 2 = can't change 
+ */
+int
+is_password_change_required(struct passwd *pw)
+{
+	password_change_required = 0;
+	/* re-test, sets password_change_required */
+	if (getpwnamallow(pw->pw_name) == NULL)
+		password_change_required = 2;
+	debug3("%s change required %d", __func__, password_change_required);
+	return password_change_required;
+}
+
+/*
+ * Because an expired password is changed after forking to exec the user's
+ * shell, restoring the port forwarding flags is done by sending a
+ * USR1 signal to the parent after the password is changed successfully.
+ */
+void
+flag_password_change_required(void)
+{
+	debug("%s disabling forwarding flags", __func__);
+	/* 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 handler to reset flags */
+	password_change_pid = getpid();
+	mysignal(SIGUSR1, password_change_successful_handler);
+}
+
+/*
+ * password change successful, tell parent to restore port
+ * forwarding flags
+ */
+void
+flag_password_change_successful(void)
+{
+	debug("%s signalling parent to reset forwarding flags", __func__);
+	kill(password_change_pid, SIGUSR1);
+
+	/* reset flags in local process too */
+	password_change_required = 0;
+	no_port_forwarding_flag &= ~2;
+	no_agent_forwarding_flag &= ~2;
+	no_x11_forwarding_flag &= ~2;
+}
+
+/*
+ * signal handler to reset change flags
+ */
+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();
 }
Index: auth.c
===================================================================
RCS file: /cvs/openssh/auth.c,v
retrieving revision 1.66
diff -u -r1.66 auth.c
--- auth.c	9 Jan 2003 04:04:28 -0000	1.66
+++ auth.c	14 Jan 2003 11:53:18 -0000
@@ -52,12 +52,17 @@
 #include "bufaux.h"
 #include "packet.h"
 
+#define PWCHG_FORCED	"You must change your password now.\n"
+#define PWCHG_EXPIRED	"Your password has expired, you must change it now.\n"
+
 /* import */
 extern ServerOptions options;
 
 /* Debugging messages */
 Buffer auth_debug;
 int auth_debug_init;
+extern int password_change_required;
+extern Buffer expire_message;
 
 /*
  * Check if the user is allowed to log in via ssh. If user is listed
@@ -75,13 +80,10 @@
 	const char *hostname = NULL, *ipaddr = NULL, *passwd;
 	char *shell;
 	int i;
-#ifdef WITH_AIXAUTHENTICATE
-	char *loginmsg;
-#endif /* WITH_AIXAUTHENTICATE */
 #if defined(HAVE_SHADOW_H) && !defined(DISABLE_SHADOW)
 	struct spwd *spw;
 #if !defined(USE_PAM) && defined(HAS_SHADOW_EXPIRE)
-	time_t today;
+	time_t today, expiredate;
 #endif
 #endif
 
@@ -106,13 +108,13 @@
 		return 0;
 	}
 
+#define	DAY		(24L * 60 * 60) /* 1 day in seconds */
 #if !defined(USE_PAM) && defined(HAVE_SHADOW_H) && \
     !defined(DISABLE_SHADOW) && defined(HAS_SHADOW_EXPIRE)
-#define	DAY		(24L * 60 * 60) /* 1 day in seconds */
 	today = time(NULL) / DAY;
 	debug3("allowed_user: today %d sp_expire %d sp_lstchg %d"
-	    " sp_max %d", (int)today, (int)spw->sp_expire,
-	    (int)spw->sp_lstchg, (int)spw->sp_max);
+	    " sp_max %d sp_warn %d", (int)today, (int)spw->sp_expire,
+	    (int)spw->sp_lstchg, (int)spw->sp_max, (int)spw->sp_warn);
 
 	/*
 	 * We assume account and password expiration occurs the
@@ -121,22 +123,105 @@
 	if (spw->sp_expire != -1 && today > spw->sp_expire) {
 		log("Account %.100s has expired", pw->pw_name);
 		return 0;
+	} else if (spw->sp_expire != -1 &&
+	    spw->sp_expire - today < spw->sp_warn) {
+		char msg[100];
+
+		snprintf(msg, 100,
+		    "Your account will expire in %d days.\n",
+		    (int)(spw->sp_expire - today));
+		buffer_append(&expire_message, msg, strlen(msg));
 	}
 
+	expiredate = spw->sp_lstchg + spw->sp_max;
 	if (spw->sp_lstchg == 0) {
 		log("User %.100s password has expired (root forced)",
 		    pw->pw_name);
-		return 0;
-	}
-
-	if (spw->sp_max != -1 &&
-	    today > spw->sp_lstchg + spw->sp_max) {
+		flag_password_change_required();
+		buffer_append(&expire_message, PWCHG_FORCED,
+		    strlen(PWCHG_FORCED));
+	} else if (spw->sp_max == -1) {
+	       debug3("%s password aging disabled", __func__);
+	} else if (today > expiredate) {
 		log("User %.100s password has expired (password aged)",
 		    pw->pw_name);
-		return 0;
+		flag_password_change_required();
+		buffer_append(&expire_message, PWCHG_EXPIRED,
+		    strlen(PWCHG_EXPIRED));
+	} else if (expiredate - today < spw->sp_warn) {
+		char msg[100];
+
+		snprintf(msg, 100,
+		    "Your password will expire in %d day%s.\n",
+		    (int)(expiredate - today),
+		    expiredate - today > 1 ? "s" : "");
+		buffer_append(&expire_message, msg, strlen(msg));
 	}
 #endif
 
+#if !defined(USE_PAM) && defined(HAVE_STRUCT_PASSWD_PW_AGE)
+	/* do /etc/passwd style password expiry */
+	if (pw->pw_age && pw->pw_age[0] != '\0') {
+		long int max = -1 , min = -1, last = -1;
+		int warnweeks = 2;
+		char s[2] = "\0\0";
+		time_t thisweek, expireweek;
+
+		/*
+		 * Decode expiry string.  The components are base64
+		 * encoded digits, and is of the * form "MmLL" where:
+		 * M = maximum password age in (weeks)
+		 * m = minimum password age in (weeks)
+		 * LL = last password change (weeks since 1 Jan 1970)
+		 */
+
+		/* grab max age from 1st digit */
+		s[0] = pw->pw_age[0];
+		max = a64l(s);
+		if (pw->pw_age[1] != '\0') {
+			/* grab min age from 2nd digit */
+			s[0] = pw->pw_age[1];
+			min = a64l(s);
+			if (pw->pw_age[2] != '\0') {
+				/* grab last change time from remainder */
+				last = a64l((pw->pw_age)+2);
+			}
+		}
+		
+		thisweek = time(NULL) / DAY / 7;
+		expireweek = last + max;
+		debug3("%s: thisweek %d pw_age max %ld min %ld last %ld\n",
+		    __func__, (int)thisweek, max, min, last);
+
+		if (max == -1) {
+			debug3("%s: password aging disabled", __func__);
+		} else if (min > max) {
+			debug3("%s: min > max, password can't be changed",
+			    __func__);
+		} else if (min == 0 && max == 0) {
+			log("User %.100s password has expired (root forced)",
+			    pw->pw_name);
+			flag_password_change_required();
+			buffer_append(&expire_message, PWCHG_FORCED,
+			    strlen(PWCHG_FORCED));
+		} else if (last + max < thisweek) {
+			log("User %.100s password has expired (password aged)",
+			    pw->pw_name);
+			flag_password_change_required();
+			buffer_append(&expire_message, PWCHG_EXPIRED,
+			    strlen(PWCHG_EXPIRED));
+		} else if (last + max - warnweeks <= thisweek ) {
+			char msg[100];
+
+			snprintf(msg, 100,
+			    "Your password will expire in %d week%s.\n",
+			    (int)(expireweek - thisweek),
+			    expireweek - thisweek > 1 ? "s" : "");
+			buffer_append(&expire_message, msg, strlen(msg));
+		}
+	}
+
+#endif
 	/*
 	 * Get the shell from the password data.  An empty shell field is
 	 * legal, and means /bin/sh.
@@ -222,27 +307,60 @@
 	 * PermitRootLogin to control logins via ssh), or if running as
 	 * non-root user (since loginrestrictions will always fail).
 	 */
-	if ( (pw->pw_uid != 0) && (geteuid() == 0) &&
-	    loginrestrictions(pw->pw_name, S_RLOGIN, NULL, &loginmsg) != 0) {
+	if ( (pw->pw_uid != 0) && (geteuid() == 0) ) {
 		int loginrestrict_errno = errno;
+		char *msg;
 
-		if (loginmsg && *loginmsg) {
-			/* Remove embedded newlines (if any) */
-			char *p;
-			for (p = loginmsg; *p; p++) {
-				if (*p == '\n')
-					*p = ' ';
+		/* check for AIX account restrictions */
+		if (loginrestrictions(pw->pw_name, S_RLOGIN, NULL, &msg) != 0) {
+			if (msg && *msg) {
+				aix_remove_embedded_newlines(msg);
+				log("Login restricted for %s: %.100s",
+				    pw->pw_name, msg);
+				xfree(msg);
 			}
-			/* Remove trailing newline */
-			*--p = '\0';
-			log("Login restricted for %s: %.100s", pw->pw_name, 
-			    loginmsg);
-		}
-		/* Don't fail if /etc/nologin  set */
-	    	if (!(loginrestrict_errno == EPERM && 
-		    stat(_PATH_NOLOGIN, &st) == 0))
-			return 0;
+
+			/* Don't fail if /etc/nologin  set */
+	   	 	if (!(loginrestrict_errno == EPERM && 
+			    stat(_PATH_NOLOGIN, &st) == 0))
+				return 0;
+		}
 	}
+
+	/*
+	 * Check AIX password expiry.  Only check when running as root.
+	 * Unpriv'ed users can't access /etc/security/passwd or
+	 * /etc/security/user so passwdexpired will always fail.
+	 */
+	if (geteuid() == 0) {
+		char *msg;
+		int passexpcode;
+
+		enduserdb();	/* flush cached results for passwdexpired */
+		passexpcode = passwdexpired(pw->pw_name, &msg);
+		buffer_append(&expire_message, msg, strlen(msg));
+		if (msg && *msg)
+			aix_remove_embedded_newlines(msg);
+		debug("AIX passwdexpired returned %d errno %d msg %.100s",
+		    errno, passexpcode, msg);
+
+		switch (passexpcode) {
+			case 0:	/* success, password not expired */
+				break;
+			case 1:	/* expired, password change required */
+				flag_password_change_required();
+				break;
+			default: /* only admin can change (2) or other error (-1) */
+				log("Password can't be changed for user %s: %.100s",
+				    pw->pw_name, msg);
+				if (msg)
+					xfree(msg);
+				return 0;
+  		}
+		if (msg)
+			xfree(msg);
+
+  	}
 #endif /* WITH_AIXAUTHENTICATE */
 
 	/* We found no reason not to let this user try to log on... */
@@ -508,6 +626,7 @@
 #endif
 	struct passwd *pw;
 
+	buffer_init(&expire_message);
 	pw = getpwnam(user);
 	if (pw == NULL) {
 		log("Illegal user %.100s from %.100s",
Index: auth.h
===================================================================
RCS file: /cvs/openssh/auth.h,v
retrieving revision 1.44
diff -u -r1.44 auth.h
--- auth.h	27 Sep 2002 03:26:01 -0000	1.44
+++ auth.h	14 Jan 2003 11:53:18 -0000
@@ -101,6 +101,8 @@
 
 int	 auth_rhosts_rsa(struct passwd *, char *, Key *);
 int      auth_password(Authctxt *, const char *);
+int	 do_tty_change_password(struct passwd *pw);
+void	 flag_password_change_required(void);
 int      auth_rsa(struct passwd *, BIGNUM *);
 int      auth_rsa_challenge_dialog(Key *);
 BIGNUM	*auth_rsa_generate_challenge(Key *);
@@ -156,6 +158,8 @@
 
 int	allowed_user(struct passwd *);
 struct passwd * getpwnamallow(const char *user);
+int	is_password_change_required(struct passwd *);
+void	flag_password_change_successful(void);
 
 char	*get_challenge(Authctxt *);
 int	verify_response(Authctxt *, const char *);
@@ -183,6 +187,8 @@
 void	 auth_debug_add(const char *fmt,...) __attribute__((format(printf, 1, 2)));
 void	 auth_debug_send(void);
 void	 auth_debug_reset(void);
+
+void	password_change_successful_handler(int);
 
 #define AUTH_FAIL_MAX 6
 #define AUTH_FAIL_LOG (AUTH_FAIL_MAX/2)
Index: configure.ac
===================================================================
RCS file: /cvs/openssh/configure.ac,v
retrieving revision 1.100
diff -u -r1.100 configure.ac
--- configure.ac	13 Jan 2003 05:55:23 -0000	1.100
+++ configure.ac	14 Jan 2003 11:53:27 -0000
@@ -41,6 +41,13 @@
 	fi
 fi
 
+AC_PATH_PROG(PASSWD_PROGRAM_PATH, passwd)
+if test ! -z "$PASSWD_PROGRAM_PATH" ; then
+	AC_DEFINE_UNQUOTED(PASSWD_PROGRAM_PATH, "$PASSWD_PROGRAM_PATH")
+else
+	AC_MSG_ERROR([*** passwd command not found - check config.log ***])
+fi
+
 if test -z "$LD" ; then
 	LD=$CC
 fi
@@ -1532,6 +1539,7 @@
 OSSH_CHECK_HEADER_FOR_FIELD(ut_tv, utmpx.h, HAVE_TV_IN_UTMPX)
 
 AC_CHECK_MEMBERS([struct stat.st_blksize])
+AC_CHECK_MEMBERS([struct passwd.pw_age])
 
 AC_CACHE_CHECK([for ss_family field in struct sockaddr_storage],
 		ac_cv_have_ss_family_in_struct_ss, [
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	14 Jan 2003 11:53:33 -0000
@@ -98,6 +98,7 @@
 int mm_answer_moduli(int, Buffer *);
 int mm_answer_sign(int, Buffer *);
 int mm_answer_pwnamallow(int, Buffer *);
+int mm_answer_is_pwchange_reqd(int, Buffer *);
 int mm_answer_auth2_read_banner(int, Buffer *);
 int mm_answer_authserv(int, Buffer *);
 int mm_answer_authpassword(int, Buffer *);
@@ -183,6 +184,7 @@
     {MONITOR_REQ_PTY, 0, mm_answer_pty},
     {MONITOR_REQ_PTYCLEANUP, 0, mm_answer_pty_cleanup},
     {MONITOR_REQ_TERM, 0, mm_answer_term},
+    {MONITOR_REQ_PWCHANGE_REQD, 0, mm_answer_is_pwchange_reqd},
     {0, 0, NULL}
 };
 
@@ -219,6 +221,7 @@
     {MONITOR_REQ_PTY, MON_ONCE, mm_answer_pty},
     {MONITOR_REQ_PTYCLEANUP, MON_ONCE, mm_answer_pty_cleanup},
     {MONITOR_REQ_TERM, 0, mm_answer_term},
+    {MONITOR_REQ_PWCHANGE_REQD, 0, mm_answer_is_pwchange_reqd},
     {0, 0, NULL}
 };
 
@@ -320,10 +323,12 @@
 		monitor_permit(mon_dispatch, MONITOR_REQ_MODULI, 1);
 		monitor_permit(mon_dispatch, MONITOR_REQ_SIGN, 1);
 		monitor_permit(mon_dispatch, MONITOR_REQ_TERM, 1);
+		monitor_permit(mon_dispatch, MONITOR_REQ_PWCHANGE_REQD, 1);
 
 	} else {
 		mon_dispatch = mon_dispatch_postauth15;
 		monitor_permit(mon_dispatch, MONITOR_REQ_TERM, 1);
+		monitor_permit(mon_dispatch, MONITOR_REQ_PWCHANGE_REQD, 1);
 	}
 	if (!no_pty_flag) {
 		monitor_permit(mon_dispatch, MONITOR_REQ_PTY, 1);
@@ -558,6 +563,21 @@
 #ifdef USE_PAM
 	monitor_permit(mon_dispatch, MONITOR_REQ_PAM_START, 1);
 #endif
+
+	return (0);
+}
+
+int
+mm_answer_is_pwchange_reqd(int socket, Buffer *m)
+{
+	int required;
+
+	buffer_clear(m);
+	required = is_password_change_required(authctxt->pw);
+	buffer_put_int(m, required);
+
+	debug3("%s: sending MONITOR_ANS_PWCHANGE_REQD: %d", __func__, required);
+	mm_request_send(socket, MONITOR_ANS_PWCHANGE_REQD, m);
 
 	return (0);
 }
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	14 Jan 2003 11:53:34 -0000
@@ -33,6 +33,7 @@
 	MONITOR_REQ_FREE, MONITOR_REQ_AUTHSERV,
 	MONITOR_REQ_SIGN, MONITOR_ANS_SIGN,
 	MONITOR_REQ_PWNAM, MONITOR_ANS_PWNAM,
+	MONITOR_REQ_PWCHANGE_REQD, MONITOR_ANS_PWCHANGE_REQD,
 	MONITOR_REQ_AUTH2_READ_BANNER, MONITOR_ANS_AUTH2_READ_BANNER,
 	MONITOR_REQ_AUTHPASSWORD, MONITOR_ANS_AUTHPASSWORD,
 	MONITOR_REQ_BSDAUTHQUERY, MONITOR_ANS_BSDAUTHQUERY,
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	14 Jan 2003 11:53:34 -0000
@@ -207,6 +207,24 @@
 	return (pw);
 }
 
+int
+mm_is_password_change_required(struct passwd *pw)
+{
+	Buffer m;
+	int result;
+
+	debug3("%s entering", __func__);
+	buffer_init(&m);
+	mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_PWCHANGE_REQD, &m);
+	buffer_clear(&m);
+	mm_request_receive_expect(pmonitor->m_recvfd,
+	    MONITOR_ANS_PWCHANGE_REQD, &m);
+	result = buffer_get_int(&m);
+	buffer_free(&m);
+
+	return (result);
+}
+
 char *mm_auth2_read_banner(void)
 {
 	Buffer m;
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	14 Jan 2003 11:53:34 -0000
@@ -44,6 +44,7 @@
 int mm_key_sign(Key *, u_char **, u_int *, u_char *, u_int);
 void mm_inform_authserv(char *, char *);
 struct passwd *mm_getpwnamallow(const char *);
+int mm_is_password_change_required(struct passwd *);
 char *mm_auth2_read_banner(void);
 int mm_auth_password(struct Authctxt *, char *);
 int mm_key_allowed(enum mm_keytype, char *, char *, Key *);
Index: session.c
===================================================================
RCS file: /cvs/openssh/session.c,v
retrieving revision 1.228
diff -u -r1.228 session.c
--- session.c	9 Jan 2003 22:53:12 -0000	1.228
+++ session.c	14 Jan 2003 11:53:42 -0000
@@ -102,10 +102,9 @@
 /* data */
 #define MAX_SESSIONS 10
 Session	sessions[MAX_SESSIONS];
-
-#ifdef WITH_AIXAUTHENTICATE
-char *aixloginmsg;
-#endif /* WITH_AIXAUTHENTICATE */
+Buffer expire_message;	/* "password will expire/has expired" messages */
+Buffer login_message;	/* message to be displayed after login */
+int password_change_required = 0;
 
 #ifdef HAVE_LOGIN_CAP
 login_cap_t *lc;
@@ -456,10 +455,11 @@
 #if defined(USE_PAM)
 	do_pam_session(s->pw->pw_name, NULL);
 	do_pam_setcred(1);
-	if (is_pam_password_change_required())
+#endif /* USE_PAM */
+
+	if (password_change_required)
 		packet_disconnect("Password change required but no "
 		    "TTY available");
-#endif /* USE_PAM */
 
 	/* Fork the child. */
 	if ((pid = fork()) == 0) {
@@ -723,6 +723,7 @@
 	socklen_t fromlen;
 	struct sockaddr_storage from;
 	struct passwd * pw = s->pw;
+	int password_changed = 0;
 	pid_t pid = getpid();
 
 	/*
@@ -746,16 +747,22 @@
 		    options.verify_reverse_mapping),
 		    (struct sockaddr *)&from, fromlen);
 
-#ifdef USE_PAM
 	/*
 	 * If password change is needed, do it now.
 	 * This needs to occur before the ~/.hushlogin check.
 	 */
+#ifdef USE_PAM
 	if (is_pam_password_change_required()) {
 		print_pam_messages();
 		do_pam_chauthtok();
 	}
 #endif
+	buffer_append(&expire_message, "\0", 1);
+	if (password_change_required) {
+		puts((char *)buffer_ptr(&expire_message));
+		do_tty_change_password(pw);
+		password_changed = 1;
+	}
 
 	if (check_quietlogin(s, command))
 		return;
@@ -764,10 +771,12 @@
 	if (!is_pam_password_change_required())
 		print_pam_messages();
 #endif /* USE_PAM */
-#ifdef WITH_AIXAUTHENTICATE
-	if (aixloginmsg && *aixloginmsg)
-		printf("%s\n", aixloginmsg);
-#endif /* WITH_AIXAUTHENTICATE */
+	if (!password_changed)
+		printf("%s", (char *)buffer_ptr(&expire_message));
+
+	/* display post-login message */
+	buffer_append(&login_message, "\0", 1);
+	puts((char *)buffer_ptr(&login_message));
 
 #ifndef NO_SSH_LASTLOG
 	if (options.print_lastlog && s->last_login_time != 0) {
Index: openbsd-compat/port-aix.c
===================================================================
RCS file: /cvs/openssh/openbsd-compat/port-aix.c,v
retrieving revision 1.6
diff -u -r1.6 port-aix.c
--- openbsd-compat/port-aix.c	7 Jul 2002 02:17:36 -0000	1.6
+++ openbsd-compat/port-aix.c	14 Jan 2003 11:54:03 -0000
@@ -52,5 +52,25 @@
 	xfree(cp);
 }
 
-#endif /* _AIX */
+#ifdef WITH_AIXAUTHENTICATE
+/*
+ * Remove embedded newlines in string (if any).
+ * Used before logging messages returned by AIX authentication functions
+ * so the message is logged on one line.
+ */
+void
+aix_remove_embedded_newlines(char *p)
+{
+	if (p == NULL)
+		return;
+
+	for (; *p; p++) {
+		if (*p == '\n')
+			*p = ' ';
+	}
+	/* Remove trailing newline */
+	*--p = '\0';
+}
+#endif /* WITH_AIXAUTHENTICATE */
 
+#endif /* _AIX */
Index: openbsd-compat/port-aix.h
===================================================================
RCS file: /cvs/openssh/openbsd-compat/port-aix.h,v
retrieving revision 1.6
diff -u -r1.6 port-aix.h
--- openbsd-compat/port-aix.h	7 Jul 2002 02:17:36 -0000	1.6
+++ openbsd-compat/port-aix.h	14 Jan 2003 11:54:03 -0000
@@ -26,4 +26,5 @@
 
 #ifdef _AIX
 void aix_usrinfo(struct passwd *pw);
+void aix_remove_embedded_newlines(char *);
 #endif /* _AIX */
