- markus@cvs.openbsd.org 2004/01/30 09:48:57
     [auth-passwd.c auth.h pathnames.h session.c]
     support for password change; ok dtucker@
     (set password-dead=1w in login.conf to use this).
     In -Portable, this is currently only platforms using bsdauth.
diff --git a/ChangeLog b/ChangeLog
index 7bc43d0..518c51b 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -12,6 +12,12 @@
  - (dtucker) [openbsd-compat/port-aix.c openbsd-compat/port-aix.h] Bug #796:
    Restore previous authdb setting after auth calls.  Fixes problems with
    setpcred failing on accounts that use AFS or NIS password registries.
+ - (dtucker) OpenBSD CVS Sync
+   - markus@cvs.openbsd.org 2004/01/30 09:48:57
+     [auth-passwd.c auth.h pathnames.h session.c]
+     support for password change; ok dtucker@
+     (set password-dead=1w in login.conf to use this).
+     In -Portable, this is currently only platforms using bsdauth.
 
 20040129
  - (dtucker) OpenBSD CVS Sync regress/
@@ -1797,4 +1803,4 @@
  - Fix sshd BindAddress and -b options for systems using fake-getaddrinfo.
    Report from murple@murple.net, diagnosis from dtucker@zip.com.au
 
-$Id: ChangeLog,v 1.3211 2004/02/06 05:18:47 dtucker Exp $
+$Id: ChangeLog,v 1.3212 2004/02/06 05:24:31 dtucker Exp $
diff --git a/acconfig.h b/acconfig.h
index 27366ed..62252d7 100644
--- a/acconfig.h
+++ b/acconfig.h
@@ -1,4 +1,4 @@
-/* $Id: acconfig.h,v 1.172 2004/01/23 11:03:10 dtucker Exp $ */
+/* $Id: acconfig.h,v 1.173 2004/02/06 05:24:31 dtucker Exp $ */
 
 /*
  * Copyright (c) 1999-2003 Damien Miller.  All rights reserved.
@@ -65,6 +65,9 @@
 /* from environment and PATH */
 #undef LOGIN_PROGRAM_FALLBACK
 
+/* Full path of your "passwd" program */
+#undef _PATH_PASSWD_PROG
+
 /* Define if your password has a pw_class field */
 #undef HAVE_PW_CLASS_IN_PASSWD
 
diff --git a/auth-passwd.c b/auth-passwd.c
index a27170c..d12996b 100644
--- a/auth-passwd.c
+++ b/auth-passwd.c
@@ -42,11 +42,21 @@
 #include "log.h"
 #include "servconf.h"
 #include "auth.h"
+#include "auth-options.h"
 #ifdef WITH_AIXAUTHENTICATE
 # include "canohost.h"
 #endif
 
 extern ServerOptions options;
+int sys_auth_passwd(Authctxt *, const char *);
+
+static void
+disable_forwarding(void)
+{
+	no_port_forwarding_flag = 1;
+	no_agent_forwarding_flag = 1;
+	no_x11_forwarding_flag = 1;
+}
 
 /*
  * Tries to authenticate the user using password.  Returns true if
@@ -66,17 +76,21 @@
 		return 0;
 
 #if defined(HAVE_OSF_SIA)
+	/*
+	 * XXX: any reason this is before krb?  could be moved to
+	 * sys_auth_passwd()?  -dt
+	 */
 	return auth_sia_password(authctxt, password) && ok;
-#else
-# ifdef KRB5
+#endif
+#ifdef KRB5
 	if (options.kerberos_authentication == 1) {
 		int ret = auth_krb5_password(authctxt, password);
 		if (ret == 1 || ret == 0)
 			return ret && ok;
 		/* Fall back to ordinary passwd authentication. */
 	}
-# endif
-# ifdef HAVE_CYGWIN
+#endif
+#ifdef HAVE_CYGWIN
 	if (is_winnt) {
 		HANDLE hToken = cygwin_logon_user(pw, password);
 
@@ -85,41 +99,57 @@
 		cygwin_set_impersonation_token(hToken);
 		return ok;
 	}
-# endif
-# ifdef WITH_AIXAUTHENTICATE
-	if (aix_authenticate(pw->pw_name, password,
-	    get_canonical_hostname(options.use_dns)) == 0)
-		return 0;
-	else
-		return ok;
-# endif
-# ifdef BSD_AUTH
-	if (auth_userokay(pw->pw_name, authctxt->style, "auth-ssh",
-	    (char *)password) == 0)
-		return 0;
-	else
-		return ok;
-# else
-	{
+#endif
+	return (sys_auth_passwd(authctxt, password) && ok);
+}
+
+#ifdef BSD_AUTH
+int
+sys_auth_passwd(Authctxt *authctxt, const char *password)
+{
+	struct passwd *pw = authctxt->pw;
+	auth_session_t *as;
+
+	as = auth_usercheck(pw->pw_name, authctxt->style, "auth-ssh",
+	    (char *)password);
+	if (auth_getstate(as) & AUTH_PWEXPIRED) {
+		auth_close(as);
+		disable_forwarding();
+		authctxt->force_pwchange = 1;
+		return (1);
+	} else {
+		return (auth_close(as));
+	}
+}
+#elif defined(WITH_AIXAUTHENTICATE)
+int
+sys_auth_passwd(Authctxt *authctxt, const char *password)
+{
+	return (aix_authenticate(authctxt->pw->pw_name, password,
+	    get_canonical_hostname(options.use_dns)));
+}
+#else
+int
+sys_auth_passwd(Authctxt *authctxt, const char *password)
+{
+	struct passwd *pw = authctxt->pw;
+	char *encrypted_password;
+
 	/* Just use the supplied fake password if authctxt is invalid */
 	char *pw_password = authctxt->valid ? shadow_pw(pw) : pw->pw_passwd;
 
 	/* Check for users with no password. */
 	if (strcmp(pw_password, "") == 0 && strcmp(password, "") == 0)
-		return ok;
-	else {
-		/* Encrypt the candidate password using the proper salt. */
-		char *encrypted_password = xcrypt(password,
-		    (pw_password[0] && pw_password[1]) ? pw_password : "xx");
+		return (1);
 
-		/*
-		 * Authentication is accepted if the encrypted passwords
-		 * are identical.
-		 */
-		return (strcmp(encrypted_password, pw_password) == 0) && ok;
-	}
+	/* Encrypt the candidate password using the proper salt. */
+	encrypted_password = xcrypt(password,
+	    (pw_password[0] && pw_password[1]) ? pw_password : "xx");
 
-	}
-# endif
-#endif /* !HAVE_OSF_SIA */
+	/*
+	 * Authentication is accepted if the encrypted passwords
+	 * are identical.
+	 */
+	return (strcmp(encrypted_password, pw_password) == 0);
 }
+#endif
diff --git a/auth.h b/auth.h
index 0be1f88..de2f1e8 100644
--- a/auth.h
+++ b/auth.h
@@ -52,6 +52,7 @@
 	int		 valid;		/* user exists and is allowed to login */
 	int		 attempt;
 	int		 failures;
+	int		 force_pwchange;
 	char		*user;		/* username sent by the client */
 	char		*service;
 	struct passwd	*pw;		/* set if 'valid' */
diff --git a/configure.ac b/configure.ac
index 768b174..6464521 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,4 +1,4 @@
-# $Id: configure.ac,v 1.192 2004/02/06 04:59:06 dtucker Exp $
+# $Id: configure.ac,v 1.193 2004/02/06 05:24:31 dtucker Exp $
 
 AC_INIT
 AC_CONFIG_SRCDIR([ssh.c])
@@ -42,6 +42,11 @@
 	fi
 fi
 
+AC_PATH_PROG(PATH_PASSWD_PROG, passwd)
+if test ! -z "$PATH_PASSWD_PROG" ; then
+	AC_DEFINE_UNQUOTED(_PATH_PASSWD_PROG, "$PATH_PASSWD_PROG")
+fi
+
 if test -z "$LD" ; then
 	LD=$CC
 fi
diff --git a/pathnames.h b/pathnames.h
index 89e22c7..edeff2d 100644
--- a/pathnames.h
+++ b/pathnames.h
@@ -150,6 +150,11 @@
 #define _PATH_PRIVSEP_CHROOT_DIR	"/var/empty"
 #endif
 
+/* for passwd change */
+#ifndef _PATH_PASSWD_PROG
+#define _PATH_PASSWD_PROG             "/usr/bin/passwd"
+#endif
+
 #ifndef _PATH_LS
 #define _PATH_LS			"ls"
 #endif
diff --git a/session.c b/session.c
index 02c5dca..5742296 100644
--- a/session.c
+++ b/session.c
@@ -33,7 +33,7 @@
  */
 
 #include "includes.h"
-RCSID("$OpenBSD: session.c,v 1.171 2004/01/13 19:23:15 markus Exp $");
+RCSID("$OpenBSD: session.c,v 1.172 2004/01/30 09:48:57 markus Exp $");
 
 #include "ssh.h"
 #include "ssh1.h"
@@ -1304,6 +1304,22 @@
 }
 
 static void
+do_pwchange(Session *s)
+{
+	fprintf(stderr, "WARNING: Your password has expired.\n");
+	if (s->ttyfd != -1) {
+	    	fprintf(stderr,
+		    "You must change your password now and login again!\n");
+		execl(_PATH_PASSWD_PROG, "passwd", (char *)NULL);
+		perror("passwd");
+	} else {
+		fprintf(stderr,
+		    "Password change required but no TTY available.\n");
+	}
+	exit(1);
+}
+
+static void
 launch_login(struct passwd *pw, const char *hostname)
 {
 	/* Launch login(1). */
@@ -1324,6 +1340,40 @@
 	exit(1);
 }
 
+static void
+child_close_fds(void)
+{
+	int i;
+
+	if (packet_get_connection_in() == packet_get_connection_out())
+		close(packet_get_connection_in());
+	else {
+		close(packet_get_connection_in());
+		close(packet_get_connection_out());
+	}
+	/*
+	 * Close all descriptors related to channels.  They will still remain
+	 * open in the parent.
+	 */
+	/* XXX better use close-on-exec? -markus */
+	channel_close_all();
+
+	/*
+	 * Close any extra file descriptors.  Note that there may still be
+	 * descriptors left by system functions.  They will be closed later.
+	 */
+	endpwent();
+
+	/*
+	 * Close any extra open file descriptors so that we don\'t have them
+	 * hanging around in clients.  Note that we want to do this after
+	 * initgroups, because at least on Solaris 2.3 it leaves file
+	 * descriptors open.
+	 */
+	for (i = 3; i < 64; i++)
+		close(i);
+}
+
 /*
  * Performs common processing for the child, such as setting up the
  * environment, closing extra file descriptors, setting the user and group
@@ -1337,11 +1387,18 @@
 	char *argv[10];
 	const char *shell, *shell0, *hostname = NULL;
 	struct passwd *pw = s->pw;
-	u_int i;
 
 	/* remove hostkey from the child's memory */
 	destroy_sensitive_data();
 
+	/* Force a password change */
+	if (s->authctxt->force_pwchange) {
+		do_setusercontext(pw);
+		child_close_fds();
+		do_pwchange(s);
+		exit(1);
+	}
+
 	/* login(1) is only called if we execute the login shell */
 	if (options.use_login && command != NULL)
 		options.use_login = 0;
@@ -1392,33 +1449,7 @@
 	 * closed before building the environment, as we call
 	 * get_remote_ipaddr there.
 	 */
-	if (packet_get_connection_in() == packet_get_connection_out())
-		close(packet_get_connection_in());
-	else {
-		close(packet_get_connection_in());
-		close(packet_get_connection_out());
-	}
-	/*
-	 * Close all descriptors related to channels.  They will still remain
-	 * open in the parent.
-	 */
-	/* XXX better use close-on-exec? -markus */
-	channel_close_all();
-
-	/*
-	 * Close any extra file descriptors.  Note that there may still be
-	 * descriptors left by system functions.  They will be closed later.
-	 */
-	endpwent();
-
-	/*
-	 * Close any extra open file descriptors so that we don\'t have them
-	 * hanging around in clients.  Note that we want to do this after
-	 * initgroups, because at least on Solaris 2.3 it leaves file
-	 * descriptors open.
-	 */
-	for (i = 3; i < 64; i++)
-		close(i);
+	child_close_fds();
 
 	/*
 	 * Must take new environment into use so that .ssh/rc,