From ae77bdb6022576985644bae7ed70ecda79718fe1 Mon Sep 17 00:00:00 2001
From: "Leigh B. Stoller" <stoller@flux.utah.edu>
Date: Mon, 26 Aug 2002 23:06:38 +0000
Subject: [PATCH] Rework all of the ssh key handling. Moved the parsing and
 verification to an external perl script, and use ssh-keygen to attempt
 conversion off SSH2/SECSH key formats. This is actually a simplification of
 the php code, which is not generally very good at this kind of thing (or
 maybe I mean perl is just better at it). The parsing and error handling it
 also much improved.

---
 account/addpubkey.in         | 285 +++++++++++++++++++++++++++++++++++
 account/webaddpubkey.in      |  25 +++
 utils/GNUmakefile.in         |   2 +-
 utils/addpubkey.in           | 285 +++++++++++++++++++++++++++++++++++
 utils/webaddpubkey.in        |  25 +++
 www/cdromcheckin.php3        |  21 +--
 www/defs.php3.in             |  13 ++
 www/joinproject.php3         | 114 +++++++-------
 www/newproject.php3          | 125 +++++++--------
 www/showpubkeys.php3         | 157 ++++++++-----------
 www/stoller-emulab-defs.php3 |  10 +-
 www/stoller-home-defs.php3   |  10 +-
 12 files changed, 821 insertions(+), 251 deletions(-)
 create mode 100644 account/addpubkey.in
 create mode 100644 account/webaddpubkey.in
 create mode 100644 utils/addpubkey.in
 create mode 100644 utils/webaddpubkey.in

diff --git a/account/addpubkey.in b/account/addpubkey.in
new file mode 100644
index 0000000000..53fc4db1db
--- /dev/null
+++ b/account/addpubkey.in
@@ -0,0 +1,285 @@
+#!/usr/bin/perl -wT
+#
+# EMULAB-COPYRIGHT
+# Copyright (c) 2000-2002 University of Utah and the Flux Group.
+# All rights reserved.
+#
+use English;
+use Getopt::Std;
+
+#
+# Parse ssh public keys and enter into the DB. The default format is
+# openssh, but if the key is not in that format, then use ssh-keygen
+# to see if it can be converted from either SSH2 or SECSH format into
+# openssh format. This gets called from the webpage to parse keys
+# uploaded by users.
+#
+sub usage()
+{
+    print "Usage: addpubkeys [-n] [-a] [-k] <user> [<keyfile> | <key>]\n";
+    print "Options:\n";
+    print " -k      Indicates that key was passed in on the command line\n";
+    print " -n      Verify key format only; do not enter into into DB\n";
+    print " -a      Audit mode; send audit message to log file\n";
+    exit(-1);
+}
+my $optlist   = "kna";
+my $iskey     = 0;
+my $verify    = 0;
+my $auditmode = 0;
+
+#
+# Configure variables
+#
+my $TB		= "@prefix@";
+my $TBOPS       = "@TBOPSEMAIL@";
+my $TBAUDIT     = "@TBAUDITEMAIL@";
+
+#
+# Testbed Support libraries
+#
+use lib "@prefix@/lib";
+use libdb;
+use libtestbed;
+
+#
+# Turn off line buffering on output
+#
+$| = 1;
+
+#
+# Untaint the path
+# 
+$ENV{'PATH'} = "/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin";
+delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
+
+#
+# Please do not run it as root. Hard to track what has happened.
+# 
+if ($UID == 0) {
+    die("*** $0:\n".
+	"    Please do not run this as root!\n");
+}
+
+#
+# Parse command arguments. Once we return from getopts, all that should be
+# left are the required arguments.
+#
+%options = ();
+if (! getopts($optlist, \%options)) {
+    usage();
+}
+if (defined($options{"k"})) {
+    $iskey = 1;
+}
+if (defined($options{"n"})) {
+    $verify = 1;
+}
+if (defined($options{"a"})) {
+    $auditmode = 1;
+}
+if (@ARGV != 2) {
+    usage();
+}
+my $user    = $ARGV[0];
+my $keyfile = $ARGV[1];
+my $keyline;
+my $key;
+my $comment;
+my $db_uid;
+my $db_name  = "Testbed Operations";
+my $db_email = $TBOPS;
+my $user_name;
+my $user_email;
+
+#
+# Untaint the arguments.
+#
+if ($iskey) {
+    if ($keyfile =~ /^([-\w\s\.\@\+\/\=]*)$/) {
+	$keyfile = $1;
+    }
+    else {
+	fatal("Tainted key: $keyfile");
+    }
+    $keyline = $keyfile;
+    print "$keyline\n";
+}
+else {
+    if ($keyfile =~ /^([-\w\.\/]+)$/) {
+	$keyfile = $1;
+    }
+    else {
+	fatal("Tainted filename: $keyfile");
+    }
+    if (! -e $keyfile) {
+	fatal("*** $0\n".
+	    "    No such file: $keyfile\n");
+    }
+    $keyline = `head -1 $keyfile`;
+}
+if ($user =~ /^([a-z0-9]+)$/i) {
+    $user = $1;
+}
+else {
+    fatal("Tainted username: $user");
+}
+
+#
+# Check user and get his DB uid and email stuff, unless only verfying
+# the format of the key, or if its a new user and the caller is "nobody".
+# In that case, the user better not exist.
+#
+if (getpwuid($UID) eq "nobody") {
+    if (getpwnam($user) ||
+	UserDBInfo($user, \$user_name, \$user_email)) {
+	fatal("*** $0:\n".
+	      "    Attempt to insert first key for existing user!\n");
+    }
+}
+elsif (! $verify) {
+    if (! UNIX2DBUID($UID, \$db_uid)) {
+	fatal("*** $0:\n".
+	      "    You do not exist in the Emulab Database.\n");
+    }
+    if (! UserDBInfo($db_uid, \$db_name, \$db_email)) {
+	fatal("*** $0:\n".
+	      "    Cannot determine your name and email address.\n");
+    }
+    if ($user ne $db_uid) {
+	#
+	# Only admins can set pubkeys for another user.
+	#
+	if (!TBAdmin($UID)) {
+	    fatal("*** $0:\n".
+		  "    You are not allowed to set pubkeys for $user.\n");
+	}
+    
+	if (! UserDBInfo($user, \$user_name, \$user_email)) {
+	    fatal("*** $0:\n".
+		  "    Cannot determine name and email address for $user.\n");
+	}
+	# Always audit when setting other people's keys.
+	$auditmode = 1;
+    }
+    else {
+	$user_name  = $db_name;
+	$user_email = $db_email;
+    }
+}
+
+#
+# Grab the first line of the file. Parse it to see if its in the
+# format we like (openssh), either protocol 1 or 2.
+#
+if (ParseKey($keyline)) {
+    if ($auditmode) {
+	audit();
+    }
+    exit 0;
+}
+# If the key was entered on the command line, then nothing more to do.
+if ($iskey) {
+    exit 1;
+}
+
+#
+# Run ssh-keygen over it and see if it can convert it. 
+#
+if (! open(KEYGEN, "ssh-keygen -i -f $keyfile 2>/dev/null |")) {
+    fatal("*** $0:\n".
+	  "    Could not start ssh-keygen\n");
+}
+$keyline = <KEYGEN>;
+if (close(KEYGEN) && ParseKey($keyline)) {
+    if ($auditmode) {
+	audit();
+    }
+    exit 0;
+}
+exit 1;
+
+sub ParseKey($) {
+    my ($keyline) = @_;
+    
+    if ($keyline =~ /^(\d*\s\d*\s[0-9a-zA-Z]*) ([-\w\@\.]*)$/) {
+        # Protocol 1
+	$type    = "ssh-rsa1";
+	$key     = $1;
+	$comment = $2;
+    }
+    elsif ($keyline =~ /^(\d*\s\d*\s[0-9a-zA-Z]*)\s*$/) {
+        # Protocol 1 but no comment field.
+	$type    = "ssh-rsa1";
+	$key     = $1;
+    }
+    elsif ($keyline =~
+	   /^(ssh-rsa|ssh-dss) ([-\w\.\@\+\/\=]*) ([-\w\@\.]*)$/) {
+        # Protocol 2
+	$type    = $1;
+	$key     = "$1 $2";
+	$comment = $3;
+    }
+    elsif ($keyline =~ /^(ssh-rsa|ssh-dss) ([-\w\.\@\+\/\=]*)$/) {
+        # Protocol 2 but no comment field
+	$type    = $1;
+	$key     = "$1 $2";
+    }
+
+    if (!defined($key)) {
+	return 0;
+    }
+    # Do not enter into DB if in verify mode.
+    if ($verify) {
+	return 1;
+    }
+
+    #
+    # Make up a comment field for the DB index. Need something. 
+    #
+    if (!defined($comment)) {
+	$comment = "$type-${user_email}";
+    }
+    $key = "$key $comment";
+
+    DBQueryFatal("replace into user_pubkeys ".
+		 "values ('$user', '$comment', '$key', now())");
+
+    return 1;
+}
+
+sub audit()
+{
+    my $chunked = "";
+
+    while (length($key)) {
+	$chunked .= substr($key, 0, 65, "");
+	if (length($key)) {
+	    $chunked .= "\n";
+	}
+    }
+    
+    SENDMAIL("$user_name <$user_email>",
+	     "SSH Public Key for '$user' Added",
+	     "SSH Public Key for '$user' added by '$db_uid'.\n".
+	     "\n".
+	     "$chunked\n",
+	     "$db_name <$db_email>", "Bcc: $TBAUDIT");
+}
+
+sub fatal($)
+{
+    my($mesg) = $_[0];
+
+    print STDERR "$mesg\n";
+    
+    #
+    # Send a message to the testbed list. 
+    #
+    SENDMAIL($TBOPS, 
+	     "SSH Public key insertion failed!",
+	     $mesg,
+	     "$db_name <$db_email>");
+    exit(-1);
+}
+
diff --git a/account/webaddpubkey.in b/account/webaddpubkey.in
new file mode 100644
index 0000000000..bcbd52680b
--- /dev/null
+++ b/account/webaddpubkey.in
@@ -0,0 +1,25 @@
+#!/usr/bin/perl -w
+
+#
+# EMULAB-COPYRIGHT
+# Copyright (c) 2000-2002 University of Utah and the Flux Group.
+# All rights reserved.
+#
+
+use English;
+
+#
+# This gets invoked from the Web interface. Simply a wrapper ...
+#
+
+#
+# Configure variables
+#
+my $TB       = "@prefix@";
+
+#
+# Run the real thing, and never return.
+# 
+exec "$TB/bin/addpubkey", @ARGV;
+
+die("webaddpubkey: Could not exec addpubkey: $!");
diff --git a/utils/GNUmakefile.in b/utils/GNUmakefile.in
index 85c504e9e8..a511c86986 100644
--- a/utils/GNUmakefile.in
+++ b/utils/GNUmakefile.in
@@ -12,7 +12,7 @@ UNIFIED         = @UNIFIED_BOSS_AND_OPS@
 
 include $(OBJDIR)/Makeconf
 
-BIN_SCRIPTS	= delay_config sshtb create_image node_admin 
+BIN_SCRIPTS	= delay_config sshtb create_image node_admin addpubkey
 SBIN_SCRIPTS	= vlandiff vlansync withadminprivs export_tables cvsupd.pl
 LIBEXEC_SCRIPTS	= webcreateimage webaddpubkey
 
diff --git a/utils/addpubkey.in b/utils/addpubkey.in
new file mode 100644
index 0000000000..53fc4db1db
--- /dev/null
+++ b/utils/addpubkey.in
@@ -0,0 +1,285 @@
+#!/usr/bin/perl -wT
+#
+# EMULAB-COPYRIGHT
+# Copyright (c) 2000-2002 University of Utah and the Flux Group.
+# All rights reserved.
+#
+use English;
+use Getopt::Std;
+
+#
+# Parse ssh public keys and enter into the DB. The default format is
+# openssh, but if the key is not in that format, then use ssh-keygen
+# to see if it can be converted from either SSH2 or SECSH format into
+# openssh format. This gets called from the webpage to parse keys
+# uploaded by users.
+#
+sub usage()
+{
+    print "Usage: addpubkeys [-n] [-a] [-k] <user> [<keyfile> | <key>]\n";
+    print "Options:\n";
+    print " -k      Indicates that key was passed in on the command line\n";
+    print " -n      Verify key format only; do not enter into into DB\n";
+    print " -a      Audit mode; send audit message to log file\n";
+    exit(-1);
+}
+my $optlist   = "kna";
+my $iskey     = 0;
+my $verify    = 0;
+my $auditmode = 0;
+
+#
+# Configure variables
+#
+my $TB		= "@prefix@";
+my $TBOPS       = "@TBOPSEMAIL@";
+my $TBAUDIT     = "@TBAUDITEMAIL@";
+
+#
+# Testbed Support libraries
+#
+use lib "@prefix@/lib";
+use libdb;
+use libtestbed;
+
+#
+# Turn off line buffering on output
+#
+$| = 1;
+
+#
+# Untaint the path
+# 
+$ENV{'PATH'} = "/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin";
+delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
+
+#
+# Please do not run it as root. Hard to track what has happened.
+# 
+if ($UID == 0) {
+    die("*** $0:\n".
+	"    Please do not run this as root!\n");
+}
+
+#
+# Parse command arguments. Once we return from getopts, all that should be
+# left are the required arguments.
+#
+%options = ();
+if (! getopts($optlist, \%options)) {
+    usage();
+}
+if (defined($options{"k"})) {
+    $iskey = 1;
+}
+if (defined($options{"n"})) {
+    $verify = 1;
+}
+if (defined($options{"a"})) {
+    $auditmode = 1;
+}
+if (@ARGV != 2) {
+    usage();
+}
+my $user    = $ARGV[0];
+my $keyfile = $ARGV[1];
+my $keyline;
+my $key;
+my $comment;
+my $db_uid;
+my $db_name  = "Testbed Operations";
+my $db_email = $TBOPS;
+my $user_name;
+my $user_email;
+
+#
+# Untaint the arguments.
+#
+if ($iskey) {
+    if ($keyfile =~ /^([-\w\s\.\@\+\/\=]*)$/) {
+	$keyfile = $1;
+    }
+    else {
+	fatal("Tainted key: $keyfile");
+    }
+    $keyline = $keyfile;
+    print "$keyline\n";
+}
+else {
+    if ($keyfile =~ /^([-\w\.\/]+)$/) {
+	$keyfile = $1;
+    }
+    else {
+	fatal("Tainted filename: $keyfile");
+    }
+    if (! -e $keyfile) {
+	fatal("*** $0\n".
+	    "    No such file: $keyfile\n");
+    }
+    $keyline = `head -1 $keyfile`;
+}
+if ($user =~ /^([a-z0-9]+)$/i) {
+    $user = $1;
+}
+else {
+    fatal("Tainted username: $user");
+}
+
+#
+# Check user and get his DB uid and email stuff, unless only verfying
+# the format of the key, or if its a new user and the caller is "nobody".
+# In that case, the user better not exist.
+#
+if (getpwuid($UID) eq "nobody") {
+    if (getpwnam($user) ||
+	UserDBInfo($user, \$user_name, \$user_email)) {
+	fatal("*** $0:\n".
+	      "    Attempt to insert first key for existing user!\n");
+    }
+}
+elsif (! $verify) {
+    if (! UNIX2DBUID($UID, \$db_uid)) {
+	fatal("*** $0:\n".
+	      "    You do not exist in the Emulab Database.\n");
+    }
+    if (! UserDBInfo($db_uid, \$db_name, \$db_email)) {
+	fatal("*** $0:\n".
+	      "    Cannot determine your name and email address.\n");
+    }
+    if ($user ne $db_uid) {
+	#
+	# Only admins can set pubkeys for another user.
+	#
+	if (!TBAdmin($UID)) {
+	    fatal("*** $0:\n".
+		  "    You are not allowed to set pubkeys for $user.\n");
+	}
+    
+	if (! UserDBInfo($user, \$user_name, \$user_email)) {
+	    fatal("*** $0:\n".
+		  "    Cannot determine name and email address for $user.\n");
+	}
+	# Always audit when setting other people's keys.
+	$auditmode = 1;
+    }
+    else {
+	$user_name  = $db_name;
+	$user_email = $db_email;
+    }
+}
+
+#
+# Grab the first line of the file. Parse it to see if its in the
+# format we like (openssh), either protocol 1 or 2.
+#
+if (ParseKey($keyline)) {
+    if ($auditmode) {
+	audit();
+    }
+    exit 0;
+}
+# If the key was entered on the command line, then nothing more to do.
+if ($iskey) {
+    exit 1;
+}
+
+#
+# Run ssh-keygen over it and see if it can convert it. 
+#
+if (! open(KEYGEN, "ssh-keygen -i -f $keyfile 2>/dev/null |")) {
+    fatal("*** $0:\n".
+	  "    Could not start ssh-keygen\n");
+}
+$keyline = <KEYGEN>;
+if (close(KEYGEN) && ParseKey($keyline)) {
+    if ($auditmode) {
+	audit();
+    }
+    exit 0;
+}
+exit 1;
+
+sub ParseKey($) {
+    my ($keyline) = @_;
+    
+    if ($keyline =~ /^(\d*\s\d*\s[0-9a-zA-Z]*) ([-\w\@\.]*)$/) {
+        # Protocol 1
+	$type    = "ssh-rsa1";
+	$key     = $1;
+	$comment = $2;
+    }
+    elsif ($keyline =~ /^(\d*\s\d*\s[0-9a-zA-Z]*)\s*$/) {
+        # Protocol 1 but no comment field.
+	$type    = "ssh-rsa1";
+	$key     = $1;
+    }
+    elsif ($keyline =~
+	   /^(ssh-rsa|ssh-dss) ([-\w\.\@\+\/\=]*) ([-\w\@\.]*)$/) {
+        # Protocol 2
+	$type    = $1;
+	$key     = "$1 $2";
+	$comment = $3;
+    }
+    elsif ($keyline =~ /^(ssh-rsa|ssh-dss) ([-\w\.\@\+\/\=]*)$/) {
+        # Protocol 2 but no comment field
+	$type    = $1;
+	$key     = "$1 $2";
+    }
+
+    if (!defined($key)) {
+	return 0;
+    }
+    # Do not enter into DB if in verify mode.
+    if ($verify) {
+	return 1;
+    }
+
+    #
+    # Make up a comment field for the DB index. Need something. 
+    #
+    if (!defined($comment)) {
+	$comment = "$type-${user_email}";
+    }
+    $key = "$key $comment";
+
+    DBQueryFatal("replace into user_pubkeys ".
+		 "values ('$user', '$comment', '$key', now())");
+
+    return 1;
+}
+
+sub audit()
+{
+    my $chunked = "";
+
+    while (length($key)) {
+	$chunked .= substr($key, 0, 65, "");
+	if (length($key)) {
+	    $chunked .= "\n";
+	}
+    }
+    
+    SENDMAIL("$user_name <$user_email>",
+	     "SSH Public Key for '$user' Added",
+	     "SSH Public Key for '$user' added by '$db_uid'.\n".
+	     "\n".
+	     "$chunked\n",
+	     "$db_name <$db_email>", "Bcc: $TBAUDIT");
+}
+
+sub fatal($)
+{
+    my($mesg) = $_[0];
+
+    print STDERR "$mesg\n";
+    
+    #
+    # Send a message to the testbed list. 
+    #
+    SENDMAIL($TBOPS, 
+	     "SSH Public key insertion failed!",
+	     $mesg,
+	     "$db_name <$db_email>");
+    exit(-1);
+}
+
diff --git a/utils/webaddpubkey.in b/utils/webaddpubkey.in
new file mode 100644
index 0000000000..bcbd52680b
--- /dev/null
+++ b/utils/webaddpubkey.in
@@ -0,0 +1,25 @@
+#!/usr/bin/perl -w
+
+#
+# EMULAB-COPYRIGHT
+# Copyright (c) 2000-2002 University of Utah and the Flux Group.
+# All rights reserved.
+#
+
+use English;
+
+#
+# This gets invoked from the Web interface. Simply a wrapper ...
+#
+
+#
+# Configure variables
+#
+my $TB       = "@prefix@";
+
+#
+# Run the real thing, and never return.
+# 
+exec "$TB/bin/addpubkey", @ARGV;
+
+die("webaddpubkey: Could not exec addpubkey: $!");
diff --git a/www/cdromcheckin.php3 b/www/cdromcheckin.php3
index 99d54be00a..376a1cd702 100644
--- a/www/cdromcheckin.php3
+++ b/www/cdromcheckin.php3
@@ -54,7 +54,8 @@ if (! mysql_num_rows($query_result)) {
     SPITSTATUS(CDROMSTATUS_BADCDKEY);
     return;
 }
-$row = mysql_fetch_array($query_result);
+$row    = mysql_fetch_array($query_result);
+$cdvers = $row[version];
 
 #
 # Grab the privkey record. First squeeze out any spaces.
@@ -148,15 +149,7 @@ DBQueryFatal("update widearea_privkeys ".
 
 header("Content-Type: text/plain");
 echo "privkey=$newkey\n";
-if (0) {
-    echo "fdisk=http://${WWWHOST}/images/image.fdisk\n";
-    echo "slice1_image=http://${WWWHOST}/images/slice1.ndz\n";
-    echo "slice1_md5=2970e2cf045f5872c6728eeea3b51dae\n";
-    echo "slicex_mount=/users\n";
-    echo "slicex_tarball=http://${WWWHOST}/images/slicex.tar.gz\n";
-    echo "slicex_md5=1f84fbc3434d174151ac3a2b8389799a\n";
-}
-else {
+if ($cdvers == 1) {
     echo "fdisk=image.fdisk\n";
     echo "slice1_image=slice1.ndz\n";
     echo "slice1_md5=cb810b43f49d15b3ac4122ff42f8925d\n";
@@ -164,5 +157,13 @@ else {
     echo "slicex_tarball=slicex.tar.gz\n";
     echo "slicex_md5=1f84fbc3434d174151ac3a2b8389799a\n";
 }
+else {
+    echo "fdisk=http://${WWWHOST}/images/image.fdisk\n";
+    echo "slice1_image=http://${WWWHOST}/images/slice1-v2.ndz\n";
+    echo "slice1_md5=5389a2687cee16ff212bbd842585a5d5\n";
+    echo "slicex_mount=/users\n";
+    echo "slicex_tarball=http://${WWWHOST}/images/slicex.tar.gz\n";
+    echo "slicex_md5=1f84fbc3434d174151ac3a2b8389799a\n";
+}
 echo "emulab_status=0\n";
 
diff --git a/www/defs.php3.in b/www/defs.php3.in
index 5ce574dc35..2686563544 100644
--- a/www/defs.php3.in
+++ b/www/defs.php3.in
@@ -169,6 +169,19 @@ function SUEXEC($uid, $gid, $cmdandargs, $die) {
     return $retval;
 }
 
+function ADDPUBKEY($uid, $cmdandargs) {
+    global $TBSUEXEC_PATH;
+
+    ignore_user_abort(1);
+
+    $output = array();
+    $retval = 0;
+    $result = exec("$TBSUEXEC_PATH $uid nobody $cmdandargs",
+		   $output, $retval);
+
+    return $retval;
+}
+
 #
 # Verify a URL.
 #
diff --git a/www/joinproject.php3 b/www/joinproject.php3
index f5a13983c1..0392343955 100644
--- a/www/joinproject.php3
+++ b/www/joinproject.php3
@@ -44,9 +44,10 @@ function SPITFORM($formfields, $returning, $errors)
     PAGEHEADER("Apply for Project Membership");
 
     if ($errors) {
-	echo "<table align=center border=0 cellpadding=0 cellspacing=2>
+	echo "<table class=stealth
+                     align=center border=0 cellpadding=0 cellspacing=2>
               <tr>
-                 <td nowrap align=center colspan=3>
+                 <td class=stealth nowrap align=center colspan=3>
                    <font size=+1 color=red>
                       Oops, please fix the following errors!
                    </font>
@@ -55,9 +56,11 @@ function SPITFORM($formfields, $returning, $errors)
 
 	while (list ($name, $message) = each ($errors)) {
 	    echo "<tr>
-                     <td align=right><font color=red>$name:</font></td>
-                     <td>&nbsp &nbsp</td>
-                     <td align=left><font color=red>$message</font></td>
+                     <td class=stealth align=right>
+                         <font color=red>$name:</font></td>
+                     <td class=stealth>&nbsp &nbsp</td>
+                     <td class=stealth align=left>
+                         <font color=red>$message</font></td>
                   </tr>\n";
 	}
 	echo "</table><br>\n";
@@ -204,6 +207,7 @@ function SPITFORM($formfields, $returning, $errors)
                       <br>
 	              <input type=text
                              name=\"formfields[usr_key]\"
+                             value=\"$formfields[usr_key]\"
 	                     size=50
 	                     maxlength=1024>
                   </td>
@@ -275,7 +279,15 @@ function SPITFORM($formfields, $returning, $errors)
     if (! $returning) {
 	echo "<li> If you want us to use your existing ssh public key,
                    then either paste it in or specify the path to your
-                   your identity.pub file.
+                   your identity.pub file.  <font color=red>NOTE:</font>
+                   We use the <a href=www.openssh.org>OpenSSH</a> key format,
+                   which has a slightly different protocol 2 public key format
+                   than some of the commercial vendors such as
+                   <a href=www.ssh.com>SSH Communications</a>. If you
+                   use one of these commercial vendors, then please
+                   upload the public  key file and we will convert it
+                   for you. <i>Please do not paste it in.</i>\n
+
               <li> Note to <a href=http://www.opera.com><b>Opera 5</b></a>
                    users: The file upload mechanism is broken in Opera, so
                    you cannot specify a local file for upload. Instead,
@@ -451,55 +463,41 @@ if (!$returning) {
     }
 
     #
-    # Pasted in key.
-    # 
+    # Pub Key.
+    #
     if (isset($formfields[usr_key]) &&
 	strcmp($formfields[usr_key], "")) {
         #
-        # Replace any embedded newlines first.
-        #
-	$formfields[usr_key] = ereg_replace("[\n]", "", $formfields[usr_key]);
-
+        # This is passed off to the shell, so taint check it.
+        # 
 	if (! preg_match("/^[-\w\s\.\@\+\/\=]*$/", $formfields[usr_key])) {
 	    $errors["PubKey"] = "Invalid characters";
-
-	    SPITFORM($formfields, $errors);
-	    PAGEFOOTER();
-	    return;
 	}
 	else {
-	    $usr_key[] = $formfields[usr_key];
+            #
+            # Replace any embedded newlines first.
+            #
+	    $formfields[usr_key] =
+		ereg_replace("[\n]", "", $formfields[usr_key]);
+	    $usr_key = $formfields[usr_key];
+	    $addpubkeyargs = "-k $joining_uid '$usr_key' ";
 	}
     }
-    
+
     #
     # If usr provided a file for the key, it overrides the paste in text.
-    # Must read and check it.
     #
     if (isset($usr_keyfile) &&
 	strcmp($usr_keyfile, "") &&
 	strcmp($usr_keyfile, "none")) {
 
-	if (! ($fp = fopen($usr_keyfile, "r"))) {
-	    TBERROR("Could not open $usr_keyfile", 1);
+	if (! stat($usr_keyfile)) {
+	    $errors["PubKey File"] = "No such file";
 	}
-	while (!feof($fp)) {
-	    $buffer = fgets($fp, 4096);
-
-	    if (ereg("^[\n\#]", $buffer))
-		continue;
-
-	    if (! preg_match("/^[-\w\s\.\@\+\/\=\r\n]*$/", $buffer)) {
-		$errors["PubKey File Contents"] = "Invalid characters";
-
-		fclose($fp);
-		SPITFORM($formfields, $returning, $errors);
-		PAGEFOOTER();
-		return;
-	    }
-	    $usr_key[] = Chop($buffer);
+	else {
+	    $addpubkeyargs = "$joining_uid $usr_keyfile";
+	    chmod($usr_keyfile, 0640);	
 	}
-	fclose($fp);
     }
 }
 else {
@@ -542,6 +540,14 @@ elseif (TBGroupMember($joining_uid, $pid, $gid, $approved)) {
     $errors["Membership"] = "You are already a member";
 }
 
+#
+# Verify key format.
+#
+if (isset($addpubkeyargs) &&
+    ADDPUBKEY("nobody", "webaddpubkey -n $addpubkeyargs")) {
+    $errors["Pubkey Format"] = "Could not be parsed. Is it a public key?";
+}
+
 if (count($errors)) {
     SPITFORM($formfields, $returning, $errors);
     PAGEFOOTER();
@@ -557,6 +563,14 @@ if (count($errors)) {
 if (! $returning) {
     $encoding = crypt("$password1");
 
+    #
+    # Must be done before user record is inserted!
+    # XXX Since user does not exist, must run as nobody. Script checks. 
+    # 
+    if (isset($addpubkeyargs)) {
+	ADDPUBKEY("nobody", "webaddpubkey $addpubkeyargs");
+    }
+
     DBQueryFatal("INSERT INTO users ".
 	"(uid,usr_created,usr_expires,usr_name,usr_email,usr_addr,".
 	" usr_URL,usr_phone,usr_title,usr_affil,usr_pswd,unix_uid,".
@@ -567,32 +581,6 @@ if (! $returning) {
         "'$encoding', NULL, 'newuser', ".
 	"date_add(now(), interval 1 year), now())");
 
-    if (isset($usr_key)) {
-	while (list ($idx, $stuff) = each ($usr_key)) {
-            #
-            # Need to separate out the comment field. 
-            #
-	    $pieces = explode(" ", $stuff);
-
-	    if (count($pieces) == 4) {
-		$key     = "$pieces[0] $pieces[1] $pieces[2] $pieces[3]";
-		$comment = $pieces[3];
-	    }
-	    elseif (count($pieces) == 3) {
-		$key     = "$pieces[0] $pieces[1] $pieces[2]";
-		$comment = $pieces[0] . "-" . $pieces[2];
-	    }
-	    elseif (count($pieces) == 1) {
-		continue;
-	    }
-	    else {
-		TBERROR("Improper key: $stuff", 0);
-	    }
-	    DBQueryFatal("replace into user_pubkeys ".
-			 "values ('$joining_uid', '$comment', '$key', now())");
-	}
-    }
-    
     $key = GENKEY($joining_uid);
 
     TBMAIL("$usr_name '$joining_uid' <$usr_email>",
diff --git a/www/newproject.php3 b/www/newproject.php3
index 781c32158f..ab11eef96f 100755
--- a/www/newproject.php3
+++ b/www/newproject.php3
@@ -51,9 +51,10 @@ function SPITFORM($formfields, $returning, $errors)
           </font></center><br>\n";
 
     if ($errors) {
-	echo "<table align=center border=0 cellpadding=0 cellspacing=2>
+	echo "<table class=stealth
+                     align=center border=0 cellpadding=0 cellspacing=2>
               <tr>
-                 <td nowrap align=center colspan=3>
+                 <td class=stealth nowrap align=center colspan=3>
                    <font size=+1 color=red>
                       Oops, please fix the following errors!
                    </font>
@@ -62,9 +63,11 @@ function SPITFORM($formfields, $returning, $errors)
 
 	while (list ($name, $message) = each ($errors)) {
 	    echo "<tr>
-                     <td align=right><font color=red>$name:</font></td>
-                     <td>&nbsp &nbsp</td>
-                     <td align=left><font color=red>$message</font></td>
+                     <td class=stealth align=right>
+                         <font color=red>$name:</font></td>
+                     <td class=stealth>&nbsp &nbsp</td>
+                     <td class=stealth align=left>
+                         <font color=red>$message</font></td>
                   </tr>\n";
 	}
 	echo "</table><br>\n";
@@ -220,6 +223,7 @@ function SPITFORM($formfields, $returning, $errors)
                       <br>
 	              <input type=text
                              name=\"formfields[usr_key]\"
+                             value=\"$formfields[usr_key]\"
 	                     size=50
 	                     maxlength=1024>
                   </td>
@@ -431,7 +435,15 @@ function SPITFORM($formfields, $returning, $errors)
     if (! $returning) {
 	echo "<li> If you want us to use your existing ssh public key,
                    then either paste it in or specify the path to your
-                   your identity.pub file.
+                   your identity.pub file. <font color=red>NOTE:</font>
+                   We use the <a href=www.openssh.org>OpenSSH</a> key format,
+                   which has a slightly different protocol 2 public key format
+                   than some of the commercial vendors such as
+                   <a href=www.ssh.com>SSH Communications</a>. If you
+                   use one of these commercial vendors, then please
+                   upload the public  key file and we will convert it
+                   for you. <i>Please do not paste it in.</i>\n
+
               <li> Note to <a href=http://www.opera.com><b>Opera 5</b></a>
                    users: The file upload mechanism is broken in Opera, so
                    you cannot specify a local file for upload. Instead,
@@ -574,11 +586,6 @@ if (! $returning) {
 			    $formfields[usr_email], $checkerror)) {
 	$errors["Password"] = "$checkerror";
     }
-    if (isset($formfields[usr_key]) &&
-	strcmp($formfields[usr_key], "") &&
-	! ereg("^[0-9a-zA-Z\@\. ]*$", $formfields[usr_key])) {
-	$errors["PubKey"] = "Invalid characters";
-    }
 }
 
 if (!isset($formfields[pid]) ||
@@ -694,55 +701,54 @@ if (!$returning) {
     }
     
     #
-    # Pasted in key.
-    # 
+    # Pub Key.
+    #
     if (isset($formfields[usr_key]) &&
 	strcmp($formfields[usr_key], "")) {
         #
-        # Replace any embedded newlines first.
-        #
-	$formfields[usr_key] = ereg_replace("[\n]", "", $formfields[usr_key]);
-
+        # This is passed off to the shell, so taint check it.
+        # 
 	if (! preg_match("/^[-\w\s\.\@\+\/\=]*$/", $formfields[usr_key])) {
 	    $errors["PubKey"] = "Invalid characters";
-
-	    SPITFORM($formfields, $errors);
-	    PAGEFOOTER();
-	    return;
 	}
 	else {
-	    $usr_key[] = $formfields[usr_key];
+            #
+            # Replace any embedded newlines first.
+            #
+	    $formfields[usr_key] =
+		ereg_replace("[\n]", "", $formfields[usr_key]);
+	    $usr_key = $formfields[usr_key];
+	    $addpubkeyargs = "-k $proj_head_uid '$usr_key' ";
 	}
     }
-    
+
     #
     # If usr provided a file for the key, it overrides the paste in text.
-    # Must read and check it.
     #
     if (isset($usr_keyfile) &&
 	strcmp($usr_keyfile, "") &&
 	strcmp($usr_keyfile, "none")) {
 
-	if (! ($fp = fopen($usr_keyfile, "r"))) {
-	    TBERROR("Could not open $usr_keyfile", 1);
+	if (! stat($usr_keyfile)) {
+	    $errors["PubKey File"] = "No such file";
 	}
-	while (!feof($fp)) {
-	    $buffer = fgets($fp, 4096);
-
-	    if (ereg("^[\n\#]", $buffer))
-		continue;
-
-	    if (! preg_match("/^[-\w\s\.\@\+\/\=\r\n]*$/", $buffer)) {
-		$errors["PubKey File Contents"] = "Invalid characters";
-
-		fclose($fp);
-		SPITFORM($formfields, $returning, $errors);
-		PAGEFOOTER();
-		return;
-	    }
-	    $usr_key[] = Chop($buffer);
+	else {
+	    $addpubkeyargs = "$proj_head_uid $usr_keyfile";
+	    chmod($usr_keyfile, 0640);	
 	}
-	fclose($fp);
+    }
+    #
+    # Verify key format.
+    #
+    if (isset($addpubkeyargs) &&
+	ADDPUBKEY("nobody", "webaddpubkey -n $addpubkeyargs")) {
+	$errors["Pubkey Format"] = "Could not be parsed. Is it a public key?";
+    }
+
+    if (count($errors)) {
+	SPITFORM($formfields, $returning, $errors);
+	PAGEFOOTER();
+	return;
     }
 }
 else {
@@ -811,6 +817,14 @@ if (mysql_num_rows($query_result)) {
 if (! $returning) {
     $encoding = crypt("$password1");
 
+    #
+    # Must be done before user record is inserted!
+    # XXX Since, user does not exist, must run as nobody. Script checks. 
+    # 
+    if (isset($addpubkeyargs)) {
+	ADDPUBKEY("nobody", "webaddpubkey $addpubkeyargs");
+    }
+
     DBQueryFatal("INSERT INTO users ".
 	 "(uid,usr_created,usr_expires,usr_name,usr_email,usr_addr,".
 	 " usr_URL,usr_title,usr_affil,usr_phone,usr_pswd,unix_uid,".
@@ -820,33 +834,6 @@ if (! $returning) {
 	 "'$usr_phone', '$encoding', NULL, 'newuser', ".
 	 "date_add(now(), interval 1 year), now())");
 
-    if (isset($usr_key)) {
-	while (list ($idx, $stuff) = each ($usr_key)) {
-            #
-            # Need to separate out the comment field. 
-            #
-	    $pieces = explode(" ", $stuff);
-
-	    if (count($pieces) == 4) {
-		$key     = "$pieces[0] $pieces[1] $pieces[2] $pieces[3]";
-		$comment = $pieces[3];
-	    }
-	    elseif (count($pieces) == 3) {
-		$key     = "$pieces[0] $pieces[1] $pieces[2]";
-		$comment = $pieces[0] . "-" . $pieces[2];
-	    }
-	    elseif (count($pieces) == 1) {
-		continue;
-	    }
-	    else {
-		TBERROR("Improper key: $stuff", 0);
-	    }
-	    DBQueryFatal("replace into user_pubkeys ".
-			 "values ('$proj_head_uid', ".
-			 "         '$comment', '$key', now())");
-	}
-    }
-    
     $key = GENKEY($proj_head_uid);
 
     TBMAIL("$usr_name '$proj_head_uid' <$usr_email>",
diff --git a/www/showpubkeys.php3 b/www/showpubkeys.php3
index cf1d583b53..2737e13c53 100644
--- a/www/showpubkeys.php3
+++ b/www/showpubkeys.php3
@@ -112,9 +112,10 @@ function SPITFORM($formfields, $errors)
           </center><br>\n";
 
     if ($errors) {
-	echo "<table align=center border=0 cellpadding=0 cellspacing=2>
+	echo "<table class=stealth
+                     align=center border=0 cellpadding=0 cellspacing=2>
               <tr>
-                 <td align=center colspan=3>
+                 <td class=stealth align=center colspan=3>
                    <font size=+1 color=red>
                       Oops, please fix the following errors!
                    </font>
@@ -123,9 +124,11 @@ function SPITFORM($formfields, $errors)
 
 	while (list ($name, $message) = each ($errors)) {
 	    echo "<tr>
-                     <td align=right><font color=red>$name:</font></td>
-                     <td>&nbsp</td>
-                     <td align=left><font color=red>$message</font></td>
+                     <td class=stealth align=right>
+                           <font color=red>$name:</font></td>
+                     <td class=stealth>&nbsp</td>
+                     <td class=stealth align=left>
+                           <font color=red>$message</font></td>
                   </tr>\n";
 	}
 	echo "</table><br>\n";
@@ -153,6 +156,7 @@ function SPITFORM($formfields, $errors)
                   <br>
 	          <input type=text
                          name=\"formfields[usr_key]\"
+                         value=\"$formfields[usr_key]\"
 	                 size=50
 	                 maxlength=1024>
               </td>
@@ -199,6 +203,14 @@ function SPITFORM($formfields, $errors)
                  when adding new ssh public keys. 
           </ol>
           </blockquote></blockquote></blockquote>\n";
+
+    echo "<font color=red>NOTE:</font> We use the
+          <a href=www.openssh.org>OpenSSH</a> key format, which has a slightly
+          different protocol 2 public key format than some of the commercial 
+          vendors such as <a href=www.ssh.com>SSH Communications</a>. If you
+          use one of these commercial vendors, then please upload the public
+          key file and we will convert it for you. <i>Please do not paste
+          it in.</i>\n";
 }
 
 #
@@ -219,58 +231,43 @@ $errors = array();
 
 if (isset($formfields[usr_key]) &&
     strcmp($formfields[usr_key], "")) {
-    #
-    # Replace any embedded newlines first.
-    #
-    $formfields[usr_key] = ereg_replace("[\n]", "", $formfields[usr_key]);
 
+    #
+    # This is passed off to the shell, so taint check it.
+    # 
     if (! preg_match("/^[-\w\s\.\@\+\/\=]*$/", $formfields[usr_key])) {
 	$errors["PubKey"] = "Invalid characters";
-
-	SPITFORM($formfields, $errors);
-	PAGEFOOTER();
-	return;
     }
     else {
-	$usr_key[] = $formfields[usr_key];
+        #
+        # Replace any embedded newlines first.
+        #
+	$formfields[usr_key] = ereg_replace("[\n]", "", $formfields[usr_key]);
+	$usr_key = $formfields[usr_key];
+	$addpubkeyargs = "-k $uid '$usr_key' ";
     }
 }
 
 #
 # If usr provided a file for the key, it overrides the paste in text.
-# Must read and check it.
 #
 if (isset($usr_keyfile) &&
     strcmp($usr_keyfile, "") &&
     strcmp($usr_keyfile, "none")) {
 
-    if (! ($fp = fopen($usr_keyfile, "r"))) {
-	TBERROR("Could not open $usr_keyfile", 1);
+    if (! stat($usr_keyfile)) {
+	$errors["PubKey File"] = "No such file";
     }
-    while (!feof($fp)) {
-	$buffer = fgets($fp, 4096);
-
-	if (ereg("^[\n\#]", $buffer))
-	    continue;
-
-	if (! preg_match("/^[-\w\s\.\@\+\/\=\r\n]*$/", $buffer)) {
-	    $errors["PubKey File Contents"] = "Invalid characters";
-
-	    fclose($fp);
-	    SPITFORM($formfields, $errors);
-	    PAGEFOOTER();
-	    return;
-	}
-	$usr_key[] = Chop($buffer);
+    else {
+	$addpubkeyargs = "$uid $usr_keyfile";
+	chmod($usr_keyfile, 0640);	
     }
-    fclose($fp);
 }
 
 #
-# Insert each key. The comment field serves as the secondary key
-# to avoid duplication.
-# 
-if (isset($usr_key)) {
+# Must verify passwd to add keys.
+#
+if (isset($addpubkeyargs)) {
     if (! $isadmin) {
 	if (!isset($formfields[password]) ||
 	    strcmp($formfields[password], "") == 0) {
@@ -279,71 +276,35 @@ if (isset($usr_key)) {
 	elseif (VERIFYPASSWD($target_uid, $formfields[password]) != 0) {
 	    $errors["Password"] = "Incorrect password";
 	}
-
-	if (count($errors)) {
-	    SPITFORM($formfields, $errors);
-	    PAGEFOOTER();
-	    return;
-	}
-    }
-    
-    $chunky = "";
-    
-    while (list ($idx, $stuff) = each ($usr_key)) {
-	#
-	# Need to separate out the comment field. 
-	#
-	$pieces = explode(" ", $stuff);
-
-	if (count($pieces) == 4) {
-	    $key     = "$pieces[0] $pieces[1] $pieces[2] $pieces[3]";
-	    $comment = $pieces[3];
-	}
-	elseif (count($pieces) == 3) {
-	    $key     = "$pieces[0] $pieces[1] $pieces[2]";
-	    $comment = $pieces[0] . "-" . $pieces[2];
-	}
-	elseif (count($pieces) == 1) {
-	    continue;
-	}
-	else {
-	    TBERROR("Improper key: $stuff", 0);
-	}
-	DBQueryFatal("replace into user_pubkeys ".
-		     "values ('$target_uid', '$comment', '$key', now())");
-
-	$chunky .= chunk_split($key, 70, "\n");
-	$chunky .= "\n";
     }
+}
+else {
+    $errors["Missing Args"] = "Please supply a key or a keyfile";
+}
 
-    DBQueryFatal("update users set usr_modified=now() ".
-		 "where uid='$target_uid'");
-
-    #
-    # Audit
-    #
-    TBUserInfo($uid, $uid_name, $uid_email);
-    TBUserInfo($target_uid, $targuid_name, $targuid_email);
-
-    TBMAIL("$targuid_name <$targuid_email>",
-	   "SSH Public Key for '$target_uid' Added",
-	   "\n".
-	   "SSH Public Key for '$target_uid' added by '$uid'.\n".
-	   "\n".
-	   "$chunky\n".
-	   "\n".
-	   "Thanks,\n".
-	   "Testbed Ops\n".
-	   "Utah Network Testbed\n",
-	   "From: $uid_name <$uid_email>\n".
-	   "Cc: $TBMAIL_AUDIT\n".
-	   "Errors-To: $TBMAIL_WWW");
+# Spit the errors
+if (count($errors)) {
+    SPITFORM($formfields, $errors);
+    PAGEFOOTER();
+    return;
+}
 
-    #
-    # mkacct updates the user pubkeys.
-    # 
-    SUEXEC($uid, $TBADMINGROUP, "webmkacct -a $target_uid", 0);
+#
+# Okay, first run the script in verify mode to see if the key is
+# parsable. If it is, then do it for real.
+#
+if (ADDPUBKEY($uid, "webaddpubkey -n $addpubkeyargs")) {
+    $errors["Pubkey Format"] = "Could not be parsed. Is it a public key?";
+    SPITFORM($formfields, $errors);
+    PAGEFOOTER();
+    return;
 }
+ADDPUBKEY($uid, "webaddpubkey -a $addpubkeyargs");
+
+#
+# mkacct updates the user pubkeys in ~ssh/authorized_keys.
+# 
+SUEXEC($uid, $TBADMINGROUP, "webmkacct -a $target_uid", 0);
 
 header("Location: showpubkeys.php3?target_uid=$target_uid&finished=1");
 ?>
diff --git a/www/stoller-emulab-defs.php3 b/www/stoller-emulab-defs.php3
index 55abbf60ad..ba5f05d2fe 100644
--- a/www/stoller-emulab-defs.php3
+++ b/www/stoller-emulab-defs.php3
@@ -24,14 +24,14 @@ $BANNERCOLOR    = "#ABABE0";
 $THISHOMEBASE   = "Stoller.Emulab.Net";
 $THISPROJECT    = "Stoller's Utah Network Testbed";
 
-$TBMAILADDR_OPS		= "stoller@fast.cs.utah.edu";
+$TBMAILADDR_OPS		= "stoller@flux.utah.edu";
 $TBMAIL_OPS		= "Testbed Ops <$TBMAILADDR_OPS>";
-$TBMAILADDR_WWW		= "stoller@fast.cs.utah.edu";
+$TBMAILADDR_WWW		= "stoller@flux.utah.edu";
 $TBMAIL_WWW		= "Testbed WWW <$TBMAILADDR_WWW>";
-$TBMAILADDR_APPROVAL	= "stoller@fast.cs.utah.edu";
+$TBMAILADDR_APPROVAL	= "stoller@flux.utah.edu";
 $TBMAIL_APPROVAL	= "Testbed Approval <$TBMAILADDR_APPROVAL>";
-$TBMAILADDR_LOGS	= "stoller@fast.cs.utah.edu";
+$TBMAILADDR_LOGS	= "stoller@flux.utah.edu";
 $TBMAIL_LOGS		= "Testbed Logs <$TBMAILADDR_LOGS>";
-$TBMAILADDR_AUDIT	= "stoller@fast.cs.utah.edu";
+$TBMAILADDR_AUDIT	= "stoller@flux.utah.edu";
 $TBMAIL_AUDIT		= "Testbed Audit <$TBMAILADDR_AUDIT>";
 ?>
diff --git a/www/stoller-home-defs.php3 b/www/stoller-home-defs.php3
index d46a381afa..8d9e5ddccf 100644
--- a/www/stoller-home-defs.php3
+++ b/www/stoller-home-defs.php3
@@ -25,14 +25,14 @@ $BANNERCOLOR    = "#ABABE0";
 $THISHOMEBASE   = "Stoller.Emulab.Net";
 $THISPROJECT    = "My Home Network Testbed";
 
-$TBMAILADDR_OPS		= "stoller@fast.cs.utah.edu";
+$TBMAILADDR_OPS		= "stoller@stoller.casco.net";
 $TBMAIL_OPS		= "Testbed Ops <$TBMAILADDR_OPS>";
-$TBMAILADDR_WWW		= "stoller@fast.cs.utah.edu";
+$TBMAILADDR_WWW		= "stoller@stoller.casco.net";
 $TBMAIL_WWW		= "Testbed WWW <$TBMAILADDR_WWW>";
-$TBMAILADDR_APPROVAL	= "stoller@fast.cs.utah.edu";
+$TBMAILADDR_APPROVAL	= "stoller@stoller.casco.net";
 $TBMAIL_APPROVAL	= "Testbed Approval <$TBMAILADDR_APPROVAL>";
-$TBMAILADDR_LOGS	= "stoller@fast.cs.utah.edu";
+$TBMAILADDR_LOGS	= "stoller@stoller.casco.net";
 $TBMAIL_LOGS		= "Testbed Logs <$TBMAILADDR_LOGS>";
-$TBMAILADDR_AUDIT	= "stoller@fast.cs.utah.edu";
+$TBMAILADDR_AUDIT	= "stoller@stoller.casco.net";
 $TBMAIL_AUDIT		= "Testbed Audit <$TBMAILADDR_AUDIT>";
 ?>
-- 
GitLab