diff --git a/tbsetup/mkproj.in b/tbsetup/mkproj.in
index 4dbce9a4bcfcf30fc51d850bf08482e707db47df..14c349caebee3737be1ddc2b51102e53cb6ddd65 100755
--- a/tbsetup/mkproj.in
+++ b/tbsetup/mkproj.in
@@ -1,48 +1,64 @@
 #!/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;
-
-#
-# Make a project directory hierarchy. Must be called as tbroot.
-# Creates a directory rooted /proj/pid. The directory is setuid
-# to the project leader, and setgid to the project gid. We get
-# this info from the database.
-#
-# usage: mkproj <pid>
-#
-
+use Getopt::Std;
+
+#
+# Perform project approval. Does a lot of stuff, see below!
+#
+sub usage()
+{
+    print(STDERR
+	  "Usage: mkproj [-s] [-h leader_uid] [-m <message> | -f <file>] ".
+	  "<pid>\n".
+	  "switches and arguments:\n".
+	  "-s         - silent; do not send approval email to leader\n".
+	  "-h <uid>   - switch project leader to specified uid\n".
+	  "-m <text>  - Include text in approval email message\n".
+	  "-f <file>  - Include text from file in approval email message\n".
+	  "<pid>      - project to approve.\n");
+    exit(-1);
+}
+my $optlist    = "qsh:m:f:";
+my $quiet      = 0;
+my $silent     = 0;
+my $leader_uid;
+my $message;
+my $mfilename;
+my $pid;
+
+# Protos
 sub fatal($);
 
 #
 # Configure variables
 #
-my $TB       = "@prefix@";
-my $TBOPS    = "@TBOPSEMAIL@";
-my $MKGROUP  = "$TB/sbin/mkgroup";
-my $MODGROUPS= "$TB/sbin/modgroups";
-my $MKACCT   = "$TB/sbin/tbacct add";
-my $CVSBIN   = "/usr/bin/cvs";
-my $CHOWN    = "/usr/sbin/chown";
-my $GRANTTYPE= "$TB/sbin/grantnodetype -d";
+my $TB            = "@prefix@";
+my $TBOPS         = "@TBOPSEMAIL@";
+my $TBAPPROVAL	  = "@TBAPPROVALEMAIL@";
+my $TBBASE        = "@TBBASE@";
+my $MKGROUP       = "$TB/sbin/mkgroup";
+my $MODGROUPS     = "$TB/sbin/modgroups";
+my $MKACCT        = "$TB/sbin/tbacct add";
+my $CVSBIN        = "/usr/bin/cvs";
+my $CHOWN         = "/usr/sbin/chown";
+my $GRANTTYPE     = "$TB/sbin/grantnodetype -d";
 my $WIKISUPPORT   = @WIKISUPPORT@;
 my $BUGDBSUPPORT  = @BUGDBSUPPORT@;
 my $OPSDBSUPPORT  = @OPSDBSUPPORT@;
 my $CVSSUPPORT    = @CVSSUPPORT@;
 my $MAILMANSUPPORT= @MAILMANSUPPORT@;
-my $ADDWIKIPROJ = "$TB/sbin/addwikiproj";
-my $ADDBUGDBPROJ= "$TB/sbin/addbugdbproj";
-my $ADDMMLIST   = "$TB/sbin/addmmlist";
-my $OPSDBCONTROL= "$TB/sbin/opsdb_control";
+my $ADDWIKIPROJ   = "$TB/sbin/addwikiproj";
+my $ADDBUGDBPROJ  = "$TB/sbin/addbugdbproj";
+my $ADDMMLIST     = "$TB/sbin/addmmlist";
+my $OPSDBCONTROL  = "$TB/sbin/opsdb_control";
 	  
 my @DIRLIST  = ("exp", "images", "logs", "deltas", "tarfiles", "rpms",
 		"groups", "tiplogs", "images/sigs", "templates");
-my $projhead;
 
 #
 # Untaint the path
@@ -62,6 +78,8 @@ use lib "@prefix@/lib";
 use libaudit;
 use libdb;
 use libtestbed;
+use User;
+use Project;
 
 my $PROJROOT     = PROJROOT();
 my $GRPROOT      = GROUPROOT();
@@ -73,8 +91,12 @@ my $SCRATCHROOT  = SCRATCHROOT();
 my $TFTPDIR  = "/tftpboot/$PROJROOT";
 my $CVSREPOS = "$PROJROOT/cvsrepos";
 
+# Locals
+my $leader;
+my $projhead;
+
 #
-# We don't want to run this script unless its the real version.
+# We do not want to run this script unless its the real version.
 #
 if ($EUID != 0) {
     die("*** $0:\n".
@@ -93,36 +115,53 @@ if ($UID == 0) {
 #
 # Check args.
 #
-if ($#ARGV < 0) {
-    die("Usage: mkprojdir <pid>\n");
+my %options = ();
+if (! getopts($optlist, \%options)) {
+    usage();
+}
+if (defined($options{"q"})) {
+    $quiet = 1;
 }
-my $pid = $ARGV[0];
+if (defined($options{"s"})) {
+    $silent = 1;
+}
+if (defined($options{"m"})) {
+    $message = $options{"m"};
+}
+if (defined($options{"f"})) {
+    $mfilename = $options{"f"};
+    fatal("$mfilename does not exist!")
+	if (! -e $mfilename);
+}
+if (defined($options{"h"})) {
+    $leader_uid = $options{"h"};
+}
+usage()
+    if (! @ARGV);
+
+$pid = $ARGV[0];
 
 #
 # Untaint the argument.
 #
-if ($pid =~ /^([-\@\w.]+)$/) {
+if ($pid =~ /^([-\w]+)$/) {
     $pid = $1;
 }
 else {
     die("Invalid pid '$pid' contains illegal characters.\n");
 }
 
-#
-# Figure out who called us. Only with admin status in the DB can run
-# this script.
-#
-if (!TBAdmin($UID)) {
-    die("*** $0:\n".
-	"    You must be a TB administrator to run this script!\n");
+# Map invoking user to object.
+my $this_user = User->ThisUser();
+if (! defined($this_user)) {
+    fatal("You ($UID) do not exist!");
 }
 
 #
-# We need the project leader name.
+# Figure out who called us. Must have admin status to do this.
 #
-if (! ($projhead = ProjLeader($pid))) {
-    die("*** $0:\n".
-	"    Could not get project leader for project $pid!\n");
+if (!TBAdmin()) {
+    fatal("You must be a TB administrator to run this script!");
 }
 
 #
@@ -135,6 +174,74 @@ if (AuditStart(0)) {
     exit(0);
 }
 
+#
+# Map project name to object.
+#
+my $target_project = Project->Lookup($pid);
+if (! defined($target_project)) {
+    fatal("Could not map project $pid to its object!");
+}
+
+#
+# The welcome message ...
+#
+if (defined($mfilename)) {
+    open(MFILE, $mfilename) or
+	fatal("Could not open $mfilename");
+
+    $message = "";
+    while (<MFILE>) {
+	$message .= $_;
+    }
+    close(MFILE);
+}
+
+#
+# If a leader uid was provided on the command line, we are changing the
+# leader. Note that this is allowed *only* for projects that have not
+# been approved yet. 
+#
+if (defined($leader_uid)) {
+    $leader = User->Lookup($leader_uid);
+    if (!defined($leader)) {
+	fatal("Could not map user $leader_uid to its object!");
+    }
+    # See if already did this; is so skip the following checks.
+    my $curleader = $target_project->GetLeader();
+    if (!defined($curleader)) {
+	fatal("Could not map current leader of project $pid to its object!");
+    }
+    if (! $curleader->SameUser($leader)) {
+	fatal("Not allowed to change the leader of an approved project!")
+	    if ($target_project->approved());
+
+	# Update the project structure with the new leader. We are going
+	# to set the approved bit below, so this is the last chance to do
+	# this until we have code in place to change it later.
+	$target_project->ChangeLeader($leader) == 0 or
+	    fatal("Could not change project leader for $pid to $leader_uid!");
+    }
+}
+else {
+    $leader = $target_project->GetLeader();
+    if (!defined($leader)) {
+	fatal("Could not map current leader of project $pid to its object!");
+    }
+}
+# Avoid taint check problem.
+$leader_uid = $leader->uid();
+
+# Approve the project; we are committed to the leader.
+$target_project->SetApproved(1) == 0 or
+    fatal("Could not set the approval bit on project $target_project!");
+
+#
+# Leader needs to have his approved bit set. Eventually this should be done
+# in mkaccount when that code moves from the web interface.
+#
+$leader->SetStatus(USERSTATUS_ACTIVE()) == 0 or
+    fatal("Could not change $leader to active!");
+
 #
 # Before we can proceed, we need to create the project (unix) group
 # and then create an account for the project leader. We pass this off
@@ -163,19 +270,19 @@ if ($MAILMANSUPPORT) {
 	fatal("$ADDMMLIST -a ${pid}-users failed!");
 }
 
-system("$MKACCT $projhead") == 0 or
-    fatal("$MKACCT $projhead failed!");
+system("$MKACCT $leader_uid") == 0 or
+    fatal("$MKACCT $leader_uid failed!");
 
-system("$MODGROUPS -a $pid:$pid:project_root $projhead") == 0 or
-    fatal("$MODGROUPS -a $pid:$pid:project_root $projhead failed!");
+system("$MODGROUPS -a $pid:$pid:project_root $leader_uid") == 0 or
+    fatal("$MODGROUPS -a $pid:$pid:project_root $leader_uid failed!");
 
 $EUID = 0;
 
 #
 # This acts as check (and we need the numeric uid) in case mkacct failed!
 # 
-my (undef,undef,$uid) = getpwnam($projhead)
-    or fatal("$projhead not in passwd file");
+my (undef,undef,$uid) = getpwnam($leader_uid)
+    or fatal("$leader_uid not in passwd file");
 
 my (undef,undef,$gid) = getgrnam($pid)
     or fatal("$pid not in group file");
@@ -354,6 +461,28 @@ if ($query_result->num_rows) {
     }
 }
 
+# Send email, unless silent option given.
+if (!$silent) {
+    my $leader_name  = $leader->name();
+    my $leader_email = $leader->email();
+    
+    SENDMAIL("$leader_name <$leader_email>",
+     "Project '$pid' Approval",
+     "\n".
+     "This message is to notify you that your project '$pid'\n".
+     "has been approved.  We recommend that you save this link so that\n".
+     "you can send it to people you wish to have join your project.\n".
+     "Otherwise, tell them to go to ${TBBASE} and join it.\n".
+     "\n".
+     "    ${TBBASE}/joinproject.php3?target_pid=$pid\n".
+     (defined($message) ? "\n${message}\n" : "") .
+     "\n".
+     "Thanks,\n".
+     "Testbed Operations\n",
+     "$TBAPPROVAL",
+     "Bcc: $TBAPPROVAL");
+}
+
 print "Project Creation Completed!\n";
 exit(0);
 
diff --git a/www/approveproject.php3 b/www/approveproject.php3
index 48edeca0e8f0e04dd8b0f89115f7254004c1a6a0..cae6f0604ac849aca5b6b33f381242fb859ab09b 100644
--- a/www/approveproject.php3
+++ b/www/approveproject.php3
@@ -1,7 +1,7 @@
 <?php
 #
 # EMULAB-COPYRIGHT
-# Copyright (c) 2000-2003, 2005, 2006 University of Utah and the Flux Group.
+# Copyright (c) 2000-2007 University of Utah and the Flux Group.
 # All rights reserved.
 #
 include("defs.php3");
@@ -49,7 +49,7 @@ $headuid = $this_project->head_uid();
 # If the user wanted to change the head uid, do that now (we change both
 # the head_uid and the leader of the default project)
 #
-if (isset($head_uid) && $head_uid != "") {
+if ($approval == "approve" && isset($head_uid) && $head_uid != "") {
     if (! ($newleader = User::Lookup($head_uid))) {
 	TBERROR("Unknown user $head_uid", 1);
     }
@@ -78,6 +78,7 @@ if (!isset($user_interface) ||
 $curstatus     = $leader->status();
 $headuid_email = $leader->email();
 $headname      = $leader->name();
+#$headidx       = $leader->uid_idx();
 #echo "Status = $curstatus, Email = $headuid_email<br>\n";
 
 #
@@ -175,28 +176,15 @@ elseif ((strcmp($approval, "deny") == 0) ||
           </h3>\n";
 }
 elseif (strcmp($approval, "approve") == 0) {
-
-    #
-    # Change the status if necessary. This only happens for new users
-    # being approved in their first project. After this, the status is
-    # going to be "active", and we just leave it that way.
-    #
-    if (strcmp($curstatus, "active")) {
-        if (strcmp($curstatus, "unapproved") == 0) {
-	    $newstatus = "active";
-        }
-        else {
-	    TBERROR("Invalid $headuid status $curstatus in ".
-                    "approveproject.php3", 1);
-        }
-	$leader->SetUserInterface($user_interface);
-	$leader->SetStatus($newstatus);
+    $optargs = "";
+    
+    # Sanity check the leader status.
+    if ($curstatus != TBDB_USERSTATUS_ACTIVE &&
+	$curstatus != TBDB_USERSTATUS_UNAPPROVED) {
+	TBERROR("Invalid $headuid status $curstatus", 1);
     }
-
-    #
-    # Set the project "approved" field to true. 
-    #
-    $this_project->SetApproved(1);
+    # Why is this here?
+    $leader->SetUserInterface($user_interface);
 
     #
     # XXX
@@ -218,41 +206,43 @@ elseif (strcmp($approval, "approve") == 0) {
 	    $this_project->SetRemoteOK($foo);
     }
 
+    unset($tmpfname);
+    if (isset($message)) {
+	$tmpfname = tempnam("/tmp", "approveproj");
+	$fp = fopen($tmpfname, "w");
+	fwrite($fp, $message);
+	fclose($fp);
+	
+	$optargs = " -f " . escapeshellarg($tmpfname);
+    }
+
     #
     # Invoke the script. This does it all. If it fails, we will find out
     # about it.
     #
-    echo "<br>
-          Project '$pid' is being created!<br><br>
-          This will take a minute or two. <b>Please</b> do not click the Stop
-          button during this time. If you do not receive notification within
-          a reasonable amount of time, please contact $TBMAILADDR.\n";
-    flush();
+    STARTBUSY("Project '$pid' is being created");
+    
+    $retval = SUEXEC($uid, $TBADMINGROUP, "webmkproj $optargs $pid",
+		     SUEXEC_ACTION_IGNORE);
 
-    SUEXEC($uid, $TBADMINGROUP, "webmkproj $pid", SUEXEC_ACTION_DIE);
+    CLEARBUSY();
 
-    TBMAIL("$headname '$headuid' <$headuid_email>",
-         "Project '$pid' Approval",
-         "\n".
-	 "This message is to notify you that your project '$pid'\n".
-	 "has been approved.  We recommend that you save this link so that\n".
-	 "you can send it to people you wish to have join your project.\n".
-	 "Otherwise, tell them to go to ${TBBASE} and join it.\n".
-	 "\n".
-	 "    ${TBBASE}/joinproject.php3?target_pid=$pid\n".
-         "\n".
-	 "$message\n".
-         "\n".
-         "Thanks,\n".
-         "Testbed Operations\n",
-         "From: $TBMAIL_APPROVAL\n".
-         "Bcc: $TBMAIL_APPROVAL\n".
-         "Errors-To: $TBMAIL_WWW");
+    if (isset($tmpfname)) {
+	unlink($tmpfname);
+    }
+    if ($retval) {
+	# Lets tack the message onto the output so we have a record.
+	if (isset($message)) {
+	    $suexec_output .= "\n\n*** Saved approval message text:\n\n";
+	    $suexec_output .= $message;
+	}
+	SUEXECERROR(SUEXEC_ACTION_DIE);
+	return;
+    }
 
     if (!$FirstInitState) {
-	echo "<p><b>
-                 Project $pid (User: $headuid) has been approved.
-                </b>\n";
+	sleep(1);
+	PAGEREPLACE(CreateURL("showproject", $this_project));
     }
     else {
 	echo "<br><br><font size=+1>\n";