Commit 32983db4 authored by Leigh Stoller's avatar Leigh Stoller

Move most of the password changing code to the backend, as I just did

for email changes. Currently, the hash is passed in on the command
line from the web interface, and there is no method for invoking it on
the command line and providing a text password, but that is an easy
change now that the bulk of the code is in the backend instead of the
web interface.

Note that this change took longer cause we allow inactive,frozen, and
wikionly users to change their password, but since they do not have
accounts (yet) the operation is invoked as user "nobody" and tbacct
about to me made aware of that possibility.

Also add equivalent auditing email message that goes to the user when
password is changed.

Also more cleanup and conversion to objects.
parent 6d50ce56
......@@ -123,6 +123,7 @@ use libaudit;
use libdb;
use libtestbed;
use User;
use Project;
#
# Function prototypes
......@@ -141,6 +142,16 @@ sub fatal($);
my $HOMEDIR = USERROOT();
#
# Rewrite audit version of ARGV to prevent password in mail logs.
#
if ($ARGV[0] eq "passwd" && scalar(@ARGV) == 3) {
my @NEWARGV = @ARGV;
$NEWARGV[scalar(@NEWARGV) - 1] = "**********";
AuditSetARGV(@NEWARGV);
}
#
# Parse command arguments. Once we return from getopts, all that should be
# left are the required arguments.
......@@ -188,10 +199,23 @@ if (! defined($target_user)) {
fatal("$user does not exist!");
}
# Map invoking user to object.
my $this_user = User->Lookup($UID);
if (! defined($this_user)) {
fatal("You ($UID) do not exist!");
#
# Map invoking user to object.
# If invoked as "nobody" its for a user with no actual account, and so
# just set the current user to that user. If we make any callouts it will
# fail (verbosely of course).
#
my $this_user;
if (getpwuid($UID) eq "nobody") {
$this_user = $target_user;
}
else {
$this_user = User->Lookup($UID);
if (! defined($this_user)) {
fatal("You ($UID) do not exist!");
}
}
#
......@@ -207,6 +231,7 @@ if (AuditStart(0)) {
#
# Get the user info (the user being operated on).
#
my $dbid = $target_user->dbid();
my $pswd = $target_user->pswd();
my $user_number = $target_user->unix_uid();
my $fullname = $target_user->name();
......@@ -223,19 +248,17 @@ my $wikionly = $target_user->wikionly();
# for the case that the account is being (re)created. We convert that to
# the unix info.
#
my $firstproject;
my $default_groupname;
my $default_groupgid;
$query_result =
DBQueryFatal("select m.pid from group_membership as m ".
"where m.uid='$user' and m.pid=m.gid and m.trust!='none' ".
"order by date_approved asc limit 1");
if ($target_user->FirstApprovedProject(\$firstproject) < 0) {
fatal("Could not determine first approved project for $target_user");
}
if (my ($defpid) = $query_result->fetchrow_array) {
if (! TBGroupUnixInfo($defpid, $defpid,
\$default_groupgid, \$default_groupname)) {
fatal("No info for default project $defpid!");
}
if (defined($firstproject)) {
$default_groupname = $firstproject->unix_name();
$default_groupgid = $firstproject->unix_gid();
}
else {
print "No group membership for $user; using the guest group!\n";
......@@ -359,6 +382,16 @@ sub AddUser()
fatal("Could not add user $user ($user_number) to $CONTROL.");
}
}
# shell escape.
$pswd =~ s/\$/\\\$/g;
$pswd =~ s/\*/\\\*/g;
print "Initializing user $user password on $CONTROL.\n";
if (system("$SSH -host $CONTROL $CHPASS -p '$pswd' $user")) {
fatal("Could not initialize password for user $user on $CONTROL!");
}
}
$UID = $SAVEUID;
......@@ -415,8 +448,7 @@ sub AddUser()
if ($CONTROL ne $BOSSNODE) {
GenerateSFSKey();
}
return UpdatePassword();
return 0;
}
#
......@@ -493,43 +525,110 @@ sub DelUser()
#
sub UpdatePassword()
{
# shell escape.
$pswd =~ s/\$/\\\$/g;
$pswd =~ s/\*/\\\*/g;
#
# Check status. Ignore if user is not active.
# New password (encrypted) comes in on the command line.
#
if ($status ne USERSTATUS_ACTIVE) {
print("$user is not active! Not updating the password!\n");
usage()
if (! @ARGV);
my $new_pswd = shift(@ARGV);
# Lets not do this if no changes.
if ($new_pswd eq $target_user->pswd()) {
print "Password has not changed ...\n";
return 0;
}
# Lets prevent any odd characters.
if ($new_pswd =~ /[\'\\\"\&]+/) {
fatal("Invalid characters in new password encryption string!");
}
#
# Insert into database. When changing password for someone else,
# always set the expiration to right now so that the target user
# is "forced" to change it.
#
my $expires;
if (! $target_user->SameUser($this_user)) {
$expires = "now()";
}
else {
$expires = "date_add(now(), interval 1 year)";
}
if ($target_user->SetPassword($new_pswd, $expires)) {
fatal("Could not update password encryption string for $target_user");
}
# Send auditing email before next step in case of failure.
SENDMAIL("$fullname <$user_email>",
"Password for '$user' has been changed",
"\n".
"Emulab password for '$user' has been changed by " .
$this_user->uid() ."\n".
"\n".
"Name: " . $target_user->name() . "\n".
"IDX: " . $target_user->uid_idx() . "\n".
"\n".
"If this is unexpected, please contact Testbed Operations\n".
"($TBOPS) immediately!\n".
"\n",
"$TBOPS",
"Bcc: $TBAUDIT");
# Go no further if a webonly user.
return 0
if ($webonly);
#
# Go no further if user is not active or frozen.
#
return 0
if (! ($status eq USERSTATUS_ACTIVE || $status eq USERSTATUS_FROZEN));
#
# Change on ops only if there is a real account there.
#
if (! $wikionly) {
#
# Grab from the DB to avoid taint checking sillyness.
#
my $safe_pswd = $target_user->pswd();
# shell escape.
$safe_pswd =~ s/\$/\\\$/g;
$safe_pswd =~ s/\*/\\\*/g;
$UID = 0;
if ($CONTROL ne $BOSSNODE) {
print "Updating user $user password on $CONTROL.\n";
if (system("$SSH -host $CONTROL $CHPASS -p '$pswd' $user")) {
if (system("$SSH -host $CONTROL $CHPASS -p '$safe_pswd' $user")) {
fatal("Could not change password for user $user on $CONTROL!");
}
}
$UID = $SAVEUID;
}
#
# Ick. If invoked as "nobody" then the user was either frozen or
# inactive. Lets skip the rest of this for now. Needs more thought
# and cleanup in the web interface to this, since we cannot call
# out to these scripts as "nobody" (yet).
#
return 0
if (getpwuid($UID) eq "nobody");
$EUID = $UID;
# And to the wiki if enabled.
# And the wiki if enabled.
system("$ADDWIKIUSER -u $user")
if ($WIKISUPPORT && $user ne $PROTOUSER);
if ($WIKISUPPORT && $user ne $PROTOUSER && !$webonly);
# And to the bugdb if enabled.
system("$ADDBUGDBUSER -m $user")
if ($BUGDBSUPPORT && $user ne $PROTOUSER);
if ($BUGDBSUPPORT && $user ne $PROTOUSER && ! ($wikionly || $webonly));
# And to the OPS db if enabled.
system("$OPSDBCONTROL adduser $user")
if ($OPSDBSUPPORT && $user ne $PROTOUSER);
$EUID = 0;
return 0;
......
#!/usr/bin/perl -wT
#
# EMULAB-COPYRIGHT
# Copyright (c) 2005, 2006 University of Utah and the Flux Group.
# Copyright (c) 2005, 2006, 2007 University of Utah and the Flux Group.
# All rights reserved.
#
package User;
......@@ -21,6 +21,7 @@ use English;
use Data::Dumper;
use File::Basename;
use overload ('""' => 'Stringify');
use Project;
# Configure variables
my $TB = "@prefix@";
......@@ -46,15 +47,29 @@ sub mysystem($)
#
sub Lookup($$)
{
my ($class, $uid_idx) = @_;
my ($class, $token) = @_;
my $query_result;
# Look in cache first
return $users{"$uid_idx"}
if (exists($users{"$uid_idx"}));
return $users{"$token"}
if (exists($users{"$token"}));
#
# For backwards compatability, look to see if the token is numeric
# or alphanumeric. If numeric, assumes its an idx, otherwise a name.
#
if ($token =~ /^\d*$/) {
$query_result =
DBQueryWarn("select * from users where uid_idx='$token'");
}
elsif ($token =~ /^\w*$/) {
$query_result =
DBQueryWarn("select * from users where uid='$token'");
}
else {
return undef;
}
my $query_result =
DBQueryWarn("select * from users where uid_idx=$uid_idx");
return undef
if (!$query_result || !$query_result->numrows);
......@@ -64,13 +79,14 @@ sub Lookup($$)
bless($self, $class);
# Add to cache.
$users{"$uid_idx"} = $self;
$users{$self->{'USER'}->{'uid_idx'}} = $self;
return $self;
}
# accessors
sub field($$) { return ((! ref($_[0])) ? -1 : $_[0]->{'USER'}->{$_[1]}); }
sub uid_idx($) { return field($_[0], "uid_idx"); }
sub dbid($) { return field($_[0], "uid_idx"); }
sub uid($) { return field($_[0], "uid"); }
sub created($) { return field($_[0], "usr_created"); }
sub expires($) { return field($_[0], "usr_expires"); }
......@@ -124,15 +140,7 @@ sub LookupByUid($$)
{
my ($class, $uid) = @_;
my $query_result =
DBQueryWarn("select uid_idx from users where uid='$uid'");
return undef
if (! $query_result || !$query_result->numrows);
my ($uid_idx) = $query_result->fetchrow_array();
return User->Lookup($uid_idx);
return User->Lookup($uid);
}
#
......@@ -223,5 +231,78 @@ sub Update($$)
return Refresh($self);
}
#
# Equality test for two users. Not strictly necessary in perl, but good form.
#
sub SameUser($$)
{
my ($self, $other) = @_;
# Must be a real reference.
return -1
if (! (ref($self) && ref($other)));
return $self->uid_idx() == $other->uid_idx();
}
#
# First approved project.
#
sub FirstApprovedProject($$)
{
my ($self, $pptr) = @_;
# Must be a real reference.
return -1
if (! ref($self));
my $uid_idx = $self->uid_idx();
my $query_result =
DBQueryWarn("select pid_idx from group_membership ".
"where uid_idx='$uid_idx' and pid_idx=gid_idx and ".
" trust!='none' ".
"order by date_approved asc limit 1");
if (! $query_result || !$query_result->numrows) {
$pptr = undef;
return 0;
}
my ($pid_idx) = $query_result->fetchrow_array();
my $project = Project->Lookup($pid_idx);
if (! defined($project)) {
warn("*** User::FirstApprovedProject: ".
"Could not load project $pid_idx!");
return -1;
}
$$pptr = $project;
return 0;
}
#
# Set password for user.
#
sub SetPassword($$$)
{
my ($self, $encoding, $expires) = @_;
# Must be a real reference.
return -1
if (! ref($self));
my $uid_idx = $self->uid_idx();
# Clear the chpasswd stuff anytime passwd is set.
return -1
if (! DBQueryWarn("update users set ".
" usr_pswd='$encoding', pswd_expires=$expires, ".
" chpasswd_key=NULL,chpasswd_expires=0 ".
"where uid_idx='$uid_idx'"));
return Refresh($self);
}
# _Always_ make sure that this 1 is at the end of the file...
1;
<?php
#
# EMULAB-COPYRIGHT
# Copyright (c) 2000-2003, 2005, 2006 University of Utah and the Flux Group.
# Copyright (c) 2000-2003, 2005, 2006, 2007 University of Utah and the Flux Group.
# All rights reserved.
#
include("defs.php3");
......@@ -191,33 +191,25 @@ setcookie($TBAUTHCOOKIE, "", time() - 1000000, "/", $TBAUTHDOMAIN, 0);
PAGEHEADER("Reset Your Password", $view);
$encoding = crypt("$password1");
$expires = "date_add(now(), interval 1 year)";
$safe_encoding = escapeshellarg($encoding);
$target_user->SetPassword($encoding, $expires);
STARTBUSY("Resetting your password");
if (HASREALACCOUNT($target_uid)) {
STARTBUSY("Resetting your password");
SUEXEC($target_uid, "nobody", "webtbacct passwd $target_uid",
#
# Invoke backend to deal with this.
#
if (!HASREALACCOUNT($target_uid)) {
SUEXEC("nobody", "nobody",
"webtbacct passwd $target_uid $safe_encoding",
SUEXEC_ACTION_DIE);
CLEARBUSY();
}
TBMAIL("$usr_name <$usr_email>",
"Password Reset for '$target_uid'",
"\n".
"The password for '$target_uid' has been reset via the web interface.\n".
"If this message is unexpected, please contact Testbed Operations\n".
"($TBMAILADDR_OPS) immediately!\n".
"\n".
"The change originated from IP: " . $_SERVER['REMOTE_ADDR'] . "\n".
"\n".
"Thanks,\n".
"Testbed Operations\n",
"From: $TBMAIL_OPS\n".
"Bcc: $TBMAIL_AUDIT\n".
"Errors-To: $TBMAIL_WWW");
}
else {
SUEXEC($target_uid, "nobody",
"webtbacct passwd $target_uid $safe_encoding",
SUEXEC_ACTION_DIE);
}
CLEARBUSY();
echo "<br>
Your password has been changed.\n";
......
......@@ -245,6 +245,7 @@ function SPITFORM($formfields, $errors)
<td class=left>
<input type=password
name=\"formfields[password1]\"
value=\"" . $formfields[password1] . "\"
size=8></td>
</tr>\n";
......@@ -253,6 +254,7 @@ function SPITFORM($formfields, $errors)
<td class=left>
<input type=password
name=\"formfields[password2]\"
value=\"" . $formfields[password2] . "\"
size=8></td>
</tr>\n";
......@@ -656,28 +658,20 @@ if ((isset($formfields["password1"]) && $formfields["password1"] != "") &&
# Do it again. This ensures we use the current algorithm, not whatever
# it was encoded with last time.
#
$new_encoding = crypt($formfields["password1"]);
$new_encoding = crypt($formfields["password1"]);
$safe_encoding = escapeshellarg($new_encoding);
#
# Insert into database. When changing password for someone else,
# always set the expiration to right now so that the target user
# is "forced" to change it.
# Invoke backend to deal with this.
#
if (! $target_user->SameUser($this_user))
$expires = "now()";
else
$expires = "date_add(now(), interval 1 year)";
$target_user->SetPassword($new_encoding, $expires);
if ($wikionly) {
if ($CHECKLOGIN_STATUS & CHECKLOGIN_ACTIVE) {
SUEXEC("nobody", "nobody", "webtbacct passwd $target_uid",
SUEXEC_ACTION_DIE);
}
if (!HASREALACCOUNT($uid)) {
SUEXEC("nobody", "nobody",
"webtbacct passwd $target_uid $safe_encoding",
SUEXEC_ACTION_DIE);
}
elseif (HASREALACCOUNT($uid) && HASREALACCOUNT($target_uid)) {
SUEXEC($uid, "nobody", "webtbacct passwd $target_uid",
else {
SUEXEC($uid, "nobody",
"webtbacct passwd $target_uid $safe_encoding",
SUEXEC_ACTION_DIE);
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment