The primary purpose of the attached patches is for portable OpenSSH to
support changing expired passwords as specified in shadow password files.
To support that, I did a couple enhancements to the base OpenBSD OpenSSH
code. They are:
1. Consolidated the handling of "forced_command" into a do_exec()
function in session.c. These were being handled inconsistently and
allocated memory was not always being properly freed.
2. Added log messages to say why a user is disallowed by allowed_user()
in session.c.
Those two changes are in attachment #1, against the current OpenBSD OpenSSH
CVS. I hope Markus will accept them.
Attachment #2 contains additional changes against the portable OpenSSH CVS
to invoke the 'passwd' command whenever a shadow password entry expires
and
a pseudo-tty is available. This approach is similar to what was used by
SSH 1.2.27, but more robust because 1.2.27 would attempt to change a
password when there was no pty and fail (not to mention that it didn't work
on Linux at all because the Linux "passwd" command only permits root
to
pass a username), and 1.2.27 gave no clue to the user as to why it was
asking for the password. I decided that the 1.2.27 sshd_config option
ForcedPasswdChange was not worth putting in because I can't see why anybody
would want to turn it off and always deny expired passwords (as currently
happens in OpenSSH); if somebody wants to completely expire an account on a
specific date, there's a separate field for that in the shadow password
file.
I have tested these changes on Solaris 2.7, Linux 2.4, Irix 6.2, and
Unixware 1.1.2. Apply attachment #1 first, then attachment #2 to the
current portable OpenSSH CVS.
Attachment #3 is a single patch file for all the changes against
OpenSSH_2.9p2 in case anybody else on the list wants to try it.
- Dave Dykstra
-------------- next part --------------
*** auth.c.O Mon Jun 18 09:31:58 2001
--- auth.c Mon Jun 18 09:35:08 2001
***************
*** 68,83 ****
shell = (pw->pw_shell[0] == '\0') ? _PATH_BSHELL :
pw->pw_shell;
/* deny if shell does not exists or is not executable */
! if (stat(shell, &st) != 0)
return 0;
! if (!((st.st_mode & S_IFREG) && (st.st_mode &
(S_IXOTH|S_IXUSR|S_IXGRP))))
return 0;
/* Return false if user is listed in DenyUsers */
if (options.num_deny_users > 0) {
for (i = 0; i < options.num_deny_users; i++)
! if (match_pattern(pw->pw_name, options.deny_users[i]))
return 0;
}
/* Return false if AllowUsers isn't empty and user isn't listed there
*/
if (options.num_allow_users > 0) {
--- 68,92 ----
shell = (pw->pw_shell[0] == '\0') ? _PATH_BSHELL :
pw->pw_shell;
/* deny if shell does not exists or is not executable */
! if (stat(shell, &st) != 0) {
! log("User %.100s not allowed because shell %.100s does not exist",
! pw->pw_name, shell);
return 0;
! }
! if (!((st.st_mode & S_IFREG) && (st.st_mode &
(S_IXOTH|S_IXUSR|S_IXGRP)))) {
! log("User %.100s not allowed because shell %.100s is not
executable",
! pw->pw_name, shell);
return 0;
+ }
/* Return false if user is listed in DenyUsers */
if (options.num_deny_users > 0) {
for (i = 0; i < options.num_deny_users; i++)
! if (match_pattern(pw->pw_name, options.deny_users[i])) {
! log("User %.100s not allowed because listed in DenyUsers",
! pw->pw_name);
return 0;
+ }
}
/* Return false if AllowUsers isn't empty and user isn't listed there
*/
if (options.num_allow_users > 0) {
***************
*** 85,97 ****
if (match_pattern(pw->pw_name, options.allow_users[i]))
break;
/* i < options.num_allow_users iff we break for loop */
! if (i >= options.num_allow_users)
return 0;
}
if (options.num_deny_groups > 0 || options.num_allow_groups > 0) {
/* Get the user's group access list (primary and supplementary) */
! if (ga_init(pw->pw_name, pw->pw_gid) == 0)
return 0;
/* Return false if one of user's groups is listed in DenyGroups */
if (options.num_deny_groups > 0)
--- 94,112 ----
if (match_pattern(pw->pw_name, options.allow_users[i]))
break;
/* i < options.num_allow_users iff we break for loop */
! if (i >= options.num_allow_users) {
! log("User %.100s not allowed because not listed in AllowUsers",
! pw->pw_name);
return 0;
+ }
}
if (options.num_deny_groups > 0 || options.num_allow_groups > 0) {
/* Get the user's group access list (primary and supplementary) */
! if (ga_init(pw->pw_name, pw->pw_gid) == 0) {
! log("User %.100s not allowed because not in any group",
! pw->pw_name);
return 0;
+ }
/* Return false if one of user's groups is listed in DenyGroups */
if (options.num_deny_groups > 0)
***************
*** 98,103 ****
--- 113,120 ----
if (ga_match(options.deny_groups,
options.num_deny_groups)) {
ga_free();
+ log("User %.100s not allowed because a group is listed in
DenyGroups",
+ pw->pw_name);
return 0;
}
/*
***************
*** 108,113 ****
--- 125,132 ----
if (!ga_match(options.allow_groups,
options.num_allow_groups)) {
ga_free();
+ log("User %.100s not allowed because none of user's group are
listed in AllowGroups",
+ pw->pw_name);
return 0;
}
ga_free();
*** session.c.O Mon Jun 18 13:59:57 2001
--- session.c Mon Jun 18 14:15:29 2001
***************
*** 93,98 ****
--- 93,99 ----
void session_close(Session *s);
void do_exec_pty(Session *s, const char *command);
void do_exec_no_pty(Session *s, const char *command);
+ void do_exec(Session *s, const char *command);
void do_login(Session *s, const char *command);
void do_child(Session *s, const char *command);
void do_motd(void);
***************
*** 270,286 ****
command = NULL;
packet_integrity_check(plen, 0, type);
}
! if (forced_command != NULL) {
! original_command = command;
! command = forced_command;
! debug("Forced command '%.500s'", forced_command);
! }
! if (s->ttyfd != -1)
! do_exec_pty(s, command);
! else
! do_exec_no_pty(s, command);
! if (command != NULL)
! xfree(command);
session_close(s);
return;
--- 271,277 ----
command = NULL;
packet_integrity_check(plen, 0, type);
}
! do_exec(s, command);
session_close(s);
return;
***************
*** 504,509 ****
--- 495,529 ----
}
}
+ /*
+ * This is called to fork and execute a command. If another command is
+ * to be forced, execute that instead.
+ */
+ void
+ do_exec(Session *s, const char *command)
+ {
+ if (forced_command) {
+ original_command = command;
+ command = forced_command;
+ forced_command = NULL;
+ debug("Forced command '%.900s'", command);
+ }
+
+ if (s->ttyfd != -1)
+ do_exec_pty(s, command);
+ else
+ do_exec_no_pty(s, command);
+
+ if (command != NULL)
+ xfree(command);
+
+ if (original_command != NULL) {
+ xfree(original_command);
+ original_command = NULL;
+ }
+ }
+
+
/* administrative, login(1)-like work */
void
do_login(Session *s, const char *command)
***************
*** 1288,1300 ****
int
session_shell_req(Session *s)
{
- /* if forced_command == NULL, the shell is execed */
- char *shell = forced_command;
packet_done();
! if (s->ttyfd == -1)
! do_exec_no_pty(s, shell);
! else
! do_exec_pty(s, shell);
return 1;
}
--- 1308,1315 ----
int
session_shell_req(Session *s)
{
packet_done();
! do_exec(s, NULL);
return 1;
}
***************
*** 1304,1320 ****
u_int len;
char *command = packet_get_string(&len);
packet_done();
! if (forced_command) {
! original_command = command;
! command = forced_command;
! debug("Forced command '%.500s'", forced_command);
! }
! if (s->ttyfd == -1)
! do_exec_no_pty(s, command);
! else
! do_exec_pty(s, command);
! if (forced_command == NULL)
! xfree(command);
return 1;
}
--- 1319,1325 ----
u_int len;
char *command = packet_get_string(&len);
packet_done();
! do_exec(s, command);
return 1;
}
-------------- next part --------------
*** auth.c.O2 Mon Jun 18 14:21:06 2001
--- auth.c Mon Jun 18 14:24:33 2001
***************
*** 47,52 ****
--- 47,55 ----
#include "buffer.h"
#include "bufaux.h"
+ /* set when password has expired */
+ int forced_passwd_change = 0;
+
/* import */
extern ServerOptions options;
***************
*** 81,93 ****
int days = time(NULL) / 86400;
/* Check account expiry */
! if ((spw->sp_expire >= 0) && (days > spw->sp_expire))
return 0;
/* Check password expiry */
if ((spw->sp_lstchg >= 0) && (spw->sp_max >= 0)
&&
! (days > (spw->sp_lstchg + spw->sp_max)))
! return 0;
}
#else
/* Shouldn't be called if pw is NULL, but better safe than sorry... */
--- 84,106 ----
int days = time(NULL) / 86400;
/* Check account expiry */
! if ((spw->sp_expire >= 0) && (days > spw->sp_expire)) {
! log("User %.100s not allowed because account expired",
! pw->pw_name);
return 0;
+ }
/* Check password expiry */
if ((spw->sp_lstchg >= 0) && (spw->sp_max >= 0)
&&
! (days > (spw->sp_lstchg + spw->sp_max))) {
! if ((pw->pw_uid == 0)) {
! log("User %.100s not allowed because password expired",
! pw->pw_name);
! return 0;
! }
!
! forced_passwd_change = 1;
! }
}
#else
/* Shouldn't be called if pw is NULL, but better safe than sorry... */
***************
*** 177,183 ****
}
/* Remove trailing newline */
*--p = '\0';
! log("Login restricted for %s: %.100s", pw->pw_name, loginmsg);
}
return 0;
}
--- 190,196 ----
}
/* Remove trailing newline */
*--p = '\0';
! log("Login restricted for %.100s: %.100s", pw->pw_name,
loginmsg);
}
return 0;
}
*** auth.h.O Tue Jun 5 15:25:06 2001
--- auth.h Mon Jun 18 14:28:04 2001
***************
*** 35,40 ****
--- 35,43 ----
#include <bsd_auth.h>
#endif
+ /* set when password has expired */
+ extern int forced_passwd_change;
+
typedef struct Authctxt Authctxt;
typedef struct KbdintDevice KbdintDevice;
*** session.c.O2 Mon Jun 18 14:21:15 2001
--- session.c Mon Jun 18 14:39:36 2001
***************
*** 603,608 ****
--- 603,636 ----
debug("Forced command '%.900s'", command);
}
+ if (forced_passwd_change) {
+ char *user = s->pw->pw_name;
+ char *msg;
+
+ if (command != NULL)
+ xfree(command);
+
+ if (s->ttyfd != -1) {
+ msg = "Password for %.100s has expired, running 'passwd' to
reset it";
+ /*
+ * Can't pass "user" to 'passwd' because Linux
doesn't
+ * allow it.
+ * Also, the prompt is friendlier without "user".
+ */
+ command = xstrdup(PASSWD_PATH);
+ } else {
+ msg = "Password for %.100s has expired and cannot be changed without a
pty";
+ /*
+ * Without a pty, Solaris 'passwd' prints "Permission
+ * denied", but Linux attempts to change the password
+ * and fails miserably, so echo an error message instead
+ */
+ command = xstrdup("/bin/sh -c 'echo Permission denied >&2;
exit 1'");
+ }
+ log(msg, user);
+ packet_send_debug(msg, user);
+ }
+
if (s->ttyfd != -1)
do_exec_pty(s, command);
else
*** configure.in.O Sun Jun 10 12:24:52 2001
--- configure.in Mon Jun 18 14:27:23 2001
***************
*** 1304,1309 ****
--- 1304,1313 ----
AC_DEFINE_UNQUOTED(RSH_PATH, "$rsh_path")
fi
+ AC_PATH_PROG(PASSWD_PATH, passwd)
+ AC_DEFINE_UNQUOTED(PASSWD_PATH, "$PASSWD_PATH")
+
+
# Check for mail directory (last resort if we cannot get it from headers)
if test ! -z "$MAIL" ; then
maildir=`dirname $MAIL`
*** acconfig.h.O Tue May 8 15:33:06 2001
--- acconfig.h Mon Jun 18 14:30:16 2001
***************
*** 211,216 ****
--- 211,219 ----
/* Define if rsh is found in your path */
#undef RSH_PATH
+ /* Define if passwd is found in your path */
+ #undef PASSWD_PATH
+
/* Define if you want to allow MD5 passwords */
#undef HAVE_MD5_PASSWORDS
-------------- next part --------------
*** auth.c.O Mon Jun 18 14:50:25 2001
--- auth.c Mon Jun 18 14:50:29 2001
***************
*** 41,46 ****
--- 41,49 ----
#include "auth-options.h"
#include "canohost.h"
+ /* set when password has expired */
+ int forced_passwd_change = 0;
+
/* import */
extern ServerOptions options;
***************
*** 75,87 ****
int days = time(NULL) / 86400;
/* Check account expiry */
! if ((spw->sp_expire >= 0) && (days > spw->sp_expire))
return 0;
/* Check password expiry */
if ((spw->sp_lstchg >= 0) && (spw->sp_max >= 0)
&&
! (days > (spw->sp_lstchg + spw->sp_max)))
! return 0;
}
#else
/* Shouldn't be called if pw is NULL, but better safe than sorry... */
--- 78,100 ----
int days = time(NULL) / 86400;
/* Check account expiry */
! if ((spw->sp_expire >= 0) && (days > spw->sp_expire)) {
! log("User %.100s not allowed because account expired",
! pw->pw_name);
return 0;
+ }
/* Check password expiry */
if ((spw->sp_lstchg >= 0) && (spw->sp_max >= 0)
&&
! (days > (spw->sp_lstchg + spw->sp_max))) {
! if ((pw->pw_uid == 0)) {
! log("User %.100s not allowed because password expired",
! pw->pw_name);
! return 0;
! }
!
! forced_passwd_change = 1;
! }
}
#else
/* Shouldn't be called if pw is NULL, but better safe than sorry... */
***************
*** 96,111 ****
shell = (pw->pw_shell[0] == '\0') ? _PATH_BSHELL :
pw->pw_shell;
/* deny if shell does not exists or is not executable */
! if (stat(shell, &st) != 0)
return 0;
! if (!((st.st_mode & S_IFREG) && (st.st_mode &
(S_IXOTH|S_IXUSR|S_IXGRP))))
return 0;
/* Return false if user is listed in DenyUsers */
if (options.num_deny_users > 0) {
for (i = 0; i < options.num_deny_users; i++)
! if (match_pattern(pw->pw_name, options.deny_users[i]))
return 0;
}
/* Return false if AllowUsers isn't empty and user isn't listed there
*/
if (options.num_allow_users > 0) {
--- 109,133 ----
shell = (pw->pw_shell[0] == '\0') ? _PATH_BSHELL :
pw->pw_shell;
/* deny if shell does not exists or is not executable */
! if (stat(shell, &st) != 0) {
! log("User %.100s not allowed because shell %.100s does not exist",
! pw->pw_name, shell);
return 0;
! }
! if (!((st.st_mode & S_IFREG) && (st.st_mode &
(S_IXOTH|S_IXUSR|S_IXGRP)))) {
! log("User %.100s not allowed because shell %.100s is not
executable",
! pw->pw_name, shell);
return 0;
+ }
/* Return false if user is listed in DenyUsers */
if (options.num_deny_users > 0) {
for (i = 0; i < options.num_deny_users; i++)
! if (match_pattern(pw->pw_name, options.deny_users[i])) {
! log("User %.100s not allowed because listed in DenyUsers",
! pw->pw_name);
return 0;
+ }
}
/* Return false if AllowUsers isn't empty and user isn't listed there
*/
if (options.num_allow_users > 0) {
***************
*** 113,125 ****
if (match_pattern(pw->pw_name, options.allow_users[i]))
break;
/* i < options.num_allow_users iff we break for loop */
! if (i >= options.num_allow_users)
return 0;
}
if (options.num_deny_groups > 0 || options.num_allow_groups > 0) {
/* Get the user's group access list (primary and supplementary) */
! if (ga_init(pw->pw_name, pw->pw_gid) == 0)
return 0;
/* Return false if one of user's groups is listed in DenyGroups */
if (options.num_deny_groups > 0)
--- 135,153 ----
if (match_pattern(pw->pw_name, options.allow_users[i]))
break;
/* i < options.num_allow_users iff we break for loop */
! if (i >= options.num_allow_users) {
! log("User %.100s not allowed because not listed in AllowUsers",
! pw->pw_name);
return 0;
+ }
}
if (options.num_deny_groups > 0 || options.num_allow_groups > 0) {
/* Get the user's group access list (primary and supplementary) */
! if (ga_init(pw->pw_name, pw->pw_gid) == 0) {
! log("User %.100s not allowed because not in any group",
! pw->pw_name);
return 0;
+ }
/* Return false if one of user's groups is listed in DenyGroups */
if (options.num_deny_groups > 0)
***************
*** 126,131 ****
--- 154,161 ----
if (ga_match(options.deny_groups,
options.num_deny_groups)) {
ga_free();
+ log("User %.100s not allowed because a group is listed in
DenyGroups",
+ pw->pw_name);
return 0;
}
/*
***************
*** 136,141 ****
--- 166,173 ----
if (!ga_match(options.allow_groups,
options.num_allow_groups)) {
ga_free();
+ log("User %.100s not allowed because none of user's group are
listed in AllowGroups",
+ pw->pw_name);
return 0;
}
ga_free();
***************
*** 152,158 ****
}
/* Remove trailing newline */
*--p = '\0';
! log("Login restricted for %s: %.100s", pw->pw_name, loginmsg);
}
return 0;
}
--- 184,190 ----
}
/* Remove trailing newline */
*--p = '\0';
! log("Login restricted for %.100s: %.100s", pw->pw_name,
loginmsg);
}
return 0;
}
*** auth.h.O Mon Jun 18 14:50:25 2001
--- auth.h Mon Jun 18 14:50:29 2001
***************
*** 35,40 ****
--- 35,43 ----
#include <bsd_auth.h>
#endif
+ /* set when password has expired */
+ extern int forced_passwd_change;
+
typedef struct Authctxt Authctxt;
struct Authctxt {
int success;
*** session.c.O Mon Jun 18 14:50:25 2001
--- session.c Mon Jun 18 14:50:29 2001
***************
*** 126,131 ****
--- 126,132 ----
void session_proctitle(Session *s);
void do_exec_pty(Session *s, const char *command);
void do_exec_no_pty(Session *s, const char *command);
+ void do_exec(Session *s, const char *command);
void do_login(Session *s, const char *command);
#ifdef LOGIN_NEEDS_UTMPX
void do_pre_login(Session *s);
***************
*** 394,411 ****
command = NULL;
packet_integrity_check(plen, 0, type);
}
! if (forced_command != NULL) {
! original_command = command;
! command = forced_command;
! debug("Forced command '%.500s'", forced_command);
! }
! if (have_pty)
! do_exec_pty(s, command);
! else
! do_exec_no_pty(s, command);
!
! if (command != NULL)
! xfree(command);
return;
default:
--- 395,401 ----
command = NULL;
packet_integrity_check(plen, 0, type);
}
! do_exec(s, command);
return;
default:
***************
*** 680,685 ****
--- 670,732 ----
}
#endif
+ /*
+ * This is called to fork and execute a command. If another command is
+ * to be forced, execute that instead.
+ */
+ void
+ do_exec(Session *s, const char *command)
+ {
+ if (forced_command) {
+ original_command = command;
+ command = forced_command;
+ forced_command = NULL;
+ debug("Forced command '%.900s'", command);
+ }
+
+ if (forced_passwd_change) {
+ char *user = s->pw->pw_name;
+ char *msg;
+
+ if (command != NULL)
+ xfree(command);
+
+ if (s->ttyfd != -1) {
+ msg = "Password for %.100s has expired, running 'passwd' to
reset it";
+ /*
+ * Can't pass "user" to 'passwd' because Linux
doesn't
+ * allow it.
+ * Also, the prompt is friendlier without "user".
+ */
+ command = xstrdup(PASSWD_PATH);
+ } else {
+ msg = "Password for %.100s has expired and cannot be changed without a
pty";
+ /*
+ * Without a pty, Solaris 'passwd' prints "Permission
+ * denied", but Linux attempts to change the password
+ * and fails miserably, so echo an error message instead
+ */
+ command = xstrdup("/bin/sh -c 'echo Permission denied >&2;
exit 1'");
+ }
+ log(msg, user);
+ packet_send_debug(msg, user);
+ }
+
+ if (s->ttyfd != -1)
+ do_exec_pty(s, command);
+ else
+ do_exec_no_pty(s, command);
+
+ if (command != NULL)
+ xfree(command);
+
+ if (original_command != NULL) {
+ xfree(original_command);
+ original_command = NULL;
+ }
+ }
+
+
/* administrative, login(1)-like work */
void
do_login(Session *s, const char *command)
***************
*** 1737,1749 ****
int
session_shell_req(Session *s)
{
- /* if forced_command == NULL, the shell is execed */
- char *shell = forced_command;
packet_done();
! if (s->ttyfd == -1)
! do_exec_no_pty(s, shell);
! else
! do_exec_pty(s, shell);
return 1;
}
--- 1784,1791 ----
int
session_shell_req(Session *s)
{
packet_done();
! do_exec(s, NULL);
return 1;
}
***************
*** 1753,1769 ****
u_int len;
char *command = packet_get_string(&len);
packet_done();
! if (forced_command) {
! original_command = command;
! command = forced_command;
! debug("Forced command '%.500s'", forced_command);
! }
! if (s->ttyfd == -1)
! do_exec_no_pty(s, command);
! else
! do_exec_pty(s, command);
! if (forced_command == NULL)
! xfree(command);
return 1;
}
--- 1795,1801 ----
u_int len;
char *command = packet_get_string(&len);
packet_done();
! do_exec(s, command);
return 1;
}
*** configure.in.O Mon Jun 18 14:50:25 2001
--- configure.in Mon Jun 18 14:50:29 2001
***************
*** 1302,1307 ****
--- 1302,1311 ----
AC_DEFINE_UNQUOTED(RSH_PATH, "$rsh_path")
fi
+ AC_PATH_PROG(PASSWD_PATH, passwd)
+ AC_DEFINE_UNQUOTED(PASSWD_PATH, "$PASSWD_PATH")
+
+
# Check for mail directory (last resort if we cannot get it from headers)
if test ! -z "$MAIL" ; then
maildir=`dirname $MAIL`
*** acconfig.h.O Mon Jun 18 14:50:25 2001
--- acconfig.h Mon Jun 18 14:50:31 2001
***************
*** 211,216 ****
--- 211,219 ----
/* Define if rsh is found in your path */
#undef RSH_PATH
+ /* Define if passwd is found in your path */
+ #undef PASSWD_PATH
+
/* Define if you want to allow MD5 passwords */
#undef HAVE_MD5_PASSWORDS