diff -ru openssh-3.5p1.orig/auth-pam.c openssh-3.5p1-passexpire/auth-pam.c --- openssh-3.5p1.orig/auth-pam.c Mon Jul 29 06:24:08 2002 +++ openssh-3.5p1-passexpire/auth-pam.c Sun Oct 27 14:34:59 2002 @@ -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; diff -ru openssh-3.5p1.orig/auth-passwd.c openssh-3.5p1-passexpire/auth-passwd.c --- openssh-3.5p1.orig/auth-passwd.c Thu Sep 26 09:14:16 2002 +++ openssh-3.5p1-passexpire/auth-passwd.c Sun Oct 27 21:47:34 2002 @@ -42,6 +42,8 @@ #include "log.h" #include "servconf.h" #include "auth.h" +#include "misc.h" +#include "xmalloc.h" #if !defined(USE_PAM) && !defined(HAVE_OSF_SIA) /* Don't need any of these headers for the PAM or SIA cases */ @@ -81,13 +83,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 +153,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 +247,50 @@ /* Authentication is accepted if the encrypted passwords are identical. */ return (strcmp(encrypted_password, pw_password) == 0); #endif /* !USE_PAM && !HAVE_OSF_SIA */ +} + +/* password change for protocol 2 */ +int +auth_change_password(Authctxt *authctxt, const char *oldpasswd, const char *newpasswd) +{ +#ifdef WITH_AIXAUTHENTICATE + return aix_change_password(authctxt->pw, oldpasswd, newpasswd); +#endif + return 0; +} + + +/* + * 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-passexpire/auth.c --- openssh-3.5p1.orig/auth.c Sun Sep 22 01:26:53 2002 +++ openssh-3.5p1-passexpire/auth.c Sun Oct 27 21:30:46 2002 @@ -59,6 +59,10 @@ Buffer auth_debug; int auth_debug_init; +/* Password change flag */ +int password_change_required = 0; +char *password_expire_message = NULL; + /* * 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 +79,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; @@ -106,14 +107,18 @@ if (spw->sp_lstchg == 0) { log("User %.100s password has expired (root forced)", pw->pw_name); - return 0; + password_change_required = 1; + password_expire_message = + xstrdup("Your password has expired (root forced)"); } if (spw->sp_max != -1 && today > spw->sp_lstchg + spw->sp_max) { log("User %.100s password has expired (password aged)", pw->pw_name); - return 0; + password_change_required = 1; + password_expire_message = + xstrdup("Your password has expired"); } } #else @@ -202,19 +207,48 @@ } #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, &password_expire_message); + debug("passwdexpired() returned %d", passexpcode); + + 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) */ + /* make local copy of message and remove newlines for logging */ + if (password_expire_message && *password_expire_message) { + expiremsg = xstrdup(password_expire_message); + 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/auth.h openssh-3.5p1-passexpire/auth.h --- openssh-3.5p1.orig/auth.h Fri Sep 27 13:26:01 2002 +++ openssh-3.5p1-passexpire/auth.h Sun Oct 27 16:01:44 2002 @@ -101,6 +101,7 @@ int auth_rhosts_rsa(struct passwd *, char *, Key *); int auth_password(Authctxt *, const char *); +int auth_change_password(Authctxt *, const char *, const char *); int auth_rsa(struct passwd *, BIGNUM *); int auth_rsa_challenge_dialog(Key *); BIGNUM *auth_rsa_generate_challenge(Key *); diff -ru openssh-3.5p1.orig/auth2-passwd.c openssh-3.5p1-passexpire/auth2-passwd.c --- openssh-3.5p1.orig/auth2-passwd.c Fri Jun 7 06:27:56 2002 +++ openssh-3.5p1-passexpire/auth2-passwd.c Sun Oct 27 16:06:02 2002 @@ -31,28 +31,48 @@ #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 (PRIVSEP(auth_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; diff -ru openssh-3.5p1.orig/auth2.c openssh-3.5p1-passexpire/auth2.c --- openssh-3.5p1.orig/auth2.c Thu Sep 26 10:38:49 2002 +++ openssh-3.5p1-passexpire/auth2.c Sun Oct 27 21:29:30 2002 @@ -40,6 +40,7 @@ extern ServerOptions options; extern u_char *session_id2; extern int session_id2_len; +extern char *password_expire_message; 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_expire_message == NULL) + password_expire_message = (char *)default_prompt; + debug("sending SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ"); + packet_start(SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ); + packet_put_cstring(password_expire_message); + 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-passexpire/monitor.c --- openssh-3.5p1.orig/monitor.c Fri Sep 27 13:26:02 2002 +++ openssh-3.5p1-passexpire/monitor.c Sun Oct 27 21:49:43 2002 @@ -101,6 +101,7 @@ int mm_answer_auth2_read_banner(int, Buffer *); int mm_answer_authserv(int, Buffer *); int mm_answer_authpassword(int, Buffer *); +int mm_answer_auth_change_password(int, Buffer *); int mm_answer_bsdauthquery(int, Buffer *); int mm_answer_bsdauthrespond(int, Buffer *); int mm_answer_skeyquery(int, Buffer *); @@ -161,6 +162,7 @@ {MONITOR_REQ_AUTHSERV, MON_ONCE, mm_answer_authserv}, {MONITOR_REQ_AUTH2_READ_BANNER, MON_ONCE, mm_answer_auth2_read_banner}, {MONITOR_REQ_AUTHPASSWORD, MON_AUTH, mm_answer_authpassword}, + {MONITOR_REQ_CHPASS, MON_AUTH, mm_answer_auth_change_password}, #ifdef USE_PAM {MONITOR_REQ_PAM_START, MON_ONCE, mm_answer_pam_start}, #endif @@ -267,6 +269,7 @@ /* Permit requests for moduli and signatures */ monitor_permit(mon_dispatch, MONITOR_REQ_MODULI, 1); monitor_permit(mon_dispatch, MONITOR_REQ_SIGN, 1); + monitor_permit(mon_dispatch, MONITOR_REQ_CHPASS, 1); } else { mon_dispatch = mon_dispatch_proto15; @@ -275,8 +278,11 @@ authctxt = authctxt_new(); - /* The first few requests do not require asynchronous access */ - while (!authenticated) { + /* The first few requests do not require asynchronous access + * exit loop if authenticated and password change no required (proto 2) + * or if password correct (proto 1) + */ + while ((compat20 && authenticated != 1) || (!compat20 && !authenticated)) { authenticated = monitor_read(pmonitor, mon_dispatch, &ent); if (authenticated) { if (!(ent->flags & MON_AUTHDECIDE)) @@ -600,13 +606,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); @@ -624,6 +631,32 @@ /* Causes monitor loop to terminate if authenticated */ return (authenticated); +} + +int +mm_answer_auth_change_password(int socket, Buffer *m) +{ + char *oldpass, *newpass; + int changed; + + oldpass = buffer_get_string(m, NULL); + newpass = buffer_get_string(m, NULL); + + /* Only attempt if the context is valid */ + if ( options.password_authentication && authctxt->valid ) + changed = auth_change_password(authctxt, oldpass, newpass); + + buffer_clear(m); + buffer_put_int(m, changed); + + mm_request_send(socket, MONITOR_ANS_CHPASS, m); + + memset(oldpass, 0, strlen(oldpass)); + xfree(oldpass); + memset(newpass, 0, strlen(newpass)); + xfree(newpass); + + return changed; } #ifdef BSD_AUTH diff -ru openssh-3.5p1.orig/monitor.h openssh-3.5p1-passexpire/monitor.h --- openssh-3.5p1.orig/monitor.h Fri Sep 27 13:26:02 2002 +++ openssh-3.5p1-passexpire/monitor.h Sun Oct 27 15:36:25 2002 @@ -35,6 +35,7 @@ MONITOR_REQ_PWNAM, MONITOR_ANS_PWNAM, MONITOR_REQ_AUTH2_READ_BANNER, MONITOR_ANS_AUTH2_READ_BANNER, MONITOR_REQ_AUTHPASSWORD, MONITOR_ANS_AUTHPASSWORD, + MONITOR_REQ_CHPASS, MONITOR_ANS_CHPASS, MONITOR_REQ_BSDAUTHQUERY, MONITOR_ANS_BSDAUTHQUERY, MONITOR_REQ_BSDAUTHRESPOND, MONITOR_ANS_BSDAUTHRESPOND, MONITOR_REQ_SKEYQUERY, MONITOR_ANS_SKEYQUERY, diff -ru openssh-3.5p1.orig/monitor_wrap.c openssh-3.5p1-passexpire/monitor_wrap.c --- openssh-3.5p1.orig/monitor_wrap.c Fri Sep 27 13:26:03 2002 +++ openssh-3.5p1-passexpire/monitor_wrap.c Sun Oct 27 17:05:03 2002 @@ -256,7 +256,7 @@ buffer_put_cstring(&m, password); mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_AUTHPASSWORD, &m); - debug3("%s: waiting for MONITOR_ANS_AUTHPASSWORD", __func__); + debug3("%s: waiting for MONITOR_ANS_AUTHPASSWORD (type %d)", __func__, MONITOR_ANS_AUTHPASSWORD); mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_AUTHPASSWORD, &m); authenticated = buffer_get_int(&m); @@ -266,6 +266,32 @@ debug3("%s: user %sauthenticated", __func__, authenticated ? "" : "not "); return (authenticated); +} + +int +mm_auth_change_password(Authctxt *authctxt, char *oldpass, char *newpass) +{ + Buffer m; + int changed = 0; + + debug3("%s entering", __func__); + + buffer_init(&m); + buffer_put_cstring(&m, oldpass); + buffer_put_cstring(&m, newpass); + + mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_CHPASS, &m); + + debug3("%s: waiting for MONITOR_ANS_CHPASS (type %d)", __func__, MONITOR_ANS_CHPASS); + mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_CHPASS, &m); + + changed = buffer_get_int(&m); + + debug3("%s: password %schanged", + __func__, changed ? "" : "not "); + + buffer_free(&m); + return changed; } int diff -ru openssh-3.5p1.orig/monitor_wrap.h openssh-3.5p1-passexpire/monitor_wrap.h --- openssh-3.5p1.orig/monitor_wrap.h Fri Sep 27 13:26:04 2002 +++ openssh-3.5p1-passexpire/monitor_wrap.h Sun Oct 27 16:07:18 2002 @@ -46,6 +46,7 @@ struct passwd *mm_getpwnamallow(const char *); char *mm_auth2_read_banner(void); int mm_auth_password(struct Authctxt *, char *); +int mm_auth_change_password(struct Authctxt *, char *, char *); int mm_key_allowed(enum mm_keytype, char *, char *, Key *); int mm_user_key_allowed(struct passwd *, Key *); int mm_hostbased_key_allowed(struct passwd *, char *, char *, Key *); diff -ru openssh-3.5p1.orig/openbsd-compat/port-aix.c openssh-3.5p1-passexpire/openbsd-compat/port-aix.c --- openssh-3.5p1.orig/openbsd-compat/port-aix.c Sun Jul 7 12:17:36 2002 +++ openssh-3.5p1-passexpire/openbsd-compat/port-aix.c Sun Oct 27 19:20:33 2002 @@ -24,11 +24,15 @@ * */ #include "includes.h" +#include "misc.h" +#include "log.h" #ifdef _AIX #include #include <../xmalloc.h> +#include +#include /* * AIX has a "usrinfo" area where logname and other stuff is stored - @@ -52,5 +56,74 @@ 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, const char *oldpassword, const 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; + } + + /* + * Validate current password. Because we should never be called before the user + * has been successfully authenticated, failure here is fatal + */ + if (strcmp(upw->upw_passwd, crypt(oldpassword, upw->upw_passwd)) != 0) + fatal("aix_change_password: old password does not match database"); + + 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-passexpire/openbsd-compat/port-aix.h --- openssh-3.5p1.orig/openbsd-compat/port-aix.h Sun Jul 7 12:17:36 2002 +++ openssh-3.5p1-passexpire/openbsd-compat/port-aix.h Sun Oct 27 18:06:14 2002 @@ -25,5 +25,12 @@ */ #ifdef _AIX + void aix_usrinfo(struct passwd *pw); + +#ifdef WITH_AIXAUTHENTICATE +void aix_remove_embedded_newlines(char *); +int aix_change_password(struct passwd *, const char *, const char *); +#endif + #endif /* _AIX */ diff -ru openssh-3.5p1.orig/session.c openssh-3.5p1-passexpire/session.c --- openssh-3.5p1.orig/session.c Thu Sep 26 10:38:50 2002 +++ openssh-3.5p1-passexpire/session.c Sun Oct 27 21:31:31 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; +extern char *password_expire_message; + #ifdef WITH_AIXAUTHENTICATE -char *aixloginmsg; +char *aixloginmsg; /* message returned by loginsuccess() */ #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,11 @@ } #endif + if (!compat20 && password_change_required) { + printf("%s\n", password_expire_message); + do_tty_change_password(pw); + } + if (check_quietlogin(s, command)) return; @@ -764,9 +779,18 @@ if (!is_pam_password_change_required()) print_pam_messages(); #endif /* USE_PAM */ + + if (password_expire_message && *password_expire_message) { + if (!password_change_required) + printf("%s\n", password_expire_message); + xfree(password_expire_message); + } + #ifdef WITH_AIXAUTHENTICATE - if (aixloginmsg && *aixloginmsg) + if (aixloginmsg && *aixloginmsg) { printf("%s\n", aixloginmsg); + xfree(aixloginmsg); + } #endif /* WITH_AIXAUTHENTICATE */ #ifndef NO_SSH_LASTLOG