diff --git a/bugdb/addbugdbuser.in b/bugdb/addbugdbuser.in
index d3ae26402fec0da3bc62dd3ad85a8ab761d4f2e5..309786ba084a3e3f61b8852b64f96501ac342db8 100644
--- a/bugdb/addbugdbuser.in
+++ b/bugdb/addbugdbuser.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.
 #
 use English;
@@ -50,6 +50,7 @@ sub fatal($);
 use lib "@prefix@/lib";
 use libdb;
 use libtestbed;
+use User;
 
 #
 # We don't want to run this script unless its the real version.
@@ -105,15 +106,14 @@ else {
     die("Bad data in uid: $uid");
 }
 
-# Need the password hash ...
-$query_result =
-    DBQueryFatal("select u.usr_pswd from users as u ".
-		 "where u.uid='$uid'");
-
-if ($query_result->numrows == 0) {
-    fatal("No such user $uid!");
+# Map target user to object.
+my $target_user = User->Lookup($uid);
+if (! defined($target_user)) {
+    fatal("$uid does not exist!");
 }
-my ($passhash) = $query_result->fetchrow_array();
+
+# Need the password hash ...
+my $passhash = $target_user->pswd();
 
 # shell escape.
 $passhash =~ s/\$/\\\$/g;
diff --git a/bugdb/setbugdbgroups.in b/bugdb/setbugdbgroups.in
index ffced4d952eb3f0e296bc90e537bd722950aa958..32707f4eeb29133ef9cc841f75869ffbde1f86bb 100644
--- a/bugdb/setbugdbgroups.in
+++ b/bugdb/setbugdbgroups.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.
 #
 use English;
@@ -50,6 +50,7 @@ sub fatal($);
 use lib "@prefix@/lib";
 use libdb;
 use libtestbed;
+use User;
 
 #
 # We don't want to run this script unless its the real version.
@@ -102,6 +103,13 @@ else {
     die("Bad data in user: $user.");
 }
 
+# Map target user to object.
+my $target_user = User->Lookup($user);
+if (! defined($target_user)) {
+    fatal("$user does not exist!");
+}
+my $user_dbid = $target_user->dbid();
+
 #
 # This script always does the right thing, so no permission checks.
 # In fact, all it does is call over to ops to run a script over there.
@@ -113,7 +121,8 @@ else {
 my $query_result =
     DBQueryFatal("select p.pid,p.trust from group_membership as p ".
 		 "left join groups as g on g.pid=p.pid and g.gid=p.gid ".
-		 "where uid='$user' and p.pid=g.gid and trust!='none'");
+		 "where uid_idx='$user_dbid' and p.pid=g.gid and ".
+		 "      trust!='none'");
 
 while (my ($pid,$trust) = $query_result->fetchrow_array()) {
     #
@@ -136,10 +145,8 @@ exit(0)
 # report bugs about Emulab!
 #
 # Admin users ... TBAdmin() test does not work for this test ...
-$query_result =
-    DBQueryFatal("select admin from users where uid='$user'");
-my ($isadmin) = $query_result->fetchrow_array();
-if ($isadmin) {
+#
+if ($target_user->admin()) {
     push(@glist, "Emulab/admin");
 }
 else {
diff --git a/collab/jabber/addjabberuser.in b/collab/jabber/addjabberuser.in
index 614725e884352f7a875207a107d671debcd7393e..d054aa6270cc14caf5c6cf86fa972730d102db50 100644
--- a/collab/jabber/addjabberuser.in
+++ b/collab/jabber/addjabberuser.in
@@ -1,7 +1,7 @@
 #!/usr/bin/perl -wT
 #
 # EMULAB-COPYRIGHT
-# Copyright (c) 2005 University of Utah and the Flux Group.
+# Copyright (c) 2005, 2007 University of Utah and the Flux Group.
 # All rights reserved.
 #
 use English;
@@ -47,6 +47,7 @@ $| = 1;
 use lib "@prefix@/lib";
 use libdb;
 use libtestbed;
+use User;
 
 #
 # We don't want to run this script unless its the real version.
@@ -105,20 +106,11 @@ else {
 # Note that adduser will just update the password if the user already
 # exist in the wiki. 
 #
-
-#
-# Look in the DB to see if there is already a wikiname defined. If
-# we use that. Otherwise have to form one from the user name. Ick.
-#
-my $query_result =
-    DBQueryFatal("select mailman_password ".
-		 "from users where uid='$user'");
-
-if (!$query_result->numrows) {
-    fatal("No such user $user in the DB!");
+my $target_user = User->Lookup($user);
+if (! defined($target_user)) {
+    fatal("$user does not exist!");
 }
-my ($password) = $query_result->fetchrow_array();
-
+my $password = $target_user->mailman_password();
 if (!defined($password)) {
     fatal("No password defined for $user!");
 }
diff --git a/collab/mailman/addmmuser.in b/collab/mailman/addmmuser.in
index ef5b3013e6c24bc49658bcf82b2a15e765dbf556..7b1e6b585c73832719b7907ac87d8832466d214f 100644
--- a/collab/mailman/addmmuser.in
+++ b/collab/mailman/addmmuser.in
@@ -1,7 +1,7 @@
 #!/usr/bin/perl -wT
 #
 # EMULAB-COPYRIGHT
-# Copyright (c) 2005 University of Utah and the Flux Group.
+# Copyright (c) 2005, 2007 University of Utah and the Flux Group.
 # All rights reserved.
 #
 use English;
@@ -54,6 +54,7 @@ $| = 1;
 use lib "@prefix@/lib";
 use libdb;
 use libtestbed;
+use User;
 
 #
 # We don't want to run this script unless its the real version.
@@ -80,14 +81,6 @@ if (! $MAILMANSUPPORT) {
     exit(0);
 }
 
-#
-# Get user DB uid.
-#
-if (! UNIX2DBUID($UID, \$dbuid)) {
-    die("*** $0:\n".
-        "    You do not exist in the Emulab Database!\n");
-}
-
 #
 # Parse command arguments. Once we return from getopts, all that should be
 # left are the required arguments.
@@ -114,15 +107,13 @@ else {
     die("Bad data in uid: $target_uid");
 }
 
-my $query_result =
-    DBQueryFatal("select usr_email, mailman_password, usr_name ".
-		 "from users where uid='$target_uid'");
-
-fatal("No such user in DB: $target_uid!")
-    if (!$query_result->numrows);
-
-my ($email, $password, $fullname) = $query_result->fetchrow_array();
-
+my $target_user = User->Lookup($target_uid);
+if (! defined($target_user)) {
+    fatal("$target_uid does not exist!");
+}
+my $email    = $target_user->email();
+my $password = $target_user->mailman_password();
+my $fullname = $target_user->name();
 
 #
 # Note that since we are sending cleartext passwords over, pipe the info
@@ -141,6 +132,9 @@ if ($CONTROL ne $BOSSNODE) {
     TBScriptLock("mailman_update") == 0 or
 	fatal("Could not get the lock!");
 
+    # Watch for embedded quotes.
+    $fullname =~ s/(\')/\'\\'\'/g;
+
     system("echo \"$password \'$fullname\'\" | ".
            "$SSH -host $CONTROL $MMPROXY $optarg adduser $target_uid $email");
 
diff --git a/collab/mailman/delmmuser.in b/collab/mailman/delmmuser.in
index acd0ac8da84b78d8829f3a49eed4e922c0cca737..418e0a05df6bbe4e84996f2cc7b4ac6a82c71379 100644
--- a/collab/mailman/delmmuser.in
+++ b/collab/mailman/delmmuser.in
@@ -1,7 +1,7 @@
 #!/usr/bin/perl -wT
 #
 # EMULAB-COPYRIGHT
-# Copyright (c) 2005 University of Utah and the Flux Group.
+# Copyright (c) 2005, 2007 University of Utah and the Flux Group.
 # All rights reserved.
 #
 use English;
@@ -54,6 +54,7 @@ $| = 1;
 use lib "@prefix@/lib";
 use libdb;
 use libtestbed;
+use User;
 
 #
 # We don't want to run this script unless its the real version.
@@ -80,14 +81,6 @@ if (! $MAILMANSUPPORT) {
     exit(0);
 }
 
-#
-# Get user DB uid.
-#
-if (! UNIX2DBUID($UID, \$dbuid)) {
-    die("*** $0:\n".
-        "    You do not exist in the Emulab Database!\n");
-}
-
 #
 # Parse command arguments. Once we return from getopts, all that should be
 # left are the required arguments.
@@ -114,18 +107,12 @@ else {
     die("Bad data in uid: $target_uid");
 }
 
-my $query_result =
-    DBQueryFatal("select usr_email, mailman_password, usr_name ".
-		 "from users where uid='$target_uid'");
-
-fatal("No such user in DB: $target_uid!")
-    if (!$query_result->numrows);
-
-my ($email, $password, $fullname) = $query_result->fetchrow_array();
+my $target_user = User->Lookup($target_uid);
+if (! defined($target_user)) {
+    fatal("$target_uid does not exist!");
+}
+my $email = $target_user->email();
 
-#
-# Note that since we are sending cleartext passwords over, pipe the info
-# into its STDIN so that the passwords are not visible in a ps listing.
 #
 # For ssh.
 #
diff --git a/collab/mailman/mmmodifymember.in b/collab/mailman/mmmodifymember.in
index 9b6da6921ebbd449e96c8429851240c12c802e4d..83d4b3141b90fdce34910ab53b8e1db59073cd93 100644
--- a/collab/mailman/mmmodifymember.in
+++ b/collab/mailman/mmmodifymember.in
@@ -1,7 +1,7 @@
 #!/usr/bin/perl -wT
 #
 # EMULAB-COPYRIGHT
-# Copyright (c) 2005 University of Utah and the Flux Group.
+# Copyright (c) 2005, 2007 University of Utah and the Flux Group.
 # All rights reserved.
 #
 use English;
@@ -53,6 +53,7 @@ $| = 1;
 use lib "@prefix@/lib";
 use libdb;
 use libtestbed;
+use User;
 
 #
 # We don't want to run this script unless its the real version.
@@ -79,14 +80,6 @@ if (! $MAILMANSUPPORT) {
     exit(0);
 }
 
-#
-# Get user DB uid.
-#
-if (! UNIX2DBUID($UID, \$dbuid)) {
-    die("*** $0:\n".
-        "    You do not exist in the Emulab Database!\n");
-}
-
 #
 # Parse command arguments. Once we return from getopts, all that should be
 # left are the required arguments.
@@ -113,14 +106,13 @@ else {
     die("Bad data in uid: $target_uid");
 }
 
-my $query_result =
-    DBQueryFatal("select usr_email, mailman_password, usr_name ".
-		 "from users where uid='$target_uid'");
-
-fatal("No such user in DB: $target_uid!")
-    if (!$query_result->numrows);
-
-my ($email, $password, $fullname) = $query_result->fetchrow_array();
+my $target_user = User->Lookup($target_uid);
+if (! defined($target_user)) {
+    fatal("$target_uid does not exist!");
+}
+my $email    = $target_user->email();
+my $password = $target_user->mailman_password();
+my $fullname = $target_user->name();
 
 #
 # Note that since we are sending cleartext passwords over, pipe the info
@@ -139,6 +131,9 @@ if ($CONTROL ne $BOSSNODE) {
     TBScriptLock("mailman_update") == 0 or
 	fatal("Could not get the lock!");
 
+    # Watch for embedded quotes.
+    $fullname =~ s/(\')/\'\\'\'/g;
+
     system("echo \"$password \'$fullname\'\" | ".
            "  $SSH -host $CONTROL $MMPROXY ".
 	   "  $optarg modifymember $target_uid $email");
diff --git a/collab/mailman/mmsetup.in b/collab/mailman/mmsetup.in
index b56c7a468e8a2bff2b98c4febf68420b99aa09db..c660341d840db0814e0ddc0aac7c4f8652628249 100644
--- a/collab/mailman/mmsetup.in
+++ b/collab/mailman/mmsetup.in
@@ -1,7 +1,7 @@
 #!/usr/bin/perl -w
 #
 # 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.
 #
 use English;
@@ -52,6 +52,7 @@ $| = 1;
 use lib "@prefix@/lib";
 use libdb;
 use libtestbed;
+use User;
 
 #
 # If no bugdb support, just exit. 
@@ -89,7 +90,8 @@ my $optarg = ($debug ? "-d" : "");
 # Initialize a mailman password for all users. 
 #
 my $query_result =
-    DBQueryFatal("select uid from users where mailman_password is NULL");
+    DBQueryFatal("select uid from users ".
+		 "where mailman_password is NULL and status!='archived'");
 
 while (my ($uid) = $query_result->fetchrow_array()) {
     print "Setting initial mailman password for $uid\n"
diff --git a/db/User.pm.in b/db/User.pm.in
index 5b701ba51822042f4c435f2431ef5fbd2a0ac555..edf41da2c189fb60f858bc5dcf15a3511327ef63 100644
--- a/db/User.pm.in
+++ b/db/User.pm.in
@@ -78,6 +78,7 @@ sub mysystem($)
 sub Lookup($$)
 {
     my ($class, $token) = @_;
+    my $status_archived = $USERSTATUS_ARCHIVED;
     my $query_result;
 
     # Look in cache first
@@ -88,13 +89,18 @@ sub Lookup($$)
     # 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*$/) {
+    if ($token =~ /^\d+$/) {
 	$query_result =
 	    DBQueryWarn("select * from users where uid_idx='$token'");
     }
-    elsif ($token =~ /^\w*$/) {
+    elsif ($token =~ /^\w+$/) {
+	# When looking up by uid, only look for non-archived users;
+	# Must use an idx if you really want an archived user. This
+	# will prevent problems with code that has not yet been
+	# changed to use the idx field.
 	$query_result =
-	    DBQueryWarn("select * from users where uid='$token'");
+	    DBQueryWarn("select * from users ".
+			"where uid='$token' and status!='$status_archived'");
     }
     else {
 	return undef;
@@ -208,10 +214,12 @@ sub LookupByUnixId($$)
 sub LookupByWikiName($$)
 {
     my ($class, $wikiname) = @_;
-
+    my $status_archived = $USERSTATUS_ARCHIVED;
+    
     my $query_result =
 	DBQueryFatal("select uid_idx from users ".
-		     "where wikiname='$wikiname'");
+		     "where wikiname='$wikiname' and ".
+		     "      status!='$status_archived'");
 
     return undef
 	if (! $query_result || !$query_result->numrows);
@@ -228,10 +236,12 @@ sub LookupByWikiName($$)
 sub LookupByEmail($$)
 {
     my ($class, $email) = @_;
+    my $status_archived = $USERSTATUS_ARCHIVED;
 
     my $query_result =
 	DBQueryFatal("select uid_idx from users ".
-		     "where LCASE(usr_email)=LCASE('$email')");
+		     "where LCASE(usr_email)=LCASE('$email') and ".
+		     "      status!='$status_archived'");
 
     return undef
 	if (! $query_result || !$query_result->numrows);
@@ -440,6 +450,18 @@ sub ThisUser($)
     return User->LookupByUnixId($UID);
 }
 
+#
+# The "implied" user is the user the web interface says we are running as.
+#
+sub ImpliedUser($)
+{
+    return undef
+	if (! exists($ENV{'HTTP_INVOKING_USER'}));
+    
+    # The lookup routine checks it argument, so no need to taint check.
+    return User->Lookup($ENV{'HTTP_INVOKING_USER'});
+}
+
 #
 # Refresh a class instance by reloading from the DB.
 #
diff --git a/db/elabinelab_bossinit.in b/db/elabinelab_bossinit.in
index 2f3a13607586fb8f6f939def0004be49a0baa03c..0c1c1d543e8fbf205425249806941c4aafec92f2 100755
--- a/db/elabinelab_bossinit.in
+++ b/db/elabinelab_bossinit.in
@@ -1,7 +1,7 @@
 #!/usr/bin/perl -wT
 #
 # EMULAB-COPYRIGHT
-# Copyright (c) 2000-2004 University of Utah and the Flux Group.
+# Copyright (c) 2000-2004, 2007 University of Utah and the Flux Group.
 # All rights reserved.
 #
 use English;
@@ -99,7 +99,7 @@ mysystem("$TB/sbin/mkproj $pid");
 #
 my $users_result =
     DBQueryFatal("select distinct u.uid,u.admin from group_membership as m ".
-		 "left join users as u on u.uid=m.uid ".
+		 "left join users as u on u.uid_idx=m.uid_idx ".
 		 "where u.status='" . USERSTATUS_ACTIVE() . "'");
 while (my ($uid,$admin) = $users_result->fetchrow_array()) {
     next
diff --git a/db/genelists.in b/db/genelists.in
index 8342889b17634f3b2706f0542e6a2c9f6ac5c9b6..fa3b3829fadccdf3660c81d0197f08a951148b6c 100644
--- a/db/genelists.in
+++ b/db/genelists.in
@@ -2,7 +2,7 @@
 
 #
 # EMULAB-COPYRIGHT
-# Copyright (c) 2000-2006 University of Utah and the Flux Group.
+# Copyright (c) 2000-2007 University of Utah and the Flux Group.
 # All rights reserved.
 #
 use Fcntl ':flock';
@@ -73,6 +73,7 @@ use lib "@prefix@/lib";
 use libdb;
 use libtestbed;
 use libtblog;
+use User;
 
 #
 # We don't want to run this script unless its the real version.
@@ -300,7 +301,7 @@ sub ActiveUsers()
 	   DBQuery("SELECT DISTINCT u.usr_email from experiments as e ".
 		   "left join group_membership as p ".
 		   "     on e.pid=p.pid and p.pid=p.gid ".
-		   "left join users as u on u.uid=p.uid ".
+		   "left join users as u on u.uid_idx=p.uid_idx ".
 		   "where u.status='active' and ".
 		   "      e.state='active' ".
 		   "order by u.usr_email"))) {
@@ -325,7 +326,7 @@ sub RecentUsers()
 
     if (! ($query_result =
 	   DBQuery("select distinct u.usr_email from user_stats as s ".
-		   "left join users as u on u.uid=s.uid ".
+		   "left join users as u on u.uid_idx=s.uid_idx ".
 		   "where ((UNIX_TIMESTAMP(now()) - ".
 		   "       UNIX_TIMESTAMP(s.last_activity)) <= $limit) ".
 		   "order by u.usr_email"))) {
@@ -352,7 +353,7 @@ sub RecentProjects()
 	   DBQuery("select distinct u.usr_email from project_stats as s ".
 		   "left join group_membership as g on ".
 		   "  g.pid=s.pid and g.gid=g.pid ".
-		   "left join users as u on u.uid=g.uid ".
+		   "left join users as u on u.uid_idx=g.uid_idx ".
 		   "where u.status='active' and ".
 		   "      ((UNIX_TIMESTAMP(now()) - ".
 		   "       UNIX_TIMESTAMP(s.last_activity)) <= $limit) ".
@@ -379,7 +380,7 @@ sub RecentProjectLeaders()
 	   DBQuery("select distinct u.usr_email from project_stats as s ".
 		   "left join group_membership as g on ".
 		   "  g.pid=s.pid and g.gid=g.pid ".
-		   "left join users as u on u.uid=g.uid ".
+		   "left join users as u on u.uid_idx=g.uid_idx ".
                    "left join projects as p on u.uid=p.head_uid ".
 		   "where u.status='active' and ".
 		   "      ((UNIX_TIMESTAMP(now()) - ".
@@ -424,7 +425,7 @@ sub WideAreaPeople()
     my $query_result =
 	DBQueryFatal("SELECT DISTINCT u.usr_email from projects as p ".
 		     "left join group_membership as m on m.pid=p.pid ".
-		     "left join users as u on u.uid=m.uid ".
+		     "left join users as u on u.uid_idx=m.uid_idx ".
 		     "where p.approved!=0 and p.pcremote_ok is not null ".
 		     "      and m.trust!='none' and u.status='active' ".
 		     "order by usr_email");
@@ -442,7 +443,7 @@ sub ProjectLeaders()
 		     ($MAILMANSUPPORT ?
 		      ", u.uid ,u.usr_name, u.mailman_password " : "") .
 		     "  from projects as p ".
-		     "left join users as u on u.uid=p.head_uid ".
+		     "left join users as u on u.uid_idx=p.head_idx ".
 		     "where p.approved!=0 ".
 		     "order by usr_email");
 
diff --git a/db/libdb.pm.in b/db/libdb.pm.in
index 938cfccfcee50d9c069cd27da0b78417e6986c59..b82cea3005019d64aea2a98bf2a04643adf2b6fe 100644
--- a/db/libdb.pm.in
+++ b/db/libdb.pm.in
@@ -499,6 +499,7 @@ sub USERSTATUS_FROZEN()		{ "frozen"; }
 sub USERSTATUS_UNAPPROVED()	{ "unapproved"; }
 sub USERSTATUS_UNVERIFIED()	{ "unverified"; }
 sub USERSTATUS_NEWUSER()	{ "newuser"; }
+sub USERSTATUS_ARCHIVED()	{ "archived"; }
 
 #
 # We want valid project membership to be non-zero for easy membership
@@ -911,19 +912,26 @@ sub TBGrpTrust($$$)
 	$gid = $pid;
     }
 
+    #
+    # Must map to an existing user to be trusted, obviously
+    #
+    my $target_user = User->Lookup($uid);
+    return PROJMEMBERTRUST_NONE
+	if (! defined($target_user));
+    my $uid_idx = $target_user->uid_idx();
+
     #
     # User must be active to be trusted.
     #
-    my $query_result =
-	DBQueryFatal("select status from users ".
-		     "where uid='$uid' and status='" . USERSTATUS_ACTIVE() . "'");
-    if ($query_result->numrows == 0) {
-	return PROJMEMBERTRUST_NONE;
-    }
+    return PROJMEMBERTRUST_NONE
+	if ($target_user->status() ne USERSTATUS_ACTIVE());
 
+    #
+    # Must be a member of the group.
+    #
     $query_result =
 	DBQueryFatal("select trust from group_membership ".
-		     "where uid='$uid' and pid='$pid' and gid='$gid'");
+		     "where uid_idx='$uid_idx' and pid='$pid' and gid='$gid'");
 
     #
     # No membership is the same as no trust. True? Maybe an error instead?
@@ -954,20 +962,15 @@ sub TBProjTrust($$)
 }
 
 #
-# Test admin status. Optional argument is the UID or Name to test. If not
-# provided, then test the current UID.
-#
-# XXX Argument is *either* a numeric UID, or a string name.
+# Test admin status. Ignore argument; we only care if the current user
+# has admin privs turned on.
 #
-# usage: TBAdmin([int or char* uid]);
+# usage: TBAdmin();
 #        returns 1 if an admin type.
 #        returns 0 if a mere user.
 #
 sub TBAdmin(;$)
 {
-    my($uid) = @_;
-    my($name);
-
     #
     # No one is considered an admin unless they have the magic environment
     # variable set (so that you have to be a bit more explict about wanting
@@ -979,54 +982,27 @@ sub TBAdmin(;$)
 	return 0;
     }
 
-    if (!defined($uid)) {
-	$uid = $UID;
-    }
-
-    #
-    # Test if numeric. Map to name if it is.
-    #
-    if ($uid =~ /^[0-9]+$/) {
-	($name) = getpwuid($uid)
-	    or die "$uid not in passwd file\n";
-    }
-    else {
-	$name = $uid;
-    }
-
-    my $query_result =
-	DBQueryFatal("select admin from users where uid='$name'");
+    # Map current user to object and confirm admin status from db.
+    my $this_user = User->ThisUser();
+    return 0
+	if (! defined($this_user));
 
-    my @row = $query_result->fetchrow_array();
-    if ($row[0] == 1) {
-	return 1;
-    }
-    return 0;
+    return $this_user->admin();
 }
 
 #
 # Test whether current user is a member of the emulab-ops project.
+# We ignore the argument; always test the current user. 
 # 
 sub TBOpsGuy(;$)
 {
-    my($uid) = @_;
-    my($name);
-
-    if (!defined($uid)) {
-	$uid = $UID;
-    }
+    # Map current user to object and confirm admin status from db.
+    my $this_user = User->ThisUser();
+    return 0
+	if (! defined($this_user));
 
-    #
-    # Test if numeric. Map to name if it is.
-    #
-    if ($uid =~ /^[0-9]+$/) {
-	($name) = getpwuid($uid)
-	    or die "$uid not in passwd file\n";
-    }
-    else {
-	$name = $uid;
-    }
-    return TBMinTrust(TBProjTrust($name, $TBOPSPID), PROJMEMBERTRUST_USER());
+    return TBMinTrust(TBProjTrust($this_user->uid_idx(), $TBOPSPID),
+		      PROJMEMBERTRUST_USER());
 }
 
 #
@@ -1495,7 +1471,7 @@ sub TBLeaderMailList($;$) {
     # have the strings in variables...
     my $query_result =
     DBQueryFatal("select distinct usr_name,u.uid,usr_email from users as u ".
-                 "left join group_membership as gm on gm.uid=u.uid ".
+                 "left join group_membership as gm on gm.uid_idx=u.uid_idx ".
                  "where (trust='project_root' and pid='$pid') or ".
 		 "(trust='group_root' and pid='$pid' and gid='$gid') ".
                  "order by trust DESC, usr_name");
@@ -2477,20 +2453,16 @@ sub TBImageLoadMaxOkay($$;@)
 #        returns 1 if the UID is okay.
 #        returns 0 if the UID is bogus.
 #
-sub UserDBInfo ($$$) {
-    my($dbuid, $username, $useremail) = @_;
-
-    my $query_result =
-	DBQueryWarn("select usr_name,usr_email from users ".
-		    "where uid='$dbuid'");
-
-    if (!$query_result || $query_result->num_rows < 1) {
-	return 0;
-    }
+sub UserDBInfo($$$)
+{
+    my ($dbuid, $username, $useremail) = @_;
 
-    my @row = $query_result->fetchrow_array();
-    $$username  = $row[0];
-    $$useremail = $row[1];
+    my $target_user = User->Lookup($dbuid);
+    return 0
+	if (! defined($target_user));
+    
+    $$username  = $target_user->name();
+    $$useremail = $target_user->email();
     return 1;
 }
 
@@ -2544,10 +2516,7 @@ sub TBUnixGroupList ($) {
 }
 
 #
-# Map UID to DB UID (login). Does a DB check to make sure user is known to
-# the DB (user obviously has a regular account), and that account will
-# always match what the DB says. Redundant, I know. But consider it a
-# sanity (or consistency) check.
+# Map UID to DB UID (login). This function will eventually be tossed.
 #
 # usage: UNIX2DBUID(int uid, \$login)
 #        returns 1 if the UID is okay.
@@ -2556,23 +2525,11 @@ sub TBUnixGroupList ($) {
 sub UNIX2DBUID ($$) {
     my($unix_uid, $userlogin) = @_;
 
-    my $query_result =
-	DBQueryFatal("select uid from users where unix_uid='$unix_uid'");
-
-    if ($query_result->num_rows < 1) {
-	return 0;
-    }
-    my @row = $query_result->fetchrow_array();
-
-    my ($pwname) = getpwuid($unix_uid) or
-	die("*** $unix_uid is not in the password file!");
-
-    if ($row[0] ne $pwname) {
-	warn("*** WARNING: $pwname does not match $row[0]\n");
-	return 0;
-    }
+    my $target_user = User->LookupByUnixId($unix_uid);
+    return 0
+	if (! defined($target_user));
 
-    $$userlogin = $row[0];
+    $$userlogin = $target_user->uid();
     return 1;
 }
 
@@ -4160,9 +4117,10 @@ sub TBNodeUpdateAccountsByUID($)
     my $query_result =
 	DBQueryFatal("select p.pid,pcremote_ok from users as u ".
 		     "left join group_membership as g on ".
-		     "  u.uid=g.uid and g.pid=g.gid ".
+		     "  u.uid_idx=g.uid_idx and g.pid=g.gid ".
 		     "left join projects as p on p.pid=g.pid ".
-		     "where u.uid='$uid' and p.pid is not null");
+		     "where u.uid='$uid' and u.status='active' and ".
+		     "      p.pid is not null");
 
     while (my %row = $query_result->fetchhash()) {
 	my $pid      = $row{'pid'};
diff --git a/db/libdb.py.in b/db/libdb.py.in
index c1a4b8a7bd5771dddc9cd813752cee73fd4e3c9e..80eed5dad6b39a0f7ee0faf75c03afe7a6221705 100644
--- a/db/libdb.py.in
+++ b/db/libdb.py.in
@@ -318,7 +318,7 @@ def TBMapUIDtoIDX(uid):
     uid = DBQuoteSpecial(uid)
     
     qres = DBQueryFatal("select uid_idx from users "
-                        "where uid=%s", (uid,))
+                        "where uid=%s and status!='archived'", (uid,))
 
     if len(qres) == 0:
         return 0
diff --git a/db/webcontrol.in b/db/webcontrol.in
index 52d5c22fdf7dea61bfaec8b0ed71da713e76e5d0..b935927c8c892274b520e238c830fa21aa9ecd3b 100644
--- a/db/webcontrol.in
+++ b/db/webcontrol.in
@@ -2,7 +2,7 @@
 
 #
 # EMULAB-COPYRIGHT
-# Copyright (c) 2000-2003 University of Utah and the Flux Group.
+# Copyright (c) 2000-2003, 2007 University of Utah and the Flux Group.
 # All rights reserved.
 #
 use English;
@@ -103,16 +103,7 @@ if ($setlogin) {
 	#
 	# Find all non admins and log them out.
 	# 
-	my $query_result =
-	    DBQueryFatal("select users.uid from login ".
-			 "left join users on login.uid=users.uid ".
-			 "where users.admin=0");
-	
-	while (my @row = $query_result->fetchrow_array()) {
-	    my $uid = $row[0];
-
-	    DBQueryFatal("delete from login where uid='$uid'");
-	}
+	DBQueryFatal("delete from login where adminon=0'");
     }
 }
 
diff --git a/tbsetup/checkup/checkup_daemon.in b/tbsetup/checkup/checkup_daemon.in
index d4f40753f3d5467ec99c20ad6792352e2506cabb..9c5bda039361c5e12bc61992d75bd8323edcdb51 100644
--- a/tbsetup/checkup/checkup_daemon.in
+++ b/tbsetup/checkup/checkup_daemon.in
@@ -2,7 +2,7 @@
 
 #
 # 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.
 #
 
@@ -40,9 +40,8 @@ my $TBOPS    = "@TBOPSEMAIL@";
 use lib "@prefix@/lib";
 use libdb;
 use libtestbed;
-
-# Be careful not to exit on transient error
-$libdb::DBQUERY_MAXTRIES = 30;
+use User;
+use Project;
 
 #
 # These come from the library.
@@ -53,7 +52,6 @@ my $TBOPSPID= TBOPSPID;
 my $HOME= USERROOT() . "/elabckup";
 
 sub fatal($);
-sub notify($);
 sub daemonize();
 sub misconfig($$);
 sub IsNodeFree($);
@@ -98,25 +96,22 @@ if (! $debug) {
 
 print "Checkup daemon starting... pid $$, at ".`date`;
 
-# Switch to the elabckup user.
-my $query_result = 
-    DBQueryFatal("select unix_uid from users where uid='elabckup'");
-
-if (! $query_result || $query_result->numrows == 0) {
-    fatal("Cannot get elabckup uid\n");
-}
-
-my ($ev_uid) = $query_result->fetchrow_array;
+# Need the unix uid for the backup user.
+my $user = User->Lookup('elabckup');
+fatal("Could not get object for backup user")
+    if (!defined($user));
+my $ev_uid   = $user->unix_uid();
 
-my ($unix_gid, $unix_gidname);
-if (! TBGroupUnixInfo($TBOPSPID, $TBOPSPID, \$unix_gid, \$unix_gidname)) {
-    die("*** $0:\n".
-	"    Could not get unix group info for $TBOPSPID!\n");
-}
+# and need the unix gid for the group.
+my $project = Project->Lookup($TBOPSPID);
+fatal("Could not get object for $TBOPSPID project")
+    if (!defined($project));
+my $unix_gid = $project->unix_gid();
 
 print "Experiment head: $ev_uid\n"
     if ($debug);
 
+# Switch to the elabckup user.
 $GID = $unix_gid;
 $EGID = "$unix_gid";
 $EUID = $UID = $ev_uid;
@@ -454,3 +449,10 @@ sub daemonize()
 
     return 0;
 }
+
+sub fatal($) {
+    my($mesg) = $_[0];
+
+    die("*** $0:\n".
+	"    $mesg\n");
+}
diff --git a/tbsetup/elabinelab.in b/tbsetup/elabinelab.in
index 67670d78815b5f9240306b535a43d96a030ee55b..2cb31d56714e71a09e304c11723bb4c762b9fb94 100644
--- a/tbsetup/elabinelab.in
+++ b/tbsetup/elabinelab.in
@@ -1,7 +1,7 @@
 #!/usr/bin/perl -wT
 #
 # EMULAB-COPYRIGHT
-# Copyright (c) 2004-2006 University of Utah and the Flux Group.
+# Copyright (c) 2004-2007 University of Utah and the Flux Group.
 # All rights reserved.
 #
 # TODO: ntpinfo table.
@@ -697,10 +697,10 @@ sub DumpDBGoo()
 
 	DBQueryWarn("create temporary table temp_$table ".
 		    "select t.* from group_membership as gm ".
-		    "left join users as u on u.uid=gm.uid ".
-		    "left join $table as t on t.uid=u.uid ".
+		    "left join users as u on u.uid_idx=gm.uid_idx ".
+		    "left join $table as t on t.uid_idx=u.uid_idx ".
 		    "where gm.pid='$pid' and gm.gid=gm.pid ".
-		    " and t.uid is not NULL and ".
+		    " and t.uid_idx is not NULL and ".
 		    " u.status='" . USERSTATUS_ACTIVE() . "'")
 	    or die("*** $0:\n".
 		   "    Could not create table temp_$table\n");
@@ -721,7 +721,7 @@ sub DumpDBGoo()
 
     # The group_membership is also special.
     DBQueryWarn("select gm.* from group_membership as gm ".
-		"left join users as u on u.uid=gm.uid ".
+		"left join users as u on u.uid_idx=gm.uid_idx ".
 		"where (gm.pid='$pid' or ".
 		"       gm.pid='" . TBOPSPID() . "') and ".
 		" u.status='" . USERSTATUS_ACTIVE() . "' ".
diff --git a/tbsetup/exports_setup.in b/tbsetup/exports_setup.in
index 25cb18534526da970d680361b2a8fc5f60eccfd6..248b2980599259414b5d078d5340cde381d85a09 100644
--- a/tbsetup/exports_setup.in
+++ b/tbsetup/exports_setup.in
@@ -2,7 +2,7 @@
 
 #
 # EMULAB-COPYRIGHT
-# Copyright (c) 2000-2006 University of Utah and the Flux Group.
+# Copyright (c) 2000-2007 University of Utah and the Flux Group.
 # All rights reserved.
 #
 
@@ -233,7 +233,7 @@ while (@row = $nodes_result->fetchrow_array) {
 	# XXX needs to be fixed for shared experiments?
 	$users_result =
 	    DBQueryFatal("select distinct g.uid from group_membership as g ".
-			 "left join users as u on u.uid=g.uid ".
+			 "left join users as u on u.uid_idx=g.uid_idx ".
 			 "where g.pid='$pid' and g.gid='$gid' and ".
 			 "      (g.trust!='none' and ".
 			 "       u.webonly=0 and ".
diff --git a/tbsetup/libtestbed.pm.in b/tbsetup/libtestbed.pm.in
index 5d362a8a289e1c40ee210c179de740b5e41a502a..75d9f3f5b680195499eb647a51d745f3f5dcff13 100644
--- a/tbsetup/libtestbed.pm.in
+++ b/tbsetup/libtestbed.pm.in
@@ -2,7 +2,7 @@
 
 #
 # EMULAB-COPYRIGHT
-# Copyright (c) 2000-2006 University of Utah and the Flux Group.
+# Copyright (c) 2000-2007 University of Utah and the Flux Group.
 # All rights reserved.
 #
 
@@ -18,7 +18,7 @@ use Exporter;
 	 TBSCRIPTLOCK_OKAY TBSCRIPTLOCK_TIMEDOUT
 	 TBSCRIPTLOCK_IGNORE TBSCRIPTLOCK_FAILED
 	 PROJROOT GROUPROOT USERROOT SCRATCHROOT SHAREROOT
-	 TBValidUserDir TBValidUserDirList);
+	 TBValidUserDir TBValidUserDirList TBMakeTempFile);
 
 # After package decl.
 use English;
@@ -291,23 +291,31 @@ sub TBBackGround($)
 }
 
 #
-# Create a logname and untaint it!
+# Create a temporary file, untaint the name, return it. 
 #
-sub TBMakeLogname($)
+sub TBMakeTempFile($)
 {
     my($prefix) = @_;
-    my $logname;
+    my $fname;
     
-    $logname = `mktemp /tmp/${prefix}.XXXXXX`;
+    $fname = `mktemp /tmp/${prefix}.XXXXXX`;
 
-    if ($logname =~ /^([-\@\w\.\/]+)$/) {
-	$logname = $1;
+    if ($fname =~ /^([-\@\w\.\/]+)$/) {
+	$fname = $1;
     }
     else {
-	die("Bad data in logfile name: $logname");
+	die("Bad data in filename: $fname");
     }
 
-    return $logname;
+    return $fname;
+}
+
+# Ditto for a temporary file.
+sub TBMakeLogname($)
+{
+    my ($prefix) = @_;
+
+    return TBMakeTempFile($prefix);
 }
 
 #
diff --git a/tbsetup/plab/libplab.py.in b/tbsetup/plab/libplab.py.in
index eb9f37b43a0419d326927e0aee76dc397605f3ea..d4729b24e2ee1f7fcc04d479b5f295040d9813dc 100644
--- a/tbsetup/plab/libplab.py.in
+++ b/tbsetup/plab/libplab.py.in
@@ -1087,7 +1087,7 @@ class Slice:
         try:
             qres = DBQueryFatal("select u.uid, u.usr_email from users as u "
                                 "left join experiments as e "
-                                "on u.uid = e.expt_swap_uid "
+                                "on u.uid_idx = e.swapper_idx "
                                 "where e.pid=%s and e.eid=%s",
                                 (self.pid, self.eid))
             if not len(qres):
diff --git a/tbsetup/repos_daemon.in b/tbsetup/repos_daemon.in
index 165064354dd1603db0411242db5a5d155c0b25a2..9aee0ff8fef2c2b34e25c4da6f10de0ccb829751 100644
--- a/tbsetup/repos_daemon.in
+++ b/tbsetup/repos_daemon.in
@@ -2,7 +2,7 @@
 
 #
 # 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.
 #
 
@@ -49,6 +49,8 @@ my $FLOOR = 4;
 use lib "@prefix@/lib";
 use libdb;
 use libtestbed;
+use User;
+use Project;
 
 # Be careful not to exit on transient error
 $libdb::DBQUERY_MAXTRIES = 30;
@@ -128,21 +130,17 @@ if (! $debug) {
 
 print "Repositioning Daemon starting... pid $$, at ".`date`;
 
-# XXX Need to get an emulab-ops user to run the event system as.
-$query_result = 
-    DBQueryFatal("select unix_uid from users where uid='elabman'");
-
-if (! $query_result || $query_result->numrows == 0) {
-    fatal("Cannot get elabman uid\n");
-}
-
-my ($ev_uid) = $query_result->fetchrow;
-
-my ($unix_gid, $unix_gidname);
-if (! TBGroupUnixInfo($REPOSPID, $REPOSPID, \$unix_gid, \$unix_gidname)) {
-    die("*** $0:\n".
-	"    Could not get unix group info for $REPOSPID!\n");
-}
+# Need the unix uid for the emulab-ops user.
+my $user = User->Lookup('elabman');
+fatal("Could not get object for emulab-ops user")
+    if (!defined($user));
+my $ev_uid   = $user->unix_uid();
+
+# and need the unix gid for the group.
+my $project = Project->Lookup($REPOSEID);
+fatal("Could not get object for $REPOSEID project")
+    if (!defined($project));
+my $unix_gid = $project->unix_gid();
 
 print "Experiment head: $ev_uid\n"
     if ($debug);
diff --git a/tbsetup/setgroups.in b/tbsetup/setgroups.in
index 86da3db8398add5b7b31230d2ae5c4da51eeec47..865d6ca6459ad82640dae7f67c795ffef1862d8e 100755
--- a/tbsetup/setgroups.in
+++ b/tbsetup/setgroups.in
@@ -2,7 +2,7 @@
 
 #
 # EMULAB-COPYRIGHT
-# Copyright (c) 2000-2006 University of Utah and the Flux Group.
+# Copyright (c) 2000-2007 University of Utah and the Flux Group.
 # All rights reserved.
 #
 
@@ -88,6 +88,7 @@ use lib "@prefix@/lib";
 use libaudit;
 use libdb;
 use libtestbed;
+use User;
 
 #
 # We do not want to run this script unless its the real version.
@@ -155,21 +156,13 @@ if (!defined($pid) && !scalar(@userlist)) {
     usage();
 }
 
-#
-# Get user DB uid.
-#
-if (! UNIX2DBUID($UID, \$dbuid)) {
-    die("*** $0:\n".
-        "    You do not exist in the Emulab Database!\n");
-}
-
-#
-# Get email info.
-#
-if (! UserDBInfo($dbuid, \$user_name, \$user_email)) {
-    die("*** $0:\n".
-        "    Cannot determine email info for you!\n");
+# Map invoking user to object.
+my $this_user = User->ThisUser();
+if (! defined($this_user)) {
+    fatal("You ($UID) do not exist!");
 }
+my $user_name  = $this_user->name();
+my $user_email = $this_user->email();
 
 #
 # This script always does the right thing, so it does not matter who
@@ -215,9 +208,12 @@ foreach my $uid (@userlist) {
     my $groupargument;
     my $project;
 
-    $query_result = DBQueryFatal("select webonly from users ".
-				 "where uid='$uid' and webonly=1");
-    if ($query_result->numrows) {
+    my $user = User->Lookup($uid);
+    fatal("Could not map user $uid to object")
+	if (!defined($user));
+    my $uid_idx = $user->uid_idx();
+
+    if ($user->webonly()) {
 	print "Skipping $uid; webonly account!\n";
 	next;
     }
@@ -231,7 +227,8 @@ foreach my $uid (@userlist) {
     $query_result =
 	DBQueryFatal("select g.unix_name from group_membership as m ".
 		     "left join groups as g on m.pid=g.pid and m.gid=g.gid ".
-		     "where m.uid='$uid' and m.pid=m.gid and m.trust!='none'");
+		     "where m.uid_idx='$uid_idx' and m.pid=m.gid and ".
+		     "      m.trust!='none'");
 
     if (!$query_result->numrows) {
 	#
@@ -240,13 +237,8 @@ foreach my $uid (@userlist) {
 	# (non-fatal) since there can be group members not approved,
 	# and this is called from the editgroups web page.
 	#
-	$query_result =
-	    DBQueryFatal("select status from users ".
-			 "where uid='$uid' and webonly=0 ".
-			 " and status='" . USERSTATUS_ACTIVE . "'");
-
-	if (!$query_result->numrows) {
-	    print "Skipping $uid; not in any groups!\n";
+	if ($user->status() ne USERSTATUS_ACTIVE()) {
+	    print "Skipping $uid; not an active user yet!\n";
 	    next;
 	}
 	push(@groupnames, "guest");
@@ -269,8 +261,8 @@ foreach my $uid (@userlist) {
     $query_result =
 	DBQueryFatal("select g.unix_name from group_membership as m ".
 		     "left join groups as g on m.pid=g.pid and m.gid=g.gid ".
-		     "where m.uid='$uid' and m.pid!=m.gid ".
-		     " and m.trust!='none'");
+		     "where m.uid_idx='$uid_idx' and m.pid!=m.gid and ".
+		     "      m.trust!='none'");
 
     while (@db_row = $query_result->fetchrow_array() ) {
 	    my $groupname = $db_row[0];
diff --git a/tbsetup/sfskey_update.in b/tbsetup/sfskey_update.in
index ac4c31f551e9d83cf44a274eb25410f463a8d7d7..5dbc2ddd206f340ff54df937245235659c92c4ad 100644
--- a/tbsetup/sfskey_update.in
+++ b/tbsetup/sfskey_update.in
@@ -1,7 +1,7 @@
 #!/usr/bin/perl -wT
 #
 # EMULAB-COPYRIGHT
-# Copyright (c) 2000-2003 University of Utah and the Flux Group.
+# Copyright (c) 2000-2003, 2007 University of Utah and the Flux Group.
 # All rights reserved.
 #
 use English;
@@ -145,7 +145,7 @@ else {
 #
 my $query_result =
     DBQueryFatal("select sfs.pubkey from users as u ".
-		 "left join user_sfskeys as sfs on sfs.uid=u.uid ".
+		 "left join user_sfskeys as sfs on sfs.uid_idx=u.uid_idx ".
 		 "where u.status='active' and u.webonly=0");
 
 while (my ($pubkey) = $query_result->fetchrow_array()) {
diff --git a/tmcd/tmcd.c b/tmcd/tmcd.c
index 88978ab30f1574367bb8c68dab7e156852a04736..95c82584d8c18b6da9abf2d81910ece42002a989 100644
--- a/tmcd/tmcd.c
+++ b/tmcd/tmcd.c
@@ -1797,7 +1797,7 @@ COMMAND_PROTOTYPE(doaccounts)
 				 "  UNIX_TIMESTAMP(u.usr_modified), "
 				 "  u.usr_email,u.usr_shell "
 				 "from group_membership as p "
-				 "left join users as u on p.uid=u.uid "
+				 "left join users as u on p.uid_idx=u.uid_idx "
 				 "left join groups as g on p.pid=g.pid "
 				 "where p.trust!='none' "
 				 "      and u.webonly=0 "
@@ -1822,7 +1822,7 @@ COMMAND_PROTOTYPE(doaccounts)
 				 "  u.widearearoot,u.wideareajailroot, "
 				 "  u.usr_w_pswd "
 				 "from group_membership as p "
-				 "left join users as u on p.uid=u.uid "
+				 "left join users as u on p.uid_idx=u.uid_idx "
 				 "left join groups as g on "
 				 "     p.pid=g.pid and p.gid=g.gid "
 				 "where ((p.pid='%s')) and p.trust!='none' "
@@ -1845,7 +1845,7 @@ COMMAND_PROTOTYPE(doaccounts)
 			     "  u.widearearoot,u.wideareajailroot, "
 			     "  u.usr_w_pswd "
 			     "from group_membership as p "
-			     "left join users as u on p.uid=u.uid "
+			     "left join users as u on p.uid_idx=u.uid_idx "
 			     "left join groups as g on "
 			     "     p.pid=g.pid and p.gid=g.gid "
 			     "where (p.pid='%s') and p.trust!='none' "
@@ -1877,7 +1877,7 @@ COMMAND_PROTOTYPE(doaccounts)
 				 "  on m.pid=p.pid "
 				 "left join groups as g on "
 				 "  g.pid=m.pid and g.gid=m.gid "
-				 "left join users as u on u.uid=m.uid "
+				 "left join users as u on u.uid_idx=m.uid_idx "
 				 "where p.approved!=0 "
 				 "      and FIND_IN_SET('%s',pcremote_ok)>0 "
 				 "      and m.trust!='none' "
@@ -2205,7 +2205,7 @@ COMMAND_PROTOTYPE(doaccounts)
 				 "u.usr_email,u.usr_shell, "
 				 "u.widearearoot,u.wideareajailroot "
 				 "from widearea_accounts as w "
-				 "left join users as u on u.uid=w.uid "
+				 "left join users as u on u.uid_idx=w.uid_idx "
 				 "where w.trust!='none' and "
 				 "      u.status='active' and "
 				 "      node_id='%s' "
@@ -3192,7 +3192,8 @@ COMMAND_PROTOTYPE(domounts)
 	 */
 #ifdef  NOSHAREDEXPTS
 	res = mydb_query("select u.uid from users as u "
-			 "left join group_membership as p on p.uid=u.uid "
+			 "left join group_membership as p on "
+			 "     p.uid_idx=u.uid_idx "
 			 "where p.pid='%s' and p.gid='%s' and "
 			 "      u.status='active' and "
 			 "      u.webonly=0 and "
@@ -3202,7 +3203,8 @@ COMMAND_PROTOTYPE(domounts)
 	res = mydb_query("select distinct u.uid from users as u "
 			 "left join exppid_access as a "
 			 " on a.exp_pid='%s' and a.exp_eid='%s' "
-			 "left join group_membership as p on p.uid=u.uid "
+			 "left join group_membership as p on "
+			 "     p.uid_idx=u.uid_idx "
 			 "where ((p.pid='%s' and p.gid='%s') or p.pid=a.pid) "
 			 "       and u.status='active' and "
 			 "       u.webonly=0 and "
diff --git a/utils/firstuser.in b/utils/firstuser.in
index 2762202b6c9577c6dcaa2876458954a74918dd5a..1ccfa60a363d4bf709e1d43025a5f103af2b457a 100755
--- a/utils/firstuser.in
+++ b/utils/firstuser.in
@@ -1,7 +1,7 @@
 #!/usr/bin/perl -w
 #
 # EMULAB-COPYRIGHT
-# Copyright (c) 2000-2006 University of Utah and the Flux Group.
+# Copyright (c) 2000-2007 University of Utah and the Flux Group.
 # All rights reserved.
 #
 #
@@ -14,6 +14,7 @@ use Getopt::Std;
 use lib '@prefix@/lib';
 use libdb;
 use libtestbed;
+use User;
 
 my $tbadmin    = '@TBADMINGROUP@';
 my $ELABINELAB = @ELABINELAB@;
@@ -72,9 +73,9 @@ if (defined($opts{e})) {
     $protouser_email = $opts{e};
 }
 
-my $result = DBQueryFatal("select * from users where uid='$protouser'");
-if ($result->num_rows()) {
-    die "This script has already been run, there is no need to run it again\n";
+my $user = User->Lookup('$protouser');
+if (defined($user)) {
+    die("This script has already been run, no need to run it again!\n");
 }
 
 if ($UID != 0) {
diff --git a/utils/opsdb_control.in b/utils/opsdb_control.in
index ad83d9c147b9606520bc54ad82301289f4b83773..9720b9841a1efe791faec4c85b4c863658622ff5 100644
--- a/utils/opsdb_control.in
+++ b/utils/opsdb_control.in
@@ -199,14 +199,12 @@ sub AddUser(@)
 	die("Bad data in uid: $target_uid");
     }
 
-    my $query_result =
-	DBQueryFatal("select mailman_password ".
-		     "from users where uid='$target_uid'");
-
+    # Map target user to object.
+    my $target_user = User->Lookup($target_uid);
     fatal("No such user in DB: $target_uid!")
-	if (!$query_result->numrows);
+	if (!defined($user));
 
-    my ($password) = $query_result->fetchrow_array();
+    my $password = $target_user->mailman_password();
     fatal("No password defined for $target_uid!")
 	if (!defined($password) || $password eq "");
 
@@ -242,12 +240,10 @@ sub DelUser(@)
 	die("Bad data in uid: $target_uid");
     }
 
-    my $query_result =
-	DBQueryFatal("select mailman_password ".
-		     "from users where uid='$target_uid'");
-
+    # Map target user to object.
+    my $target_user = User->Lookup($target_uid);
     fatal("No such user in DB: $target_uid!")
-	if (!$query_result->numrows);
+	if (!defined($user));
 
     print "Removing user '$target_uid' from mysql database on $CONTROL.\n";
     my $retval = DoOpsStuff("deluser $target_uid");
@@ -539,7 +535,7 @@ sub AddExpDB(@)
     my $users_result =
 	DBQueryFatal("select distinct g.uid ".
 		     "  from group_membership as g ".
-		     "left join users as u on u.uid=g.uid ".
+		     "left join users as u on u.uid_idx=g.uid_idx ".
 		     "where (u.status='active' or u.status='frozen') and ".
 		     "      g.trust!='none' and ".
   		     "      g.pid='$pid' and g.gid='$gid'");
@@ -766,7 +762,7 @@ sub AddTempDB(@)
     my $users_result =
 	DBQueryFatal("select distinct g.uid ".
 		     "  from group_membership as g ".
-		     "left join users as u on u.uid=g.uid ".
+		     "left join users as u on u.uid_idx=g.uid_idx ".
 		     "where (u.status='active' or u.status='frozen') and ".
 		     "      g.trust!='none' and ".
   		     "      g.pid='$pid' and g.gid='$gid'");
@@ -963,7 +959,7 @@ sub Initialize()
     my $users_result =
 	DBQueryFatal("select distinct g.uid ".
 		     "  from group_membership as g ".
-		     "left join users as u on u.uid=g.uid ".
+		     "left join users as u on u.uid_idx=g.uid_idx ".
 		     "where u.status='active' or u.status='frozen' ".
 #  		     "  and (g.pid='testbed' or g.pid='emulab-ops' or ".
 #		     "       g.pid='tbres' or g.pid='utahstud')" .
diff --git a/wiki/addwikiuser.in b/wiki/addwikiuser.in
index 7f865c2771d49917813aa0e5621844f55f7743c8..e58fc6cebcbd895342aefe9fa8729d8aa6bc92d3 100644
--- a/wiki/addwikiuser.in
+++ b/wiki/addwikiuser.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.
 #
 use English;
@@ -49,6 +49,7 @@ $| = 1;
 use lib "@prefix@/lib";
 use libdb;
 use libtestbed;
+use User;
 
 # Protos
 sub fatal($);
@@ -107,6 +108,12 @@ else {
     die("Bad data in user: $user.");
 }
 
+# Map target user to object.
+my $target_user = User->Lookup($user);
+if (! defined($target_user)) {
+    fatal("$user does not exist!");
+}
+
 #
 # We need to serialize this script to avoid a trashed map file. Use
 # a dummy file in /var/tmp, opened for writing and flock'ed. 
@@ -182,15 +189,10 @@ utime $now, $now, $lockfile;
 # Look in the DB to see if there is already a wikiname defined. If
 # we use that. Otherwise have to form one from the user name. Ick.
 #
-my $query_result =
-    DBQueryFatal("select wikiname,usr_name,usr_email,usr_pswd ".
-		 "from users where uid='$user'");
-
-if (!$query_result->numrows) {
-    fatal("No such user $user in the DB!");
-}
-my ($wikiname,$usr_name,$usr_email,$usr_pswd) =
-    $query_result->fetchrow_array();
+my $wikiname  = $target_user->wikiname();
+my $usr_name  = $target_user->name();
+my $usr_email = $target_user->email();
+my $usr_pswd  = $target_user->pswd();
 
 if (!defined($wikiname)) {
     # In update mode, do nothing if no wikiname.
@@ -221,19 +223,17 @@ if (!defined($wikiname)) {
     }
 
     #
-    # Make sure that no other user has the same wikiname but a different
-    # email address. 
+    # Make sure that no other user has the same wikiname.
     #
-    $query_result =
-	DBQueryFatal("select uid,usr_name from users ".
-		     "where wikiname='$wikiname' and usr_email!='$usr_email'");
+    fatal("The wikiname for $user ($wikiname) is already in use!")
+	if (User->LookupByWikiName($wikiname));
 
-    if ($query_result->numrows) {
-	fatal("The wikiname for $user ($wikiname) is already in use!");
-    }
     print "Selecting wikiname '$wikiname' for user $user\n";
 
-    DBQueryFatal("update users set wikiname='$wikiname' where uid='$user'");
+    my %update_args = ("wikiname" => $wikiname);
+
+    fatal("Could not update wikiname for $target_user")
+	if ($target_user->Update(\%update_args) != 0);
 }
 
 #
diff --git a/wiki/delwikiuser.in b/wiki/delwikiuser.in
index fbedb14d44c473ea9a5a0609d34cd601f4350d61..a3c14601a745e77ca2845cd6ba7a98bd511785cc 100644
--- a/wiki/delwikiuser.in
+++ b/wiki/delwikiuser.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.
 #
 use English;
@@ -48,6 +48,7 @@ $| = 1;
 use lib "@prefix@/lib";
 use libdb;
 use libtestbed;
+use User;
 
 # Protos
 sub fatal($);
@@ -178,14 +179,11 @@ utime $now, $now, $lockfile;
 # Look in the DB to see if there is already a wikiname defined. If
 # we use that. Otherwise have to form one from the user name. Ick.
 #
-my $query_result =
-    DBQueryFatal("select wikiname ".
-		 "from users where uid='$user'");
-
-if (!$query_result->numrows) {
-    fatal("No such user $user in the DB!");
+my $target_user = User->Lookup($user);
+if (! defined($target_user)) {
+    fatal("$user does not exist!");
 }
-my ($wikiname) = $query_result->fetchrow_array();
+my $wikiname = $target_user->wikiname();
 
 if (!defined($wikiname)) {
     print "There is no wikiname defined in the DB. ".
diff --git a/wiki/setwikigroups.in b/wiki/setwikigroups.in
index b9e9dfde694799c2ec81f79b4b4c7ddf90c374b3..6a7f382afcadbf7ee5357b83eb61b772ceee7ac6 100644
--- a/wiki/setwikigroups.in
+++ b/wiki/setwikigroups.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.
 #
 use English;
@@ -47,6 +47,7 @@ $| = 1;
 use lib "@prefix@/lib";
 use libdb;
 use libtestbed;
+use User;
 
 # Protos
 sub fatal($);
@@ -102,6 +103,13 @@ else {
     die("Bad data in user: $user.");
 }
 
+# Map target user to object.
+my $target_user = User->Lookup($user);
+if (! defined($target_user)) {
+    fatal("$user does not exist!");
+}
+my $uid_idx = $target_user->uid_idx();
+
 #
 # This script always does the right thing, so no permission checks.
 # In fact, all it does is call over to ops to run a script over there.
@@ -111,7 +119,7 @@ else {
 my $query_result =
     DBQueryFatal("select p.pid,g.wikiname,p.trust from group_membership as p ".
 		 "left join groups as g on g.pid=p.pid and g.gid=p.gid ".
-		 "where uid='$user' and p.pid=g.gid and trust!='none'");
+		 "where uid_idx='$uid_idx' and p.pid=g.gid and trust!='none'");
 
 while (my ($pid,$wikiname,$trust) = $query_result->fetchrow_array()) {
     if (!defined($wikiname)) {
@@ -129,18 +137,16 @@ while (my ($pid,$wikiname,$trust) = $query_result->fetchrow_array()) {
     }
 }
 
-# Admin users ... TBAdmin() test does not work for this test ...
-$query_result =
-    DBQueryFatal("select wikiname,admin from users where uid='$user'");
-my ($wikiname,$isadmin) = $query_result->fetchrow_array();
-if ($isadmin) {
-    push(@glist, "TWikiAdmin");
-}
+my $wikiname = $target_user->wikiname();
 if (!defined($wikiname)) {
     print "There is no wikiname defined in the DB. ".
 	"Must not have a wiki account!\n";
     exit(0);
 }
+
+if ($target_user->admin()) {
+    push(@glist, "TWikiAdmin");
+}
 exit(0)
     if (! @glist);
 
diff --git a/www/dbdefs.php3.in b/www/dbdefs.php3.in
index ab4bab3aaf2ad0b49341da8f7f507396e88c4489..303304ca50339eb3a50cebd35d865f948d194ae7 100644
--- a/www/dbdefs.php3.in
+++ b/www/dbdefs.php3.in
@@ -53,6 +53,7 @@ define("TBDB_USERSTATUS_NEWUSER",	"newuser");
 define("TBDB_USERSTATUS_UNAPPROVED",	"unapproved");
 define("TBDB_USERSTATUS_UNVERIFIED",	"unverified");
 define("TBDB_USERSTATUS_FROZEN",	"frozen");
+define("TBDB_USERSTATUS_ARCHIVED",	"archived");
 
 #
 # Type of new account.
diff --git a/www/news-rss.php3 b/www/news-rss.php3
index 9b4c3f0c3393bce91875a5bf46a9f38f333fd2e9..52c4f3db60a859a6dc210a588b3370eca9f1b635 100644
--- a/www/news-rss.php3
+++ b/www/news-rss.php3
@@ -1,7 +1,7 @@
 <?php
 #
 # EMULAB-COPYRIGHT
-# Copyright (c) 2005 University of Utah and the Flux Group.
+# Copyright (c) 2005, 2007 University of Utah and the Flux Group.
 # All rights reserved.
 #
 include("defs.php3");
@@ -10,7 +10,7 @@ header("Content-type: text/xml");
 
 $query_result=
     DBQueryFatal("SELECT subject, author, body, msgid, ".
-    		 "date, usr_name, usr_email " .
+    		 "date, usr_name " .
 		 "FROM webnews ".
                  "LEFT JOIN users on webnews.author = users.uid " .
                  "WHERE archived=0 " .
diff --git a/www/plabstats.php3 b/www/plabstats.php3
index 8f3bfb5f9d3d2120be2a283ae7d949812479492f..3b59937754bd24209cbe1df88e756715dff9d771 100644
--- a/www/plabstats.php3
+++ b/www/plabstats.php3
@@ -1,7 +1,7 @@
 <?php
 #
 # EMULAB-COPYRIGHT
-# Copyright (c) 2000-2003, 2006 University of Utah and the Flux Group.
+# Copyright (c) 2000-2003, 2006, 2007 University of Utah and the Flux Group.
 # All rights reserved.
 #
 include("defs.php3");
@@ -80,7 +80,7 @@ $query_result =
 		 " from experiment_resources as r ".
 		 "left join experiment_stats as s on r.exptidx=s.exptidx ".
 		 "left join testbed_stats as t on t.rsrcidx=r.idx ".
-		 "left join users as u on u.uid=t.uid ".
+		 "left join users as u on u.uid_idx=t.uid_idx ".
 		 "where r.plabnodes!=0 and t.exitcode=0 and ".
 		 "     (t.action='start' or t.action='swapin' or ".
 		 "      t.action='swapout') $wclause ".
diff --git a/www/tbauth.php3 b/www/tbauth.php3
index 682956c6cf8f559efffa88a0122843725bd5e003..881ecba8e3c4f123319c4a59c2db1a618c8a5144 100644
--- a/www/tbauth.php3
+++ b/www/tbauth.php3
@@ -1,7 +1,7 @@
 <?php
 #
 # EMULAB-COPYRIGHT
-# Copyright (c) 2000-2006 University of Utah and the Flux Group.
+# Copyright (c) 2000-2007 University of Utah and the Flux Group.
 # All rights reserved.
 #
 #
@@ -108,6 +108,7 @@ function GETLOGIN() {
 # 
 function GETUID() {
     global $TBNAMECOOKIE;
+    $status_archived = TBDB_USERSTATUS_ARCHIVED;
 
     if (isset($_GET['nocookieuid'])) {
 	$uid = $_GET['nocookieuid'];
@@ -126,7 +127,9 @@ function GETUID() {
 	# Map this to an index (from a uid).
 	#
 	$query_result =
-	    DBQueryFatal("select uid_idx from users where uid='$safe_uid'");
+	    DBQueryFatal("select uid_idx from users ".
+			 "where uid='$safe_uid' and ".
+			 "      status!='$status_archived'");
     
 	if (! mysql_num_rows($query_result))
 	    return FALSE;
@@ -412,6 +415,15 @@ function LoginStatus() {
     if ($admin && $adminon) {
     	putenv("HTTP_WITH_TB_ADMIN_PRIVS=1");
     }
+    #
+    # This environment variable is likely to become the new method for
+    # specifying the credentials of the invoking user. Still thinking
+    # about this, but the short story is that the web interface should
+    # not invoke so much stuff as the user, but rather as a neutral user
+    # with implied credentials. 
+    #
+    putenv("HTTP_INVOKING_USER=" . $CHECKLOGIN_USER->webid());
+    
     # XXX Temporary.
     if ($stud) {
 	$EXPOSEARCHIVE = 1;
diff --git a/www/user_defs.php b/www/user_defs.php
index 354e601e5593bc43d6778ef31f81146cf63b7bd8..df21506bd7b52f4cfc18cfd2a5210bc77cc20784 100644
--- a/www/user_defs.php
+++ b/www/user_defs.php
@@ -1,7 +1,7 @@
 <?php
 #
 # EMULAB-COPYRIGHT
-# Copyright (c) 2006 University of Utah and the Flux Group.
+# Copyright (c) 2006, 2007 University of Utah and the Flux Group.
 # All rights reserved.
 #
 
@@ -74,9 +74,12 @@ class User
     # Backwards compatable lookup by uid. Will eventually flush this.
     function &LookupByUid($uid) {
 	$safe_uid = addslashes($uid);
+	$status_archived = TBDB_USERSTATUS_ARCHIVED;
 
 	$query_result =
-	    DBQueryWarn("select uid_idx from users where uid='$safe_uid'");
+	    DBQueryWarn("select uid_idx from users ".
+			"where uid='$safe_uid' and ".
+			"      status!='$status_archived'");
 
 	if (!$query_result || !mysql_num_rows($query_result)) {
 	    return null;
@@ -91,10 +94,12 @@ class User
     # locally unique.
     function &LookupByEmail($email) {
 	$safe_email = addslashes($email);
+	$status_archived = TBDB_USERSTATUS_ARCHIVED;
 
 	$query_result =
 	    DBQueryWarn("select uid_idx from users ".
-			"where LCASE(usr_email)=LCASE('$safe_email')");
+			"where LCASE(usr_email)=LCASE('$safe_email') and ".
+			"      status!='$status_archived'");
 
 	if (!$query_result || !mysql_num_rows($query_result)) {
 	    return null;
@@ -109,10 +114,12 @@ class User
     # locally unique.
     function &LookupByWikiName($wikiname) {
 	$safe_wikiname = addslashes($wikiname);
+	$status_archived = TBDB_USERSTATUS_ARCHIVED;
 
 	$query_result =
 	    DBQueryWarn("select uid_idx from users ".
-			"where wikiname='$safe_wikiname'");
+			"where wikiname='$safe_wikiname' and ".
+			"      status!='$status_archived'");
 
 	if (!$query_result || !mysql_num_rows($query_result)) {
 	    return null;