diff --git a/account/tbacct.in b/account/tbacct.in
index c6a9101ce9f6ca8cf6099e4227e4269b9003355e..0617316df3a3d328944fa836183e89b89f46af6d 100644
--- a/account/tbacct.in
+++ b/account/tbacct.in
@@ -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;
diff --git a/db/User.pm.in b/db/User.pm.in
index 8cee2bbf4e1ba2c9e0016acab8496f089dc572e6..c87011c27b7deceb1eaec670e68df21f2f9e9186 100644
--- a/db/User.pm.in
+++ b/db/User.pm.in
@@ -1,7 +1,7 @@
#!/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;
diff --git a/www/chpasswd.php3 b/www/chpasswd.php3
index 4cc42a964f8307a940e018cadbc386eea0570261..3caaab37040adef5ec2f20b8dc6d32ae359a1dde 100644
--- a/www/chpasswd.php3
+++ b/www/chpasswd.php3
@@ -1,7 +1,7 @@
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 "
Your password has been changed.\n";
diff --git a/www/moduserinfo.php3 b/www/moduserinfo.php3
index 197437bfa8007b27e0bfe083083b2151df177d1c..0ec0fc798f48034da76e9f9e2e208fd130e92785 100644
--- a/www/moduserinfo.php3
+++ b/www/moduserinfo.php3
@@ -245,6 +245,7 @@ function SPITFORM($formfields, $errors)