This is an attempt to simplify the AIX expiry-via-passwd stuff and make it more generic. (There's actually a net reduction in #ifdefs). Patch against CVS: 1) configure finds passwd. 2) sshd uses passwd during session if required. 3) sshd uses passwd for PAM change if privsep disabled. 4) sshd uses Buffers for expire and post-login messages (no longer AIX specific). 5) password_change_required generalized (no longer PAM specific). Tested on the following configurations: Redhat 8 (without PAM) AIX 4.3.3 Solaris 8 (without PAM) HP-UX 11.0 (trusted configuration, with PAM) I'm confused about this from auth-pam.c: /* 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(); Isn't this all in the post-auth privsep slave? Or am I overlooking something? -- 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 -------------- 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 */