[PATCH] AIX password expiration

Darren Tucker dtucker at zip.com.au
Sat Oct 26 17:33:53 EST 2002


Ben Lindstrom wrote:
> > You mean it should use SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ? If so I had a
> > look at auth2.c and I've got no idea where to even start. Any clues or
> > pointers (other than RTFRFC, which I'm about to do).

> <nod>  Yes it should.  I agree with it being confusing.  I only spent an
> hour or so looking at it before I was distracted with other issues.

> The patch really should be generic enough that I can split it and get the
> password change functional upstream to OpenBSD and allow the rest of the
> patch to be in portable tree.

It's still rough but PASSWD_CHANGEREQ in protocol 2 now pretty much
works on AIX.

(Without privsep, that is. I've learnt enough to understand why it
doesn't work with privsep, but not how to fix it. Help getting it to
work would be appreciated.)

See attached patch against 3.5p1.

Changes:
* password_change_required flag is global (not just #ifdef WITH_PAM)
* the "exec /usr/bin/passwd" changer runs only for protocol 1
* there's an AIX-specific password change routine for protocol 2
* the password auth routines return 2 to indicate password expiry

exec'ing /bin/passwd may be usable for other platforms for protocol 1.
Probably should have configure find passwd.
do_pam_chauthtok() looks like it needs a tty.

Comments?
		-Daz.

Example (protocol 2):
$ ssh -p 2022 -l testuser localhost
testuser at localhost's password:
You are required to change your password. Please choose a new one.

Enter testuser at localhost's old password:
Enter testuser at localhost's new password:
Retype testuser at localhost's new password:
Last unsuccessful login: Sat Oct 26 17:01:39 2002 on ssh from localhost
Last login: Sat Oct 26 17:02:00 2002 on ssh from localhost
[snip]

-- 
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 --------------
diff -ru openssh-3.5p1.orig/auth-pam.c openssh-3.5p1-aixpassexpire/auth-pam.c
--- openssh-3.5p1.orig/auth-pam.c	Mon Jul 29 06:24:08 2002
+++ openssh-3.5p1-aixpassexpire/auth-pam.c	Sat Oct 26 10:24:59 2002
@@ -59,8 +59,6 @@
 
 /* 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;
 /* remember whether the last pam_authenticate() succeeded or not */
 static int was_authenticated = 0;
 
diff -ru openssh-3.5p1.orig/auth-passwd.c openssh-3.5p1-aixpassexpire/auth-passwd.c
--- openssh-3.5p1.orig/auth-passwd.c	Thu Sep 26 09:14:16 2002
+++ openssh-3.5p1-aixpassexpire/auth-passwd.c	Sat Oct 26 16:35:19 2002
@@ -42,6 +42,7 @@
 #include "log.h"
 #include "servconf.h"
 #include "auth.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,13 +82,15 @@
 #endif /* !USE_PAM && !HAVE_OSF_SIA */
 
 extern ServerOptions options;
+extern int password_change_required;
 #ifdef WITH_AIXAUTHENTICATE
 extern char *aixloginmsg;
 #endif
 
 /*
- * Tries to authenticate the user using password.  Returns true if
- * authentication succeeds.
+ * Tries to authenticate the user using password.  Returns true (1) if
+ * authentication succeeds, (2) if authentication succeeds but password
+ * change required.
  */
 int
 auth_password(Authctxt *authctxt, const char *password)
@@ -149,14 +152,25 @@
 #endif
 #ifdef WITH_AIXAUTHENTICATE
 	authsuccess = (authenticate(pw->pw_name,password,&reenter,&authmsg) == 0);
+	aix_remove_embedded_newlines(authmsg);
 
-	if (authsuccess)
+	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;
+	} else {
+		debug("authenticate() failed for user %s: %.100s", pw->pw_name, authmsg);
+	}
+	if (authmsg)
+		xfree(authmsg);
 
+	debug("auth_password: authsuccess = %d", authsuccess);
+	if (authsuccess && password_change_required) {
+		return 2;
+	}
 	return(authsuccess);
 #endif
 #ifdef KRB4
@@ -232,4 +246,39 @@
 	/* Authentication is accepted if the encrypted passwords are identical. */
 	return (strcmp(encrypted_password, pw_password) == 0);
 #endif /* !USE_PAM && !HAVE_OSF_SIA */
+}
+
+/*
+ * generic password change routine. requires session established and tty alloced 
+ * 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);
+		execl("/usr/bin/passwd","passwd",pw->pw_name,
+			(char *)NULL);
+		/* execl shouldn't return */
+		fatal("Couldn't exec /usr/bin/passwd");
+		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);
 }

diff -ru openssh-3.5p1.orig/auth.c openssh-3.5p1-aixpassexpire/auth.c
--- openssh-3.5p1.orig/auth.c	Sun Sep 22 01:26:53 2002
+++ openssh-3.5p1-aixpassexpire/auth.c	Sat Oct 26 16:35:59 2002
@@ -59,6 +59,14 @@
 Buffer auth_debug;
 int auth_debug_init;
 
+/* Password change flag */
+int password_change_required = 0;
+char *password_change_prompt = NULL;
+
+#ifdef WITH_AIXAUTHENTICATE
+extern char *aixexpiremsg;
+#endif
+
 /*
  * Check if the user is allowed to log in via ssh. If user is listed
  * in DenyUsers or one of user's groups is listed in DenyGroups, false
@@ -75,9 +83,6 @@
 	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;
@@ -202,19 +207,49 @@
 	}
 
 #ifdef WITH_AIXAUTHENTICATE
-	if (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 = ' ';
+	/*
+	 * 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) ) {
+		char *restrictmsg, *expiremsg;
+		int passexpcode;
+
+		/* check for AIX account restrictions */
+		if (loginrestrictions(pw->pw_name, S_RLOGIN, NULL, &restrictmsg) != 0) {
+			if (restrictmsg && *restrictmsg) {
+				aix_remove_embedded_newlines(restrictmsg);
+				log("Login restricted for %s: %.100s", pw->pw_name, restrictmsg);
+				xfree(restrictmsg);
 			}
-			/* Remove trailing newline */
-			*--p = '\0';
-			log("Login restricted for %s: %.100s", pw->pw_name, loginmsg);
+			return 0;
+		}
+
+		/* check for AIX expired account */
+		passexpcode = passwdexpired(pw->pw_name, &aixexpiremsg);
+		debug("passwdexpired() returned %d", passexpcode);
+
+		switch (passexpcode) {
+			case 0:		/* success, password not expired */
+				break;
+			case 1:		/* expired, password change required */
+				password_change_required = 1;
+				password_change_prompt = aixexpiremsg;
+				break;
+			default:	/* expired too long (2) or other error (-1) */
+				/* make local copy of message and remove newlines for logging */
+				if (aixexpiremsg && *aixexpiremsg) {
+					expiremsg = xstrdup(aixexpiremsg);
+					aix_remove_embedded_newlines(expiremsg);
+				}
+				debug("passwdexpired() returned %d", passexpcode);
+				log("Password expired too long or system failure for user %s: %.100s",
+				    pw->pw_name, expiremsg);
+				if (expiremsg)
+					xfree(expiremsg);
+				return 0;
 		}
-		return 0;
 	}
 #endif /* WITH_AIXAUTHENTICATE */
 
diff -ru openssh-3.5p1.orig/auth2-passwd.c openssh-3.5p1-aixpassexpire/auth2-passwd.c
--- openssh-3.5p1.orig/auth2-passwd.c	Fri Jun  7 06:27:56 2002
+++ openssh-3.5p1-aixpassexpire/auth2-passwd.c	Sat Oct 26 16:36:33 2002
@@ -31,31 +31,60 @@
 #include "auth.h"
 #include "monitor_wrap.h"
 #include "servconf.h"
+#include "ssh2.h"
 
 /* import */
 extern ServerOptions options;
+extern int password_change_required;
 
 static int
 userauth_passwd(Authctxt *authctxt)
 {
-	char *password;
-	int authenticated = 0;
-	int change;
-	u_int len;
-	change = packet_get_char();
-	if (change)
-		log("password change not supported");
+	char *password, *npassword;
+	int authenticated = 0, change_requested;
+	u_int len, nlen;
+
+	change_requested = packet_get_char();
 	password = packet_get_string(&len);
+	if (change_requested) {
+		debug("userauth_passwd: password change requested by client");
+		npassword = packet_get_string(&nlen);
+	}
 	packet_check_eom();
+
 	if (authctxt->valid &&
 #ifdef HAVE_CYGWIN
 	    check_nt_auth(1, authctxt->pw) &&
 #endif
-	    PRIVSEP(auth_password(authctxt, password)) == 1)
-		authenticated = 1;
+	    (authenticated = (PRIVSEP(auth_password(authctxt, password))))) {
+		debug("auth_password returned %d, pid=%d ppid=%d",
+		    authenticated, getpid(), getppid());
+
+		/* now that the password has been checked, change password
+		 * if requested by client and revalidate new password */
+		if (change_requested) {
+			if (userauth_change_password(authctxt, password, npassword)) {
+				debug("userauth_passwd: password changed successfully");
+				authenticated = 1;
+			} else {
+				debug("userauth_passwd: password change failed");
+			}
+			memset(npassword, 0, nlen);
+			xfree(npassword);
+		}
+	}
 	memset(password, 0, len);
 	xfree(password);
 	return authenticated;
+}
+
+/* password change for protocol 2 */
+int
+userauth_change_password(Authctxt *authctxt, char *oldpasswd, char *newpasswd)
+{
+#ifdef WITH_AIXAUTHENTICATE
+	return aix_change_password(authctxt->pw, oldpasswd, newpasswd);
+#endif
 }
 
 Authmethod method_passwd = {

diff -ru openssh-3.5p1.orig/auth2.c openssh-3.5p1-aixpassexpire/auth2.c
--- openssh-3.5p1.orig/auth2.c	Thu Sep 26 10:38:49 2002
+++ openssh-3.5p1-aixpassexpire/auth2.c	Sat Oct 26 16:22:40 2002
@@ -40,6 +40,7 @@
 extern ServerOptions options;
 extern u_char *session_id2;
 extern int session_id2_len;
+extern char *password_change_prompt;
 
 Authctxt *x_authctxt = NULL;
 
@@ -199,6 +200,7 @@
 userauth_finish(Authctxt *authctxt, int authenticated, char *method)
 {
 	char *methods;
+	static const char default_prompt[] = "You must change your password now.";
 
 	if (!authctxt->valid && authenticated)
 		fatal("INTERNAL ERROR: authenticated invalid user %s",
@@ -238,6 +240,15 @@
 		packet_write_wait();
 		/* now we can break out */
 		authctxt->success = 1;
+	} else if (authenticated == 2 ) { /* password change required */
+		if (password_change_prompt == NULL)
+			password_change_prompt = (char *)default_prompt;
+		debug("sending SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ");
+		packet_start(SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ);
+		packet_put_cstring(password_change_prompt);
+		packet_put_cstring("");                 /* language */
+		packet_send();
+		packet_write_wait();
 	} else {
 		if (authctxt->failures++ > AUTH_FAIL_MAX) {
 			packet_disconnect(AUTH_FAIL_MSG, authctxt->user);

diff -ru openssh-3.5p1.orig/monitor.c openssh-3.5p1-aixpassexpire/monitor.c
--- openssh-3.5p1.orig/monitor.c	Fri Sep 27 13:26:02 2002
+++ openssh-3.5p1-aixpassexpire/monitor.c	Sat Oct 26 14:55:29 2002
@@ -600,13 +600,14 @@
 {
 	static int call_count;
 	char *passwd;
-	int authenticated;
+	int authenticated = 0;
 	u_int plen;
 
 	passwd = buffer_get_string(m, &plen);
 	/* Only authenticate if the context is valid */
-	authenticated = options.password_authentication &&
-	    authctxt->valid && auth_password(authctxt, passwd);
+	if ( options.password_authentication && authctxt->valid ) 
+		authenticated = auth_password(authctxt, passwd);
+	
 	memset(passwd, 0, strlen(passwd));
 	xfree(passwd);
 
diff -ru openssh-3.5p1.orig/openbsd-compat/port-aix.c openssh-3.5p1-aixpassexpire/openbsd-compat/port-aix.c
--- openssh-3.5p1.orig/openbsd-compat/port-aix.c	Sun Jul  7 12:17:36 2002
+++ openssh-3.5p1-aixpassexpire/openbsd-compat/port-aix.c	Sat Oct 26 13:20:21 2002
@@ -24,12 +24,19 @@
  *
  */
 #include "includes.h"
+#include "misc.h"
+#include "log.h"
 
 #ifdef _AIX
 
 #include <uinfo.h>
 #include <../xmalloc.h>
 
+#ifdef WITH_AIXAUTHENTICATE
+#include <userpw.h>
+#include <usersec.h>
+#endif
+
 /*
  * AIX has a "usrinfo" area where logname and other stuff is stored - 
  * a few applications actually use this and die if it's not set
@@ -52,5 +59,67 @@
 	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';
+}
+
+/*
+ * aix_change_password: AIX password change routine
+ */
+int
+aix_change_password(struct passwd *pw, char *oldpassword, char *newpassword)
+{
+	struct userpw *upw;
+
+	debug("userauth_change_password: changing password for %s", pw->pw_name);
+
+	if (setpwdb(S_READ|S_WRITE) == -1) {
+		debug("Couldn't open authentication database: %s", strerror(errno));
+		return 0;
+	}
+
+	if ((upw = getuserpw(pw->pw_name)) == NULL) {
+		debug("Couldn't get user details for %s: %s",
+		    pw->pw_name, strerror(errno));
+		enduserdb();
+		return 0;
+	}
+
+	upw->upw_passwd = crypt(newpassword, upw->upw_passwd);
+	pw->pw_passwd = upw->upw_passwd;
+	upw->upw_flags &= ~PW_ADMCHG;	/* clear password change flag */
+	if (putuserpw(upw) == -1) {
+		debug("Couldn't update user details for %s: %s",
+		    pw->pw_name, strerror(errno));
+		enduserdb();
+		return 0;
+	}
+	if(enduserdb() == -1) {
+		debug("Error closing authentication database: %s",
+		    strerror(errno));
+		return 0;
+	}
+
+	return 1;
+}
+
+#endif /* WITH_AIXAUTHENTICATE */
+
+#endif /* _AIX */

diff -ru openssh-3.5p1.orig/openbsd-compat/port-aix.h openssh-3.5p1-aixpassexpire/openbsd-compat/port-aix.h
--- openssh-3.5p1.orig/openbsd-compat/port-aix.h	Sun Jul  7 12:17:36 2002
+++ openssh-3.5p1-aixpassexpire/openbsd-compat/port-aix.h	Sat Oct 26 16:33:48 2002
@@ -26,4 +26,8 @@
 
 #ifdef _AIX
 void aix_usrinfo(struct passwd *pw);
+#ifdef WITH_AIXAUTHENTICATE
+void aix_remove_embedded_newlines(char *);
+int aix_change_password(struct passwd *, char *, char *);
+#endif
 #endif /* _AIX */

diff -ru openssh-3.5p1.orig/session.c openssh-3.5p1-aixpassexpire/session.c
--- openssh-3.5p1.orig/session.c	Thu Sep 26 10:38:50 2002
+++ openssh-3.5p1-aixpassexpire/session.c	Sat Oct 26 11:22:40 2002
@@ -103,8 +103,12 @@
 #define MAX_SESSIONS 10
 Session	sessions[MAX_SESSIONS];
 
+void do_tty_change_password(struct passwd *);
+extern int password_change_required;
+
 #ifdef WITH_AIXAUTHENTICATE
-char *aixloginmsg;
+char *aixloginmsg;	/* message returned by loginsuccess() */
+char *aixexpiremsg;	/* message returned by passwdexpire() */
 #endif /* WITH_AIXAUTHENTICATE */
 
 #ifdef HAVE_LOGIN_CAP
@@ -461,6 +465,12 @@
 		    "TTY available");
 #endif /* USE_PAM */
 
+#ifdef WITH_AIXAUTHENTICATE
+	if (!compat20 && password_change_required)
+		packet_disconnect("Password change required but no "
+		    "TTY available");
+#endif /* WITH_AIXAUTHENTICATE */
+
 	/* Fork the child. */
 	if ((pid = fork()) == 0) {
 		fatal_remove_all_cleanups();
@@ -757,6 +767,13 @@
 	}
 #endif
 
+#ifdef WITH_AIXAUTHENTICATE
+	if (!compat20 && password_change_required) {
+		printf("%s\n", aixexpiremsg);
+		do_tty_change_password(pw);
+	}
+#endif
+
 	if (check_quietlogin(s, command))
 		return;
 
@@ -764,9 +781,17 @@
 	if (!is_pam_password_change_required())
 		print_pam_messages();
 #endif /* USE_PAM */
+
 #ifdef WITH_AIXAUTHENTICATE
-	if (aixloginmsg && *aixloginmsg)
+	if (aixexpiremsg && *aixexpiremsg) {
+		if (!compat20 && !password_change_required)
+			printf("%s\n", aixexpiremsg);
+		xfree(aixexpiremsg);
+	}
+	if (aixloginmsg && *aixloginmsg) {
 		printf("%s\n", aixloginmsg);
+		xfree(aixloginmsg);
+	}
 #endif /* WITH_AIXAUTHENTICATE */
 
 #ifndef NO_SSH_LASTLOG


More information about the openssh-unix-dev mailing list