Commit 16aaa101 authored by Leigh B. Stoller's avatar Leigh B. Stoller

Move the bulk (or guts) of newuser and newproject from the web

interface to the backend. There are new scripts that can be called
from the command line:

	newuser xmlfile
	newproj xmlfile

They both run from small xmlfiles that are generated by the web
interface from the form data. I also moved user verification to the
backend so that we do not have duplicated email functions, but that
was a small change.

Upon error, the xmlfile is saved and sent to tbops so that we can
rerun the command by hand, rather then force user to fill out form
again. I also do a better job of putting the form back up intact when
there are internal errors.

If the user provides an initial public key, that is put into the xml
file as well and addpubkey is called from newuser instead of the web
interface. A more general change to addpukey is that it is now
*always* called as "nobody". This script was a morass of confusion
cause of having to call it as nobody before the user actually
exists. In fact, another of my ongoing projects is to reduce the
number of scripts called as a particular user, but thats a story for
another day. Anyway, the script is always called as nobody, but we
pass along the implied user in the environment so that it can do
permission checks.
parent a1cfdc36
#
# 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.
#
# Most of the configure-substitution magic is done here.
......@@ -37,6 +37,7 @@ TBAUDITEMAIL = @TBAUDITEMAIL@
TBACTIVEARCHIVE = @TBACTIVEARCHIVE@
TBUSERSARCHIVE = @TBUSERSARCHIVE@
TBERRORSEMAIL = @TBERRORSEMAIL@
TBAPPROVALEMAIL = @TBAPPROVALEMAIL@
BOSSNODE = @BOSSNODE@
USERNODE = @USERNODE@
FSNODE = @FSNODE@
......
......@@ -12,8 +12,10 @@ UNIFIED = @UNIFIED_BOSS_AND_OPS@
include $(OBJDIR)/Makeconf
SBIN_STUFF = tbacct addsfskey addpubkey mkusercert quotamail genpubkeys
LIBEXEC_STUFF = webtbacct webaddsfskey webaddpubkey webmkusercert
SBIN_STUFF = tbacct addsfskey addpubkey mkusercert quotamail genpubkeys \
newuser newproj
LIBEXEC_STUFF = webtbacct webaddsfskey webaddpubkey webmkusercert \
webnewuser webnewproj
# These scripts installed setuid, with sudo.
SETUID_BIN_SCRIPTS =
......
......@@ -171,22 +171,25 @@ if (defined($user)) {
$user_email = $target_user->email();
$user_dbid = $target_user->dbid();
$user_uid = $target_user->uid();
$USERUID = $user_uid;
}
#
# If invoked as "nobody" its for a user with no actual account.
# If invoked as "nobody" we came from the web interface. We have to have
# a credential in the environment, unless its just a key verification
# operation, which anyone can do.
#
if (getpwuid($UID) eq "nobody") {
if ($initmode || $genmode) {
fatal("Bad usage as 'nobody'");
$this_user = User->ImpliedUser();
if (($initmode || $genmode || !$verify) && !defined($this_user)) {
fatal("Bad usage from web interface");
}
$nobody = 1;
}
else {
$USERUID = getpwnam($user);
# Map invoking user to object.
$this_user = User->LookupByUnixId($UID);
# From the command line; map invoking user to object.
$this_user = User->ThisUser();
if (! defined($this_user)) {
fatal("You ($UID) do not exist!");
......@@ -194,16 +197,17 @@ else {
}
#
# Initmode or genmode, do it and exit.
# Initmode or genmode, do it and exit. Eventually get rid of the switch
# to the target user.
#
if ($initmode) {
# Drop root privs, switch to target user.
$EUID = $USERUID;
$EUID = $UID = $USERUID;
exit InitUser();
}
if ($genmode) {
# Drop root privs, switch to target user.
$EUID = $USERUID;
$EUID = $UID = $USERUID;
exit GenerateKeyFile();
}
......@@ -235,10 +239,12 @@ else {
#
if (!$verify) {
# If its the user himself, then we can generate a new authkeys file.
if (!$nobody && !TBAdmin() && !$target_user->SameUser($this_user)) {
if (!$target_user->SameUser($this_user) && !TBAdmin()) {
fatal("You are not allowed to set pubkeys for $target_user\n");
}
if (-d "$HOMEDIR/$user_uid/.ssh") {
# Drop root privs, switch to target user.
$EUID = $UID = $USERUID;
$genmode = 1;
}
......@@ -250,11 +256,6 @@ if (!$verify) {
AuditStart(0);
}
# Drop root privs, switching to user.
if (!$nobody) {
$EUID = $USERUID;
}
#
# Grab the first line of the file. Parse it to see if its in the
# format we like (openssh), either protocol 1 or 2.
......
#!/usr/bin/perl -wT
#
# EMULAB-COPYRIGHT
# Copyright (c) 2000-2007 University of Utah and the Flux Group.
# All rights reserved.
#
use English;
use strict;
use Getopt::Std;
use XML::Simple;
use Data::Dumper;
#
# Create a new user from a XML description.
#
sub usage()
{
print("Usage: newproj <xmlfile>\n");
print(" newproj -m <pid_idx>\n");
exit(-1);
}
my $optlist = "dm:";
my $debug = 1;
my $resend;
#
# Configure variables
#
my $TB = "@prefix@";
my $TBOPS = "@TBOPSEMAIL@";
my $TBAPPROVAL = "@TBAPPROVALEMAIL@";
my $TBAUDIT = "@TBAUDITEMAIL@";
my $TBBASE = "@TBBASE@";
my $TBWWW = "@TBWWW@";
#
# This script is setuid, so please do not run it as root. Hard to track
# what has happened.
#
if ($EUID == 0) {
die("*** $0:\n".
" Please do not run this as root!\n");
}
#
# Untaint the path
#
$ENV{'PATH'} = "$TB/bin:$TB/sbin:/bin:/usr/bin:/usr/bin:/usr/sbin";
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
#
# Turn off line buffering on output
#
$| = 1;
#
# Load the Testbed support stuff.
#
use lib "@prefix@/lib";
use libdb;
use libtestbed;
use Project;
use User;
# Protos
sub fatal($);
sub UserError($);
# Locals
my $SAVEUID = $UID;
my $xmlfile;
#
# Parse command arguments. Once we return from getopts, all that should be
# left are the required arguments.
#
my %options = ();
if (! getopts($optlist, \%options)) {
usage();
}
if (defined($options{"d"})) {
$debug = 1;
}
if (defined($options{"m"})) {
$resend = $options{"m"};
if ($resend =~ /^(\d*)$/) {
$resend = $1;
}
else {
fatal("Bad characters in -m option: $resend");
}
}
else {
usage()
if (@ARGV != 1);
$xmlfile = shift(@ARGV);
}
#
# Map invoking user to object.
# If invoked as "nobody" we are coming from the web interface and there
# is no current user context.
#
my $this_user;
if (getpwuid($UID) ne "nobody") {
$this_user = User->ThisUser();
if (! defined($this_user)) {
fatal("You ($UID) do not exist!");
}
fatal("You must have admin privledges to create new projects!")
if (!TBAdmin());
}
#
# Resend email message and exit.
#
if (defined($resend)) {
my $project = Project->Lookup($resend);
fatal("Could not map project $resend to object!")
if (!defined($project));
exit($project->SendNewProjectEmail());
}
#
# Check the filename when invoked from the web interface; must be a
# file in /tmp.
#
if (! defined($this_user)) {
if ($xmlfile =~ /^([-\w\.\/]+)$/) {
$xmlfile = $1;
}
else {
fatal("Bad data in pathname: $xmlfile");
}
# Use realpath to resolve any symlinks.
my $translated = `realpath $xmlfile`;
if ($translated =~ /^(\/tmp\/[-\w\.\/]+)$/) {
$xmlfile = $1;
}
else {
fatal("Bad data in translated pathname: $xmlfile");
}
}
#
# These are the fields that we allow to come in from the XMLfile.
#
my %required = ("name" => "pid",
"leader" => "head_uid",
"short description" => "name",
"URL" => "URL",
"funders" => "funders",
"long description" => "why",
"public" => "public",
"num_pcs" => "num_pcs",
"linkedtous" => "linked_to_us");
my %optional = ("members" => "num_members",
"ron" => "num_ron",
"plab" => "num_pcplab",
"whynotpublic" => "public_whynot",
"user_interface" => "default_user_interface");
#
# This script is not audited cause we want the output to be sent back
# to the web interface. Thats okay since we send email from the script
# anyway.
#
#
# Must wrap the parser in eval since it exits on error.
#
my $xmlparse = eval { XMLin($xmlfile,
VarAttr => 'name',
ContentKey => '-content',
SuppressEmpty => undef); };
fatal($@)
if ($@);
#
# Make sure all the required arguments were provided.
#
foreach my $key (keys(%required)) {
fatal("Missing required attribute '$key'")
if (! exists($xmlparse->{'attribute'}->{"$key"}));
}
#
# We build up an array of arguments to pass to User->Create() as we check
# the attributes.
#
my %newproj_args = ();
foreach my $key (keys(%{ $xmlparse->{'attribute'} })) {
my $value = $xmlparse->{'attribute'}->{"$key"}->{'value'};
if ($debug) {
print STDERR "Project attribute: '$key' -> '$value'\n";
}
my $dbslot;
# Must be in the allowed lists above, with exceptions handled below
if (exists($required{$key})) {
$dbslot = $required{$key};
next
if (!defined($dbslot));
fatal("Null value for required field $key")
if (!defined($value));
}
elsif (exists($optional{$key})) {
$dbslot = $optional{$key};
next
if (!defined($dbslot) || !defined($value));
}
else {
fatal("Invalid attribute in XML: '$key' -> '$value'\n");
}
# Now check that the value is legal.
if (! TBcheck_dbslot($value, "projects", $dbslot,
TBDB_CHECKDBSLOT_WARN|TBDB_CHECKDBSLOT_ERROR)) {
UserError("Illegal data: $key - $value");
}
#
# Do a taint check to avoid warnings, since the values are abviously okay.
#
if ($value =~ /^(.*)$/) {
$value = $1;
}
$newproj_args{$dbslot} = $value;
}
#
# Now do special checks.
#
UserError("Project already exists; pick another name!")
if (Project->Lookup($newproj_args{'pid'}));
my $leader = User->Lookup($newproj_args{'head_uid'});
UserError("Project leader does not exist!")
if (!defined($leader));
#
# Now safe to create the project. Move the pid out of the argument array
# since its actually an argument to the Create() routine. Ditto for the
# project leader.
#
my $new_pid = $newproj_args{'pid'};
delete($newproj_args{'pid'});
delete($newproj_args{'head_uid'});
my $newproj = Project->Create($new_pid, $leader, \%newproj_args);
if (!defined($newproj)) {
fatal("Could not create new project!");
}
my $new_idx = $newproj->pid_idx();
#
# See if we are in an initial Emulab setup. If so, no email sent.
#
my $firstinitstate;
TBGetSiteVar("general/firstinit/state", \$firstinitstate);
#
# Send the email notification.
#
$newproj->SendNewProjectEmail($firstinitstate eq "createproject");
# The web interface requires this line to be printed!
print "User $new_pid/$new_idx has been created\n";
exit(0);
sub fatal($) {
my($mesg) = $_[0];
print STDERR "*** $0:\n".
" $mesg\n";
exit(-1);
}
sub UserError($) {
my($mesg) = $_[0];
print $mesg;
exit(1);
}
sub escapeshellarg($)
{
my ($str) = @_;
$str =~ s/(')/'\\''/g;
return $str;
}
#!/usr/bin/perl -wT
#
# EMULAB-COPYRIGHT
# Copyright (c) 2000-2007 University of Utah and the Flux Group.
# All rights reserved.
#
use English;
use strict;
use Getopt::Std;
use XML::Simple;
use Data::Dumper;
#
# Create a new user from a XML description.
#
sub usage()
{
print("Usage: newuser -t <type> <xmlfile>\n");
exit(-1);
}
my $optlist = "dt:";
my $debug = 1;
my $type = "";
#
# Configure variables
#
my $TB = "@prefix@";
my $TBOPS = "@TBOPSEMAIL@";
my $TBAPPROVAL = "@TBAPPROVALEMAIL@";
my $TBAUDIT = "@TBAUDITEMAIL@";
my $TBBASE = "@TBBASE@";
my $TBWWW = "@TBWWW@";
my $WIKISUPPORT = @WIKISUPPORT@;
my $TBADMINGROUP= "@TBADMINGROUP@";
my $checkpass = "$TB/libexec/checkpass";
my $addpubkey = "$TB/sbin/addpubkey";
#
# This script is setuid, so please do not run it as root. Hard to track
# what has happened.
#
if ($EUID == 0) {
die("*** $0:\n".
" Please do not run this as root!\n");
}
#
# Untaint the path
#
$ENV{'PATH'} = "$TB/bin:$TB/sbin:/bin:/usr/bin:/usr/bin:/usr/sbin";
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
#
# Turn off line buffering on output
#
$| = 1;
#
# Load the Testbed support stuff.
#
use lib "@prefix@/lib";
use libdb;
use libtestbed;
use User;
# Protos
sub fatal($);
sub UserError($);
sub escapeshellarg($);
# Locals
my $SAVEUID = $UID;
my $keyfile;
#
# Parse command arguments. Once we return from getopts, all that should be
# left are the required arguments.
#
my %options = ();
if (! getopts($optlist, \%options)) {
usage();
}
if (defined($options{"d"})) {
$debug = 1;
}
if (defined($options{"t"})) {
$type = $options{"t"};
}
if (@ARGV != 1) {
usage();
}
my $xmlfile = shift(@ARGV);
#
# Map invoking user to object.
# If invoked as "nobody" we are coming from the web interface and there
# is no current user context.
#
my $this_user;
if (getpwuid($UID) ne "nobody") {
$this_user = User->ThisUser();
if (! defined($this_user)) {
fatal("You ($UID) do not exist!");
}
fatal("You must have admin privledges to create new users")
if (!TBAdmin());
}
else {
#
# Check the filename when invoked from the web interface; must be a
# file in /tmp.
#
if ($xmlfile =~ /^([-\w\.\/]+)$/) {
$xmlfile = $1;
}
else {
fatal("Bad data in pathname: $xmlfile");
}
# Use realpath to resolve any symlinks.
my $translated = `realpath $xmlfile`;
if ($translated =~ /^(\/tmp\/[-\w\.\/]+)$/) {
$xmlfile = $1;
}
else {
fatal("Bad data in translated pathname: $xmlfile");
}
}
#
# These are the fields that we allow to come in from the XMLfile.
#
my %required = ("name" => "usr_name",
"email" => "usr_email",
"address" => "usr_addr",
"city" => "usr_city",
"state" => "usr_state",
"zip" => "usr_zip",
"country" => "usr_country",
"phone" => "usr_phone",
"title" => "usr_title",
"affiliation" => "usr_affil",
"password" => undef,
"wikiname" => "wikiname");
my %optional = ("login" => "uid",
"address2" => "usr_addr2",
"URL" => "usr_URL",
"shell" => "usr_shell",
"pubkey" => undef);
#
# Must wrap the parser in eval since it exits on error.
#
my $xmlparse = eval { XMLin($xmlfile,
VarAttr => 'name',
ContentKey => '-content',
SuppressEmpty => undef); };
fatal($@)
if ($@);
#
# Make sure all the required arguments were provided.
#
foreach my $key (keys(%required)) {
fatal("Missing required attribute '$key'")
if (! exists($xmlparse->{'attribute'}->{"$key"}));
}
#
# We build up an array of arguments to pass to User->Create() as we check
# the attributes.
#
my %newuser_args = ();
foreach my $key (keys(%{ $xmlparse->{'attribute'} })) {
my $value = $xmlparse->{'attribute'}->{"$key"}->{'value'};
if ($debug) {
print STDERR "User attribute: '$key' -> '$value'\n";
}
my $dbslot;
# Must be in the allowed lists above, with exceptions handled below
if (exists($required{$key})) {
$dbslot = $required{$key};
next
if (!defined($dbslot));
fatal("Null value for required field $key")
if (!defined($value));
}
elsif (exists($optional{$key})) {
$dbslot = $optional{$key};
next
if (!defined($dbslot) || !defined($value));
}
else {
fatal("Invalid attribute in XML: '$key' -> '$value'\n");
}
# Now check that the value is legal.
if (! TBcheck_dbslot($value, "users", $dbslot,
TBDB_CHECKDBSLOT_WARN|TBDB_CHECKDBSLOT_ERROR)) {
UserError("Illegal data: $key - $value");
}
#
# Do a taint check to avoid warnings, since the values are abviously okay.
#
if ($value =~ /^(.*)$/) {
$value = $1;
}
$newuser_args{$dbslot} = $value;
}
#
# Now do special checks.
#
#
# If user selected his own uid, must be unique.
#
if (exists($newuser_args{'uid'})) {
UserError("User already exists; pick another login name!")
if (User->Lookup($newuser_args{'uid'}));
UserError("Reserved user name; pick another login name!")
if (getpwnam($newuser_args{'uid'}));
}
#
# User name must be at least two tokens.
#