From d0cfcfc20ac3ae005faa7c5582364ad0e27cf5ad Mon Sep 17 00:00:00 2001
From: Leigh B Stoller <stoller@flux.utah.edu>
Date: Fri, 22 Mar 2013 09:12:56 -0600
Subject: [PATCH] Changes to allow passing password hash from the portal master
 to the portal peers.

Also add locking between the portal daemon and manageremote.
---
 account/dumpuser.in            |  4 +--
 account/manageremote.in        | 51 ++++++++++++++++++++++++----------
 account/newuser.in             | 10 +++++--
 account/tbacct.in              |  6 ++++
 backend/moduserinfo.in         |  4 ++-
 protogeni/lib/GeniEmulab.pm.in | 50 +++++++++++++++++++++++++++++++++
 tbsetup/portal_daemon.in       | 12 ++++++--
 7 files changed, 115 insertions(+), 22 deletions(-)

diff --git a/account/dumpuser.in b/account/dumpuser.in
index d5ced425a5..1233571820 100644
--- a/account/dumpuser.in
+++ b/account/dumpuser.in
@@ -1,6 +1,6 @@
 #!/usr/bin/perl -w
 #
-# Copyright (c) 2010-2011 University of Utah and the Flux Group.
+# Copyright (c) 2010-2013 University of Utah and the Flux Group.
 # 
 # {{{EMULAB-LICENSE
 # 
@@ -121,7 +121,7 @@ sub DumpUser($)
 			    "optional"	=> 0 },
 	"email"         => {"tag"       => "email",
 			   "optional"	=> 0 },
-	"pswd"		=> {"tag"       => "password",
+	"pswd"		=> {"tag"       => "passhash",
 			    "optional"	=> 0 },
 	"uid"		=> {"tag"       => "uid",
 			    "optional"	=> 0 },
diff --git a/account/manageremote.in b/account/manageremote.in
index f0b07d8dbd..528d9b4350 100644
--- a/account/manageremote.in
+++ b/account/manageremote.in
@@ -49,10 +49,12 @@ sub usage()
     print "       manageremote addgroup <remote> <gid>\n";
     exit(1);
 }
-my $optlist    = "dnf";
+my $optlist    = "dnfp";
 my $debug      = 0;
 my $force      = 0;
 my $impotent   = 0;
+my $locked     = 0;
+my $fromdaemon = 0;
 
 #
 # Function prototypes
@@ -121,8 +123,11 @@ if (defined($options{"f"})) {
 if (defined($options{"n"})) {
     $impotent = 1;
 }
+if (defined($options{"p"})) {
+    $fromdaemon = 1;
+}
 usage()
-    if (@ARGV < 2 || @ARGV > 4);
+    if (@ARGV < 2 || @ARGV > 5);
 
 my $cmd      = shift(@ARGV);
 my $peername = shift(@ARGV);
@@ -170,26 +175,36 @@ my $credential = GeniCredential->GetSelfCredential($me);
 if (!defined($credential)) {
     fatal("Could not create self credential for $me");
 }
+my $authority;
 
 #
 # All operations other then AddPeer require that the peer be
 # in the DB.
 #
-if ($cmd eq "addpeer") {
-    AddPeer();
-    exit(0);
-}
-else {
+if ($cmd ne "addpeer") {
     my $query_result =
 	DBQueryFatal("select name,urn from emulab_peers ".
 		     "where name='$peername' or urn='$peername'");
     fatal("Unknown peer")
 	if (!$query_result->numrows);
     ($peername,$peerurn) = $query_result->fetchrow_array();
+
+    $authority = GeniAuthority->CreateFromRegistry("sa", $peerurn);
+    if (!defined($authority)) {
+	fatal("Could not locate authority for $peername");
+    }
 }
-my $authority = GeniAuthority->CreateFromRegistry("sa", $peerurn);
-if (!defined($authority)) {
-    fatal("Could not locate authority for $peername");
+
+#
+# All operations other then xlogin require locking to avoid a
+# race with the portal_daemon. 
+#
+if ($cmd ne "xlogin" && !$fromdaemon) {
+    while (TBScriptLock("portal_op", 0, 5) != TBSCRIPTLOCK_OKAY()) {
+	print "Could not get the lock; trying again ... ^C to stop trying.\n";
+	next;
+    }
+    $locked = 1;
 }
 
 #
@@ -204,6 +219,10 @@ SWITCH: for ($cmd) {
 	AddUser();
 	last SWITCH;
     };
+    /^addpeer$/ && do {
+	AddPeer();
+	last SWITCH;
+    };
     /^deluser$/ && do {
 	DeleteUser();
 	last SWITCH;
@@ -230,8 +249,12 @@ SWITCH: for ($cmd) {
     };
     
     # Default
+    TBScriptUnlock()
+	if ($locked);
     usage();
 }
+TBScriptUnlock()
+    if ($locked);
 exit(0);
 
 #
@@ -315,7 +338,7 @@ sub AddUser(;$)
     }
     my $urn = GeniHRN::Generate($OURDOMAIN, "user", $user->uid());
     
-    my $xmlgoo = emutil::ExecQuiet("$DUMPUSER -p $uid");
+    my $xmlgoo = emutil::ExecQuiet("$DUMPUSER $uid");
     if ($?) {
 	fatal("$DUMPUSER failed");
     }
@@ -393,7 +416,7 @@ sub ModifyUser()
     }
     my $urn = GeniHRN::Generate($OURDOMAIN, "user", $user->uid());
     
-    my $xmlgoo = emutil::ExecQuiet("$DUMPUSER -p $uid");
+    my $xmlgoo = emutil::ExecQuiet("$DUMPUSER $uid");
     if ($?) {
 	fatal("$DUMPUSER failed");
     }
@@ -568,9 +591,7 @@ sub AddProject()
 		 "  exported=now(), updated=now(), ".
 		 "  peer='$peername'");
     
-    if (!$leader_result->numrows) {
-	SetGroups($leader_idx);
-    }
+    SetGroups($leader_idx);
     return 0;
 }
 
diff --git a/account/newuser.in b/account/newuser.in
index 1c39350258..4555ceb844 100644
--- a/account/newuser.in
+++ b/account/newuser.in
@@ -1,6 +1,6 @@
 #!/usr/bin/perl -w
 #
-# Copyright (c) 2000-2012 University of Utah and the Flux Group.
+# Copyright (c) 2000-2013 University of Utah and the Flux Group.
 # 
 # {{{EMULAB-LICENSE
 # 
@@ -35,11 +35,12 @@ sub usage()
     print("Usage: newuser [-s] -t <type> <xmlfile>\n");
     exit(-1);
 }
-my $optlist = "dt:ns";
+my $optlist = "dt:nsp";
 my $debug   = 0;
 my $impotent= 0;
 my $type    = "";
 my $silent  = 0;
+my $portal  = 0;
 my @keyfiles = ();
 
 #
@@ -213,6 +214,11 @@ foreach my $key (keys(%required)) {
     fatal("Missing required attribute '$key'")
 	if (! exists($xmlparse->{'attribute'}->{"$key"}));
 }
+#
+# Always delete this. Used by the portal code but we ignore it.
+#
+delete($xmlparse->{'attribute'}->{"passhash"})
+    if (exists($xmlparse->{'attribute'}->{"passhash"}));
 
 #
 # We build up an array of arguments to pass to User->Create() as we check
diff --git a/account/tbacct.in b/account/tbacct.in
index d2d5ff9943..5a10283158 100644
--- a/account/tbacct.in
+++ b/account/tbacct.in
@@ -735,6 +735,9 @@ sub UpdatePassword()
     #
     return 0
 	if (getpwuid($UID) eq "nobody");
+
+    return 0
+	if ($isnonlocal || $nocollabtools);
     
     $EUID = $UID;
     # And the wiki if enabled.
@@ -882,6 +885,9 @@ sub UpdateUser(;$)
     }
     $UID = $SAVEUID;
 
+    return 0
+	if ($isnonlocal || $nocollabtools);
+    
     $EUID = $UID;
     # Update elists in case email changed.
     system("$MMMODIFYUSER $user")
diff --git a/backend/moduserinfo.in b/backend/moduserinfo.in
index 164614ac7a..16aca96d50 100644
--- a/backend/moduserinfo.in
+++ b/backend/moduserinfo.in
@@ -1,6 +1,6 @@
 #!/usr/bin/perl -wT
 #
-# Copyright (c) 2000-2012 University of Utah and the Flux Group.
+# Copyright (c) 2000-2013 University of Utah and the Flux Group.
 # 
 # {{{EMULAB-LICENSE
 # 
@@ -183,6 +183,8 @@ my %xmlfields =
      "user_interface"	=> ["user_interface",	$SLOT_OPTIONAL],
      "pubkeys"		=> ["pubkeys",		$SLOT_SKIP],
      "wikiname"		=> ["pubkeys",		$SLOT_SKIP],
+     # The portal code sets this, we ignore it here. 
+     "passhash"		=> ["passhash",		$SLOT_SKIP],
      # These are alternates.
      "name"		=> ["usr_name",		$SLOT_OPTIONAL],
      "title"	        => ["usr_title",	$SLOT_OPTIONAL],
diff --git a/protogeni/lib/GeniEmulab.pm.in b/protogeni/lib/GeniEmulab.pm.in
index 1ff58e7beb..c58f75759c 100755
--- a/protogeni/lib/GeniEmulab.pm.in
+++ b/protogeni/lib/GeniEmulab.pm.in
@@ -69,6 +69,7 @@ my $PGENIDOMAIN    = "@PROTOGENI_DOMAIN@";
 my $ELABINELAB     = "@ELABINELAB@";
 my $NEWUSER	   = "$TB/sbin/newuser";
 my $RMUSER	   = "$TB/sbin/rmuser";
+my $CHPASS	   = "$TB/sbin/tbacct passwd";
 my $ADDUSER	   = "$TB/sbin/tbacct add";
 my $MODUSER	   = "$TB/bin/moduserinfo";
 my $NEWPROJ	   = "$TB/sbin/newproj";
@@ -100,6 +101,7 @@ sub AddUser($)
     my $credentials = $argref->{'credentials'};
     my $xmlgoo      = $argref->{'xmlstring'};
     my $urn         = $argref->{'urn'};
+    my $passhash;
 
     if (! (defined($credentials) && defined($xmlgoo) && defined($urn))) {
 	return GeniResponse->MalformedArgsResponse("Missing arguments");
@@ -135,6 +137,15 @@ sub AddUser($)
     return GeniResponse->Create(GENIRESPONSE_BADARGS)
 	if (! User->ValidUID($login));
 
+    if (exists($xmlparse->{'attribute'}->{"passhash"})) {
+	$passhash = $xmlparse->{'attribute'}->{"passhash"}->{'value'};
+
+	return GeniResponse->Create(GENIRESPONSE_BADARGS, undef,
+				    "Invalid characters in password hash")
+	    if (! ($passhash =~ /^\$\d\$\w*\$[\w\/\.]*$/ ||
+		   $passhash =~ /^[\w\/\.]*$/));
+    }
+
     my $user = User->Lookup($login);
     if (defined($user)) {
 	return GeniResponse->Create(GENIRESPONSE_ALREADYEXISTS, undef,
@@ -182,6 +193,14 @@ sub AddUser($)
     $user->SetStatus($User::USERSTATUS_ACTIVE);
     $user->Update({"nocollabtools" => "1",
 		   "manager_urn"   => $credential->owner_urn()});
+
+    if (defined($passhash) &&
+	$user->SetPassword($passhash) != 0) {
+	GeniUtil::FlipToGeniUser();
+	return GeniResponse->Create(GENIRESPONSE_ERROR, undef,
+				    "Internal error setting user password");
+    }
+    
     $output = GeniUtil::ExecQuiet("$WAP $ADDUSER $login");
     if ($?) {
 	$user->Delete();
@@ -314,6 +333,37 @@ sub ModifyUser($)
 				    "Internal error modifying user");
     }
     unlink($filename);
+
+    #
+    # Look for password change. Need special treatment cause its a hash.
+    #
+    my $xmlparse = eval { XMLin($xmlgoo,
+				ForceArray => ["pubkeys"],
+				VarAttr => 'name',
+				ContentKey => '-content',
+				SuppressEmpty => undef); };
+    
+    return GeniResponse->Create(GENIRESPONSE_ERROR, undef, "$@")
+	if ($@);
+
+    if (exists($xmlparse->{'attribute'}->{"passhash"})) {
+	my $hash = $xmlparse->{'attribute'}->{"passhash"}->{'value'};
+
+	return GeniResponse->Create(GENIRESPONSE_BADARGS, undef,
+				    "Invalid characters in password hash")
+	    if (! ($hash =~ /^\$\d\$\w*\$[\w\/\.]*$/ ||
+		   $hash =~ /^[\w\/\.]*$/));
+
+	my $uid = $user->uid();
+	my $safe_encoding = User::escapeshellarg($hash);
+	$output = GeniUtil::ExecQuiet("$WAP $CHPASS $uid $safe_encoding");
+	if ($?) {
+	    GeniUtil::FlipToGeniUser();
+	    print STDERR $output;
+	    return GeniResponse->Create(GENIRESPONSE_ERROR, undef,
+				"Internal error modifying user password");
+	}
+    }
     GeniUtil::FlipToGeniUser();
     
     return GeniResponse->Create(GENIRESPONSE_SUCCESS);
diff --git a/tbsetup/portal_daemon.in b/tbsetup/portal_daemon.in
index b537b8c091..95e61bac1e 100644
--- a/tbsetup/portal_daemon.in
+++ b/tbsetup/portal_daemon.in
@@ -1,6 +1,6 @@
 #!/usr/bin/perl -w
 #
-# Copyright (c) 2009-2011 University of Utah and the Flux Group.
+# Copyright (c) 2009-2013 University of Utah and the Flux Group.
 # 
 # {{{EMULAB-LICENSE
 # 
@@ -49,7 +49,7 @@ my $PIDFILE       = "/var/run/portal_daemon.pid";
 my $SUDO          = "/usr/local/bin/sudo";
 my $PROTOUSER     = "elabman";
 my $WAP           = "$TB/sbin/withadminprivs";
-my $MANAGEREMOTE  = "$TB/sbin/manageremote";
+my $MANAGEREMOTE  = "$TB/sbin/manageremote -p";
 my $PORTAL_ENABLE = @PORTAL_ENABLE@;
 my $PORTAL_PRIMARY= @PORTAL_ISPRIMARY@;
 
@@ -377,9 +377,17 @@ while (1)
 	next;
     }
     logit("Running");
+    #
+    # Lock, to avoid race with command line tool.
+    #
+    if (TBScriptLock("portal_op", 0, 30) != TBSCRIPTLOCK_OKAY()) {
+	logit("Could not get the lock after a long time. Trying again ...\n");
+	next;
+    }
     ExportUsers();
     ExportGroups();
     UpdateUsers();
+    TBScriptUnlock();
 
     last
 	if ($oneshot);
-- 
GitLab