Commit 2181fc33 authored by Leigh Stoller's avatar Leigh Stoller

Next step in the locally unique uid project. This commits add the luid to

most of the rest of the tables in the system (still a few exceptions).
Bound to be some bugs ...
parent d243779f
#
# 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.
#
......
#!/usr/bin/perl -wT
#
# 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 English;
......@@ -48,12 +48,16 @@ my $USERUID;
# Locals
my $user;
my $this_user;
my $target_user;
my $keyfile;
my $keyline;
my $key;
my $comment;
my $user_name;
my $user_email;
my $user_dbid;
my $user_uid;
#
# Testbed Support libraries
......@@ -151,12 +155,22 @@ else {
# Untaint the arguments.
#
if (defined($user)) {
if ($user =~ /^([-\w_]+)$/i) {
if ($user =~ /^([-\w]+)$/i) {
$user = $1;
}
else {
fatal("Tainted username: $user");
}
# Map user to object.
$target_user = User->Lookup($user);
if (! defined($target_user)) {
fatal("$user does not exist!")
}
$user_name = $target_user->name();
$user_email = $target_user->email();
$user_dbid = $target_user->dbid();
$user_uid = $target_user->uid();
}
#
......@@ -170,6 +184,13 @@ if (getpwuid($UID) eq "nobody") {
}
else {
$USERUID = getpwnam($user);
# Map invoking user to object.
$this_user = User->LookupByUnixId($UID);
if (! defined($this_user)) {
fatal("You ($UID) do not exist!");
}
}
#
......@@ -214,28 +235,19 @@ else {
#
if (!$verify) {
# If its the user himself, then we can generate a new authkeys file.
if (!$nobody && getpwuid($UID) ne "$user" && !TBAdmin($UID)) {
fatal("You are not allowed to set pubkeys for $user.\n");
if (!$nobody && !TBAdmin() && !$target_user->SameUser($this_user)) {
fatal("You are not allowed to set pubkeys for $target_user\n");
}
if (-d "$HOMEDIR/$user/.ssh") {
if (-d "$HOMEDIR/$user_uid/.ssh") {
$genmode = 1;
}
#
# This script is audited when not in verify mode. Since all keys are first
# checked with verify mode, this should not cause any extra email from bad
# keys.
#
AuditStart(0);
if (! UserDBInfo($user, \$user_name, \$user_email)) {
if ($nobody) {
$noemail = 1;
}
else {
fatal("No DB info for $user!");
}
}
}
# Drop root privs, switching to user.
......@@ -323,12 +335,13 @@ sub ParseKey($) {
$key = "$key $comment";
DBQueryFatal("replace into user_pubkeys ".
"values ('$user', 0, '$key', now(), '$comment')");
"values ('$user_uid', '$user_dbid', ".
" 0, '$key', now(), '$comment')");
#
# Mark user record as modified so nodes are updated.
#
TBNodeUpdateAccountsByUID($user);
TBNodeUpdateAccountsByUID($user_uid);
my $chunked = "";
......@@ -348,8 +361,8 @@ sub ParseKey($) {
if (! $noemail) {
SENDMAIL("$user_name <$user_email>",
"SSH Public Key for '$user' Added",
"SSH Public Key for '$user' added:\n".
"SSH Public Key for '$user_uid' Added",
"SSH Public Key for '$user_uid' added:\n".
"\n".
"$chunked\n",
"$TBOPS");
......@@ -364,7 +377,7 @@ sub ParseKey($) {
#
sub InitUser()
{
my $sshdir = "$HOMEDIR/$user/.ssh";
my $sshdir = "$HOMEDIR/$user_uid/.ssh";
#
# Set up the ssh key, but only if not done so already.
......@@ -384,7 +397,7 @@ sub InitUser()
if ($ident =~ /(\d*\s\d*\s[0-9a-zA-Z]*)\s([-\w\@\.]*)/) {
DBQueryFatal("delete from user_pubkeys ".
"where uid='$user' and pubkey='$1 $2'");
"where uid_idx='$user_dbid' and pubkey='$1 $2'");
}
unlink("$sshdir/identity");
}
......@@ -404,7 +417,8 @@ sub InitUser()
if ($ident =~ /(\d*\s\d*\s[0-9a-zA-Z]*)\s([-\w\@\.]*)/) {
DBQueryFatal("replace into user_pubkeys ".
"values ('$user', 0, '$1 $2', now(), '$2')");
"values ('$user_uid', '$user_dbid', ".
" 0, '$1 $2', now(), '$2')");
}
else {
fatal("Bad protocol 1 public key: $ident\n");
......@@ -425,7 +439,7 @@ sub InitUser()
if ($ident =~
/^(ssh-rsa [-\w\.\@\+\/\=]*) ([-\w\@\.\ ]*)$/) {
DBQueryFatal("delete from user_pubkeys ".
"where uid='$user' and pubkey='$1 $2'");
"where uid_idx='$user_dbid' and pubkey='$1 $2'");
}
unlink("$sshdir/id_rsa");
}
......@@ -446,13 +460,8 @@ sub InitUser()
if ($ident =~
/^(ssh-rsa [-\w\.\@\+\/\=]*) ([-\w\@\.\ ]*)$/) {
DBQueryFatal("replace into user_pubkeys ".
"values ('$user', 0, '$1 $2', now(), '$2')");
#
# Backwards compat. Remove later.
#
DBQueryFatal("update users set emulab_pubkey='$1 $2' ".
"where uid='$user'");
"values ('$user_uid', '$user_dbid', ".
" 0, '$1 $2', now(), '$2')");
}
else {
fatal("Bad protocol 2 public key: $ident\n");
......@@ -468,7 +477,7 @@ sub InitUser()
sub GenerateKeyFile()
{
my @pkeys = ();
my $sshdir = "$HOMEDIR/$user/.ssh";
my $sshdir = "$HOMEDIR/$user_uid/.ssh";
my $keyfile = "$sshdir/authorized_keys";
if (! -e $sshdir) {
......@@ -478,7 +487,8 @@ sub GenerateKeyFile()
}
}
my $query_result =
DBQueryFatal("select pubkey from user_pubkeys where uid='$user'");
DBQueryFatal("select pubkey from user_pubkeys ".
"where uid_idx='$user_dbid'");
while (my ($key) = $query_result->fetchrow_array()) {
push(@pkeys, $key);
......
......@@ -2,7 +2,7 @@
#
# EMULAB-COPYRIGHT
# Copyright (c) 2000-2004 University of Utah and the Flux Group.
# Copyright (c) 2000-2004, 2006 University of Utah and the Flux Group.
# All rights reserved.
#
use English;
......@@ -16,6 +16,7 @@ use lib "@prefix@/lib";
use libaudit;
use libdb;
use libtestbed;
use User;
#
# Create user SSL certificates.
......@@ -48,7 +49,6 @@ my $CACONFIG = "$SSLDIR/ca.cnf";
my $EMULAB_CERT = "$TB/etc/emulab.pem";
my $EMULAB_KEY = "$TB/etc/emulab.key";
my $OPENSSL = "/usr/bin/openssl";
my $lockfile = "/var/tmp/testbed_mkusercert_lockfile";
my $WORKDIR = "$TB/ssl";
my $SAVEUID = $UID;
......@@ -137,6 +137,18 @@ else {
die("Tainted argument: $user\n");
}
# Map target user to object.
my $target_user = User->Lookup($user);
if (! defined($target_user)) {
fatal("$user does not exist!");
}
# Map invoking user to object.
my $this_user = User->LookupByUnixId($UID);
if (! defined($this_user)) {
fatal("You ($UID) do not exist!");
}
#
# This script is always audited. Mail is sent automatically upon exit.
#
......@@ -154,103 +166,38 @@ if (AuditStart(0)) {
chdir("$WORKDIR") or
fatal("Could not chdir to $WORKDIR: $!");
open(LOCK, ">>$lockfile") || fatal("Couldn't open $lockfile\n");
$count = 0;
if (flock(LOCK, LOCK_EX|LOCK_NB) == 0) {
#
# If we don't get it the first time, we wait for:
# 1) The lock to become free, in which case we do our thing
# 2) The time on the lock to change, in which case we wait for that process
# to finish
#
my $oldlocktime = (stat(LOCK))[9];
my $gotlock = 0;
while (1) {
print "Another exports update in progress, waiting for it to finish\n";
if (flock(LOCK, LOCK_EX|LOCK_NB) != 0) {
# OK, got the lock, we can do what we're supposed to
$gotlock = 1;
last;
}
$locktime = (stat(LOCK))[9];
if ($locktime != $oldlocktime) {
$oldlocktime = $locktime;
last;
}
if ($count++ > 60) {
fatal("Could not get the lock after a long time!\n");
}
sleep(1);
}
$count = 0;
#
# If we didn't get the lock, wait for the processes that did to finish
#
if (!$gotlock) {
while (1) {
if ((stat(LOCK))[9] != $oldlocktime) {
exit(0);
}
if (flock(LOCK, LOCK_EX|LOCK_NB) != 0) {
close(LOCK);
exit(0);
}
if ($count++ > 20) {
fatal("Process with the lock did not finish after ".
"a long time!\n");
}
sleep(1);
}
}
}
#
# Perl-style touch(1)
#
my $now = time;
utime $now, $now, $lockfile;
TBScriptLock("mkusercert") == 0 or
fatal("Could not get the lock!");
#
# Get the user info (the user being operated on).
#
my $query_result =
DBQueryFatal("select u.usr_name,u.usr_email,u.admin,u.unix_uid ".
"from users as u ".
"where u.uid='$user'");
if ($query_result->numrows == 0) {
fatal("$user is not in the DB. This is bad.\n");
}
my @row = $query_result->fetchrow_array();
my $fullname = $row[0];
my $user_email = $row[1];
my $usr_admin = $row[2];
my $user_number = $row[3];
my $fullname = $target_user->name();
my $user_email = $target_user->email();
my $usr_admin = $target_user->admin();
my $user_number = $target_user->uid_idx();
my $user_uid = $target_user->uid();
my $user_dbid = $target_user->dbid();
#
# Get the users earliest project membership to use as the default group
# for the case that the account is being (re)created. We convert that to
# the unix info.
#
my $default_groupname;
my $default_project;
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(\$default_project) < 0) {
fatal("Could not locate default 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($default_project)) {
$default_groupgid = $default_project->unix_gid();
}
else {
print "No group membership for $user; using the guest group!\n";
print "No group membership for $target_user; using the guest group!\n";
($default_groupname,undef,$default_groupgid,undef) = getgrnam("guest");
(undef,undef,$default_groupgid,undef) = getgrnam("guest");
}
#
......@@ -264,8 +211,8 @@ open(TEMP, ">>usercert.cnf")
or fatal("Could not open $TEMPLATE for append: $!");
print TEMP "OU\t\t= sslxmlrpc\n";
print TEMP "CN\t\t= $user\n";
print TEMP "emailAddress\t= $user" . "\@" . "$OURDOMAIN\n";
print TEMP "CN\t\t= $user_uid\n";
print TEMP "emailAddress\t= $user_uid" . "\@" . "$OURDOMAIN\n";
close(TEMP)
or fatal("Could not close usercert.cnf: $!");
......@@ -310,14 +257,15 @@ $UID = $SAVEUID;
# unencrypted). Might change that at some point.
#
DBQueryFatal("delete from user_sslcerts ".
"where uid='$user' and ".
"where uid_idx='$user_dbid' and ".
" encrypted=" . (defined($password) ? 1 : 0));
#
# Create a new entry in the table.
#
DBQueryFatal("insert into user_sslcerts (uid, idx, created, encrypted) ".
" values ('$user', $curidx, now(), ".
DBQueryFatal("insert into user_sslcerts ".
"(uid, uid_idx, idx, created, encrypted) values ".
"('$user_uid', '$user_dbid', $curidx, now(), ".
(defined($password) ? 1 : 0) . ")");
#
......@@ -353,7 +301,7 @@ close(PKEY);
$pkeystring = DBQuoteSpecial($pkeystring);
$certstring = DBQuoteSpecial($certstring);
DBQueryFatal("update user_sslcerts set cert=$certstring,privkey=$pkeystring ".
"where uid='$user' and idx=$curidx");
"where uid_idx='$user_dbid' and idx=$curidx");
#
# Combine the key and the certificate into one file which is installed
......@@ -366,7 +314,7 @@ system("cat usercert_key.pem usercert_cert.pem > usercert.pem") == 0
#
# Copy the certificate to the users .ssl directory.
#
my $ssldir = "$USERDIR/$user/.ssl";
my $ssldir = "$USERDIR/$user_uid/.ssl";
if (! -d $ssldir) {
mkdir($ssldir, 0700) or
fatal("Could not mkdir $ssldir: $!");
......@@ -390,12 +338,13 @@ system("cp -f usercert.pem $target") == 0
chown($user_number, $default_groupgid, "$target")
or fatal("Could not chown $target: $!");
close(LOCK);
TBScriptUnlock();
exit(0);
sub fatal($) {
my($mesg) = $_[0];
TBScriptUnlock();
die("*** $0:\n".
" $mesg\n");
}
......@@ -145,7 +145,7 @@ my $HOMEDIR = USERROOT();
#
# Rewrite audit version of ARGV to prevent password in mail logs.
#
if ($ARGV[0] eq "passwd" && scalar(@ARGV) == 3) {
if (scalar(@ARGV) == 3 && $ARGV[0] eq "passwd") {
my @NEWARGV = @ARGV;
$NEWARGV[scalar(@NEWARGV) - 1] = "**********";
......
#!/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 Experiment;
......@@ -130,6 +130,8 @@ sub dpdb($) { return field($_[0], 'dpdb');}
sub dpdbname($) { return field($_[0], 'dpdbname');}
sub dpdbpassword($) { return field($_[0], 'dpdbpassword');}
sub instance_idx($) { return field($_[0], 'instance_idx'); }
sub creator_idx($) { return field($_[0], 'creator_idx');}
sub swapper_idx($) { return field($_[0], 'swapper_idx');}
#
# Lookup an experiment given an experiment index.
......@@ -335,20 +337,21 @@ sub Create($$$$)
tberror("Error inserting experiment resources record for $pid/$eid!");
return undef;
}
my $rsrcidx = $query_result->insertid;
my $creator = $argref->{'expt_head_uid'};
my $gid = $argref->{'gid'};
my $batchmode = $argref->{'batchmode'};
my $rsrcidx = $query_result->insertid;
my $creator_uid = $argref->{'expt_head_uid'};
my $creator_idx = $argref->{'creator_idx'};
my $gid = $argref->{'gid'};
my $batchmode = $argref->{'batchmode'};
#
# Now create an experiment_stats record to match.
#
if (! DBQueryWarn("insert into experiment_stats ".
"(eid, pid, creator, gid, created, ".
"(eid, pid, creator, creator_idx, gid, created, ".
" batch, exptidx, rsrcidx) ".
"values('$eid', '$pid', '$creator', '$gid', ".
"FROM_UNIXTIME('$now'), ".
"$batchmode, $exptidx, $rsrcidx)")) {
"values('$eid', '$pid', '$creator_uid', '$creator_idx',".
" '$gid', FROM_UNIXTIME('$now'), ".
" $batchmode, $exptidx, $rsrcidx)")) {
DBQueryWarn("delete from experiments where pid='$pid' and eid='$eid'");
DBQueryWarn("delete from experiment_resources where idx=$rsrcidx");
DBQueryWarn("unlock tables");
......@@ -1103,7 +1106,7 @@ sub BackupUserData($)
#
sub SetSwapInfo($$)
{
my ($self, $dbuid) = @_;
my ($self, $user) = @_;
# Must be a real reference.
return -1
......@@ -1113,25 +1116,30 @@ sub SetSwapInfo($$)
my $eid = $self->eid();
TBSetExpSwapTime($pid, $eid);
TBExptSetSwapUID($pid, $eid, $dbuid);
$self->SetSwapper($user);
return $self->Refresh();
}
#
# Just the swap uid,
# Just the swap uid.
#
sub SetSwapper($$)
{
my ($self, $dbuid) = @_;
my ($self, $user) = @_;
# Must be a real reference.
return -1
if (! ref($self));
my $pid = $self->pid();
my $eid = $self->eid();
TBExptSetSwapUID($pid, $eid, $dbuid);
my $pid = $self->pid();
my $eid = $self->eid();
my $uid = $user->uid();
my $dbid = $user->dbid();
DBQueryWarn("update experiments set ".
" expt_swap_uid='$uid', swapper_idx='$dbid' ".
"where pid='$pid' and eid='$eid'");
return $self->Refresh();
}
......
......@@ -22,11 +22,41 @@ use Data::Dumper;
use File::Basename;
use overload ('""' => 'Stringify');
use Project;
use vars qw($NEWUSER_FLAGS_PROJLEADER $NEWUSER_FLAGS_WIKIONLY
$NEWUSER_FLAGS_WEBONLY $NEWUSER_FLAGS_ARCHIVED
$USERSTATUS_ACTIVE $USERSTATUS_FROZEN
$USERSTATUS_UNAPPROVED $USERSTATUS_UNVERIFIED
$USERSTATUS_NEWUSER $USERSTATUS_ARCHIVED
@EXPORT_OK);
# Configure variables
my $TB = "@prefix@";
my $BOSSNODE = "@BOSSNODE@";
my $CONTROL = "@USERNODE@";
my $TB = "@prefix@";
my $BOSSNODE = "@BOSSNODE@";
my $CONTROL = "@USERNODE@";
my $OURDOMAIN = "@OURDOMAIN@";
my $MIN_UNIX_UID = @MIN_UNIX_UID@;
my $MIN_UNIX_GID = @MIN_UNIX_GID@;
# Create() flags.
$NEWUSER_FLAGS_PROJLEADER = 0x01;
$NEWUSER_FLAGS_WIKIONLY = 0x02;
$NEWUSER_FLAGS_WEBONLY = 0x04;
$NEWUSER_FLAGS_ARCHIVED = 0x08;
# Status values.
$USERSTATUS_ACTIVE = "active";
$USERSTATUS_FROZEN = "frozen";
$USERSTATUS_UNAPPROVED = "unapproved";
$USERSTATUS_UNVERIFIED = "unverified";
$USERSTATUS_NEWUSER = "newuser";
$USERSTATUS_ARCHIVED = "archived";
# Why, why, why?
@EXPORT_OK = qw($NEWUSER_FLAGS_PROJLEADER $NEWUSER_FLAGS_WIKIONLY
$NEWUSER_FLAGS_WEBONLY $NEWUSER_FLAGS_ARCHIVED
$USERSTATUS_ACTIVE $USERSTATUS_FROZEN
$USERSTATUS_UNAPPROVED $USERSTATUS_UNVERIFIED
$USERSTATUS_NEWUSER $USERSTATUS_ARCHIVED);
# Cache of instances to avoid regenerating them.
my %users = ();
......@@ -171,6 +201,237 @@ sub LookupByUnixId($$)
return User->Lookup($uid_idx);
}
#
# Lookup user given a wikiname. This is just to make sure the wikiname
# the user picked is unique.
#
sub LookupByWikiName($$)
{
my ($class, $wikiname) = @_;
my $query_result =
DBQueryFatal("select uid_idx from users ".
"where wikiname='$wikiname'");
return undef
if (! $query_result || !$query_result->numrows);
my ($uid_idx) = $query_result->fetchrow_array();
return User->Lookup($uid_idx);
}
#
# Lookup user given a wikiname. This is just to make sure the wikiname
# the user picked is unique.
#
sub LookupByEmail($$)
{
my ($class, $email) = @_;
my $query_result =
DBQueryFatal("select uid_idx from users ".
"where LCASE(usr_email)=LCASE('$email')");
return undef
if (! $query_result || !$query_result->numrows);
my ($uid_idx) = $query_result->fetchrow_array();
return User->Lookup($uid_idx);
}
#
# Class function to create new user and return object.
#
sub Create($$$$)
{
my ($class, $uid, $flags, $argref) = @_;
my $isleader = ($flags & $NEWUSER_FLAGS_PROJLEADER ? 1 : 0);
my $wikionly = ($flags & $NEWUSER_FLAGS_WIKIONLY ? 1 : 0);
my $webonly = ($flags & $NEWUSER_FLAGS_WEBONLY ? 1 : 0);
my $archived = ($flags & $NEWUSER_FLAGS_ARCHIVED ? 1 : 0);
#
# If no uid, we need to generate a unique one for the user.
#
if (! $uid) {
#
# Take the first 5 letters of the email to form a root. That gives
# us 3 digits to make it unique, since unix uids are limited to 8
# chars, sheesh!
#
my $email = $argref->{'usr_email'};
my $token;
if ($email =~ /^([-\w\+\.]+)\@([-\w\.]+)$/) {
$token = $1;
}
else {
return undef;
}
# Squeeze out any dots or dashes.
$token =~ s/\.//g;
$token =~ s/\-//g;
# Trim off any trailing numbers or +foo tokens.
if ($token =~ /^([a-zA-Z]+)/) {
$token = $1;
}
else {
return undef;
}
# First 5 chars, at most.
$token = substr($token, 0, 5);
# Grab all root matches from the DB.
my $query_result =
DBQueryWarn("select uid from users where uid like '${token}%'");
return undef
if (!$query_result);
# Easy; no matches at all!
if (!$query_result->numrows) {
$uid = "$token" . "001";
}
else {
my $max = 0;
#
# Find unused slot. Must be a better way to do this!
#
while (my ($foo) = $query_result->fetchrow_array()) {
my $name;
my $number;
# Split name from number
if ($foo =~ /^([a-zA-Z]+)(\d*)$/) {
$name = $1;
$number = $2;
}
else {
return undef;
}
# Must be exact root
next
if ($name != $token);
# Backwards compatability; might not have appended number.
if (isset($number) && intval($number) > $max) {
$max = intval($number);
}
}
$max++;
$uid = $token . sprintf("%03d", $max);
}
}
#
# The array of inserts is assumed to be safe already. Generate
# a list of actual insert clauses to be joined below.
#
my @insert_data = (!defined($argref) ? () :
map("$_=" . DBQuoteSpecial($argref->{$_}),
keys(%{$argref})));
# Every user gets a new unique index.
my $uid_idx = TBGetUniqueIndex('next_uid');
#
# Get me an unused unix id.
#
my $unix_uid;
#
# Start here, and keep going if the one picked from the DB just
# happens to be in use (in the passwd file). Actually happens!
#
my $min_uid = $MIN_UNIX_UID;
while (! defined($unix_uid)) {
#
# Nice query, eh? Basically, find unused numbers by looking at
# existing numbers plus one, and check to see if that number
# is taken.
#