Commit 4998b2d7 authored by Leigh Stoller's avatar Leigh Stoller

Call this commit "Snow in Corvallis" ...

The major functional change in this revision is converting from user
selected UIDs to system selected UIDs. This is controlled by the
variable $USERSELECTUIDS in defs/defs.php3.in which is now set to
zero, so system selected UIDs is the default.

The algo for creating the uid is to take the email address, strip the
@whatever from it, squeeze out dots and dashes and underlines, and
make sure any +foo tokens are removed. Then make sure it is unique by
taking the first 5 characters and then adding a 3 digit number,
derived by checking the DB to see what exists.

Since we will want to (more often) change the UID selected, there is a
new admin only menu option on the Show User page. It calls the backend
script to do the work (sbin/changeuid).

The login page now defaults to storing and showing the email address
for login, rather then the UID. It will still accept either one though
(has for a long time).

Along the way I also reorg'ed a number of pages to use the new user,
group, and project classes and moved some common functionality into
the class defs.

Also changed the way addpubkey is called, to avoid some confusion.
parent 488236f2
......@@ -16,17 +16,18 @@ use Getopt::Std;
#
sub usage()
{
print "Usage: addpubkeys [-n] [-k] <user> [<keyfile> | <key>]\n";
print " addpubkeys [-i [-f] | -w] <user>\n";
print "Usage: addpubkeys [-k | -f] [-n | -u <user>] [<keyfile> | <key>]\n";
print " addpubkeys [-i [-r] | -w] <user>\n";
print "Options:\n";
print " -k Indicates that key was passed in on the command line\n";
print " -f Indicates that key was passed in as a filename\n";
print " -n Verify key format only; do not enter into into DB\n";
print " -w Generate new authkeys (protocol 1 and 2) file for user\n";
print " -i Initialize mode; generate initial key for user\n";
print " -f Force a generate of initial key for user\n";
print " -r Force a regenerate of initial key for user\n";
exit(-1);
}
my $optlist = "kniwf";
my $optlist = "kniwfu:r";
my $iskey = 0;
my $verify = 0;
my $initmode = 0;
......@@ -110,42 +111,52 @@ if (! getopts($optlist, \%options)) {
if (defined($options{"k"})) {
$iskey = 1;
}
if (defined($options{"f"})) {
$iskey = 0;
}
if (defined($options{"n"})) {
$verify = 1;
}
if (defined($options{"i"})) {
$initmode = 1;
}
if (defined($options{"f"})) {
if (defined($options{"r"})) {
$force = 1;
}
if (defined($options{"w"})) {
$genmode = 1;
}
if (defined($options{"u"})) {
$user = $options{"u"};
}
if ($verify && $genmode) {
usage();
}
if ($initmode || $genmode) {
if (@ARGV != 1) {
usage();
}
}
elsif (@ARGV == 2) {
$keyfile = $ARGV[1];
usage()
if (@ARGV != 1);
$user = $ARGV[0];
}
else {
usage();
}
$user = $ARGV[0];
usage()
if (@ARGV != 1);
usage()
if (!$verify && !defined($user));
$keyfile = $ARGV[0];
}
#
# Untaint the arguments.
#
if ($user =~ /^([-\w_]+)$/i) {
$user = $1;
}
else {
fatal("Tainted username: $user");
if (defined($user)) {
if ($user =~ /^([-\w_]+)$/i) {
$user = $1;
}
else {
fatal("Tainted username: $user");
}
}
#
......
......@@ -18,9 +18,10 @@ SBIN_SCRIPTS = avail inuse showgraph if2port backup webcontrol node_status \
idletimes idlemail setsitevar audit changeuid changepid \
elabinelab_bossinit update_permissions mysqld_watchdog \
dumperrorlog
LIBEXEC_SCRIPTS = webnodelog webnfree webnewwanode webidlemail xmlconvert
LIBEXEC_SCRIPTS = webnodelog webnfree webnewwanode webidlemail xmlconvert \
webchangeuid
LIB_SCRIPTS = libdb.pm Node.pm libdb.py libadminctrl.pm Experiment.pm \
NodeType.pm Interface.pm User.pm Group.pm Project.pm
NodeType.pm Interface.pm User.pm Group.pm Project.pm
# Stuff installed on plastic.
USERSBINS = genelists.proxy dumperrorlog.proxy
......
#!/usr/bin/perl -w
#!/usr/bin/perl -wT
#
# EMULAB-COPYRIGHT
# Copyright (c) 2004 University of Utah and the Flux Group.
# Copyright (c) 2004, 2006 University of Utah and the Flux Group.
# All rights reserved.
#
use English;
......@@ -32,9 +32,6 @@ use libaudit;
use libdb;
use libtestbed;
# Be careful not to exit on transient error
$libdb::DBQUERY_MAXTRIES = 30;
#
# Turn off line buffering on output
#
......@@ -46,14 +43,6 @@ $| = 1;
$ENV{'PATH'} = "/bin:/sbin:/usr/bin:/usr/sbin";
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
#
# We don't want to run this script unless its the real version.
#
#if ($EUID != 0) {
# die("*** $0:\n".
# " Must be setuid! Maybe its a development version?\n");
#}
#
# This script is setuid, so please do not run it as root. Hard to track
# what has happened.
......@@ -187,3 +176,6 @@ DBQueryFatal("delete from login where uid='$olduid'");
#
print "Updating users table ...\n";
DBQueryFatal("update users set uid='$newuid' where uid='$olduid'");
exit(0);
<?php
#
# EMULAB-COPYRIGHT
# Copyright (c) 2000-2003, 2005, 2006 University of Utah and the Flux Group.
# All rights reserved.
#
include("defs.php3");
include("showstuff.php3");
#
# Only admin users ...
#
$uid = GETLOGIN();
LOGGEDINORDIE($uid);
$isadmin = ISADMIN($uid);
if (!$isadmin) {
USERERROR("You do not have permission to login names!", 1);
}
#
# Verify page/form arguments. Note that the target uid comes initially as a
# page arg, but later as a form argument, hence this odd check.
#
if (! isset($_POST['submit'])) {
# First page load. Default to current user.
if (! isset($_GET['target_uid']))
$target_uid = $uid;
else
$target_uid = $_GET['target_uid'];
}
else {
# Form submitted. Make sure we have a target_uid and a new_uid.
if (! isset($_POST['target_uid']) || $_POST['target_uid'] == "" ||
! isset($_POST['new_uid']) || $_POST['new_uid'] == "") {
PAGEARGERROR("Invalid form arguments.");
}
$target_uid = $_POST['target_uid'];
$new_uid = $_POST['new_uid'];
}
# Pedantic check of uid before continuing.
if ($target_uid == "" || !TBvalid_uid($target_uid)) {
PAGEARGERROR("Invalid uid: '$target_uid'");
}
# Find user. Must be unapproved (verified user). Any other state is too hard.
if (! ($user = User::LookupByUid($target_uid))) {
USERERROR("The user $target_uid is not a valid user", 1);
}
if ($user->status() != TBDB_USERSTATUS_UNAPPROVED) {
USERERROR("The user $target_uid must be ".
"unapproved (but verified) to change!", 1);
}
function SPITFORM($user, $new_uid, $error)
{
global $TBDB_UIDLEN;
$target_uid = $user->uid();
#
# Standard Testbed Header.
#
PAGEHEADER("Change login UID for user");
if ($error) {
echo "<center>
<font size=+1 color=red>$error</font>
</center><br>\n";
}
else {
echo "<center>
<font size=+1>
Please enter the new UID for user '$target_uid'<br><br>
</font>
</center>\n";
}
echo "<table align=center border=1>
<form action=changeuid.php method=post>
<tr>
<td>New UID:</td>
<td><input type=text
name=\"new_uid\"
value=\"$new_uid\"
size=$TBDB_UIDLEN
maxlength=$TBDB_UIDLEN></td>
</tr>
<tr>
<td align=center colspan=2>
<b><input type=submit value=\"Change UID\"
name=submit></b>
</td>
</tr>
<input type=hidden name=target_uid value=$target_uid>
</form>
</table>\n";
echo "<br><br>\n";
echo "<center>\n";
SHOWUSER($target_uid);
echo "</center>\n";
PAGEFOOTER();
return;
}
#
# If not clicked, then put up a form.
#
if (! isset($_POST['submit'])) {
SPITFORM($user, "", null);
return;
}
# Sanity checks
$error = null;
if (!TBvalid_uid($new_uid)) {
$error = "UID: " . TBFieldErrorString();
}
elseif (User::LookupByUid($new_uid) || posix_getpwnam($new_uid)) {
$error = "UID: Already in use. Pick another";
}
if ($error) {
SPITFORM($user, $new_uid, $error);
return;
}
#
# Standard Testbed Header.
#
PAGEHEADER("Change login UID for user");
# Okay, call out to backend to change.
STARTBUSY("Changing UID");
#
# Run the backend script.
#
SUEXEC($uid, $TBADMINGROUP,
"webchangeuid $target_uid $new_uid", SUEXEC_ACTION_USERERROR);
# Stop the busy indicator and zap to user page.
STOPBUSY();
PAGEREPLACE("showuser.php3?target_uid=$new_uid");
#
# Standard Testbed Footer
#
PAGEFOOTER();
?>
......@@ -36,6 +36,7 @@ $EXPOSELINKTEST = 1;
$EXPOSESTATESAVE= 0;
$EXPOSEARCHIVE = 0;
$EXPOSETEMPLATES= 0;
$USERSELECTUIDS = 0;
$TBMAILADDR_OPS = "@TBOPSEMAIL_NOSLASH@";
$TBMAILADDR_WWW = "@TBWWWEMAIL_NOSLASH@";
......@@ -81,6 +82,7 @@ if ($TBSCRATCH_DIR) {
$TBAUTHCOOKIE = "HashCookie" . $TBCOOKIESUFFIX;
$TBNAMECOOKIE = "MyUidCookie" . $TBCOOKIESUFFIX;
$TBEMAILCOOKIE = "MyEmailCookie" . $TBCOOKIESUFFIX;
$TBLOGINCOOKIE = "LoginCookie" . $TBCOOKIESUFFIX;
$HTTPTAG = "http://";
......@@ -161,6 +163,8 @@ function TBERROR ($message, $death, $xmp = 0) {
global $session_interactive, $session_errorhandler;
$script = urldecode($_SERVER['REQUEST_URI']);
CLEARBUSY();
TBMAIL($TBMAIL_OPS,
"WEB ERROR REPORT",
"\n".
......@@ -190,6 +194,8 @@ function USERERROR($message, $death) {
global $TBMAILADDR;
global $session_interactive, $session_errorhandler;
CLEARBUSY();
if (! $session_interactive) {
if ($session_errorhandler)
$session_errorhandler($message, $death);
......@@ -339,7 +345,7 @@ function ADDPUBKEY($uid, $cmdandargs) {
# nobody. We will get audit email in case we need to track what has
# happened.
#
if (! HASREALACCOUNT($uid)) {
if (!$uid || !HASREALACCOUNT($uid)) {
$uid = "nobody";
}
return SUEXEC($uid, "nobody", $cmdandargs, 0);
......
......@@ -7,6 +7,7 @@
class Group
{
var $group;
var $project;
#
# Constructor by lookup on unique index.
......@@ -22,7 +23,8 @@ class Group
$this->group = NULL;
return;
}
$this->group = mysql_fetch_array($query_result);
$this->group = mysql_fetch_array($query_result);
$this->project = null;
}
# Hmm, how does one cause an error in a php constructor?
......@@ -82,6 +84,19 @@ class Group
return 0;
}
#
# Load the project for a group lazily.
#
function LoadProject() {
$pid_idx = $this->pid_idx();
if (! ($project = Project::Lookup($pid_idx))) {
TBERROR("Group::LoadProject: Could not load project $pid_idx!", 1);
}
$this->project = $project;
return 0;
}
# accessors
function field($name) {
return (is_null($this->group) ? -1 : $this->group[$name]);
......@@ -108,6 +123,17 @@ class Group
global $TBBASE, $TBMAIL_APPROVAL, $TBMAIL_AUDIT, $TBMAIL_WWW;
global $MIN_UNIX_GID;
#
# Check that we can guarantee uniqueness of the unix group name.
#
$query_result =
DBQueryFatal("select gid from groups ".
"where unix_name='$unix_name'");
if (mysql_num_rows($query_result)) {
TBERROR("Could not form a unique Unix group name: $unix_name!", 1);
}
# Every group gets a new unique index.
$gid_idx = TBGetUniqueIndex('next_gid');
......@@ -228,6 +254,69 @@ class Group
return 0;
}
#
# Notify leaders of new (and verified) group member.
#
function NewMemberNotify($user) {
global $TBWWW, $TBMAIL_APPROVAL, $TBMAIL_AUDIT, $TBMAIL_WWW;
if (! $this->project) {
$this->LoadProject();
}
$project = $this->project;
$pid = $project->pid();
$gid = $project->gid();
$leader = $project->Leader();
$leader_name = $leader->name();
$leader_email = $leader->email();
$leader_uid = $leader->uid();
$allleaders = TBLeaderMailList($pid, $gid);
$joining_uid = $user->uid();
$usr_title = $user->title();
$usr_name = $user->name();
$usr_affil = $user->affil();
$usr_email = $user->email();
$usr_addr = $user->addr();
$usr_addr2 = $user->addr2();
$usr_city = $user->city();
$usr_state = $user->state();
$usr_zip = $user->zip();
$usr_country = $user->country();
$usr_phone = $user->phone();
$usr_URL = $user->URL();
TBMAIL("$leader_name '$leader_uid' <$leader_email>",
"$joining_uid $pid Project Join Request",
"$usr_name is trying to join your group $gid in project $pid.\n".
"\n".
"Contact Info:\n".
"Name: $usr_name\n".
"Emulab ID: $joining_uid\n".
"Email: $usr_email\n".
"User URL: $usr_URL\n".
"Job Title: $usr_title\n".
"Affiliation: $usr_affil\n".
"Address 1: $usr_addr\n".
"Address 2: $usr_addr2\n".
"City: $usr_city\n".
"State: $usr_state\n".
"ZIP/Postal Code: $usr_zip\n".
"Country: $usr_country\n".
"Phone: $usr_phone\n".
"\n".
"Please return to $TBWWW,\n".
"log in, and select the 'New User Approval' page to enter your\n".
"decision regarding $usr_name's membership in your project.\n\n".
"Thanks,\n".
"Testbed Operations\n",
"From: $TBMAIL_APPROVAL\n".
"Cc: $allleaders\n".
"Bcc: $TBMAIL_AUDIT\n".
"Errors-To: $TBMAIL_WWW");
return 0;
}
#
# Check if user is a member of this group.
#
......
......@@ -37,6 +37,7 @@ else {
if (!isset($forwikionly)) {
$forwikionly = 0;
}
unset($addpubkeyargs);
$ACCOUNTWARNING =
"Before continuing, please make sure your username " .
......@@ -55,7 +56,7 @@ function SPITFORM($formfields, $returning, $errors)
{
global $TBDB_UIDLEN, $TBDB_PIDLEN, $TBDB_GIDLEN;
global $ACCOUNTWARNING, $EMAILWARNING;
global $WIKISUPPORT, $forwikionly, $WIKIHOME;
global $WIKISUPPORT, $forwikionly, $WIKIHOME, $USERSELECTUIDS;
if ($forwikionly)
PAGEHEADER("Wiki Registration");
......@@ -146,29 +147,31 @@ function SPITFORM($formfields, $returning, $errors)
"method=post>\n";
if (! $returning) {
#
# UserName:
#
echo "<tr>
<td colspan=2>*<a
href='docwrapper.php3?docname=security.html'
target=_blank>Username</a>
(alphanumeric, lowercase):</td>
<td class=left>
<input type=text
name=\"formfields[joining_uid]\"
value=\"" . $formfields[joining_uid] . "\"
size=$TBDB_UIDLEN
onchange=\"alert('$ACCOUNTWARNING')\"
maxlength=$TBDB_UIDLEN>
</td>
</tr>\n";
if ($USERSELECTUIDS) {
#
# UID.
#
echo "<tr>
<td colspan=2>*<a
href='docwrapper.php3?docname=security.html'
target=_blank>Username</a>
(alphanumeric, lowercase):</td>
<td class=left>
<input type=text
name=\"formfields[joining_uid]\"
value=\"" . $formfields[joining_uid] . "\"
size=$TBDB_UIDLEN
onchange=\"alert('$ACCOUNTWARNING')\"
maxlength=$TBDB_UIDLEN>
</td>
</tr>\n";
}
#
# Full Name
#
echo "<tr>
<td colspan=2>*Full Name:</td>
<td colspan=2>*Full Name (first and last):</td>
<td class=left>
<input type=text
name=\"formfields[usr_name]\"
......@@ -341,6 +344,7 @@ function SPITFORM($formfields, $returning, $errors)
<td class=left>
<input type=password
name=\"formfields[password1]\"
value=\"$formfields[password1]\"
size=8></td>
</tr>\n";
......@@ -349,6 +353,7 @@ function SPITFORM($formfields, $returning, $errors)
<td class=left>
<input type=password
name=\"formfields[password2]\"
value=\"$formfields[password2]\"
size=8></td>
</tr>\n";
}
......@@ -501,16 +506,18 @@ $errors = array();
# These fields are required!
#
if (! $returning) {
if (!isset($formfields[joining_uid]) ||
strcmp($formfields[joining_uid], "") == 0) {
$errors["Username"] = "Missing Field";
}
elseif (!TBvalid_uid($formfields[joining_uid])) {
$errors["UserName"] = TBFieldErrorString();
}
elseif (TBCurrentUser($formfields[joining_uid]) ||
posix_getpwnam($formfields[joining_uid])) {
$errors["UserName"] = "Already in use. Pick another";
if ($USERSELECTUIDS) {
if (!isset($formfields[joining_uid]) ||
strcmp($formfields[joining_uid], "") == 0) {
$errors["Username"] = "Missing Field";
}
elseif (!TBvalid_uid($formfields[joining_uid])) {
$errors["UserName"] = TBFieldErrorString();
}
elseif (TBCurrentUser($formfields[joining_uid]) ||
posix_getpwnam($formfields[joining_uid])) {
$errors["UserName"] = "Already in use. Pick another";
}
}
if (!isset($formfields[usr_name]) ||
strcmp($formfields[usr_name], "") == 0) {
......@@ -561,14 +568,8 @@ if (! $returning) {
$errors["Email Address"] = TBFieldErrorString();
}
elseif (TBCurrentEmail($formfields[usr_email])) {
#
# Treat this error separate. Not allowed.
#
PAGEHEADER("Apply for Project Membership");
USERERROR("The email address '$formfields[usr_email]' is already in ".
"use by another user.<br>Perhaps you have ".
"<a href='password.php3?email=$formfields[usr_email]'>".
"forgotten your username.</a>", 1);
$errors["Email Address"] =
"Already in use. <b>Did you forget to login?</b>";
}
if (! $forwikionly) {
if (isset($formfields[usr_URL]) &&
......@@ -636,74 +637,49 @@ if (! $returning) {
elseif (strcmp($formfields[password1], $formfields[password2])) {
$errors["Confirm Password"] = "Does not match Password";
}
elseif (! CHECKPASSWORD($formfields[joining_uid],
elseif (! CHECKPASSWORD(($USERSELECTUIDS ?
$formfields[joining_uid] : "ignored"),
$formfields[password1],
$formfields[usr_name],
$formfields[usr_email], $checkerror)) {
$errors["Password"] = "$checkerror";
}
}
if (!$forwikionly && (!isset($formfields[pid]) ||
strcmp($formfields[pid], "") == 0)) {
$errors["Project Name"] = "Missing Field";
if (!$forwikionly) {
if (!isset($formfields[pid]) || $formfields[pid] == "") {
$errors["Project Name"] = "Missing Field";
}
else {
# Confirm pid/gid early to avoid spamming the page.
$pid = $formfields[pid];
if (isset($formfields[gid]) && $formfields[gid] != "") {
$gid = $formfields[gid];
}
else {
$gid = $pid;
}
if (!TBvalid_pid($pid) || !TBValidProject($pid)) {
$errors["Project Name"] = "Invalid Project Name";
}
elseif (!TBvalid_gid($gid) || !TBValidGroup($pid, $gid)) {
$errors["Group Name"] = "Invalid Group Name";
}
}
}
# Present these errors before we call out to do pubkey stuff; saves work.
if (count($errors)) {
SPITFORM($formfields, $returning, $errors);
PAGEFOOTER();
return;
}
#
# Certain of these values must be escaped or otherwise sanitized.
#
if (!$returning) {
$joining_uid = $formfields[joining_uid];
$usr_name = addslashes($formfields[usr_name]);
$usr_email = $formfields[usr_email];
$password1 = $formfields[password1];
$password2 = $formfields[password2];
$wikiname = ($WIKISUPPORT ? $formfields[wikiname] : "");
if (!$forwikionly) {
$usr_affil = addslashes($formfields[usr_affil]);
$usr_title = addslashes($formfields[usr_title]);
$usr_addr = addslashes($formfields[usr_addr]);
$usr_city = addslashes($formfields[usr_city]);
$usr_state = addslashes($formfields[usr_state]);
$usr_zip = addslashes($formfields[usr_zip]);
$usr_country = addslashes($formfields[usr_country]);
$usr_phone = $formfields[usr_phone];
}
else {
$usr_affil = "";
$usr_title = "";
$usr_addr = "";
$usr_city = "";
$usr_state = "";
$usr_zip = "";
$usr_country = "";
$usr_phone = "";
}
if (! isset($formfields[usr_URL]) ||
strcmp($formfields[usr_URL], "") == 0 ||
strcmp($formfields[usr_URL], $HTTPTAG) == 0) {
$usr_URL = "";
}
else {
$usr_URL = addslashes($formfields[usr_URL]);
}
if (! isset($formfields[usr_addr2])) {
$usr_addr2 = "";
}
else {
$usr_addr2 = addslashes($formfields[usr_addr2]);
}
# Okay, do pubkey checks.
if (!$returning && !$forwikionly) {
#
# Pub Key.
# Pub key provided in form (paste in).
#
if (isset($formfields[usr_key]) &&
strcmp($formfields[usr_key], "")) {
......@@ -720,7 +696,17 @@ if (!$returning) {
$formfields[usr_key] =
ereg_replace("[\n]", "", $formfields[usr_key]);
$usr_key = $formfields[usr_key];
$addpubkeyargs = "-k $joining_uid '$usr_key' ";
#
# Verify key format.
#
if (ADDPUBKEY(null, "webaddpubkey -n -k '$usr_key' ")) {
$errors["Pubkey Format"] =
"Could not be parsed. Is it a public key?";
}
else {
$addpubkeyargs = "-k '$usr_key' ";
}
}
}
......@@ -741,63 +727,43 @@ if (!$returning) {
$errors["PubKey File"] = "Invalid characters";
}
else {
$addpubkeyargs = "$joining_uid $usr_keyfile";
chmod($usr_keyfile, 0644);
chmod($localfile, 0644);
#
# Verify key format.
#
if (ADDPUBKEY(null, "webaddpubkey -n $localfile ")) {
$errors["Pubkey Format"] =
"Could not be parsed. Is it a public key?";
}
else {
$addpubkeyargs = "$localfile";
}
}
}
}
else {
#
# Grab info from the DB for the email message below. Kinda silly.
#
$query_result =
DBQueryFatal("select * from users where uid='$joining_uid'");
$row = mysql_fetch_array($query_result);
$usr_title = $row[usr_title];
$usr_name = $row[usr_name];
$usr_affil = $row[usr_affil];
$usr_email = $row[usr_email];
$usr_addr = $row[usr_addr];