Index: acconfig.h
===================================================================
RCS file: /cvs/openssh/acconfig.h,v
retrieving revision 1.145
diff -u -r1.145 acconfig.h
--- acconfig.h	26 Sep 2002 00:38:48 -0000	1.145
+++ acconfig.h	20 Nov 2002 13:12:12 -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	20 Nov 2002 13:12:12 -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;
 
@@ -256,7 +256,6 @@
 		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);
@@ -267,7 +266,6 @@
 			no_agent_forwarding_flag |= 2;
 			no_x11_forwarding_flag |= 2;
 			break;
-#endif
 		default:
 			log("PAM rejected by account configuration[%d]: "
 			    "%.200s", pam_retval, PAM_STRERROR(__pamh, 
@@ -352,6 +350,8 @@
 		if (pam_retval != PAM_SUCCESS)
 			fatal("PAM pam_chauthtok failed[%d]: %.200s",
 			    pam_retval, PAM_STRERROR(__pamh, pam_retval));
+		else
+			password_change_required = 0;
 #if 0
 		/* XXX: This would need to be done in the parent process,
 		 * but there's currently no way to pass such request. */
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	20 Nov 2002 13:12:13 -0000
@@ -42,6 +42,8 @@
 #include "log.h"
 #include "servconf.h"
 #include "auth.h"
+#include "buffer.h"
+#include "misc.h"
 
 #if !defined(USE_PAM) && !defined(HAVE_OSF_SIA)
 /* Don't need any of these headers for the PAM or SIA cases */
@@ -81,8 +83,10 @@
 #endif /* !USE_PAM && !HAVE_OSF_SIA */
 
 extern ServerOptions options;
+extern Buffer login_message;
+extern int password_change_required;
 #ifdef WITH_AIXAUTHENTICATE
-extern char *aixloginmsg;
+void aix_remove_embedded_newlines(char *);
 #endif
 
 /*
@@ -149,13 +153,23 @@
 #endif
 #ifdef WITH_AIXAUTHENTICATE
 	authsuccess = (authenticate(pw->pw_name,password,&reenter,&authmsg) == 0);
+	aix_remove_embedded_newlines(authmsg);
 
-	if (authsuccess)
+	if (authsuccess) {
+		char *msg;
+
+		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("authenticate() failed for user %s: %.100s", pw->pw_name, authmsg);
+	}
+	if (authmsg)
+		xfree(authmsg);
 
 	return(authsuccess);
 #endif
@@ -232,4 +246,43 @@
 	/* 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.
+ */
+void
+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));
+
+	if (WEXITSTATUS(status))	/* Passwd exited abnormally */
+		fatal("Failed to change password for %s, passwd returned %d", pw->pw_name, status);
+
+	mysignal(SIGCHLD, old_signal);
+	password_change_required = 0;
 }
Index: auth.c
===================================================================
RCS file: /cvs/openssh/auth.c,v
retrieving revision 1.61
diff -u -r1.61 auth.c
--- auth.c	9 Nov 2002 16:11:12 -0000	1.61
+++ auth.c	20 Nov 2002 13:12:13 -0000
@@ -58,6 +58,12 @@
 /* Debugging messages */
 Buffer auth_debug;
 int auth_debug_init;
+extern int password_change_required;
+extern Buffer expire_message;
+
+#ifdef WITH_AIXAUTHENTICATE
+void aix_remove_embedded_newlines(char *);
+#endif
 
 /*
  * Check if the user is allowed to log in via ssh. If user is listed
@@ -75,21 +81,22 @@
 	const char *hostname = NULL, *ipaddr = NULL;
 	char *shell;
 	int i;
-#ifdef WITH_AIXAUTHENTICATE
-	char *loginmsg;
-#endif /* WITH_AIXAUTHENTICATE */
 #if !defined(USE_PAM) && defined(HAVE_SHADOW_H) && \
 	!defined(DISABLE_SHADOW) && defined(HAS_SHADOW_EXPIRE)
 	struct spwd *spw;
+#endif
 
 	/* Shouldn't be called if pw is NULL, but better safe than sorry... */
 	if (!pw || !pw->pw_name)
 		return 0;
+	buffer_init(&expire_message);
 
+#if !defined(USE_PAM) && defined(HAVE_SHADOW_H) && \
+	!defined(DISABLE_SHADOW) && defined(HAS_SHADOW_EXPIRE)
 #define	DAY		(24L * 60 * 60) /* 1 day in seconds */
 	spw = getspnam(pw->pw_name);
 	if (spw != NULL) {
-		time_t today = time(NULL) / DAY;
+		time_t expiredate, 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);
@@ -106,20 +113,28 @@
 		if (spw->sp_lstchg == 0) {
 			log("User %.100s password has expired (root forced)",
 			    pw->pw_name);
-			return 0;
+			password_change_required = 1;
+			buffer_append(&expire_message,
+			    "You must change your password now.\n", 35); 
 		}
 
-		if (spw->sp_max != -1 &&
-		    today > spw->sp_lstchg + spw->sp_max) {
+		expiredate = spw->sp_lstchg + spw->sp_max;
+		if (spw->sp_max != -1 && today > expiredate) {
 			log("User %.100s password has expired (password aged)",
 			    pw->pw_name);
-			return 0;
+			password_change_required = 1;
+			buffer_append(&expire_message,
+			    "Your password has expired, you must change it now.\n",
+			     51); 
+		} else if (spw->sp_max != -1 && expiredate - today < 14) {
+			char msg[100];
+
+			snprintf(msg, 100,
+			    "Your password will expire in %d days.\n",
+			    (int)(expiredate - today));
+			buffer_append(&expire_message, msg, strlen(msg));
 		}
 	}
-#else
-	/* Shouldn't be called if pw is NULL, but better safe than sorry... */
-	if (!pw || !pw->pw_name)
-		return 0;
 #endif
 
 	/*
@@ -203,25 +218,46 @@
 
 #ifdef WITH_AIXAUTHENTICATE
 	/*
-	 * Don't check loginrestrictions() for root account (use
+	 * Don't check loginrestrictions or expiry for root account (use
 	 * 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 (loginmsg && *loginmsg) {
-			/* Remove embedded newlines (if any) */
-			char *p;
-			for (p = loginmsg; *p; p++) {
-				if (*p == '\n')
-					*p = ' ';
+	if ( (pw->pw_uid != 0) && (geteuid() == 0) ) {
+		char *msg;
+		int passexpcode;
+
+		/* 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);
+			return 0;
 		}
-		return 0;
-	}
+
+		/* check for AIX expired account */
+		passexpcode = passwdexpired(pw->pw_name, &msg);
+		buffer_append(&expire_message, msg, strlen(msg));
+
+		switch (passexpcode) {
+			case 0:		/* success, password not expired */
+				break;
+			case 1:		/* expired, password change required */
+				password_change_required = 1;
+				break;
+			default:	/* expired too long (2) or other error (-1) */
+				if (msg && *msg)
+					aix_remove_embedded_newlines(msg);
+				debug3("AIX passwdexpired returned %d, msg %.100s",
+				    passexpcode, msg);
+				log("Password expired too long or system failure for user %s: %.100s",
+				    pw->pw_name, msg);
+				if (msg)
+					xfree(msg);
+				return 0;
+  		}
+  	}
 #endif /* WITH_AIXAUTHENTICATE */
 
 	/* We found no reason not to let this user try to log on... */
Index: configure.ac
===================================================================
RCS file: /cvs/openssh/configure.ac,v
retrieving revision 1.92
diff -u -r1.92 configure.ac
--- configure.ac	13 Nov 2002 23:55:57 -0000	1.92
+++ configure.ac	20 Nov 2002 13:12:14 -0000
@@ -40,6 +40,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
Index: session.c
===================================================================
RCS file: /cvs/openssh/session.c,v
retrieving revision 1.222
diff -u -r1.222 session.c
--- session.c	26 Sep 2002 00:38:50 -0000	1.222
+++ session.c	20 Nov 2002 13:12:16 -0000
@@ -102,10 +102,11 @@
 /* data */
 #define MAX_SESSIONS 10
 Session	sessions[MAX_SESSIONS];
+Buffer expire_message;	/* "password will expire/has expired" messages */
+Buffer login_message;	/* message to be displayed after login */
+int password_change_required = 0;
 
-#ifdef WITH_AIXAUTHENTICATE
-char *aixloginmsg;
-#endif /* WITH_AIXAUTHENTICATE */
+void do_tty_change_password(struct passwd *);
 
 #ifdef HAVE_LOGIN_CAP
 login_cap_t *lc;
@@ -456,10 +457,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 +725,7 @@
 	socklen_t fromlen;
 	struct sockaddr_storage from;
 	struct passwd * pw = s->pw;
+	int password_changed = 0;
 	pid_t pid = getpid();
 
 	/*
@@ -746,16 +749,23 @@
 		    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();
+		if (!use_privsep)
+			do_pam_chauthtok();
 	}
 #endif
+	buffer_append(&expire_message, "\0", 1);
+	if (password_change_required) {
+		printf("%s", (char *)buffer_ptr(&expire_message));
+		do_tty_change_password(pw);
+		password_changed = 1;
+	}
 
 	if (check_quietlogin(s, command))
 		return;
@@ -764,10 +774,11 @@
 	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));
+
+	buffer_append(&login_message, "\0", 1);
+	printf("%s", (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	20 Nov 2002 13:12:16 -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 */
