CVE-2013-0267: Improper Input Validation (CWE-20)
value: The Privileges portion of the web GUI and the XMLRPC API in Apache VCL 2.3.x before 2.3.2, 2.2.x before 2.2.2 and 2.1 allow remote authenticated users with nodeAdmin, manageGroup, resourceGrant, or userGrant permissions to gain privileges, cause a denial of service, or conduct cross-site scripting (XSS) attacks by leveraging improper data validation.
CWE description: The product receives input or data, but it does
        not validate or incorrectly validates that the input has the
        properties that are required to process the data safely and
        correctly.
CVSS v3 base score: 8.8
Published: 2018-02-21T15:29Z
Repository: https://github.com/apache/vcl
Language: PHP
Files changed: web/.ht-inc/groups.php, web/.ht-inc/privileges.php, web/.ht-inc/utils.php, web/.ht-inc/xmlrpcWrappers.php
Commit date: 2013-02-07 19:43:01 +0000
Commit message: xmlrpcWrappers.php:
-added option to supply name of reservation to XMLRPCdeployServer
-XMLRPCremoveUserGroup and XMLRPCdeleteUserGroup did the same thing; made XMLRPCdeleteUserGroup just call XMLRPCremoveUserGroup - kept both so that scripts would not have to change because one was dropped
-did some updates to headers to provide better documentation generation
-changed some whitespace formatting
-modified XMLRPCgetRequestIds - added OS, isserver, state, and if it is a server, servername to returned data-modified XMLRPCgetUserGroupAttributes - added overlapResCount to returned data; modified error return code to not be duplicated with an different error
-modified XMLRPCremoveUserGroup - added ability to delete federated groups if user has access; added check for group being in use before deleting it and return error if it is in use
-modified XMLRPCeditUserGroup - added ability to modify federated groups if user has access; do not attempt to update name and affiliation if they are not changed (resulted in an duplicate group name error)
-modified XMLRPCgetUserGroupMembers - added ability to modify federated groups if user has access

privileges.php:
-additional parameter checking
-modified viewNodes - added serverProfileAdmin to $privs array used to determine which resource groups can be added at the node
-modified AJsubmitAddChildNode - moved updateUserOrGroupPrivs to inside if conditional (no need to call it if no changes)
-modified getNodeCascadePrivileges - located problem where blocking cascaded privileges does not continue to child nodes; added fix and commented it out until can properly prepare users for change

utils.php:
-modified getResourceGroups - optional argument of $id to limit returned data to just a specified resource group
-modified getUserGroupID - added optional argument of $noadd that causes the function to return NULL instead of adding the group if it does not already exist
-modified getTypes - removed conditional that checks for 'block' and 'cascade' with resource groups - type here is computer, image, etc.

groups.php:
-modified addGroup - moved conditional for editgroupid to inside 'user' section


Diff: diff --git a/web/.ht-inc/groups.php b/web/.ht-inc/groups.php
index 0046212..3dd5e12 100644
--- a/web/.ht-inc/groups.php
+++ b/web/.ht-inc/groups.php
@@ -1014,10 +1014,11 @@ function processGroupInput($checks=1) {
 /// \brief checks for $name being in usergroup/resource group (based on $type)
 /// except for $id
 ///
 ////////////////////////////////////////////////////////////////////////////////
 function checkForGroupName($name, $type, $id, $extraid) {
+	$name = mysql_real_escape_string($name);
 	if($type == "user")
 		$query = "SELECT id FROM usergroup "
 		       . "WHERE name = '$name' AND "
 		       .       "affiliationid = $extraid";
 	else
@@ -1088,13 +1089,13 @@ function updateGroup($data) {
 ///
 /// \brief performs a query to insert the group with data from $data
 ///
 ////////////////////////////////////////////////////////////////////////////////
 function addGroup($data) {
-	if($data['editgroupid'] == 0 || $data['editgroupid'] == '')
-		$data['editgroupid'] = 'NULL';
 	if($data['type'] == "user") {
+		if($data['editgroupid'] == 0 || $data['editgroupid'] == '')
+			$data['editgroupid'] = 'NULL';
 		if(! array_key_exists('custom', $data))
 			$data['custom'] = 1;
 		elseif($data['custom'] == 0) {
 			$ownerid = 'NULL';
 			$data['editgroupid'] = 'NULL';
diff --git a/web/.ht-inc/privileges.php b/web/.ht-inc/privileges.php
index 27b336c..ab524ba 100644
--- a/web/.ht-inc/privileges.php
+++ b/web/.ht-inc/privileges.php
@@ -98,29 +98,29 @@ function viewNodes() {
 		print "<TABLE summary=\"\" cellspacing=\"\" cellpadding=\"\">\n";
 		print "  <TR valign=top>\n";
 		print "    <TD><FORM action=\"" . BASEURL . SCRIPT . "\" method=post>\n";
 		print "    <button id=addNodeBtn dojoType=\"dijit.form.Button\">\n";
 		print "      Add Child\n";
-		print "	    <script type=\"dojo/method\" event=onClick>\n";
+		print "      <script type=\"dojo/method\" event=onClick>\n";
 		print "        showPrivPane('addNodePane');\n";
 		print "        return false;\n";
 		print "      </script>\n";
 		print "    </button>\n";
 		print "    </FORM></TD>\n";
 		print "    <TD><FORM action=\"" . BASEURL . SCRIPT . "\" method=post>\n";
 		print "    <button id=deleteNodeBtn dojoType=\"dijit.form.Button\">\n";
 		print "      Delete Node and Children\n";
-		print "	    <script type=\"dojo/method\" event=onClick>\n";
+		print "      <script type=\"dojo/method\" event=onClick>\n";
 		print "        dijit.byId('deleteDialog').show();\n";
 		print "        return false;\n";
 		print "      </script>\n";
 		print "    </button>\n";
 		print "    </FORM></TD>\n";
 		print "    <TD><FORM action=\"" . BASEURL . SCRIPT . "\" method=post>\n";
 		print "    <button id=renameNodeBtn dojoType=\"dijit.form.Button\">\n";
 		print "      Rename Node\n";
-		print "	    <script type=\"dojo/method\" event=onClick>\n";
+		print "      <script type=\"dojo/method\" event=onClick>\n";
 		print "        dijit.byId('renameDialog').show();\n";
 		print "        return false;\n";
 		print "      </script>\n";
 		print "    </button>\n";
 		print "    </FORM></TD>\n";
@@ -184,11 +184,11 @@ function viewNodes() {
 		print "There are no user privileges at the selected node.<br>\n";
 	}
 	if($hasUserGrant) {
 		print "<button id=addUserBtn dojoType=\"dijit.form.Button\">\n";
 		print "  Add User\n";
-		print "	<script type=\"dojo/method\" event=onClick>\n";
+		print "  <script type=\"dojo/method\" event=onClick>\n";
 		print "    showPrivPane('addUserPane');\n";
 		print "    return false;\n";
 		print "  </script>\n";
 		print "</button>\n";
 	}
@@ -234,11 +234,11 @@ function viewNodes() {
 		$groups = array();
 	}
 	if($hasUserGrant) {
 		print "<button id=addGroupBtn dojoType=\"dijit.form.Button\">\n";
 		print "  Add Group\n";
-		print "	<script type=\"dojo/method\" event=onClick>\n";
+		print "  <script type=\"dojo/method\" event=onClick>\n";
 		print "    showPrivPane('addUserGroupPane');\n";
 		print "    return false;\n";
 		print "  </script>\n";
 		print "</button>\n";
 	}
@@ -294,11 +294,11 @@ function viewNodes() {
 		$resources = array();
 	}
 	if($hasResourceGrant) {
 		print "<button id=addResourceBtn dojoType=\"dijit.form.Button\">\n";
 		print "  Add Resource Group\n";
-		print "	<script type=\"dojo/method\" event=onClick>\n";
+		print "  <script type=\"dojo/method\" event=onClick>\n";
 		print "    showPrivPane('addResourceGroupPane');\n";
 		print "    return false;\n";
 		print "  </script>\n";
 		print "</button>\n";
 	}
@@ -310,11 +310,11 @@ function viewNodes() {
 	print "<div dojoType=dijit.Dialog\n";
 	print "      id=addUserPane\n";
 	print "      title=\"Add User Permission\"\n";
 	print "      duration=250\n";
 	print "      draggable=true>\n";
-	print "	  <script type=\"dojo/connect\" event=onCancel>\n";
+	print "    <script type=\"dojo/connect\" event=onCancel>\n";
 	print "      addUserPaneHide();\n";
 	print "    </script>\n";
 	print "<H2>Add User</H2>\n";
 	print "<div id=addPaneNodeName></div>\n";
 	print "<TABLE border=1 summary=\"\">\n";
@@ -353,19 +353,19 @@ function viewNodes() {
 	print "<div id=addUserPrivStatus></div>\n";
 	print "<TABLE summary=\"\"><TR>\n";
 	print "<TD>\n";
 	print "  <button id=submitAddUserBtn dojoType=\"dijit.form.Button\">\n";
 	print "    Submit New User\n";
-	print "	  <script type=\"dojo/method\" event=onClick>\n";
+	print "    <script type=\"dojo/method\" event=onClick>\n";
 	print "      submitAddUser();\n";
 	print "    </script>\n";
 	print "  </button>\n";
 	print "</TD>\n";
 	print "<TD>\n";
 	print "  <button id=cancelAddUserBtn dojoType=\"dijit.form.Button\">\n";
 	print "    Cancel\n";
-	print "	  <script type=\"dojo/method\" event=onClick>\n";
+	print "    <script type=\"dojo/method\" event=onClick>\n";
 	print "      addUserPaneHide();\n";
 	print "    </script>\n";
 	print "  </button>\n";
 	print "</TD>\n";
 	print "</TR></TABLE>\n";
@@ -376,11 +376,11 @@ function viewNodes() {
 	print "<div dojoType=dijit.Dialog\n";
 	print "      id=addUserGroupPane\n";
 	print "      title=\"Add User Group Permission\"\n";
 	print "      duration=250\n";
 	print "      draggable=true>\n";
-	print "	  <script type=\"dojo/connect\" event=onCancel>\n";
+	print "    <script type=\"dojo/connect\" event=onCancel>\n";
 	print "      addUserGroupPaneHide();\n";
 	print "    </script>\n";
 	print "<H2>Add User Group</H2>\n";
 	print "<div id=addGroupPaneNodeName></div>\n";
 	print "<TABLE border=1 summary=\"\">\n";
@@ -421,19 +421,19 @@ function viewNodes() {
 	print "<div id=addUserGroupPrivStatus></div>\n";
 	print "<TABLE summary=\"\"><TR>\n";
 	print "<TD>\n";
 	print "  <button id=submitAddGroupBtn dojoType=\"dijit.form.Button\">\n";
 	print "    Submit New User Group\n";
-	print "	  <script type=\"dojo/method\" event=onClick>\n";
+	print "    <script type=\"dojo/method\" event=onClick>\n";
 	print "      submitAddUserGroup();\n";
 	print "    </script>\n";
 	print "  </button>\n";
 	print "</TD>\n";
 	print "<TD>\n";
 	print "  <button id=cancelAddGroupBtn dojoType=\"dijit.form.Button\">\n";
 	print "    Cancel\n";
-	print "	  <script type=\"dojo/method\" event=onClick>\n";
+	print "    <script type=\"dojo/method\" event=onClick>\n";
 	print "      addUserGroupPaneHide();\n";
 	print "    </script>\n";
 	print "  </button>\n";
 	print "</TD>\n";
 	print "</TR></TABLE>\n";
@@ -444,11 +444,11 @@ function viewNodes() {
 	print "<div dojoType=dijit.Dialog\n";
 	print "      id=addResourceGroupPane\n";
 	print "      title=\"Add Resource Group Permission\"\n";
 	print "      duration=250\n";
 	print "      draggable=true>\n";
-	print "	  <script type=\"dojo/connect\" event=onCancel>\n";
+	print "    <script type=\"dojo/connect\" event=onCancel>\n";
 	print "      addResourceGroupPaneHide();\n";
 	print "    </script>\n";
 	print "<H2>Add Resource Group</H2>\n";
 	print "<div id=addResourceGroupPaneNodeName></div>\n";
 	print "<TABLE border=1 summary=\"\">\n";
@@ -464,11 +464,12 @@ function viewNodes() {
 	}
 	print "  </TR>\n";
 	print "  <TR>\n";
 	print "    <TD>\n";
 	$resources = array();
-	$privs = array("computerAdmin","mgmtNodeAdmin",  "imageAdmin", "scheduleAdmin");
+	$privs = array("computerAdmin", "mgmtNodeAdmin", "imageAdmin",
+	               "scheduleAdmin", "serverProfileAdmin");
 	$resourcesgroups = getUserResources($privs, array("manageGroup"), 1);
 	foreach(array_keys($resourcesgroups) as $type) {
 		foreach($resourcesgroups[$type] as $id => $group) {
 			$resources[$id] = $type . "/" . $group;
 		}
@@ -499,19 +500,19 @@ function viewNodes() {
 	print "<div id=addResourceGroupPrivStatus></div>\n";
 	print "<TABLE summary=\"\"><TR>\n";
 	print "<TD>\n";
 	print "  <button dojoType=\"dijit.form.Button\">\n";
 	print "    Submit New Resource Group\n";
-	print "	  <script type=\"dojo/method\" event=onClick>\n";
+	print "    <script type=\"dojo/method\" event=onClick>\n";
 	print "      submitAddResourceGroup();\n";
 	print "    </script>\n";
 	print "  </button>\n";
 	print "</TD>\n";
 	print "<TD>\n";
 	print "  <button dojoType=\"dijit.form.Button\">\n";
 	print "    Cancel\n";
-	print "	  <script type=\"dojo/method\" event=onClick>\n";
+	print "    <script type=\"dojo/method\" event=onClick>\n";
 	print "      addResourceGroupPaneHide();\n";
 	print "    </script>\n";
 	print "  </button>\n";
 	print "</TD>\n";
 	print "</TR></TABLE>\n";
@@ -526,30 +527,30 @@ function viewNodes() {
 	print "     draggable=true>\n";
 	print "<H2>Add Child Node</H2>\n";
 	print "<div id=addChildNodeName></div>\n";
 	print "<strong>New Node:</strong>\n";
 	print "<input type=text id=childNodeName dojoType=dijit.form.TextBox>\n";
-	print "	<script type=\"dojo/connect\" event=onKeyPress args=\"e\">\n";
+	print "  <script type=\"dojo/connect\" event=onKeyPress args=\"e\">\n";
 	print "    if(e.keyCode == dojo.keys.ENTER) {\n";
 	print "      submitAddChildNode();\n";
 	print "    }\n";
 	print "  </script>\n";
 	print "</input>\n";
 	print "<div id=addChildNodeStatus></div>\n";
 	print "<TABLE summary=\"\"><TR>\n";
 	print "<TD>\n";
 	print "  <button id=submitAddNodeBtn dojoType=\"dijit.form.Button\">\n";
 	print "    Create Child\n";
-	print "	  <script type=\"dojo/method\" event=onClick>\n";
+	print "    <script type=\"dojo/method\" event=onClick>\n";
 	print "      submitAddChildNode();\n";
 	print "    </script>\n";
 	print "  </button>\n";
 	print "</TD>\n";
 	print "<TD>\n";
 	print "  <button id=cancelAddNodeBtn dojoType=\"dijit.form.Button\">\n";
 	print "    Cancel\n";
-	print "	  <script type=\"dojo/method\" event=onClick>\n";
+	print "    <script type=\"dojo/method\" event=onClick>\n";
 	print "      dojo.byId('childNodeName').value = '';\n";
 	print "      dojo.byId('addChildNodeStatus').innerHTML = '';\n";
 	print "      dijit.byId('addNodePane').hide();\n";
 	print "    </script>\n";
 	print "  </button>\n";
@@ -569,19 +570,19 @@ function viewNodes() {
 	print "<div align=center>\n";
 	print "<TABLE summary=\"\"><TR>\n";
 	print "<TD>\n";
 	print "  <button id=submitDeleteNodeBtn dojoType=\"dijit.form.Button\">\n";
 	print "    Delete Nodes\n";
-	print "	  <script type=\"dojo/method\" event=onClick>\n";
+	print "    <script type=\"dojo/method\" event=onClick>\n";
 	print "      deleteNodes();\n";
 	print "    </script>\n";
 	print "  </button>\n";
 	print "</TD>\n";
 	print "<TD>\n";
 	print "  <button id=cancelDeleteNodeBtn dojoType=\"dijit.form.Button\">\n";
 	print "    Cancel\n";
-	print "	  <script type=\"dojo/method\" event=onClick>\n";
+	print "    <script type=\"dojo/method\" event=onClick>\n";
 	print "      dijit.byId('deleteDialog').hide();\n";
 	print "    </script>\n";
 	print "  </button>\n";
 	print "</TD>\n";
 	print "</TR></TABLE>\n";
@@ -597,11 +598,11 @@ function viewNodes() {
 	print "     draggable=true>\n";
 	print "Enter a new name for the selected node:<br><br>\n";
 	print "<div id=renameNodeName></div><br>\n";
 	print "<strong>New Name:</strong>\n";
 	print "<input type=text id=newNodeName dojoType=dijit.form.TextBox>\n";
-	print "	<script type=\"dojo/connect\" event=onKeyPress args=\"e\">\n";
+	print "  <script type=\"dojo/connect\" event=onKeyPress args=\"e\">\n";
 	print "    if(e.keyCode == dojo.keys.ENTER) {\n";
 	print "      renameNode();\n";
 	print "    }\n";
 	print "  </script>\n";
 	print "</input>\n";
@@ -609,19 +610,19 @@ function viewNodes() {
 	print "<div align=center>\n";
 	print "<TABLE summary=\"\"><TR>\n";
 	print "<TD>\n";
 	print "  <button id=submitRenameNodeBtn dojoType=\"dijit.form.Button\">\n";
 	print "    Rename Node\n";
-	print "	  <script type=\"dojo/method\" event=onClick>\n";
+	print "    <script type=\"dojo/method\" event=onClick>\n";
 	print "      renameNode();\n";
 	print "    </script>\n";
 	print "  </button>\n";
 	print "</TD>\n";
 	print "<TD>\n";
 	print "  <button id=cancelRenameNodeBtn dojoType=\"dijit.form.Button\">\n";
 	print "    Cancel\n";
-	print "	  <script type=\"dojo/method\" event=onClick>\n";
+	print "    <script type=\"dojo/method\" event=onClick>\n";
 	print "      dijit.byId('renameDialog').hide();\n";
 	print "    </script>\n";
 	print "  </button>\n";
 	print "</TD>\n";
 	print "</TR></TABLE>\n";
@@ -630,11 +631,11 @@ function viewNodes() {
 	print "</div>\n";
 	print "</div>\n";
 
 	print "<div dojoType=dijit.Dialog id=workingDialog duration=250 refocus=False>\n";
 	print "Loading...\n";
-	print "	<script type=\"dojo/connect\" event=_setup>\n";
+	print "  <script type=\"dojo/connect\" event=_setup>\n";
 	print "    dojo.addClass(dijit.byId('workingDialog').titleBar, 'hidden');\n";
 	print "  </script>\n";
 	print "</div>\n";
 	if(! $hasManagePerms)
 		return;
@@ -646,14 +647,14 @@ function viewNodes() {
 	print "groups that are not specific to any nodes in the privilege tree.<br>\n";
 	print "Use this portion of the site to manage those permissions.<br><br>\n";
 	printSelectInput("editusergroupid", $groups, -1, 0, 0, 'editusergroupid', 'onChange="hideUserGroupPrivs();"');
 	$cont = addContinuationsEntry('AJpermSelectUserGroup');
 	print "<button dojoType=\"dijit.form.Button\">\n";
-	print "	Manage User Group Permissions\n";
-	print "	<script type=\"dojo/method\" event=onClick>\n";
-	print "		selectUserGroup('$cont');\n";
-	print "	</script>\n";
+	print "  Manage User Group Permissions\n";
+	print "  <script type=\"dojo/method\" event=onClick>\n";
+	print "    selectUserGroup('$cont');\n";
+	print "  </script>\n";
 	print "</button>\n";
 	print "<div id=\"extrapermsdiv\">\n";
 	print "<table summary=\"\">\n";
 	print "<tr>\n";
 	print "<td nowrap>\n";
@@ -678,21 +679,21 @@ function viewNodes() {
 	print "</table><br><br>\n";
 	print "Copy permissions from user group: ";
 	printSelectInput("copyusergroupid", $groups, -1, 0, 0, 'copyusergroupid');
 	$cont = addContinuationsEntry('AJpermSelectUserGroup');
 	print "<button dojoType=\"dijit.form.Button\" id=\"usergroupcopyprivsbtn\" disabled>\n";
-	print "	Copy Permissions\n";
-	print "	<script type=\"dojo/method\" event=onClick>\n";
-	print "		copyUserGroupPrivs('$cont');\n";
-	print "	</script>\n";
+	print "  Copy Permissions\n";
+	print "  <script type=\"dojo/method\" event=onClick>\n";
+	print "    copyUserGroupPrivs('$cont');\n";
+	print "  </script>\n";
 	print "</button><br><br>\n";
 	$cont = addContinuationsEntry('AJsaveUserGroupPrivs');
 	print "<button dojoType=\"dijit.form.Button\" id=\"usergroupsaveprivsbtn\" disabled>\n";
-	print "	Save Selected Permissions\n";
-	print "	<script type=\"dojo/method\" event=onClick>\n";
-	print "		saveUserGroupPrivs('$cont');\n";
-	print "	</script>\n";
+	print "  Save Selected Permissions\n";
+	print "  <script type=\"dojo/method\" event=onClick>\n";
+	print "    saveUserGroupPrivs('$cont');\n";
+	print "  </script>\n";
 	print "</button><br>\n";
 	print "<span id=\"userpermsubmitstatus\"></span>\n";
 	print "</div>\n";
 	print "</div>\n"; # end userperm tab
 
@@ -730,29 +731,29 @@ function selectNode() {
 		$text .= "<TABLE>";
 		$text .= "  <TR valign=top>";
 		$text .= "    <TD><FORM action=\"" . BASEURL . SCRIPT . "\" method=post>";
 		$text .= "    <button id=addNodeBtn dojoType=\"dijit.form.Button\">";
 		$text .= "      Add Child";
-		$text .= "	    <script type=\"dojo/method\" event=onClick>";
+		$text .= "      <script type=\"dojo/method\" event=onClick>";
 		$text .= "        showPrivPane(\"addNodePane\");";
 		$text .= "        return false;";
 		$text .= "      </script>";
 		$text .= "    </button>";
 		$text .= "    </FORM></TD>";
 		$text .= "    <TD><FORM action=\"" . BASEURL . SCRIPT . "\" method=post>";
 		$text .= "    <button id=deleteNodeBtn dojoType=\"dijit.form.Button\">";
 		$text .= "      Delete Node and Children";
-		$text .= "	    <script type=\"dojo/method\" event=onClick>";
+		$text .= "      <script type=\"dojo/method\" event=onClick>";
 		$text .= "        dijit.byId(\"deleteDialog\").show();";
 		$text .= "        return false;";
 		$text .= "      </script>";
 		$text .= "    </button>";
 		$text .= "    </FORM></TD>";
 		$text .= "    <TD><FORM action=\"" . BASEURL . SCRIPT . "\" method=post>";
 		$text .= "    <button id=renameNodeBtn dojoType=\"dijit.form.Button\">";
 		$text .= "      Rename Node";
-		$text .= "	    <script type=\"dojo/method\" event=onClick>";
+		$text .= "      <script type=\"dojo/method\" event=onClick>";
 		$text .= "        dijit.byId(\"renameDialog\").show();";
 		$text .= "        return false;";
 		$text .= "      </script>";
 		$text .= "    </button>";
 		$text .= "    </FORM></TD>";
@@ -857,11 +858,11 @@ function selectNode() {
 		$groups = array();
 	}
 	if($hasUserGrant) {
 		$text .= "<button id=addGroupBtn dojoType=\"dijit.form.Button\">";
 		$text .= "  Add Group";
-		$text .= "	<script type=\"dojo/method\" event=onClick>";
+		$text .= "  <script type=\"dojo/method\" event=onClick>";
 		$text .= "    showPrivPane(\"addUserGroupPane\");";
 		$text .= "    return false;";
 		$text .= "  </script>";
 		$text .= "</button>";
 	}
@@ -917,11 +918,11 @@ function selectNode() {
 		$resources = array();
 	}
 	if($hasResourceGrant) {
 		$text .= "<button id=addResourceBtn dojoType=\"dijit.form.Button\">";
 		$text .= "  Add Resource Group";
-		$text .= "	<script type=\"dojo/method\" event=onClick>";
+		$text .= "  <script type=\"dojo/method\" event=onClick>";
 		$text .= "    showPrivPane(\"addResourceGroupPane\");";
 		$text .= "    return false;";
 		$text .= "  </script>";
 		$text .= "</button>";
 	}
@@ -992,19 +993,19 @@ function AJsubmitAddChildNode() {
 		print "dojo.byId('childNodeName').value = ''; ";
 		print "dijit.byId('addNodePane').hide(); ";
 		print "alert('$text');";
 		return;
 	}
-	$nodeInfo = getNodeInfo($parent);
 	$newnode = processInputVar("newnode", ARG_STRING);
-	if(! preg_match('/^[-A-Za-z0-9_. ]+$/', $newnode)) {
-		$text = "You can only use letters, numbers, spaces,<br>"
-		      . "dashes(-), dots(.), and underscores(_).";
-		print "dojo.byId('addChildNodeStatus').innerHTML = '$text';";
+	$errmsg = '';
+	if(! validateNodeName($newnode, $errmsg)) {
+		print "dojo.byId('addChildNodeStatus').innerHTML = '$errmsg';";
 		return;
 	}
 
+	$nodeInfo = getNodeInfo($parent);
+
 	# check to see if a node with the submitted name already exists
 	$query = "SELECT id "
 	       . "FROM privnode "
 	       . "WHERE name = '$newnode' AND "
 	       .       "parent = $parent";
@@ -1031,13 +1032,14 @@ function AJsubmitAddChildNode() {
 	$privs = array();
 	foreach(array("nodeAdmin", "userGrant", "resourceGrant") as $type) {
 		if(! checkUserHasPriv($type, $user["id"], $nodeid))
 			array_push($privs, $type);
 	}
-	if(count($privs))
+	if(count($privs)) {
 		array_push($privs, "cascade");
-	updateUserOrGroupPrivs($user["id"], $nodeid, $privs, array(), "user");
+		updateUserOrGroupPrivs($user["id"], $nodeid, $privs, array(), "user");
+	}
 	print "addChildNode('$newnode', $nodeid);";
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
@@ -1057,10 +1059,31 @@ function nodeExists($node) {
 		return 1;
 	else
 		return 0;
 }
 
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \fn validateNodeName($name, &$errmsg)
+///
+/// \param $name - name for a node
+/// \param $errmsg - variable into which an error message will be placed if
+/// $name is not valid
+///
+/// \return 1 if name is okay, 0 if not; if 0, $errmsg is populated with an
+/// error message
+///
+/// \brief validates that a name for a node is okay
+///
+////////////////////////////////////////////////////////////////////////////////
+function validateNodeName($name, &$errmsg) {
+	if(preg_match('/^[-A-Za-z0-9_\. ]+$/', $name))
+		return 1;
+	$errmsg = _("Node names can only contain letters, numbers, spaces,<br>dashes(-), dots(.), and underscores(_).");
+	return 0;
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 ///
 /// \fn AJsubmitDeleteNode()
 ///
 /// \brief deletes a node and its children; calls viewNodes when finished
@@ -1108,26 +1131,33 @@ function AJsubmitRenameNode() {
 		$msg = "You do not have rights to rename this node.";
 		$arr = array('error' => 1, 'message' => $msg);
 		sendJSON($arr);
 		return;
 	}
-	# check if node matching new name already exists at parent
 	$newname = processInputVar('newname', ARG_STRING);
+	$errmsg = '';
+	if(! validateNodeName($newname, $errmsg)) {
+		$arr = array('error' => 2, 'message' => $errmsg);
+		sendJSON($arr);
+		return;
+	}
+	# check if node matching new name already exists at parent
+	$_newname = mysql_real_escape_string($newname);
 	$query = "SELECT id "
 	       . "FROM privnode "
 	       . "WHERE parent = (SELECT parent FROM privnode WHERE id = $activeNode) AND "
-	       .       "name = '$newname'";
+	       .       "name = '$_newname'";
 	$qh = doQuery($query, 101);
 	if(mysql_num_rows($qh)) {
-		$msg = "A sibling node of that name currently exists";
+		$msg = _("A sibling node of that name currently exists");
 		$arr = array('error' => 2, 'message' => $msg);
 		sendJSON($arr);
 		return;
 	}
 
 	$query = "UPDATE privnode "
-	       . "SET name = '$newname' " 
+	       . "SET name = '$_newname' " 
 	       . "WHERE id = $activeNode";
 	doQuery($query, 101);
 	$arr = array('newname' => $newname, 'node' => $activeNode);
 	sendJSON($arr);
 }
@@ -1232,11 +1262,11 @@ function userLookup() {
 
 		$userdata = getUserInfo($esc_userid);
 		if(is_null($userdata)) {
 			$userdata = getUserInfo($esc_userid, 1);
 			if(is_null($userdata)) {
-				print "<font color=red>$userid not found in any known systems</font><br>\n";
+				print "<font color=red>$userid not found</font><br>\n";
 				return;
 			}
 		}
 		$userdata["groups"] = getUsersGroups($userdata["id"], 1, 1);
 		print "<TABLE>\n";
@@ -1600,11 +1630,11 @@ function printUserPrivRow($privname, $rownum, $privs, $types,
 
 	#cascade rights
 	if(array_key_exists($privname, $privs) && 
 	   (($usergroup == 1 &&
 	   in_array("cascade", $privs[$privname])) ||
-		($usergroup == 2 &&
+	   ($usergroup == 2 &&
 	   in_array("cascade", $privs[$privname]['privs']))))
 		$checked = "checked";
 	else
 		$checked = "";
 	if($usergroup == 1)
@@ -1738,11 +1768,11 @@ function getUserPrivRowHTML($privname, $rownum, $privs, $types,
 
 	#cascade rights
 	if(array_key_exists($privname, $privs) && 
 	   (($usergroup == 1 &&
 	   in_array("cascade", $privs[$privname])) ||
-		($usergroup == 2 &&
+	   ($usergroup == 2 &&
 	   in_array("cascade", $privs[$privname]['privs']))))
 		$checked = "checked";
 	else
 		$checked = "";
 	if($usergroup == 1)
@@ -2171,11 +2201,11 @@ function getNodePrivileges($node, $type="all", $privs=0) {
 /// \fn getNodeCascadePrivileges($node, $type="all", $privs=0)
 ///
 /// \param $node - id of node
 /// \param $type - (optional) resources, users, usergroups, or all
 /// \param $privs - (optional) privilege array as returned by this function or
-/// getNodeCascadePrivileges
+/// getNodePrivileges
 ///
 /// \return an array of privileges cascaded to the node:\n
 ///Array\n
 ///(\n
 ///    [resources] => Array\n
@@ -2221,24 +2251,30 @@ function getNodeCascadePrivileges($node, $type="all", $privs=0) {
 	$nodelist = getParentNodes($node);
 
 	# get all block data
 	static $allblockdata = array();
 	if(empty($allblockdata)) {
-		$query = "SELECT g.name AS name, "
+		$query = "SELECT g.id, "
+		       .        "g.name, "
 		       .        "t.name AS type, "
 		       .        "p.privnodeid "
 		       . "FROM resourcepriv p, "
 		       .      "resourcetype t, "
 		       .      "resourcegroup g "
 		       . "WHERE p.resourcegroupid = g.id AND "
 		       .       "g.resourcetypeid = t.id AND "
 		       .       "p.type = 'block'";
 		$qh = doQuery($query);
-		while($row = mysql_fetch_assoc($qh))
+		while($row = mysql_fetch_assoc($qh)) {
 			if(! array_key_exists($row['privnodeid'], $allblockdata))
 				$allblockdata[$row['privnodeid']] = array();
+			# TODO adding the id at the end will fix the bug where blocking cascaded resource
+			#   privileges are only blocked at the node and the block is not cascaded to
+			#   child nodes
 			$allblockdata[$row['privnodeid']][] = "{$row["type"]}/{$row["name"]}";
+			#$allblockdata[$row['privnodeid']][] = "{$row["type"]}/{$row["name"]}/{$row['id']}";
+		}
 	}
 
 	# get resource group block data
 	$inlist = implode(',', $nodelist);
 	$blockdata = array();
@@ -2438,18 +2474,29 @@ function AJchangeUserPrivs() {
 		return;
 	}
 	$newuser = processInputVar("item", ARG_STRING);
 	$newpriv = processInputVar('priv', ARG_STRING);
 	$newprivval = processInputVar('value', ARG_STRING);
-	//print "alert('node: $node; newuser: $newuser; newpriv: $newpriv; newprivval: $newprivval');";
+
+	if(! validateUserid($newuser)) {
+		$text = "Invalid user submitted.";
+		print "alert('$text');";
+		return;
+	}
+
+	$privid = getUserPrivTypeID($newpriv);
+	if(is_null($privid)) {
+		$text = "Invalid user privilege submitted.";
+		print "alert('$text');";
+		return;
+	}
 
 	# get cascade privs at this node
 	$cascadePrivs = getNodeCascadePrivileges($node, "users");
 
-	// if $newprivval is true and $newuser already has $newpriv
-	//   cascaded to it, do nothing
 	if($newprivval == 'true') {
+		// if $newuser already has $newpriv cascaded to it, do nothing
 		if(array_key_exists($newuser, $cascadePrivs['users']) &&
 		   in_array($newpriv, $cascadePrivs['users'][$newuser]))
 			return;
 		// add priv
 		$adds = array($newpriv);
@@ -2479,21 +2526,32 @@ function AJchangeUserGroupPrivs() {
 		$text = "You do not have rights to modify user privileges at this node.";
 		print "alert('$text');";
 		return;
 	}
 	$newusergrpid = processInputVar("item", ARG_NUMERIC);
-	$newusergrp = getUserGroupName($newusergrpid);
 	$newpriv = processInputVar('priv', ARG_STRING);
 	$newprivval = processInputVar('value', ARG_STRING);
-	//print "alert('node: $node; newuser:grp $newuser;grp newpriv: $newpriv; newprivval: $newprivval');";
+
+	$newusergrp = getUserGroupName($newusergrpid);
+	if($newusergrp === 0) {
+		$text = "Invalid user group submitted.";
+		print "alert('$text');";
+		return;
+	}
+
+	$privid = getUserPrivTypeID($newpriv);
+	if(is_null($privid)) {
+		$text = "Invalid user privilege submitted.";
+		print "alert('$text');";
+		return;
+	}
 
 	# get cascade privs at this node
 	$cascadePrivs = getNodeCascadePrivileges($node, "usergroups");
 
-	// if $newprivval is true and $newusergrp already has $newpriv
-	//   cascaded to it, do nothing
 	if($newprivval == 'true') {
+		// if $newusergrp already has $newpriv cascaded to it, do nothing
 		if(array_key_exists($newusergrp, $cascadePrivs['usergroups']) &&
 		   in_array($newpriv, $cascadePrivs['usergroups'][$newusergrp]['privs']))
 			return;
 		// add priv
 		$adds = array($newpriv);
@@ -2525,18 +2583,51 @@ function AJchangeResourcePrivs() {
 		return;
 	}
 	$resourcegrp = processInputVar("item", ARG_STRING);
 	$newpriv = processInputVar('priv', ARG_STRING);
 	$newprivval = processInputVar('value', ARG_STRING);
-	//print "alert('node: $node; resourcegrp: $resourcegrp; newpriv: $newpriv; newprivval: $newprivval');";
+
+	$allprivs = getResourcePrivs();
+	if(! in_array($newpriv, $allprivs)) {
+		$text = "Invalid resource privilege submitted.";
+		print "alert('$text');";
+		return;
+	}
+
+	$resourcetypes = getTypes('resources');
+	$types = implode('|', $resourcetypes['resources']);
+	if(! preg_match("@($types)/([^/]+)/([0-9]+)@", $resourcegrp, $matches)) {
+		$text = "Invalid resource group submitted.";
+		print "alert('$text');";
+		return;
+	}
+
+	$type = $matches[1];
+	$groupid = $matches[3];
+
+	$groupdata = getResourceGroups($type, $groupid);
+	if(empty($groupdata)) {
+		$text = "Invalid resource group submitted.";
+		print "alert('$text');";
+		return;
+	}
+
+	// if $type is administer, manageGroup, or manageMapping, and it is not
+	# checked, and the user is not in the resource owner group, don't allow
+	# the change
+	if($newpriv != "block" && $newpriv != "cascade" && $newpriv != "available" &&
+	   ! array_key_exists($groupdata[$groupid]["ownerid"], $user["groups"])) {
+		$text = "You do not have rights to modify the submitted privilege for the submitted group.";
+		print "alert('$text');";
+		return;
+	}
 
 	# get cascade privs at this node
 	$cascadePrivs = getNodeCascadePrivileges($node, "resources");
 
-	// if $newprivval is true and $resourcegrp already has $newpriv
-	//   cascaded to it, do nothing
 	if($newprivval == 'true') {
+		// if $resourcegrp already has $newpriv cascaded to it, do nothing
 		if(array_key_exists($resourcegrp, $cascadePrivs['resources']) &&
 		   in_array($newpriv, $cascadePrivs['resources'][$resourcegrp]))
 			return;
 		// add priv
 		$adds = array($newpriv);
@@ -2545,12 +2636,11 @@ function AJchangeResourcePrivs() {
 	else {
 		// remove priv
 		$adds = array();
 		$removes = array($newpriv);
 	}
-	$tmpArr = explode('/', $resourcegrp);
-	updateResourcePrivs($tmpArr[2], $node, $adds, $removes);
+	updateResourcePrivs($groupid, $node, $adds, $removes);
 	$_SESSION['dirtyprivs'] = 1;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
@@ -2614,13 +2704,20 @@ function AJsubmitAddUserGroupPriv() {
 		print "addUserGroupPaneHide(); ";
 		print "alert('$text');";
 		return;
 	}
 	$newgroupid = processInputVar("newgroupid", ARG_NUMERIC);
-	# FIXME validate newgroupid
+
+	$newgroup = getUserGroupName($newgroupid);
+	if($newgroup === 0) {
+		$text = "Invalid user group submitted.";
+		print "alert('$text');";
+		return;
+	}
 
 	$perms = explode(':', processInputVar('perms', ARG_STRING));
+
 	$usertypes = getTypes("users");
 	array_push($usertypes["users"], "block");
 	array_push($usertypes["users"], "cascade");
 	$newgroupprivs = array();
 	foreach($usertypes["users"] as $type) {
@@ -2658,17 +2755,23 @@ function AJsubmitAddResourcePriv() {
 		return;
 	}
 	$newgroupid = processInputVar("newgroupid", ARG_NUMERIC);
 	$privs = array("computerAdmin", "mgmtNodeAdmin", "imageAdmin",
 	               "scheduleAdmin", "serverProfileAdmin");
-	$resourcesgroups = getUserResources($privs, array("manageGroup"), 1);
+	$resourcegroups = getUserResources($privs, array("manageGroup"), 1);
+
+	$groupdata = getResourceGroups('', $newgroupid);
+
+	if(empty($groupdata)) {
+		$text = "Invalid resource group submitted.";
+		print "addResourceGroupPaneHide(); ";
+		print "alert('$text');";
+		return;
+	}
 
-	if(! array_key_exists($newgroupid, $resourcesgroups['image']) &&
-	   ! array_key_exists($newgroupid, $resourcesgroups['computer']) &&
-	   ! array_key_exists($newgroupid, $resourcesgroups['managementnode']) &&
-	   ! array_key_exists($newgroupid, $resourcesgroups['schedule']) &&
-	   ! array_key_exists($newgroupid, $resourcesgroups['serverprofile'])) {
+	list($newtype, $tmp) = explode('/', $groupdata[$newgroupid]['name']);
+	if(! array_key_exists($newgroupid, $resourcegroups[$newtype])) {
 		$text = "You do not have rights to manage the specified resource group.";
 		print "addResourceGroupPaneHide(); ";
 		print "alert('$text');";
 		return;
 	}
@@ -2752,11 +2855,11 @@ function checkUserHasPriv($priv, $uid, $node, $privs=0, $cascadePrivs=0) {
 		   in_array($priv, $privs["usergroups"][$groupname]['privs'])) ||
 		   ((array_key_exists($groupname, $cascadePrivs["usergroups"]) &&
 		   $groupid == $cascadePrivs['usergroups'][$groupname]['id'] &&
 		   in_array($priv, $cascadePrivs["usergroups"][$groupname]['privs'])) &&
 		   (! array_key_exists($groupname, $privs["usergroups"]) ||
-			(! in_array("block", $privs["usergroups"][$groupname]['privs']) && 
+		   (! in_array("block", $privs["usergroups"][$groupname]['privs']) && 
 		   $groupid == $privs['usergroups'][$groupname]['id'])))) {
 			$_SESSION['userhaspriv'][$key] = 1;
 			return 1;
 		}
 	}
diff --git a/web/.ht-inc/utils.php b/web/.ht-inc/utils.php
index ad6f408..0cc0c43 100644
--- a/web/.ht-inc/utils.php
+++ b/web/.ht-inc/utils.php
@@ -2272,10 +2272,11 @@ function updateResourcePrivs($group, $node, $adds, $removes) {
 	if(is_numeric($group))
 		$groupid = $group;
 	else
 		$groupid = getResourceGroupID($group);
 	foreach($adds as $type) {
+		$type = mysql_real_escape_string($type);
 		$query = "INSERT IGNORE INTO resourcepriv ("
 		       .        "resourcegroupid, "
 		       .        "privnodeid, "
 		       .        "type) "
 		       . "VALUES ("
@@ -2283,10 +2284,11 @@ function updateResourcePrivs($group, $node, $adds, $removes) {
 		       .        "$node, "
 		       .        "'$type')";
 		doQuery($query, 377);
 	}
 	foreach($removes as $type) {
+		$type = mysql_real_escape_string($type);
 		$query = "DELETE FROM resourcepriv "
 		       . "WHERE resourcegroupid = $groupid AND "
 		       .       "privnodeid = $node AND "
 		       .       "type = '$type'";
 		doQuery($query, 378);
@@ -2625,22 +2627,26 @@ function getUserGroupPrivTypes() {
 	return $data;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn getResourceGroups($type)
+/// \fn getResourceGroups($type, $id)
 ///
 /// \param $type - (optional) a name from the resourcetype table, defaults to
 /// be empty
+/// \param $id - (optional) id of a resource group
 ///
-/// \return an array of resource group names whose index values are the ids;
-/// the names are the resource type and group name combined as 'type/name'
+/// \return an array of resource groups where each key is a group id and each
+/// value is an array with these elements:\n
+/// \b name - type and name of group combined as type/name\n
+/// \b ownerid - id of owning user group\n
+/// \b owner - name of owning user group
 ///
 /// \brief builds list of resource groups
 ///
 ////////////////////////////////////////////////////////////////////////////////
-function getResourceGroups($type="") {
+function getResourceGroups($type='', $id='') {
 	$return = array();
 	$query = "SELECT g.id AS id, "
 	       .        "g.name AS name, "
 	       .        "t.name AS type, "
 	       .        "g.ownerusergroupid AS ownerid, "
@@ -2654,10 +2660,13 @@ function getResourceGroups($type="") {
 	       .       "u.affiliationid = a.id ";
 
 	if(! empty($type))
 		$query .= "AND t.name = '$type' ";
 
+	if(! empty($id))
+		$query .= "AND g.id = $id ";
+
 	$query .= "ORDER BY t.name, g.name";
 	$qh = doQuery($query, 281);
 	while($row = mysql_fetch_assoc($qh)) {
 		if(empty($type))
 			$return[$row["id"]]["name"] = $row["type"] . "/" . $row["name"];
@@ -3308,20 +3317,20 @@ function processInputData($data, $type, $addslashes=0, $defaultvalue=NULL) {
 	elseif(! empty($return) && $type == ARG_MULTISTRING) {
 		foreach($return as $index => $value) {
 			if(! is_string($value))
 				$return[$index] = $defaultvalue;
 			elseif($addslashes)
-				$return[$index] = addslashes($value);
+				$return[$index] = mysql_real_escape_string($value);
 		}
 		return $return;
 	}
 
 	if(is_string($return)) {
 		if(strlen($return) == 0)
 			$return = $defaultvalue;
 		elseif($addslashes)
-			$return = addslashes($return);
+			$return = mysql_real_escape_string($return);
 	}
 
 	return $return;
 }
 
@@ -8208,31 +8217,34 @@ function updateGroups($newusergroups, $userid) {
 	return $newusergroups;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn getUserGroupID($name, $affilid)
+/// \fn getUserGroupID($name, $affilid, $noadd)
 ///
 /// \param $name - a group name
 /// \param $affilid - (optional, defaults to DEFAULT_AFFILID) affiliation id
 /// for $name
+/// \param $noadd - (optional, defaults to 0) set to 1 to return NULL if group
+/// does not exist instead of adding it to table
 ///
 /// \return id for $name from group table
 ///
 /// \brief looks up the id for $name in the group table; if the name is
 /// not currently in the table, adds it and returns the new id
 ///
 ////////////////////////////////////////////////////////////////////////////////
-function getUserGroupID($name, $affilid=DEFAULT_AFFILID) {
+function getUserGroupID($name, $affilid=DEFAULT_AFFILID, $noadd=0) {
 	$query = "SELECT id "
 	       . "FROM usergroup "
 	       . "WHERE name = '$name' AND "
 	       .       "affiliationid = $affilid";
 	$qh = doQuery($query, 300);
-	if($row = mysql_fetch_row($qh)) {
+	if($row = mysql_fetch_row($qh))
 		return $row[0];
-	}
+	elseif($noadd)
+		return NULL;
 	$query = "INSERT INTO usergroup "
 	       .        "(name, "
 	       .        "affiliationid, "
 	       .        "custom, "
 	       .        "courseroll) "
@@ -8785,15 +8797,12 @@ function getTypes($subtype="both") {
 		}
 	}
 	if($subtype == "resources" || $subtype == "both") {
 		$query = "SELECT id, name FROM resourcetype";
 		$qh = doQuery($query, 366);
-		while($row = mysql_fetch_assoc($qh)) {
-			if($row["name"] == "block" || $row["name"] == "cascade")
-				continue;
+		while($row = mysql_fetch_assoc($qh))
 			$types["resources"][$row["id"]] = $row["name"];
-		}
 	}
 	return $types;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -8968,12 +8977,14 @@ function getReservationLengthCeiling($length) {
 /// \return id of the group
 ///
 /// \brief gets the id from the resourcegroup table for $groupname
 ///
 ////////////////////////////////////////////////////////////////////////////////
-function getResourceGroupID($groupdname) {
-	list($type, $name) = explode('/', $groupdname);
+function getResourceGroupID($groupname) {
+	list($type, $name) = explode('/', $groupname);
+	$type = mysql_real_escape_string($type);
+	$name = mysql_real_escape_string($name);
 	$query = "SELECT g.id "
 	       . "FROM resourcegroup g, "
 	       .      "resourcetype t "
 	       . "WHERE g.name = '$name' AND "
 	       .       "t.name = '$type' AND "
@@ -8995,10 +9006,11 @@ function getResourceGroupID($groupdname) {
 ///
 /// \brief gets the id from the resourcetype table for $name
 ///
 ////////////////////////////////////////////////////////////////////////////////
 function getResourceTypeID($name) {
+	$name = mysql_real_escape_string($name);
 	$query = "SELECT id "
 	       . "FROM resourcetype "
 	       . "WHERE name = '$name'";
 	$qh = doQuery($query, 101);
 	if($row = mysql_fetch_row($qh))
@@ -9809,28 +9821,28 @@ function xmlrpccall() {
 	xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCremoveUsersFromGroup", "xmlRPChandler");
 	xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCautoCapture", "xmlRPChandler");
 	xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCdeployServer", "xmlRPChandler");
 	xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCgetNodes", "xmlRPChandler");
 	xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCaddNode", "xmlRPChandler");
-    xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCremoveNode", "xmlRPChandler");
-    xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCnodeExists", "xmlRPChandler");
-    xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCaddResourceGroupPriv", "xmlRPChandler");
-    xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCremoveResourceGroupPriv", "xmlRPChandler");
-    xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCgetResourceGroupPrivs", "xmlRPChandler");
-    xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCaddUserGroupPriv", "xmlRPChandler");
-    xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCremoveUserGroupPriv", "xmlRPChandler");
-    xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCgetUserGroupPrivs", "xmlRPChandler");
-    xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCaddResourceGroup", "xmlRPChandler");
-    xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCgetResourceGroups", "xmlRPChandler");
-    xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCremoveResourceGroup", "xmlRPChandler");
+	xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCremoveNode", "xmlRPChandler");
+	xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCnodeExists", "xmlRPChandler");
+	xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCaddResourceGroupPriv", "xmlRPChandler");
+	xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCremoveResourceGroupPriv", "xmlRPChandler");
+	xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCgetResourceGroupPrivs", "xmlRPChandler");
+	xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCaddUserGroupPriv", "xmlRPChandler");
+	xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCremoveUserGroupPriv", "xmlRPChandler");
+	xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCgetUserGroupPrivs", "xmlRPChandler");
+	xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCaddResourceGroup", "xmlRPChandler");
+	xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCgetResourceGroups", "xmlRPChandler");
+	xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCremoveResourceGroup", "xmlRPChandler");
 	xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCgetUserGroups", "xmlRPChandler");
-    xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCremoveUserGroup", "xmlRPChandler");
+	xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCremoveUserGroup", "xmlRPChandler");
 	xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCaddImageToGroup", "xmlRPChandler");
-    xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCremoveImageFromGroup", "xmlRPChandler");
-    xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCgetGroupImages", "xmlRPChandler");
-    xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCaddImageGroupToComputerGroup", "xmlRPChandler");
-    xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCremoveImageGroupFromComputerGroup", "xmlRPChandler");
+	xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCremoveImageFromGroup", "xmlRPChandler");
+	xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCgetGroupImages", "xmlRPChandler");
+	xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCaddImageGroupToComputerGroup", "xmlRPChandler");
+	xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCremoveImageGroupFromComputerGroup", "xmlRPChandler");
 
 	print xmlrpc_server_call_method($xmlrpc_handle, $HTTP_RAW_POST_DATA, '');
 	xmlrpc_server_destroy($xmlrpc_handle);
 	semUnlock();
 	dbDisconnect();
@@ -9968,11 +9980,11 @@ function xmlRPCabort($errcode, $query='') {
 /// \brief prints the XML for an RPC error
 ///
 ////////////////////////////////////////////////////////////////////////////////
 function printXMLRPCerror($errcode) {
 	global $XMLRPCERRORS;
-	print "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n";
+	print "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?" . ">\n"; # splitting the ? and > makes vim syntax highlighting work correctly
 	print "<methodResponse>\n";
 	print "<fault>\n";
 	print " <value>\n";
 	print "  <struct>\n";
 	print "   <member>\n";
@@ -10006,11 +10018,11 @@ function printXMLRPCerror($errcode) {
 /// \b managingGroup \n
 /// \b initialMaxTime \n
 /// \b totalMaxTime \n
 /// \b maxExtendTime
 /// \param $exists - 1 to check if $name\@$affiliation exists, 0 to check that
-///                  they it does not exist
+///                  it does not exist
 ///
 /// \return an array to be returned as an error status or $items with these
 /// extra keys:\n
 /// \b status - "success"\n
 /// \b managingGroupID - (if managingGroup in $items) id of managingGroup
@@ -10056,12 +10068,11 @@ function validateAPIgroupInput($items, $exists) {
 			             'errormsg' => 'submitted maxExtendTime is invalid');
 		}
 	}
 	# affiliation
 	if(array_key_exists('affiliation', $items)) {
-		$esc_affiliation = mysql_real_escape_string($items['affiliation']);
-		$affilid = getAffiliationID($esc_affiliation);
+		$affilid = getAffiliationID($items['affiliation']);
 		if(is_null($affilid)) {
 			return array('status' => 'error',
 			             'errorcode' => 17,
 			             'errormsg' => 'unknown affiliation');
 		}
@@ -10074,12 +10085,11 @@ function validateAPIgroupInput($items, $exists) {
 			             'errorcode' => 19,
 			             'errormsg' => 'Name must be between 3 and 30 characters '
 			                         . 'and can only contain letters, numbers, and '
 			                         . 'these characters: - _ . :');
 		}
-		$esc_name = mysql_real_escape_string($items['name']);
-		$doesexist = checkForGroupName($esc_name, 'user', '', $affilid);
+		$doesexist = checkForGroupName($items['name'], 'user', '', $affilid);
 		if($exists && ! $doesexist) {
 			return array('status' => 'error',
 			             'errorcode' => 18,
 			             'errormsg' => 'user group with submitted name and affiliation does not exist');
 		}
@@ -10087,16 +10097,17 @@ function validateAPIgroupInput($items, $exists) {
 			return array('status' => 'error',
 			             'errorcode' => 27,
 			             'errormsg' => 'existing user group with submitted name and affiliation');
 		}
 		elseif($exists && $doesexist) {
+			$esc_name = mysql_real_escape_string($items['name']);
 			$items['id'] = getUserGroupID($esc_name, $affilid);
 		}
 	}
 	# owner
 	if($custom && array_key_exists('owner', $items)) {
-		if(! validateUserid(mysql_real_escape_string($items['owner']))) {
+		if(! validateUserid($items['owner'])) {
 			return array('status' => 'error',
 			             'errorcode' => 20,
 			             'errormsg' => 'submitted owner is invalid');
 		}
 	}
@@ -10106,19 +10117,18 @@ function validateAPIgroupInput($items, $exists) {
 		if(count($parts) != 2) {
 			return array('status' => 'error',
 			             'errorcode' => 24,
 			             'errormsg' => 'submitted managingGroup is invalid');
 		}
-		$esc_mgName = mysql_real_escape_string($parts[0]);
-		$esc_mgAffil = mysql_real_escape_string($parts[1]);
-		$mgaffilid = getAffiliationID($esc_mgAffil);
-		if(! checkForGroupName($esc_mgName, 'user', '', $mgaffilid)) {
+		$mgaffilid = getAffiliationID($parts[1]);
+		if(is_null($mgaffilid) ||
+		   ! checkForGroupName($parts[0], 'user', '', $mgaffilid)) {
 			return array('status' => 'error',
 			             'errorcode' => 25,
 			             'errormsg' => 'submitted managingGroup does not exist');
 		}
-		$items['managingGroupID'] = getUserGroupID($esc_mgName, $mgaffilid);
+		$items['managingGroupID'] = getUserGroupID($parts[0], $mgaffilid);
 		$items['managingGroupName'] = $parts[0];
 		$items['managingGroupAffilid'] = $mgaffilid;
 	}
 	$items['status'] = 'success';
 	return $items;
diff --git a/web/.ht-inc/xmlrpcWrappers.php b/web/.ht-inc/xmlrpcWrappers.php
index 3989066..6251163 100644
--- a/web/.ht-inc/xmlrpcWrappers.php
+++ b/web/.ht-inc/xmlrpcWrappers.php
@@ -67,11 +67,11 @@
 /// \b id - id of the affiliation\n
 /// \b name - name of the affiliation
 ///
 /// \brief gets all of the affilations for which users can log in to VCL\n
 /// \b NOTE: This is the only function available for which the X-User and X-Pass
-/// HTTP headers do not need to be passed\n
+/// HTTP headers do not need to be passed
 ///
 ////////////////////////////////////////////////////////////////////////////////
 function XMLRPCaffiliations() {
 	$affils = getAffiliations();
 	$return = array();
@@ -80,31 +80,53 @@ function XMLRPCaffiliations() {
 		array_push($return, $tmp);
 	}
 	return $return;
 }
 
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \fn XMLRPCtest($string)
+///
+/// \param $string - a string
+///
+/// \return an array with 3 indices:\n
+/// \b status - will be 'success'\n
+/// \b message - will be 'RPC call worked successfully'\n
+/// \b string - contents of $string (after being sanatized)
+///
+/// \brief this is a test function that call be called when getting XML RPC
+/// calls to this site to work
+///
+////////////////////////////////////////////////////////////////////////////////
+function XMLRPCtest($string) {
+	$string = processInputData($string, ARG_STRING);
+	return array('status' => 'success',
+	             'message' => 'RPC call worked successfully',
+	             'string' => $string);
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 ///
 /// \fn XMLRPCgetImages()
 ///
 /// \return an array of image arrays, each with 2 indices:\n
 /// \b id - id of the image\n
 /// \b name - name of the image
 ///
-/// \brief gets the images to which the user has access\n
+/// \brief gets the images to which the user has access
 ///
 ////////////////////////////////////////////////////////////////////////////////
 function XMLRPCgetImages() {
 	$resources = getUserResources(array("imageAdmin", "imageCheckOut"));
 	$resources["image"] = removeNoCheckout($resources["image"]);
 	$return = array();
 	foreach($resources['image'] as $key => $val) {
-        $notes = getImageNotes($key);
-        $tmp = array('id' => $key,
-                     'name' => $val,
-                     'description' => $notes['description'],
-                     'usage' => $notes['usage']);
+		$notes = getImageNotes($key);
+		$tmp = array('id' => $key,
+		             'name' => $val,
+		             'description' => $notes['description'],
+		             'usage' => $notes['usage']);
 		array_push($return, $tmp);
 	}
 	return $return;
 }
 
@@ -130,11 +152,11 @@ function XMLRPCgetImages() {
 /// \b notavailable - no computers were available for the request\n
 /// \b success - there will be an additional element in the array:
 /// \li \b requestid - identifier that should be passed to later calls when
 /// acting on the request
 ///
-/// \brief tries to make a request\n
+/// \brief tries to make a request
 ///
 ////////////////////////////////////////////////////////////////////////////////
 function XMLRPCaddRequest($imageid, $start, $length, $foruser='') {
 	global $user;
 	$imageid = processInputData($imageid, ARG_NUMERIC);
@@ -226,11 +248,11 @@ function XMLRPCaddRequest($imageid, $start, $length, $foruser='') {
 /// \b notavailable - no computers were available for the request\n
 /// \b success - there will be an additional element in the array:
 /// \li \b requestid - identifier that should be passed to later calls when
 /// acting on the request
 ///
-/// \brief tries to make a request with the specified ending time\n
+/// \brief tries to make a request with the specified ending time
 ///
 ////////////////////////////////////////////////////////////////////////////////
 function XMLRPCaddRequestWithEnding($imageid, $start, $end, $foruser='') {
 	global $user;
 	$imageid = processInputData($imageid, ARG_NUMERIC);
@@ -312,230 +334,474 @@ function XMLRPCaddRequestWithEnding($imageid, $start, $end, $foruser='') {
 	return $return;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn XMLRPCgetRequestStatus($requestid)
+/// \fn XMLRPCdeployServer($imageid, $start, $end, $admingroup, $logingroup,
+///                        $ipaddr, $macaddr, $monitored, $foruser, $name)
 ///
-/// \param $requestid - id of a request
+/// \param $imageid - id of an image
+/// \param $start - "now" or unix timestamp for start of reservation; will
+/// use a floor function to round down to the nearest 15 minute increment
+/// for actual reservation
+/// \param $end - "indefinite" or unix timestamp for end of reservation; will
+/// use a floor function to round up to the nearest 15 minute increment
+/// for actual reservation
+/// \param $admingroup - (optional, default='') admin user group for reservation
+/// \param $logingroup - (optional, default='') login user group for reservation
+/// \param $ipaddr - (optional, default='') IP address to use for public IP of
+/// server
+/// \param $macaddr - (optional, default='') MAC address to use for public NIC
+/// of server
+/// \param $monitored - (optional, default=0) whether or not the server should
+/// be monitored - CURRENTLY, THIS IS UNSUPPORTED
+/// \param $foruser - (optional) login to be used when setting up the account
+/// on the reserved machine - CURRENTLY, THIS IS UNSUPPORTED
+/// \param $name - (optional) name for reservation
 ///
 /// \return an array with at least one index named '\b status' which will have
 /// one of these values:\n
 /// \b error - error occurred; there will be 2 additional elements in the array:
 /// \li \b errorcode - error number\n
 /// \li \b errormsg - error string\n
 ///
-/// \b ready - request is ready\n
-/// \b failed - request failed to load properly\n
-/// \b timedout - request timed out (user didn't connect before timeout
-/// expired)\n
-/// \b loading - request is still loading; there will be an additional element
-/// in the array:
-/// \li \b time - the estimated wait time (in minutes) for loading to complete\n
-///
-/// \b future - start time of request is in the future\n
+/// \b notavailable - no computers were available for the request\n
+/// \b success - there will be an additional element in the array:
+/// \li \b requestid - identifier that should be passed to later calls when
+/// acting on the request
 ///
-/// \brief determines and returns the status of the request\n
+/// \brief tries to make a server request
 ///
 ////////////////////////////////////////////////////////////////////////////////
-function XMLRPCgetRequestStatus($requestid) {
-	global $user;
-	$requestid = processInputData($requestid, ARG_NUMERIC);
-	$userRequests = getUserRequests('all', $user['id']);
-	$found = 0;
-	foreach($userRequests as $req) {
-		if($req['id'] == $requestid) {
-			$request = $req;
-			$found = 1;
-			break;
-		}
+function XMLRPCdeployServer($imageid, $start, $end, $admingroup='',
+                            $logingroup='', $ipaddr='', $macaddr='',
+                            $monitored=0, $foruser='', $name='') {
+	global $user, $remoteIP;
+	if(! in_array("serverProfileAdmin", $user["privileges"])) {
+		return array('status' => 'error',
+		             'errorcode' => 60,
+		             'errormsg' => "access denied to deploy server");
 	}
-	if(! $found)
+	$imageid = processInputData($imageid, ARG_NUMERIC);
+	$resources = getUserResources(array("imageAdmin", "imageCheckOut"));
+	$images = removeNoCheckout($resources["image"]);
+	$extraimages = getServerProfileImages($user['id']);
+	if(! array_key_exists($imageid, $images) &&
+	   ! array_key_exists($imageid, $extraimages)) {
 		return array('status' => 'error',
-		             'errorcode' => 1,
-		             'errormsg' => 'unknown requestid');
-
-	$now = time();
-	# request is ready
-	if(requestIsReady($request))
-		return array('status' => 'ready');
-	# request failed
-	elseif($request["currstateid"] == 5)
-		return array('status' => 'failed');
-	# other cases where the reservation start time has been reached
-	elseif(datetimeToUnix($request["start"]) < $now) {
-		# request has timed out
-		if($request["currstateid"] == 12 ||
-		   $request["currstateid"] == 11 ||
-		   ($request["currstateid"] == 14 &&
-		   $request["laststateid"] == 11)) {
-			return array('status' => 'timedout');
+		             'errorcode' => 3,
+		             'errormsg' => "access denied to $imageid");
+	}
+	if($admingroup != '') {
+		$admingroup = processInputData($admingroup, ARG_STRING);
+		if(get_magic_quotes_gpc())
+			$admingroup = stripslashes($admingroup);
+		if(preg_match('/@/', $admingroup)) {
+			$tmp = explode('@', $admingroup);
+			$escadmingroup = mysql_real_escape_string($tmp[0]);
+			$affilid = getAffiliationID($tmp[1]);
+			if(is_null($affilid)) {
+				return array('status' => 'error',
+				             'errorcode' => 51,
+				             'errormsg' => "unknown affiliation for admin user group: {$tmp[1]}");
+			}
 		}
-		# computer is loading
 		else {
-			$imageid = $request['imageid'];
-			$images = getImages(0, $imageid);
-			$remaining = 1;
-			$computers = getComputers(0, 0, $request['computerid']);
-			if(isComputerLoading($request, $computers)) {
-				if(datetimeToUnix($request["daterequested"]) >=
-					datetimeToUnix($request["start"]))
-					$startload = datetimeToUnix($request["daterequested"]);
-				else
-					$startload = datetimeToUnix($request["start"]);
-				$imgLoadTime = getImageLoadEstimate($imageid);
-				if($imgLoadTime == 0)
-					$imgLoadTime = $images[$imageid]['reloadtime'] * 60;
-				$tmp = ($imgLoadTime - ($now - $startload)) / 60;
-				$remaining = sprintf("%d", $tmp) + 1;
-				if($remaining < 1) {
-					$remaining = 1;
-				}
+			$escadmingroup = mysql_real_escape_string($admingroup);
+			$affilid = DEFAULT_AFFILID;
+		}
+		$admingroupid = getUserGroupID($escadmingroup, $affilid, 1);
+		if(is_null($admingroupid)) {
+			return array('status' => 'error',
+			             'errorcode' => 52,
+			             'errormsg' => "unknown admin user group: $admingroup");
+		}
+	}
+	else
+		$admingroupid = '';
+	if($logingroup != '') {
+		$logingroup = processInputData($logingroup, ARG_STRING);
+		if(get_magic_quotes_gpc())
+			$logingroup = stripslashes($logingroup);
+		if(preg_match('/@/', $logingroup)) {
+			$tmp = explode('@', $logingroup);
+			$esclogingroup = mysql_real_escape_string($tmp[0]);
+			$affilid = getAffiliationID($tmp[1]);
+			if(is_null($affilid)) {
+				return array('status' => 'error',
+				             'errorcode' => 54,
+				             'errormsg' => "unknown affiliation for login user group: {$tmp[1]}");
 			}
-			return array('status' => 'loading', 'time' => $remaining);
+		}
+		else {
+			$esclogingroup = mysql_real_escape_string($logingroup);
+			$affilid = DEFAULT_AFFILID;
+		}
+		$logingroupid = getUserGroupID($esclogingroup, $affilid, 1);
+		if(is_null($logingroupid)) {
+			return array('status' => 'error',
+			             'errorcode' => 55,
+			             'errormsg' => "unknown login user group: $logingroup");
 		}
 	}
-	# reservation is in the future
 	else
-		return array('status' => 'future');
-}
-
-////////////////////////////////////////////////////////////////////////////////
-///
-/// \fn XMLRPCgetUserGroups()
-///
-/// \return an array listing all of the groups to which the given user
-/// has read or write access. Each usergroup will be an array with the 
-/// following keys:\n
-/// id\n
-/// name\n
-/// groupaffiliation\n
-/// groupaffiliationid\n
-/// ownerid\n
-/// owner\n
-/// affiliation\n
-/// editgroupid\n
-/// editgroup\n
-/// editgroupaffiliationid\n
-/// editgroupaffiliation\n
-/// custom\n
-/// courseroll\n
-/// initialmaxtime\n
-/// maxextendtime\n
-/// overlapResCount\n
-///
-/// \brief builds a list of user groups\n
-///
-////////////////////////////////////////////////////////////////////////////////
-function XMLRPCgetUserGroups($groupType=0, $affiliationid=0) {
-    global $user;
-    $groups = getUserGroups($groupType, $affiliationid);
-
-    // Filter out any groups to which the user does not have access.
-    $usergroups = array();
-    foreach($groups as $id => $group){
-        if($group['ownerid'] == $user['id'] || 
-            (array_key_exists("editgroupid", $group) &&
-            array_key_exists($group['editgroupid'], $user["groups"])) || 
-            (array_key_exists($id, $user["groups"]))){
-            array_push($usergroups, $group);
-        }
-    }
-    return array(
-            "status" => "success",
-            "groups" => $usergroups);
-}
-
-
+		$logingroupid = '';
+	$ipaddr = processInputData($ipaddr, ARG_STRING);
+	$ipaddrArr = explode('.', $ipaddr);
+	if($ipaddr != '' && (! preg_match('/^(([0-9]){1,3}\.){3}([0-9]){1,3}$/', $ipaddr) ||
+		$ipaddrArr[0] < 1 || $ipaddrArr[0] > 255 ||
+		$ipaddrArr[1] < 0 || $ipaddrArr[1] > 255 ||
+		$ipaddrArr[2] < 0 || $ipaddrArr[2] > 255 ||
+		$ipaddrArr[3] < 0 || $ipaddrArr[3] > 255)) {
+		return array('status' => 'error',
+		             'errorcode' => 57,
+		             'errormsg' => "Invalid IP address. Must be w.x.y.z with each of "
+		                         . "w, x, y, and z being between 1 and 255 (inclusive)");
+	}
+	$macaddr = processInputData($macaddr, ARG_STRING);
+	if($macaddr != '' && ! preg_match('/^(([A-Fa-f0-9]){2}:){5}([A-Fa-f0-9]){2}$/', $macaddr)) {
+		return array('status' => 'error',
+		             'errorcode' => 58,
+		             'errormsg' => "Invalid MAC address.  Must be XX:XX:XX:XX:XX:XX "
+		                         . "with each pair of XX being from 00 to FF (inclusive)");
+	}
+	$monitored = processInputData($monitored, ARG_NUMERIC);
+	if($monitored != 0 && $monitored != 1)
+		$monitored = 0;
+	$start = processInputData($start, ARG_STRING, 1);
+	$end = processInputData($end, ARG_STRING, 1);
+	#$foruser = processInputData($foruser, ARG_STRING, 1);
 
-////////////////////////////////////////////////////////////////////////////////
-///
-/// \fn XMLRPCgetRequestConnectData($requestid, $remoteIP)
-///
-/// \param $requestid - id of a request
-/// \param $remoteIP - ip address of connecting user's computer
-///
-/// \return an array with at least one index named '\b status' which will have
-/// one of these values\n
-/// \b error - error occurred; there will be 2 additional elements in the array:
-/// \li \b errorcode - error number\n
-/// \li \b errormsg - error string\n
-///
-/// \b ready - request is ready; there will be 3 additional elements in the
-/// array:
-/// \li \b serverIP - address of the reserved machine
-/// \li \b user - user to use when connecting to the machine
-/// \li \b password - password to use when connecting to the machine
-///
-/// \b notready - request is not ready for connection
-///
-/// \brief if request is ready, adds the connecting user's computer to the
-/// request and returns info about how to connect to the computer\n
-///
-////////////////////////////////////////////////////////////////////////////////
-function XMLRPCgetRequestConnectData($requestid, $remoteIP) {
-	global $user;
-	$requestid = processInputData($requestid, ARG_NUMERIC);
-	$remoteIP = processInputData($remoteIP, ARG_STRING, 1);
-	if(! preg_match('/^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/', $remoteIP, $matches) ||
-	   $matches[1] < 1 || $matches[1] > 223 ||
-	   $matches[2] > 255 ||
-		$matches[3] > 255 ||
-		$matches[4] > 255) {
+	$name = processInputData($name, ARG_STRING);
+	if(get_magic_quotes_gpc())
+		$name = stripslashes($name);
+	if(! preg_match('/^([-a-zA-Z0-9_\. ]){0,255}$/', $name)) {
 		return array('status' => 'error',
-		             'errorcode' => 2,
-		             'errormsg' => 'invalid IP address');
+		             'errorcode' => 58,
+						 'errormsg' => "Invalid name. Can only contain letters, numbers, "
+		                         . "spaces, dashes(-), underscores(_), and periods(.) "
+		                         . "and be up to 255 characters long");
 	}
-	$userRequests = getUserRequests('all', $user['id']);
-	$found = 0;
-	foreach($userRequests as $req) {
-		if($req['id'] == $requestid) {
-			$request = $req;
-			$found = 1;
-			break;
-		}
+	$name = mysql_real_escape_string($name);
+
+	# validate $start
+	if($start != 'now' && ! is_numeric($start)) {
+		return array('status' => 'error',
+		             'errorcode' => 4,
+		             'errormsg' => "received invalid input for start");
 	}
-	if(! $found)
+	# validate $end
+	if($end != 'indefinite' && ! is_numeric($end)) {
 		return array('status' => 'error',
-		             'errorcode' => 1,
-		             'errormsg' => 'unknown requestid');
+		             'errorcode' => 59,
+		             'errormsg' => "received invalid input for end");
+	}
 
-	// FIXME - add support for cluster requests
-	if(requestIsReady($request)) {
-		$requestData = getRequestInfo($requestid);
-		$query = "UPDATE reservation "
-		       . "SET remoteIP = '$remoteIP' "
+	$nowfuture = 'future';
+	if($start == 'now') {
+		$start = unixFloor15(time());
+		$nowfuture = 'now';
+	}
+	else
+		if($start < (time() - 30))
+			return array('status' => 'error',
+			             'errorcode' => 5,
+			             'errormsg' => "start time is in the past");
+	if($end == 'indefinite')
+		$end = datetimeToUnix("2038-01-01 00:00:00");
+	elseif($end % (15 * 60))
+		$end = unixFloor15($end) + (15 * 60);
+	elseif($end < ($start + 900))
+		return array('status' => 'error',
+		             'errorcode' => 88,
+		             'errormsg' => "end time must be at least 15 minutes after start time");
+
+	$max = getMaxOverlap($user['id']);
+	if(checkOverlap($start, $end, $max)) {
+		return array('status' => 'error',
+		             'errorcode' => 7,
+		             'errormsg' => "reservation overlaps with another one you "
+		                         . "have, and you are allowed $max "
+		                         . "overlapping reservations at a time");
+	}
+
+	$images = getImages();
+	$revisionid = getProductionRevisionid($imageid);
+	$rc = isAvailable($images, $imageid, $revisionid, $start, $end,
+	                  0, 0, 0, 0, $ipaddr, $macaddr);
+	if($rc < 1) {
+		addLogEntry($nowfuture, unixToDatetime($start), 
+		            unixToDatetime($end), 0, $imageid);
+		return array('status' => 'notavailable');
+	}
+	$return['requestid']= addRequest();
+	$query = "UPDATE reservation "
+	       . "SET remoteIP = '$remoteIP' "
+	       . "WHERE requestid = {$return['requestid']}";
+	doQuery($query);
+	$fields = array('requestid');
+	$values = array($return['requestid']);
+	if($name != '') {
+		$fields[] = 'name';
+		$values[] = "'$name'";
+	}
+	if($ipaddr != '') {
+		$fields[] = 'fixedIP';
+		$values[] = "'$ipaddr'";
+	}
+	if($macaddr != '') {
+		$fields[] = 'fixedMAC';
+		$values[] = "'$macaddr'";
+	}
+	if($admingroupid != 0) {
+		$fields[] = 'admingroupid';
+		$values[] = $admingroupid;
+	}
+	if($logingroupid != 0) {
+		$fields[] = 'logingroupid';
+		$values[] = $logingroupid;
+	}
+	if($monitored != 0) {
+		$fields[] = 'monitored';
+		$values[] = 1;
+	}
+	$allfields = implode(',', $fields);
+	$allvalues = implode(',', $values);
+	$query = "INSERT INTO serverrequest ($allfields) VALUES ($allvalues)";
+	doQuery($query, 101);
+	$return['status'] = 'success';
+	return $return;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \fn XMLRPCgetRequestIds()
+///
+/// \return an array with at least one index named 'status' which will have
+/// one of these values\n
+/// \b error - error occurred; there will be 2 additional elements in the array:
+/// \li \b errorcode - error number\n
+/// \li \b errormsg - error string\n
+///
+/// \b success - request was successfully ended; there will be an additional
+/// element whose index is 'requests' which is an array of arrays, each having
+/// these elements (or empty if no existing requests):\n
+/// \li \b requestid - id of the request\n
+/// \li \b imageid - id of the image\n
+/// \li \b imagename - name of the image\n
+/// \li \b start - unix timestamp of start time\n
+/// \li \b end - unix timestamp of end time\n
+/// \li \b OS - name of OS used in image\n
+/// \li \b isserver - 0 or 1 - whether or not this is a server reservation\n
+/// \li \b state - current state of reservation\n
+/// \li \b servername - only included if isserver == 1 - name of the reservation
+///
+/// \brief gets information about all of user's requests
+///
+////////////////////////////////////////////////////////////////////////////////
+function XMLRPCgetRequestIds() {
+	global $user;
+	$requests = getUserRequests("all");
+	if(empty($requests))
+		return array('status' => 'success', 'requests' => array());
+	$states = getStates();
+	$ret = array();
+	foreach($requests as $req) {
+		$start = datetimeToUnix($req['start']);
+		$end = datetimeToUnix($req['end']);
+		$tmp = array('requestid' => $req['id'],
+		             'imageid' => $req['imageid'],
+		             'imagename' => $req['prettyimage'],
+		             'start' => $start,
+		             'end' => $end,
+		             'OS' => $req['OS'],
+		             'isserver' => $req['server']);
+		if($req['currstateid'] == 14)
+			$tmp['state'] = $states[$req['laststateid']];
+		else
+			$tmp['state'] = $states[$req['currstateid']];
+		if($req['server'])
+			$tmp['servername'] = $req['servername'];
+		array_push($ret, $tmp);
+	}
+	return array('status' => 'success', 'requests' => $ret);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \fn XMLRPCgetRequestStatus($requestid)
+///
+/// \param $requestid - id of a request
+///
+/// \return an array with at least one index named '\b status' which will have
+/// one of these values:\n
+/// \b error - error occurred; there will be 2 additional elements in the array:
+/// \li \b errorcode - error number\n
+/// \li \b errormsg - error string\n
+///
+/// \b ready - request is ready\n
+/// \b failed - request failed to load properly\n
+/// \b timedout - request timed out (user didn't connect before timeout
+/// expired)\n
+/// \b loading - request is still loading; there will be an additional element
+/// in the array:
+/// \li \b time - the estimated wait time (in minutes) for loading to complete\n
+///
+/// \b future - start time of request is in the future\n
+///
+/// \brief determines and returns the status of the request
+///
+////////////////////////////////////////////////////////////////////////////////
+function XMLRPCgetRequestStatus($requestid) {
+	global $user;
+	$requestid = processInputData($requestid, ARG_NUMERIC);
+	$userRequests = getUserRequests('all', $user['id']);
+	$found = 0;
+	foreach($userRequests as $req) {
+		if($req['id'] == $requestid) {
+			$request = $req;
+			$found = 1;
+			break;
+		}
+	}
+	if(! $found)
+		return array('status' => 'error',
+		             'errorcode' => 1,
+		             'errormsg' => 'unknown requestid');
+
+	$now = time();
+	# request is ready
+	if(requestIsReady($request))
+		return array('status' => 'ready');
+	# request failed
+	elseif($request["currstateid"] == 5)
+		return array('status' => 'failed');
+	# other cases where the reservation start time has been reached
+	elseif(datetimeToUnix($request["start"]) < $now) {
+		# request has timed out
+		if($request["currstateid"] == 12 ||
+		   $request["currstateid"] == 11 ||
+		   ($request["currstateid"] == 14 &&
+		   $request["laststateid"] == 11)) {
+			return array('status' => 'timedout');
+		}
+		# computer is loading
+		else {
+			$imageid = $request['imageid'];
+			$images = getImages(0, $imageid);
+			$remaining = 1;
+			$computers = getComputers(0, 0, $request['computerid']);
+			if(isComputerLoading($request, $computers)) {
+				if(datetimeToUnix($request["daterequested"]) >=
+					datetimeToUnix($request["start"]))
+					$startload = datetimeToUnix($request["daterequested"]);
+				else
+					$startload = datetimeToUnix($request["start"]);
+				$imgLoadTime = getImageLoadEstimate($imageid);
+				if($imgLoadTime == 0)
+					$imgLoadTime = $images[$imageid]['reloadtime'] * 60;
+				$tmp = ($imgLoadTime - ($now - $startload)) / 60;
+				$remaining = sprintf("%d", $tmp) + 1;
+				if($remaining < 1) {
+					$remaining = 1;
+				}
+			}
+			return array('status' => 'loading', 'time' => $remaining);
+		}
+	}
+	# reservation is in the future
+	else
+		return array('status' => 'future');
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \fn XMLRPCgetRequestConnectData($requestid, $remoteIP)
+///
+/// \param $requestid - id of a request
+/// \param $remoteIP - ip address of connecting user's computer
+///
+/// \return an array with at least one index named '\b status' which will have
+/// one of these values\n
+/// \b error - error occurred; there will be 2 additional elements in the array:
+/// \li \b errorcode - error number\n
+/// \li \b errormsg - error string\n
+///
+/// \b ready - request is ready; there will be 3 additional elements in the
+/// array:\n
+/// \li \b serverIP - address of the reserved machine
+/// \li \b user - user to use when connecting to the machine
+/// \li \b password - password to use when connecting to the machine
+///
+/// \b notready - request is not ready for connection
+///
+/// \brief if request is ready, adds the connecting user's computer to the
+/// request and returns info about how to connect to the computer
+///
+////////////////////////////////////////////////////////////////////////////////
+function XMLRPCgetRequestConnectData($requestid, $remoteIP) {
+	global $user;
+	$requestid = processInputData($requestid, ARG_NUMERIC);
+	$remoteIP = processInputData($remoteIP, ARG_STRING, 1);
+	if(! preg_match('/^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/', $remoteIP, $matches) ||
+	   $matches[1] < 1 || $matches[1] > 223 ||
+	   $matches[2] > 255 ||
+	   $matches[3] > 255 ||
+	   $matches[4] > 255) {
+		return array('status' => 'error',
+		             'errorcode' => 2,
+		             'errormsg' => 'invalid IP address');
+	}
+	$userRequests = getUserRequests('all', $user['id']);
+	$found = 0;
+	foreach($userRequests as $req) {
+		if($req['id'] == $requestid) {
+			$request = $req;
+			$found = 1;
+			break;
+		}
+	}
+	if(! $found)
+		return array('status' => 'error',
+		             'errorcode' => 1,
+		             'errormsg' => 'unknown requestid');
+
+	// FIXME - add support for cluster requests
+	if(requestIsReady($request)) {
+		$requestData = getRequestInfo($requestid);
+		$query = "UPDATE reservation "
+		       . "SET remoteIP = '$remoteIP' "
 		       . "WHERE requestid = $requestid";
 		$qh = doQuery($query, 101);
 		addChangeLogEntry($requestData["logid"], $remoteIP);
 		$serverIP = $requestData["reservations"][0]["reservedIP"];
 		$passwd = $requestData["reservations"][0]["password"];
-        $connectport = $requestData["reservations"][0]["connectport"];
-        $connectMethods = getImageConnectMethodTexts(
-                $requestData["reservations"][0]["imageid"],
-                $requestData["reservations"][0]["imagerevisionid"]);
+		$connectport = $requestData["reservations"][0]["connectport"];
+		$connectMethods = getImageConnectMethodTexts(
+		$requestData["reservations"][0]["imageid"],
+		$requestData["reservations"][0]["imagerevisionid"]);
 		if($requestData["forimaging"])
 			$thisuser = 'Administrator';
 		else
 			if(preg_match('/(.*)@(.*)/', $user['unityid'], $matches))
 				$thisuser = $matches[1];
 			else
 				$thisuser = $user['unityid'];
-        foreach($connectMethods as $key => $cm){
-            $connecttext = $cm["connecttext"];
-            $connecttext = preg_replace("/#userid#/", $thisuser, $connecttext); 
-            $connecttext = preg_replace("/#password#/", $passwd, $connecttext); 
-            $connecttext = preg_replace("/#connectIP#/", $serverIP, $connecttext); 
-            $connecttext = preg_replace("/#connectport#/", $connectport, $connecttext); 
-            $connectMethods[$key]["connecttext"] = $connecttext;
-        }
+		foreach($connectMethods as $key => $cm) {
+			$connecttext = $cm["connecttext"];
+			$connecttext = preg_replace("/#userid#/", $thisuser, $connecttext); 
+			$connecttext = preg_replace("/#password#/", $passwd, $connecttext); 
+			$connecttext = preg_replace("/#connectIP#/", $serverIP, $connecttext); 
+			$connecttext = preg_replace("/#connectport#/", $connectport, $connecttext); 
+			$connectMethods[$key]["connecttext"] = $connecttext;
+		}
 		return array('status' => 'ready',
 		             'serverIP' => $serverIP,
 		             'user' => $thisuser,
-                     'password' => $passwd,
-                     'connectport' => $connectport,
-                     'connectMethods' => $connectMethods);
+		             'password' => $passwd,
+		             'connectport' => $connectport,
+		             'connectMethods' => $connectMethods);
 	}
 	return array('status' => 'notready');
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -552,11 +818,11 @@ function XMLRPCgetRequestConnectData($requestid, $remoteIP) {
 /// \li \b errormsg - error string\n
 ///
 /// \b success - request was successfully extended\n
 ///
 /// \brief extends the length of an active request; if a request that has not
-/// started needs to be extended, delete the request and submit a new one\n
+/// started needs to be extended, delete the request and submit a new one
 ///
 ////////////////////////////////////////////////////////////////////////////////
 function XMLRPCextendRequest($requestid, $extendtime) {
 	global $user;
 	$requestid = processInputData($requestid, ARG_NUMERIC);
@@ -676,100 +942,279 @@ function XMLRPCextendRequest($requestid, $extendtime) {
 	return array('status' => 'success');
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn XMLRPCremoveImageFromGroup($name, $imageid)
+/// \fn XMLRPCsetRequestEnding($requestid, $end)
 ///
-/// \param $name - the name of an imageGroup
-/// \param $imageid - the id of an image
+/// \param $requestid - id of a request
+/// \param $end - unix timestamp for end of reservation; will be rounded up to
+/// the nearest 15 minute increment
 ///
 /// \return an array with at least one index named 'status' which will have
 /// one of these values\n
 /// \b error - error occurred; there will be 2 additional elements in the array:
 /// \li \b errorcode - error number\n
 /// \li \b errormsg - error string\n
 ///
-/// \b success - image was removed from the group\n
+/// \b success - request was successfully extended\n
+///
+/// \brief modifies the end time of an active request; if a request that has not
+/// started needs to be modifed, delete the request and submit a new one
 ///
-/// \brief removes an image from a resource group\n
-///
-////////////////////////////////////////////////////////////////////////////////
-function XMLRPCremoveImageFromGroup($name, $imageid){
-    $groups = getUserResources(array("imageAdmin"), array("manageGroup"), 1);
-    
-    if($groupid = getResourceGroupID("image/$name")){
-        if(!array_key_exists($groupid, $groups['image'])){
-            return array('status' => 'error',
-                         'errorcode' => 46,
-                         'errormsg' => 'Unable to access image group');
-        }
-        $resources = getUserResources(array("imageAdmin"), array("manageGroup"));
-        if(!array_key_exists($imageid, $resources['image'])){
-            return array('status' => 'error',
-                         'errorcode' => 47,
-                         'errormsg' => 'Unable to access image');
-        }
-
-        $allimages = getImages();
-        $query = "DELETE FROM resourcegroupmembers "
-               . "WHERE resourceid={$allimages[$imageid]['resourceid']} "
-               . "AND resourcegroupid=$groupid";
-        doQuery($query, 287);
-        return array('status' => 'success');
-    } else {
-        return array('status' => 'error',
-                     'errorcode' => 83,
-                     'errormsg' => 'invalid resource group name');
-    }
+////////////////////////////////////////////////////////////////////////////////
+function XMLRPCsetRequestEnding($requestid, $end) {
+	global $user;
+
+	$requestid = processInputData($requestid, ARG_NUMERIC);
+	$userRequests = getUserRequests('all', $user['id']);
+	$found = 0;
+	foreach($userRequests as $req) {
+		if($req['id'] == $requestid) {
+			$request = getRequestInfo($requestid);
+			$found = 1;
+			break;
+		}
+	}
+	if(! $found)
+		return array('status' => 'error',
+		             'errorcode' => 1,
+		             'errormsg' => 'unknown requestid');
+
+	// make sure user is a member of the 'Specify End Time' group
+	$groupid = getUserGroupID('Specify End Time');
+	$members = getUserGroupMembers($groupid);
+	if(! $request['serverrequest'] && ! array_key_exists($user['id'], $members)) {
+		return array('status' => 'error',
+		             'errorcode' => 35,
+		             'errormsg' => "access denied to specify end time");
+	}
+
+	$end = processInputData($end, ARG_NUMERIC);
+
+	$maxend = datetimeToUnix("2038-01-01 00:00:00");
+	if($end < 0 || $end > $maxend) {
+		return array('status' => 'error',
+		             'errorcode' => 36,
+		             'errormsg' => "received invalid input for end");
+	}
+
+	$startts = datetimeToUnix($request['start']);
+	if($end % (15 * 60))
+		$end= unixFloor15($end) + (15 * 60);
+
+	// check that reservation has started
+	if($startts > time()) {
+		return array('status' => 'error',
+		             'errorcode' => 38,
+		             'errormsg' => 'reservation has not started');
+	}
+
+	// check for overlap
+	$max = getMaxOverlap($user['id']);
+	if(checkOverlap($startts, $end, $max, $requestid)) {
+		return array('status' => 'error',
+		             'errorcode' => 41,
+		             'errormsg' => 'overlapping reservation restriction',
+		             'maxoverlap' => $max);
+	}
+
+	// check for computer being available for extended time?
+	$timeToNext = timeToNextReservation($request);
+	$movedall = 1;
+	if($timeToNext > -1) {
+		foreach($request["reservations"] as $res) {
+			if(! moveReservationsOffComputer($res["computerid"])) {
+				$movedall = 0;
+				break;
+			}
+		}
+	}
+	if(! $movedall) {
+		$timeToNext = timeToNextReservation($request);
+		if($timeToNext >= 15)
+			$timeToNext -= 15;
+		$oldendts = datetimeToUnix($request['end']);
+		// reservation immediately after this one, cannot extend
+		if($timeToNext < 15) {
+			return array('status' => 'error',
+			             'errorcode' => 42,
+			             'errormsg' => 'cannot extend due to another reservation immediately after this one');
+		}
+		// check that requested extension < $timeToNext
+		elseif((($end - $oldendts) / 60) > $timeToNext) {
+			$maxend = $oldendts + ($timeToNext * 60);
+			return array('status' => 'error',
+			             'errorcode' => 43,
+			             'errormsg' => 'cannot extend by requested amount due to another reservation',
+			             'maxend' => $maxend);
+		}
+	}
+	$rc = isAvailable(getImages(), $request['reservations'][0]["imageid"],
+	                  $request['reservations'][0]['imagerevisionid'],
+	                  $startts, $end, $requestid);
+	// conflicts with scheduled maintenance
+	if($rc == -2) {
+		addChangeLogEntry($request["logid"], NULL, unixToDatetime($end),
+		                  $request['start'], NULL, NULL, 0);
+		return array('status' => 'error',
+		             'errorcode' => 46,
+		             'errormsg' => 'requested time is during a maintenance window');
+	}
+	// concurrent license overlap
+	elseif($rc == -1) {
+		addChangeLogEntry($request["logid"], NULL, unixToDatetime($end),
+		                  $request['start'], NULL, NULL, 0);
+		return array('status' => 'error',
+		             'errorcode' => 44,
+		             'errormsg' => 'concurrent license restriction');
+	}
+	// could not extend for some other reason
+	elseif($rc == 0) {
+		addChangeLogEntry($request["logid"], NULL, unixToDatetime($end),
+		                  $request['start'], NULL, NULL, 0);
+		return array('status' => 'error',
+		             'errorcode' => 45,
+		             'errormsg' => 'cannot extend at this time');
+	}
+	// success
+	updateRequest($requestid);
+	return array('status' => 'success');
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn XMLRPCaddImageToGroup($name, $imageid)
+/// \fn XMLRPCendRequest($requestid)
 ///
-/// \param $name - the name of an imageGroup
-/// \param $imageid - the id of an image
+/// \param $requestid - id of a request
 ///
 /// \return an array with at least one index named 'status' which will have
 /// one of these values\n
 /// \b error - error occurred; there will be 2 additional elements in the array:
 /// \li \b errorcode - error number\n
 /// \li \b errormsg - error string\n
 ///
-/// \b success - image was added to the group\n
+/// \b success - request was successfully ended\n
+///
+/// \brief ends/deletes a request
 ///
-/// \brief adds an image to a resource group\n
-///
-////////////////////////////////////////////////////////////////////////////////
-function XMLRPCaddImageToGroup($name, $imageid){
-    $groups = getUserResources(array("imageAdmin"), array("manageGroup"), 1);
-    
-    if($groupid = getResourceGroupID("image/$name")){
-        if(!array_key_exists($groupid, $groups['image'])){
-            return array('status' => 'error',
-                         'errorcode' => 46,
-                         'errormsg' => 'Unable to access image group');
-        }
-        $resources = getUserResources(array("imageAdmin"), array("manageGroup"));
-        if(!array_key_exists($imageid, $resources['image'])){
-            return array('status' => 'error',
-                         'errorcode' => 47,
-                         'errormsg' => 'Unable to access image');
-        }
-
-        $allimages = getImages();
-        $query = "INSERT IGNORE INTO resourcegroupmembers "
-               . "(resourceid, resourcegroupid) VALUES "
-               . "({$allimages[$imageid]['resourceid']}, $groupid)";
-        doQuery($query, 287);
-        return array('status' => 'success');
-    } else {
-        return array('status' => 'error',
-                     'errorcode' => 83,
-                     'errormsg' => 'invalid resource group name');
-    }
+////////////////////////////////////////////////////////////////////////////////
+function XMLRPCendRequest($requestid) {
+	global $user;
+	$requestid = processInputData($requestid, ARG_NUMERIC);
+	$userRequests = getUserRequests('all', $user['id']);
+	$found = 0;
+	foreach($userRequests as $req) {
+		if($req['id'] == $requestid) {
+			$request = getRequestInfo($requestid);
+			$found = 1;
+			break;
+		}
+	}
+	if(! $found)
+		return array('status' => 'error',
+		             'errorcode' => 1,
+		             'errormsg' => 'unknown requestid');
+
+	deleteRequest($request);
+	return array('status' => 'success');
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \fn XMLRPCautoCapture($requestid)
+///
+/// \param $requestid - id of request to be captured
+///
+/// \return an array with at least one index named 'status' which will have
+/// one of these values:\n
+/// \b error - error occurred; there will be 2 additional elements in the array:
+/// \li \b errorcode - error number
+/// \li \b errormsg - error string
+///
+/// \b success - image was successfully set to be captured
+///
+/// \brief creates entries in appropriate tables to capture an image and sets
+/// the request state to image
+///
+////////////////////////////////////////////////////////////////////////////////
+function XMLRPCautoCapture($requestid) {
+	global $user, $xmlrpcBlockAPIUsers;
+	if(! in_array($user['id'], $xmlrpcBlockAPIUsers)) {
+		return array('status' => 'error',
+		             'errorcode' => 47,
+		             'errormsg' => 'access denied to XMLRPCautoCapture');
+	}
+	$query = "SELECT id FROM request WHERE id = $requestid";
+	$qh = doQuery($query, 101);
+	if(! mysql_num_rows($qh)) {
+		return array('status' => 'error',
+		             'errorcode' => 52,
+		             'errormsg' => 'specified request does not exist');
+	}
+	$reqData = getRequestInfo($requestid);
+	# check state of reservation
+	if($reqData['stateid'] != 14 || $reqData['laststateid'] != 8) {
+		return array('status' => 'error',
+		             'errorcode' => 51,
+		             'errormsg' => 'reservation not in valid state');
+	}
+	# check that not a cluster reservation
+	if(count($reqData['reservations']) > 1) {
+		return array('status' => 'error',
+		             'errorcode' => 48,
+		             'errormsg' => 'cannot image a cluster reservation');
+	}
+	require_once(".ht-inc/images.php");
+	$imageid = $reqData['reservations'][0]['imageid'];
+	$imageData = getImages(0, $imageid);
+	$captime = unixToDatetime(time());
+	$comments = "start: {$reqData['start']}<br>"
+	          . "end: {$reqData['end']}<br>"
+	          . "computer: {$reqData['reservations'][0]['reservedIP']}<br>"
+	          . "capture time: $captime";
+	# create new revision if requestor is owner and not a kickstart image
+	if($imageData[$imageid]['installtype'] != 'kickstart' &&
+	   $reqData['userid'] == $imageData[$imageid]['ownerid']) {
+		$rc = updateExistingImage($requestid, $reqData['userid'], $comments, 1);
+		if($rc == 0) {
+			return array('status' => 'error',
+			             'errorcode' => 49,
+			             'errormsg' => 'error encountered while attempting to create new revision');
+		}
+	}
+	# create a new image if requestor is not owner or a kickstart image
+	else {
+		$ownerdata = getUserInfo($reqData['userid'], 1, 1);
+		$desc = "This is an autocaptured image.<br>"
+		      . "captured from image: {$reqData['reservations'][0]['prettyimage']}<br>"
+		      . "captured on: $captime<br>"
+		      . "owner: {$ownerdata['unityid']}@{$ownerdata['affiliation']}<br>";
+		$connectmethods = getImageConnectMethods($imageid, $reqData['reservations'][0]['imagerevisionid']);
+		$data = array('requestid' => $requestid,
+		              'description' => $desc,
+		              'usage' => '',
+		              'owner' => "{$ownerdata['unityid']}@{$ownerdata['affiliation']}",
+		              'prettyname' => "Autocaptured ({$ownerdata['unityid']} - $requestid)",
+		              'minram' => 64,
+		              'minprocnumber' => 1,
+		              'minprocspeed' => 500,
+		              'minnetwork' => 10,
+		              'maxconcurrent' => '',
+		              'checkuser' => 1,
+		              'rootaccess' => 1,
+		              'sysprep' => 1,
+		              'comments' => $comments,
+		              'connectmethodids' => implode(',', array_keys($connectmethods)));
+		$rc = submitAddImage($data, 1);
+		if($rc == 0) {
+			return array('status' => 'error',
+			             'errorcode' => 50,
+			             'errormsg' => 'error encountered while attempting to create image');
+		}
+	}
+	return array('status' => 'success');
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
 /// \fn XMLRPCgetGroupImages($name)
@@ -780,284 +1225,360 @@ function XMLRPCaddImageToGroup($name, $imageid){
 /// one of these values\n
 /// \b error - error occurred; there will be 2 additional elements in the array:
 /// \li \b errorcode - error number\n
 /// \li \b errormsg - error string\n
 ///
-/// \b success - returns an array of images\n
+/// \b success - returns an array of images; there will be an additional element
+/// in the array with an index of 'images' that is an array of images with
+/// each element having the following two keys:\n
+/// \li \b id - id of the image\n
+/// \li \b name - name of the image
 ///
-/// \brief gets a list of all images in a particular group\n
+/// \brief gets a list of all images in a particular group
 ///
 ////////////////////////////////////////////////////////////////////////////////
-function XMLRPCgetGroupImages($name){
-    if($groupid = getResourceGroupID("image/$name")){
-        $membership = getResourceGroupMemberships('image');
-        $resources = getUserResources(array("imageAdmin"),
-                                      array("manageGroup"));
+function XMLRPCgetGroupImages($name) {
+	if($groupid = getResourceGroupID("image/$name")) {
+		$membership = getResourceGroupMemberships('image');
+		$resources = getUserResources(array("imageAdmin"), array("manageGroup"));
 
-        $images = array();
-        foreach($resources['image'] as $imageid => $image){
-            if(array_key_exists($imageid, $membership['image']) &&
-                    in_array($groupid, $membership['image'][$imageid])){
-                array_push($images, array('id' => $imageid, 'name' => $image));
-            }
-        }
-        return array('status' => 'success',
-                     'images' => $images);
+		$images = array();
+		foreach($resources['image'] as $imageid => $image) {
+			if(array_key_exists($imageid, $membership['image']) &&
+			   in_array($groupid, $membership['image'][$imageid]))
+				array_push($images, array('id' => $imageid, 'name' => $image));
+		}
+		return array('status' => 'success',
+		             'images' => $images);
 
-    } else {
-        return array('status' => 'error',
-                     'errorcode' => 83,
-                     'errormsg' => 'invalid resource group name');
-    }
+	}
+	else {
+		return array('status' => 'error',
+		             'errorcode' => 83,
+		             'errormsg' => 'invalid resource group name');
+	}
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn XMLRPCaddImageGroupToComputerGroup($imageGroup, $computerGroup)
+/// \fn XMLRPCaddImageToGroup($name, $imageid)
 ///
-/// \param $imageGroup - the name of an imageGroup
-/// \param $computerGroup - the name of a computerGroup
+/// \param $name - the name of an imageGroup
+/// \param $imageid - the id of an image
 ///
 /// \return an array with at least one index named 'status' which will have
 /// one of these values\n
 /// \b error - error occurred; there will be 2 additional elements in the array:
 /// \li \b errorcode - error number\n
 /// \li \b errormsg - error string\n
 ///
-/// \b success - successfully mapped an image group to a computer group\n
+/// \b success - image was added to the group\n
 ///
-/// \brief map an image group to a computer group\n
-///
-////////////////////////////////////////////////////////////////////////////////
-function XMLRPCaddImageGroupToComputerGroup($imageGroup, $computerGroup){
-    $imageid = getResourceGroupID("image/$imageGroup");
-    $compid = getResourceGroupID("computer/$computerGroup");
-    if($imageid && $compid){
-        $tmp = getUserResources(array("imageAdmin"),
-                                array("manageMapping"), 1);
-        $imagegroups = $tmp['image'];
-        $tmp = getUserResources(array("computerAdmin"),
-                                array("manageMapping"), 1);
-        $computergroups = $tmp['computer'];
-
-        if(array_key_exists($compid, $computergroups) &&
-            array_key_exists($imageid, $imagegroups)){
-            $mapping = getResourceMapping("image", "computer",
-                                          $imageid,
-                                          $compid);
-            if(!array_key_exists($imageid, $mapping) ||
-                !array_key_exists($compid, $mapping[$imageid])){
-                $query = "INSERT INTO resourcemap "
-                       .        "(resourcegroupid1, "
-			           .        "resourcetypeid1, "
-			           .        "resourcegroupid2, "
-			           .        "resourcetypeid2) "
-			           . "VALUES ($imageid, "
-			           .         "13, "
-			           .         "$compid, "
-			           .         "12)";
-                doQuery($query, 101);
-            }
-            return array('status' => 'success');
-        } else {
-            return array('status' => 'error',
-                         'errorcode' => 84,
-                         'errormsg' => 'cannot access computer and/or image group');
-        }
-    } else {
-        return array('status' => 'error',
-                     'errorcode' => 83,
-                     'errormsg' => 'invalid resource group name');
-    }
+/// \brief adds an image to a resource group
+///
+////////////////////////////////////////////////////////////////////////////////
+function XMLRPCaddImageToGroup($name, $imageid) {
+	if($groupid = getResourceGroupID("image/$name")) {
+		$groups = getUserResources(array("imageAdmin"), array("manageGroup"), 1);
+		if(! array_key_exists($groupid, $groups['image'])) {
+			return array('status' => 'error',
+			             'errorcode' => 46,
+			             'errormsg' => 'Unable to access image group');
+		}
+		$resources = getUserResources(array("imageAdmin"), array("manageGroup"));
+		if(! array_key_exists($imageid, $resources['image'])) {
+			return array('status' => 'error',
+			             'errorcode' => 47,
+			             'errormsg' => 'Unable to access image');
+		}
+
+		$allimages = getImages(0, $imageid);
+		$query = "INSERT IGNORE INTO resourcegroupmembers "
+		       .        "(resourceid, "
+		       .        "resourcegroupid) "
+		       . "VALUES "
+		       .       "({$allimages[$imageid]['resourceid']}, "
+		       .       "$groupid)";
+		doQuery($query);
+		return array('status' => 'success');
+	}
+	else {
+		return array('status' => 'error',
+		             'errorcode' => 83,
+		             'errormsg' => 'invalid resource group name');
+	}
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn XMLRPCremoveImageGroupFromComputerGroup($imageGroup, $computerGroup)
+/// \fn XMLRPCremoveImageFromGroup($name, $imageid)
+///
+/// \param $name - the name of an imageGroup
+/// \param $imageid - the id of an image
+///
+/// \return an array with at least one index named 'status' which will have
+/// one of these values\n
+/// \b error - error occurred; there will be 2 additional elements in the array:
+/// \li \b errorcode - error number\n
+/// \li \b errormsg - error string\n
+///
+/// \b success - image was removed from the group\n
+///
+/// \brief removes an image from a resource group
+///
+////////////////////////////////////////////////////////////////////////////////
+function XMLRPCremoveImageFromGroup($name, $imageid) {
+	if($groupid = getResourceGroupID("image/$name")) {
+		$groups = getUserResources(array("imageAdmin"), array("manageGroup"), 1);
+		if(! array_key_exists($groupid, $groups['image'])) {
+			return array('status' => 'error',
+			             'errorcode' => 46,
+			             'errormsg' => 'Unable to access image group');
+		}
+		$resources = getUserResources(array("imageAdmin"), array("manageGroup"));
+		if(! array_key_exists($imageid, $resources['image'])) {
+			return array('status' => 'error',
+			             'errorcode' => 47,
+			             'errormsg' => 'Unable to access image');
+		}
+
+		$allimages = getImages(0, $imageid);
+		$query = "DELETE FROM resourcegroupmembers "
+		       . "WHERE resourceid = {$allimages[$imageid]['resourceid']} AND "
+		       .       "resourcegroupid = $groupid";
+		doQuery($query);
+		return array('status' => 'success');
+	}
+	else {
+		return array('status' => 'error',
+		             'errorcode' => 83,
+		             'errormsg' => 'invalid resource group name');
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \fn XMLRPCaddImageGroupToComputerGroup($imageGroup, $computerGroup)
 ///
 /// \param $imageGroup - the name of an imageGroup
 /// \param $computerGroup - the name of a computerGroup
 ///
 /// \return an array with at least one index named 'status' which will have
 /// one of these values\n
 /// \b error - error occurred; there will be 2 additional elements in the array:
 /// \li \b errorcode - error number\n
 /// \li \b errormsg - error string\n
 ///
-/// \b success - successfully removed the mapping from an 
-///     image group to a computer group\n
-///
-/// \brief remove the mapping of an image group to a computer group\n
-///
-////////////////////////////////////////////////////////////////////////////////
-function XMLRPCremoveImageGroupFromComputerGroup($imageGroup, $computerGroup){
-    $imageid = getResourceGroupID("image/$imageGroup");
-    $compid = getResourceGroupID("computer/$computerGroup");
-    if($imageid && $compid){
-        $tmp = getUserResources(array("imageAdmin"),
-                                array("manageMapping"), 1);
-        $imagegroups = $tmp['image'];
-        $tmp = getUserResources(array("computerAdmin"),
-                                array("manageMapping"), 1);
-        $computergroups = $tmp['computer'];
-
-        if(array_key_exists($compid, $computergroups) &&
-            array_key_exists($imageid, $imagegroups)){
-            $mapping = getResourceMapping("image", "computer",
-                                          $imageid,
-                                          $compid);
-            if(array_key_exists($imageid, $mapping) &&
-                array_key_exists($compid, $mapping[$imageid])){
-                $query = "DELETE FROM resourcemap "
-					   . "WHERE resourcegroupid1 = $imageid AND "
-					   .       "resourcetypeid1 = 13 AND "
-					   .       "resourcegroupid2 = $compid AND "
-					   .       "resourcetypeid2 = 12";
-			    doQuery($query, 101);
-            }
-            return array('status' => 'success');
-        } else {
-            return array('status' => 'error',
-                         'errorcode' => 84,
-                         'errormsg' => 'cannot access computer and/or image group');
-        }
-    } else {
-        return array('status' => 'error',
-                     'errorcode' => 83,
-                     'errormsg' => 'invalid resource group name');
-    }
+/// \b success - successfully mapped an image group to a computer group\n
+///
+/// \brief map an image group to a computer group
+///
+////////////////////////////////////////////////////////////////////////////////
+function XMLRPCaddImageGroupToComputerGroup($imageGroup, $computerGroup) {
+	$imageid = getResourceGroupID("image/$imageGroup");
+	$compid = getResourceGroupID("computer/$computerGroup");
+	if($imageid && $compid) {
+		$tmp = getUserResources(array("imageAdmin"),
+		                        array("manageMapping"), 1);
+		$imagegroups = $tmp['image'];
+		$tmp = getUserResources(array("computerAdmin"),
+		                        array("manageMapping"), 1);
+		$computergroups = $tmp['computer'];
+
+		if(array_key_exists($compid, $computergroups) &&
+			array_key_exists($imageid, $imagegroups)) {
+			$mapping = getResourceMapping("image", "computer",
+			                              $imageid, $compid);
+			if(! array_key_exists($imageid, $mapping) ||
+			   ! in_array($compid, $mapping[$imageid])) {
+				$query = "INSERT INTO resourcemap "
+				       .        "(resourcegroupid1, "
+				       .        "resourcetypeid1, "
+				       .        "resourcegroupid2, "
+				       .        "resourcetypeid2) "
+				       . "VALUES ($imageid, "
+				       .         "13, "
+				       .         "$compid, "
+				       .         "12)";
+				doQuery($query, 101);
+			}
+			return array('status' => 'success');
+		}
+		else {
+			return array('status' => 'error',
+			             'errorcode' => 84,
+			             'errormsg' => 'cannot access computer and/or image group');
+		}
+	}
+	else {
+		return array('status' => 'error',
+		             'errorcode' => 83,
+		             'errormsg' => 'invalid resource group name');
+	}
 }
 
-
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn XMLRPCgetNodes($root)
+/// \fn XMLRPCremoveImageGroupFromComputerGroup($imageGroup, $computerGroup)
 ///
-/// \param $root - (optional) the ID of the node forming the root of the hierarchy
+/// \param $imageGroup - the name of an imageGroup
+/// \param $computerGroup - the name of a computerGroup
 ///
 /// \return an array with at least one index named 'status' which will have
 /// one of these values\n
 /// \b error - error occurred; there will be 2 additional elements in the array:
 /// \li \b errorcode - error number\n
 /// \li \b errormsg - error string\n
 ///
-/// \b success - returns an array of nodes\n
-///
-/// \brief gets a list of all nodes in the privilege tree\n
-///
-////////////////////////////////////////////////////////////////////////////////
-function XMLRPCgetNodes($root=NULL){
-    global $user;
-    if(in_array("nodeAdmin", $user['privileges'])){
-        $topNodes = $root ? getChildNodes($root) : getChildNodes();
-        $nodes = array();
-        $stack = array();
-        foreach($topNodes as $id => $node){
-            $node['id'] = $id;
-            array_push($nodes, $node);
-            array_push($stack, $node);
-        } 
-        while(count($stack)){
-            $item = array_shift($stack);
-            $children = getChildNodes($item['id']);
-            foreach($children as $id => $node){
-                $node['id'] = $id;
-                array_push($nodes, $node);
-                array_push($stack, $node);
-            }
-        }
-        return array(
-            'status' => 'success',
-            'nodes' => $nodes);
-    } else {
-        return array(
-            'status' => 'error',
-            'errorcode' => 56,
-            'errormsg' => 'User cannot access node content');
-    }
+/// \b success - successfully removed the mapping from an image group to a
+/// computer group\n
+///
+/// \brief remove the mapping of an image group to a computer group
+///
+////////////////////////////////////////////////////////////////////////////////
+function XMLRPCremoveImageGroupFromComputerGroup($imageGroup, $computerGroup) {
+	$imageid = getResourceGroupID("image/$imageGroup");
+	$compid = getResourceGroupID("computer/$computerGroup");
+	if($imageid && $compid) {
+		$tmp = getUserResources(array("imageAdmin"),
+		                        array("manageMapping"), 1);
+		$imagegroups = $tmp['image'];
+		$tmp = getUserResources(array("computerAdmin"),
+		                        array("manageMapping"), 1);
+		$computergroups = $tmp['computer'];
+
+		if(array_key_exists($compid, $computergroups) &&
+			array_key_exists($imageid, $imagegroups)) {
+			$mapping = getResourceMapping("image", "computer",
+			                              $imageid, $compid);
+			if(array_key_exists($imageid, $mapping) &&
+			   in_array($compid, $mapping[$imageid])) {
+				$query = "DELETE FROM resourcemap "
+				       . "WHERE resourcegroupid1 = $imageid AND "
+				       .       "resourcetypeid1 = 13 AND "
+				       .       "resourcegroupid2 = $compid AND "
+				       .       "resourcetypeid2 = 12";
+				doQuery($query, 101);
+			}
+			return array('status' => 'success');
+		}
+		else {
+			return array('status' => 'error',
+			             'errorcode' => 84,
+			             'errormsg' => 'cannot access computer and/or image group');
+		}
+	}
+	else {
+		return array('status' => 'error',
+		             'errorcode' => 83,
+		             'errormsg' => 'invalid resource group name');
+	}
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn XMLRPCnodeExists($nodeName, $parentNode)
+/// \fn XMLRPCgetNodes($root)
 ///
-/// \param $nodeName - the name of a node
-/// \param $parentNode - the ID of the parent node
+/// \param $root - (optional, default=top of tree) the ID of the node forming
+/// the root of the hierarchy
 ///
 /// \return an array with at least one index named 'status' which will have
 /// one of these values\n
 /// \b error - error occurred; there will be 2 additional elements in the array:
 /// \li \b errorcode - error number\n
 /// \li \b errormsg - error string\n
 ///
-/// \b success - returns an 'exists' element set to either 1 or 0\n
+/// \b success - returns an array of nodes; there will be an additional element
+/// in the array with an index of 'nodes' that is an array of nodes with each
+/// element having the following three keys:\n
+/// \li \b id - id of the node\n
+/// \li \b name - name of the node\n
+/// \li \b parent - id of the parent node
 ///
-/// \brief indicates whether a node with that name already exists at this
-///     location in the privilege tree\n
-///
-////////////////////////////////////////////////////////////////////////////////
-function XMLRPCnodeExists($nodeName, $parentNode){
-    global $user;
-    if(in_array("nodeAdmin", $user['privileges'])){
-        // does a node with this name already exist?
-        $query = "SELECT id "
-               . "FROM privnode "
-               . "WHERE name = '$nodeName' AND parent = $parentNode";
-        $qh = doQuery($query, 335);
-        if(mysql_num_rows($qh)){
-            return array('status' => 'success', 'exists' => TRUE);
-        } else {
-            return array('status' => 'success', 'exists' => FALSE);
-        }
-    } else {
-        return array(
-            'status' => 'error',
-            'errorcode' => 56,
-            'errormsg' => 'User cannot access node content');
-    }
+/// \brief gets a list of all nodes in the privilege tree
+///
+////////////////////////////////////////////////////////////////////////////////
+function XMLRPCgetNodes($root=NULL) {
+	global $user;
+	if(in_array("userGrant", $user["privileges"]) ||
+		in_array("resourceGrant", $user["privileges"]) ||
+		in_array("nodeAdmin", $user["privileges"])) {
+		$root = processInputData($root, ARG_NUMERIC);
+		$topNodes = $root ? getChildNodes($root) : getChildNodes();
+		$nodes = array();
+		$stack = array();
+		foreach($topNodes as $id => $node) {
+			$node['id'] = $id;
+			array_push($nodes, $node);
+			array_push($stack, $node);
+		} 
+		while(count($stack)) {
+			$item = array_shift($stack);
+			$children = getChildNodes($item['id']);
+			foreach($children as $id => $node) {
+				$node['id'] = $id;
+				array_push($nodes, $node);
+				array_push($stack, $node);
+			}
+		}
+		return array('status' => 'success',
+		             'nodes' => $nodes);
+	}
+	else {
+		return array('status' => 'error',
+		             'errorcode' => 70,
+		             'errormsg' => 'User cannot access node content');
+	}
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn XMLRPCremoveNode($nodeID)
+/// \fn XMLRPCnodeExists($nodeName, $parentNode)
 ///
-/// \param $nodeID - the ID of a node
+/// \param $nodeName - the name of a node
+/// \param $parentNode - the ID of the parent node
 ///
 /// \return an array with at least one index named 'status' which will have
 /// one of these values\n
 /// \b error - error occurred; there will be 2 additional elements in the array:
 /// \li \b errorcode - error number\n
 /// \li \b errormsg - error string\n
 ///
-/// \b success - node was successfully deleted
+/// \b success - returns an 'exists' element set to either 1 or 0\n
+///
+/// \brief indicates whether a node with that name already exists at this
+/// location in the privilege tree
 ///
-/// \brief delete a node in the privilege tree\n
-///
-////////////////////////////////////////////////////////////////////////////////
-function XMLRPCremoveNode($nodeID){
-    require_once(".ht-inc/privileges.php");
-    global $user;
-    if(!in_array("nodeAdmin", $user['privileges'])){
-        return array(
-            'status' => 'error',
-            'errorcode' => 56,
-            'errormsg' => 'User cannot administer nodes');
-    }
-    if(!checkUserHasPriv("nodeAdmin", $user['id'], $nodeID)){
-        return array(
-            'status' => 'error',
-            'errorcode' => 57,
-            'errormsg' => 'User cannot edit this node');
-    }
-    $nodes = recurseGetChildren($nodeID);
-    array_push($nodes, $nodeID);
-    $deleteNodes = implode(',', $nodes);
-    $query = "DELETE FROM privnode "
-           . "WHERE id IN ($deleteNodes)";
-    doQuery($query, 345);
-    return array(
-            'status' => 'success');
+////////////////////////////////////////////////////////////////////////////////
+function XMLRPCnodeExists($nodeName, $parentNode) {
+	global $user;
+	if(! is_numeric($parentNode)) {
+		return array('status' => 'error',
+		             'errorcode' => 78,
+		             'errormsg' => 'Invalid nodeid specified');
+	}
+	if(in_array("userGrant", $user["privileges"]) ||
+		in_array("resourceGrant", $user["privileges"]) ||
+		in_array("nodeAdmin", $user["privileges"])) {
+		if(get_magic_quotes_gpc())
+			$nodeName = stripslashes($nodeName);
+		$nodeName = mysql_real_escape_string($nodeName);
+		// does a node with this name already exist?
+		$query = "SELECT id "
+		       . "FROM privnode "
+		       . "WHERE name = '$nodeName' AND parent = $parentNode";
+		$qh = doQuery($query, 335);
+		if(mysql_num_rows($qh))
+			return array('status' => 'success', 'exists' => TRUE);
+		else
+			return array('status' => 'success', 'exists' => FALSE);
+	}
+	else {
+		return array('status' => 'error',
+		             'errorcode' => 70,
+		             'errormsg' => 'User cannot access node content');
+	}
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
 /// \fn XMLRPCaddNode($nodeName, $parentNode)
@@ -1071,113 +1592,116 @@ function XMLRPCremoveNode($nodeID){
 /// \li \b errorcode - error number\n
 /// \li \b errormsg - error string\n
 ///
 /// \b success - node was successfully added
 ///
-/// \brief add a node to the privilege tree as a child of the
-///     specified parent node\n
-///
-////////////////////////////////////////////////////////////////////////////////
-function XMLRPCaddNode($nodeName, $parentNode){
-    require_once(".ht-inc/privileges.php");
-    global $user;
-    if(in_array("nodeAdmin", $user['privileges'])){
-        if(!$parentNode){
-            $topNodes = getChildNodes();
-            $keys = array_keys($topNodes);
-            $parentNode = array_shift($keys);
-        }
-
-        if(!preg_match("/^[-A-Za-z0-9_\. ]+$/", $nodeName)){
-            return array('status' => 'error',
-                         'errorcode' => 48,
-                         'errormsg' => 'Invalid node name');
-        }
-
-        if(checkUserHasPriv("nodeAdmin", $user['id'], $parentNode)){
-            $nodeInfo = getNodeInfo($parentNode);
-            $query = "SELECT id "
-                   . "FROM privnode "
-                   . "WHERE name = '$nodeName' AND parent = $parentNode";
-            $qh = doQuery($query, 335);
-            if(mysql_num_rows($qh)){
-                return array('status' => 'error',
-                             'errorcode' => 50,
-                             'errormsg' => 'A node of that name already exists under ' . $nodeInfo['name']);
-            }
-            $query = "INSERT IGNORE INTO privnode "
-                   .        "(parent, name) "
-                   . "VALUES "
-                   .        "($parentNode, '$nodeName')";
-            doQuery($query, 337);
-            $qh = doQuery("SELECT LAST_INSERT_ID() FROM privnode", 101);
-            if(!$row = mysql_fetch_row($qh)){
-                return array('status' => 'error',
-                             'errorcode' => 51,
-                             'errormsg' => 'Could not add node to database');
-            }
-            $nodeid = $row[0];
-            return array('status' => 'success',
-                         'nodeid' => $nodeid);
-        } else {
-            return array('status' => 'error',
-                         'errorcode' => 49,
-                         'errormsg' => 'Unable to add node at this location');
-        }
-    } else {
-        return array(
-            'status' => 'error',
-            'errorcode' => 56,
-            'errormsg' => 'User cannot access node content');
-    }
-}
+/// \brief add a node to the privilege tree as a child of the specified parent
+/// node
+///
+////////////////////////////////////////////////////////////////////////////////
+function XMLRPCaddNode($nodeName, $parentNode) {
+	require_once(".ht-inc/privileges.php");
+	global $user;
+	if(! is_numeric($parentNode)) {
+		return array('status' => 'error',
+		             'errorcode' => 78,
+		             'errormsg' => 'Invalid nodeid specified');
+	}
+	if(in_array("nodeAdmin", $user['privileges'])) {
+		$nodeInfo = getNodeInfo($parentNode);
+		if(is_null($tmp)) {
+			return array('status' => 'error',
+			             'errorcode' => 78,
+			             'errormsg' => 'Invalid nodeid specified');
+		}
 
+		if(! validateNodeName($nodeName, $tmp)) {
+			return array('status' => 'error',
+			             'errorcode' => 81,
+			             'errormsg' => 'Invalid node name');
+		}
+
+		if(checkUserHasPriv("nodeAdmin", $user['id'], $parentNode)) {
+			$query = "SELECT id "
+			       . "FROM privnode "
+			       . "WHERE name = '$nodeName' AND parent = $parentNode";
+			$qh = doQuery($query);
+			if(mysql_num_rows($qh)) {
+				return array('status' => 'error',
+				             'errorcode' => 82,
+				             'errormsg' => 'A node of that name already exists under ' . $nodeInfo['name']);
+			}
+			$query = "INSERT IGNORE INTO privnode "
+			       .        "(parent, name) "
+			       . "VALUES "
+			       .        "($parentNode, '$nodeName')";
+			doQuery($query);
+			$qh = doQuery("SELECT LAST_INSERT_ID() FROM privnode", 101);
+			if(! $row = mysql_fetch_row($qh)) {
+				return array('status' => 'error',
+				             'errorcode' => 85,
+				             'errormsg' => 'Could not add node to database');
+			}
+			$nodeid = $row[0];
+			return array('status' => 'success',
+			             'nodeid' => $nodeid);
+		}
+		else {
+			return array('status' => 'error',
+			             'errorcode' => 49,
+			             'errormsg' => 'Unable to add node at this location');
+		}
+	}
+	else {
+		return array('status' => 'error',
+		             'errorcode' => 70,
+		             'errormsg' => 'User cannot access node content');
+	}
+}
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn XMLRPCremoveResourceGroupPriv($name, $type, $nodeid, $permissions)
+/// \fn XMLRPCremoveNode($nodeID)
 ///
-/// \param $name - the name of the resource group
-/// \param $type - the resource type
-/// \param $nodeid - the ID of the node in the privilege tree
-/// \param $permissions - a colon (:) delimited list of privileges to remove
+/// \param $nodeID - the ID of a node
 ///
 /// \return an array with at least one index named 'status' which will have
 /// one of these values\n
 /// \b error - error occurred; there will be 2 additional elements in the array:
 /// \li \b errorcode - error number\n
 /// \li \b errormsg - error string\n
 ///
-/// \b success - privileges were successfully removed
+/// \b success - node was successfully deleted
 ///
-/// \brief remove privileges from a resource group in the privilege
-///      node tree\n
-///
-////////////////////////////////////////////////////////////////////////////////
-function XMLRPCremoveResourceGroupPriv($name, $type, $nodeid, $permissions){
-    require_once(".ht-inc/privileges.php");
-    global $user;
-
-    if(! checkUserHasPriv("resourceGrant", $user['id'], $nodeid)){
-        return array('status' => 'error',
-                     'errorcode' => 53,
-                     'errormsg' => 'Unable to remove group privileges on this node');
-    }
-    if($typeid = getResourceTypeID($type)){
-        if(!checkForGroupName($name, 'resource', '', $typeid)){
-            return array('status' => 'error',
-                         'errorcode' => 28,
-                         'errormsg' => 'resource group does not exist');
-        }
-        $perms = explode(':', $permissions);
-        updateResourcePrivs("$type/$name", $nodeid, array(), $perms);
-        return array('status' => 'success');
-    } else {
-        return array('status' => 'error',
-                     'errorcode' => 56,
-                     'errormsg' => 'Invalid resource type');
-    }
+/// \brief delete a node from the privilege tree
+///
+////////////////////////////////////////////////////////////////////////////////
+function XMLRPCremoveNode($nodeID) {
+	require_once(".ht-inc/privileges.php");
+	global $user;
+	if(! is_numeric($nodeID)) {
+		return array('status' => 'error',
+		             'errorcode' => 78,
+		             'errormsg' => 'Invalid nodeid specified');
+	}
+	if(! in_array("nodeAdmin", $user['privileges'])) {
+		return array('status' => 'error',
+		             'errorcode' => 70,
+		             'errormsg' => 'User cannot administer nodes');
+	}
+	if(! checkUserHasPriv("nodeAdmin", $user['id'], $nodeID)) {
+		return array('status' => 'error',
+		             'errorcode' => 57,
+		             'errormsg' => 'User cannot edit this node');
+	}
+	$nodes = recurseGetChildren($nodeID);
+	array_push($nodes, $nodeID);
+	$deleteNodes = implode(',', $nodes);
+	$query = "DELETE FROM privnode "
+	       . "WHERE id IN ($deleteNodes)";
+	doQuery($query, 345);
+	return array('status' => 'success');
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
 /// \fn XMLRPCgetUserGroupPrivs($name, $affiliation, $nodeid)
@@ -1190,122 +1714,70 @@ function XMLRPCremoveResourceGroupPriv($name, $type, $nodeid, $permissions){
 /// one of these values\n
 /// \b error - error occurred; there will be 2 additional elements in the array:
 /// \li \b errorcode - error number\n
 /// \li \b errormsg - error string\n
 ///
-/// \b success - an additional element is returned:
-/// \li \b privileges - a list of privileges available
+/// \b success - an additional element is returned:\n
+/// \li \b privileges - array of privileges assigned at the node
 ///
-/// \brief get a list of privileges for a user group at a particular
-///     location in the privilege tree\n
+/// \brief get a list of privileges for a user group at a particular node in the
+/// privilege tree
 ///
 ////////////////////////////////////////////////////////////////////////////////
-function XMLRPCgetUserGroupPrivs($name, $affiliation, $nodeid){
-    require_once(".ht-inc/privileges.php");
-    global $user;
+function XMLRPCgetUserGroupPrivs($name, $affiliation, $nodeid) {
+	require_once(".ht-inc/privileges.php");
+	global $user;
+
+	if(! is_numeric($nodeid)) {
+		return array('status' => 'error',
+		             'errorcode' => 78,
+		             'errormsg' => 'Invalid nodeid specified');
+	}
 
-    if(! checkUserHasPriv("userGrant", $user['id'], $nodeid)){
-        return array('status' => 'error',
-                     'errorcode' => 53,
-                     'errormsg' => 'Unable to add resource group to this node');
-    }
+	if(! in_array("userGrant", $user["privileges"]) &&
+		! in_array("resourceGrant", $user["privileges"]) &&
+		! in_array("nodeAdmin", $user["privileges"])) {
+		return array('status' => 'error',
+		             'errorcode' => 62,
+		             'errormsg' => 'Unable to view user group privileges');
+	}
 
 	$validate = array('name' => $name,
 	                  'affiliation' => $affiliation);
 	$rc = validateAPIgroupInput($validate, 1);
 	if($rc['status'] == 'error')
 		return $rc;
 
-    $privileges = array();
-    $nodePrivileges = getNodePrivileges($nodeid, 'usergroups');
-    $cascadedNodePrivileges = getNodeCascadePrivileges($nodeid, 'usergroups'); 
-    $cngp = $cascadedNodePrivileges['usergroups'];
-    $ngp = $nodePrivileges['usergroups'];
-    if(array_key_exists($name, $cngp)){
-        foreach($cngp[$name]['privs'] as $p){
-            if(! array_key_exists($name, $ngp) ||
-                    ! in_array("block", $ngp[$name]['privs'])){
-                array_push($privileges, $p);
-            }
-        }
-    }
-    if(array_key_exists($name, $ngp)){
-        foreach($ngp[$name]['privs'] as $p){
-            if($p != "block"){
-                array_push($privileges, $p);
-            }
-        }
-    }
-
-    return array(
-        'status' => 'success',
-        'privileges' => array_unique($privileges));
-}
+	$privileges = array();
+	$nodePrivileges = getNodePrivileges($nodeid, 'usergroups');
+	$cascadedNodePrivileges = getNodeCascadePrivileges($nodeid, 'usergroups'); 
+	$cngp = $cascadedNodePrivileges['usergroups'];
+	$ngp = $nodePrivileges['usergroups'];
+	if(array_key_exists($name, $cngp)) {
+		foreach($cngp[$name]['privs'] as $p) {
+			if(! array_key_exists($name, $ngp) ||
+			   ! in_array("block", $ngp[$name]['privs']))
+				array_push($privileges, $p);
+		}
+	}
+	if(array_key_exists($name, $ngp)) {
+		foreach($ngp[$name]['privs'] as $p) {
+			if($p != "block")
+				array_push($privileges, $p);
+		}
+	}
 
-////////////////////////////////////////////////////////////////////////////////
-///
-/// \fn XMLRPCgetResourceGroupPrivs($name, $type, $nodeid)
-///
-/// \param $name - the name of the resource group
-/// \param $type - the resource group type
-/// \param $nodeid - the ID of the node in the privilege tree
-///
-/// \return an array with at least one index named 'status' which will have
-/// one of these values\n
-/// \b error - error occurred; there will be 2 additional elements in the array:
-/// \li \b errorcode - error number\n
-/// \li \b errormsg - error string\n
-///
-/// \b success - an additional element is returned:
-/// \li \b privileges - a list of privileges available
-///
-/// \brief get a list of privileges for a resource group at a particular
-///     location in the privilege tree\n
-///
-////////////////////////////////////////////////////////////////////////////////
-function XMLRPCgetResourceGroupPrivs($name, $type, $nodeid){
-    require_once(".ht-inc/privileges.php");
-    global $user;
-
-    if(! checkUserHasPriv("resourceGrant", $user['id'], $nodeid)){
-        return array('status' => 'error',
-                     'errorcode' => 53,
-                     'errormsg' => 'Unable to add resource group to this node');
-    }
-
-    if($typeid = getResourceTypeID($type)){
-        if(!checkForGroupName($name, 'resource', '', $typeid)){
-            return array('status' => 'error',
-                         'errorcode' => 28,
-                         'errormsg' => 'resource group does not exist');
-        }
-        $nodePrivileges = getNodePrivileges($nodeid, 'resources');
-        $nodePrivileges = getNodeCascadePrivileges($nodeid, 'resources', $nodePrivileges); 
-        foreach($nodePrivileges['resources'] as $resource => $privs){
-            if(strstr($resource, "$type/$name")){
-                return array(
-                    'status' => 'success',
-                    'privileges' => $privs);
-            }
-        }
-        return array(
-            'status' => 'error',
-            'errorcode' => 29,
-            'errormsg' => 'could not find resource name in privilege list');
-    } else {
-        return array('status' => 'error',
-                     'errorcode' => 56,
-                     'errormsg' => 'Invalid resource type');
-    }
+	return array('status' => 'success',
+	              'privileges' => array_unique($privileges));
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn XMLRPCaddResourceGroupPriv($name, $type, $nodeid, $permissions)
+/// \fn XMLRPCaddUserGroupPriv($name, $affiliation, $nodeid, $permissions)
 ///
-/// \param $name - the name of the resource group
-/// \param $type - the resource group type
+/// \param $name - the name of the user group
+/// \param $affiliation - the affiliation of the user group
 /// \param $nodeid - the ID of the node in the privilege tree
 /// \param $permissions - a colon (:) delimited list of privileges to add
 ///
 /// \return an array with at least one index named 'status' which will have
 /// one of these values\n
@@ -1313,1904 +1785,1724 @@ function XMLRPCgetResourceGroupPrivs($name, $type, $nodeid){
 /// \li \b errorcode - error number\n
 /// \li \b errormsg - error string\n
 ///
 /// \b success - privileges were successfully added
 ///
-/// \brief add privileges for a resource group at a particular
-///     location in the privilege tree\n
-///
-////////////////////////////////////////////////////////////////////////////////
-function XMLRPCaddResourceGroupPriv($name, $type, $nodeid, $permissions){
-    require_once(".ht-inc/privileges.php");
-    global $user;
-
-    if(! checkUserHasPriv("resourceGrant", $user['id'], $nodeid)){
-        return array('status' => 'error',
-                     'errorcode' => 53,
-                     'errormsg' => 'Unable to add resource group to this node');
-    }
-
-    if($typeid = getResourceTypeID($type)){
-        if(!checkForGroupName($name, 'resource', '', $typeid)){
-            return array('status' => 'error',
-                         'errorcode' => 28,
-                         'errormsg' => 'resource group does not exist');
-        }
-        $perms = explode(':', $permissions);
-        updateResourcePrivs("$type/$name", $nodeid, $perms, array());
-        return array('status' => 'success');
-    } else {
-        return array('status' => 'error',
-                     'errorcode' => 56,
-                     'errormsg' => 'Invalid resource type');
-    }
-}
-
-
-////////////////////////////////////////////////////////////////////////////////
-///
-/// \fn XMLRPCremoveResourceGroupPriv($name, $type, $nodeid, $permissions)
-///
-/// \param $name - the name of the resource group
-/// \param $type - the resource group type
-/// \param $nodeid - the ID of the node in the privilege tree
-/// \param $permissions - a colon (:) delimited list of privileges to remove
-///
-/// \return an array with at least one index named 'status' which will have
-/// one of these values\n
-/// \b error - error occurred; there will be 2 additional elements in the array:
-/// \li \b errorcode - error number\n
-/// \li \b errormsg - error string\n
-///
-/// \b success - privileges were successfully removed
-///
-/// \brief remove privileges for a resource group at a particular
-///     location in the privilege tree\n
+/// \brief add privileges for a user group at a particular node in the
+/// privilege tree
 ///
 ////////////////////////////////////////////////////////////////////////////////
-function XMLRPCremoveUserGroupPriv($name, $affiliation, $nodeid, $permissions){
-    require_once(".ht-inc/privileges.php");
+function XMLRPCaddUserGroupPriv($name, $affiliation, $nodeid, $permissions) {
+	require_once(".ht-inc/privileges.php");
 	global $user;
 
-    if(! checkUserHasPriv("userGrant", $user['id'], $nodeid)){
+	if(! is_numeric($nodeid)) {
+		return array('status' => 'error',
+		             'errorcode' => 78,
+		             'errormsg' => 'Invalid nodeid specified');
+	}
+
+	if(! checkUserHasPriv("userGrant", $user['id'], $nodeid)) {
 		return array('status' => 'error',
-                     'errorcode' => 53,
-                     'errormsg' => 'Unable to remove group privileges on this node');
-    }
+		             'errorcode' => 52,
+		             'errormsg' => 'Unable to add a user group to this node');
+	}
 
 	$validate = array('name' => $name,
 	                  'affiliation' => $affiliation);
 	$rc = validateAPIgroupInput($validate, 1);
 	if($rc['status'] == 'error')
 		return $rc;
 
-    $groupid = $rc['id'];
-    $groupname = "$name@$affiliation";
-    $perms = explode(':', $permissions);
-    $usertypes = getTypes('users');
-    array_push($usertypes["users"], "block");
+	$groupid = $rc['id'];
+	#$name = "$name@$affiliation";
+	$perms = explode(':', $permissions);
+	$usertypes = getTypes('users');
+	array_push($usertypes["users"], "block");
 	array_push($usertypes["users"], "cascade");
-    $cascadePrivs = getNodeCascadePriviliges($nodeid, "usergroups");
-    $removegroupprivs = array();
-    if(array_key_exists($groupname, $cascadePrivs['usergroups'])){
-        foreach($perms as $permission){
-            if(in_array($permission, $cascadePrivs['usergroups'][$groupname]['privs'])){
-                array_push($removegroupprivs, $permission);
-            }
-        }
-        $diff = array_diff($cascadePrivs['usergroups'][$groupname], $removegroupprivs);
-        if(count($diff) == 1 && in_array("cascade", $diff)){
-            array_push($removegroupprivs, "cascade");
-	}
-	}
-    if(empty($removegroupprivs)){
+
+	$diff = array_diff($perms, $usertypes['users']);
+	if(count($diff) || (count($perms) == 1 && $perms[0] == 'cascade')) {
 		return array('status' => 'error',
-                     'errorcode' => 53,
-                     'errormsg' => 'Invalid or missing permissions list supplied');
+		             'errorcode' => 66,
+		             'errormsg' => 'Invalid or missing permissions list supplied');
 	}
 
-    updateUserOrGroupPrivs($groupid, $nodeid, array(), $removegroupprivs, "group");
-    return array('status' => 'success');
+	$cnp = getNodeCascadePrivileges($nodeid, "usergroups");
+	$np = getNodePrivileges($nodeid, "usergroups", $cnp);
+
+	$diff = array_diff($perms, $np['usergroups'][$name]['privs']);
+	if(empty($diff))
+		return array('status' => 'success');
+
+	updateUserOrGroupPrivs($groupid, $nodeid, $diff, array(), "group");
+	return array('status' => 'success');
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn XMLRPCaddUserGroupPriv($name, $affiliation, $nodeid, $permissions)
+/// \fn XMLRPCremoveUserGroupPriv($name, $affiliation, $nodeid,
+///                                   $permissions)
 ///
 /// \param $name - the name of the user group
 /// \param $affiliation - the affiliation of the user group
 /// \param $nodeid - the ID of the node in the privilege tree
-/// \param $permissions - a colon (:) delimited list of privileges to add
+/// \param $permissions - a colon (:) delimited list of privileges to remove
 ///
 /// \return an array with at least one index named 'status' which will have
 /// one of these values\n
 /// \b error - error occurred; there will be 2 additional elements in the array:
 /// \li \b errorcode - error number\n
 /// \li \b errormsg - error string\n
 ///
-/// \b success - privileges were successfully added
+/// \b success - privileges were successfully removed
 ///
-/// \brief add privileges for a user group at a particular
-///     location in the privilege tree\n
+/// \brief remove privileges for a resource group at a particular node in the
+/// privilege tree
 ///
 ////////////////////////////////////////////////////////////////////////////////
-function XMLRPCaddUserGroupPriv($name, $affiliation, $nodeid, $permissions){
-    require_once(".ht-inc/privileges.php");
-    global $user;
+function XMLRPCremoveUserGroupPriv($name, $affiliation, $nodeid, $permissions) {
+	require_once(".ht-inc/privileges.php");
+	global $user;
+
+	if(! is_numeric($nodeid)) {
+		return array('status' => 'error',
+		             'errorcode' => 78,
+		             'errormsg' => 'Invalid nodeid specified');
+	}
 
-    if(! checkUserHasPriv("userGrant", $user['id'], $nodeid)){
+	if(! checkUserHasPriv("userGrant", $user['id'], $nodeid)) {
 		return array('status' => 'error',
-                     'errorcode' => 52,
-                     'errormsg' => 'Unable to add a user group to this node');
+		             'errorcode' => 65,
+		             'errormsg' => 'Unable to remove user group privileges on this node');
 	}
 
 	$validate = array('name' => $name,
 	                  'affiliation' => $affiliation);
 	$rc = validateAPIgroupInput($validate, 1);
 	if($rc['status'] == 'error')
 		return $rc;
 
-    $groupid = $rc['id'];
-    $perms = explode(':', $permissions);
-    $usertypes = getTypes('users');
-    array_push($usertypes["users"], "block");
+	$groupid = $rc['id'];
+	#$name = "$name@$affiliation";
+	$perms = explode(':', $permissions);
+	$usertypes = getTypes('users');
+	array_push($usertypes["users"], "block");
 	array_push($usertypes["users"], "cascade");
-	$newgroupprivs = array();
-	foreach($usertypes["users"] as $type) {
-		if(in_array($type, $perms))
-			array_push($newgroupprivs, $type);
-	}
-	if(empty($newgroupprivs) || (count($newgroupprivs) == 1 && 
-           in_array("cascade", $newgroupprivs))) {
+
+	$diff = array_diff($perms, $usertypes['users']);
+	if(count($diff)) {
 		return array('status' => 'error',
-                    'errorcode' => 53,
-                    'errormsg' => 'Invalid or missing permissions list supplied');
+		             'errorcode' => 66,
+		             'errormsg' => 'Invalid or missing permissions list supplied');
 	}
-    updateUserOrGroupPrivs($groupid, $nodeid, $newgroupprivs, array(), "group");
-	return array('status' => 'success');
-}
 
+	$cnp = getNodeCascadePrivileges($nodeid, "usergroups");
+	$np = getNodePrivileges($nodeid, "usergroups");
+
+	if(array_key_exists($name, $cnp['usergroups']) &&
+	   (! array_key_exists($name, $np['usergroups']) ||
+	   ! in_array('block', $np['usergroups'][$name]))) {
+		$intersect = array_intersect($cnp['usergroups'][$name]['privs'], $perms);
+		if(count($intersect)) {
+			return array('status' => 'error',
+			             'errorcode' => 80,
+			             'errormsg' => 'Unable to modify privileges cascaded to this node');
+		}
+	}
 
+	$diff = array_diff($np['usergroups'][$name]['privs'], $perms);
+	if(count($diff) == 1 && in_array("cascade", $diff))
+		array_push($perms, "cascade");
 
+	updateUserOrGroupPrivs($groupid, $nodeid, array(), $perms, "group");
+	return array('status' => 'success');
+}
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn XMLRPCsetRequestEnding($requestid, $end)
+/// \fn XMLRPCgetResourceGroupPrivs($name, $type, $nodeid)
 ///
-/// \param $requestid - id of a request
-/// \param $end - unix timestamp for end of reservation; will be rounded up to
-/// the nearest 15 minute increment
+/// \param $name - the name of the resource group
+/// \param $type - the resource group type
+/// \param $nodeid - the ID of the node in the privilege tree
 ///
 /// \return an array with at least one index named 'status' which will have
 /// one of these values\n
 /// \b error - error occurred; there will be 2 additional elements in the array:
 /// \li \b errorcode - error number\n
 /// \li \b errormsg - error string\n
 ///
-/// \b success - request was successfully extended\n
+/// \b success - an additional element is returned:\n
+/// \li \b privileges - array of privileges assigned at the node
 ///
-/// \brief modifies the end time of an active request; if a request that has not
-/// started needs to be modifed, delete the request and submit a new one\n
+/// \brief get a list of privileges for a resource group at a particular node in
+/// the privilege tree
 ///
 ////////////////////////////////////////////////////////////////////////////////
-function XMLRPCsetRequestEnding($requestid, $end) {
+function XMLRPCgetResourceGroupPrivs($name, $type, $nodeid) {
+	require_once(".ht-inc/privileges.php");
 	global $user;
 
-	$requestid = processInputData($requestid, ARG_NUMERIC);
-	$userRequests = getUserRequests('all', $user['id']);
-	$found = 0;
-	foreach($userRequests as $req) {
-		if($req['id'] == $requestid) {
-			$request = getRequestInfo($requestid);
-			$found = 1;
-			break;
-		}
+	if(! is_numeric($nodeid)) {
+		return array('status' => 'error',
+		             'errorcode' => 78,
+		             'errormsg' => 'Invalid nodeid specified');
 	}
-	if(! $found)
+
+	if(! in_array("userGrant", $user["privileges"]) &&
+		! in_array("resourceGrant", $user["privileges"]) &&
+		! in_array("nodeAdmin", $user["privileges"])) {
 		return array('status' => 'error',
-		             'errorcode' => 1,
-		             'errormsg' => 'unknown requestid');
+		             'errorcode' => 63,
+		             'errormsg' => 'Unable to view resource group privileges');
+	}
 
-	// make sure user is a member of the 'Specify End Time' group
-	$groupid = getUserGroupID('Specify End Time');
-	$members = getUserGroupMembers($groupid);
-	if(! $request['serverrequest'] && ! array_key_exists($user['id'], $members)) {
-		return array('status' => 'error',
-		             'errorcode' => 35,
-		             'errormsg' => "access denied to specify end time");
-	}
-
-	$end = processInputData($end, ARG_NUMERIC);
-
-	$startts = datetimeToUnix($request['start']);
-	if($end % (15 * 60))
-		$end= unixFloor15($end) + (15 * 60);
-
-	// check that reservation has started
-	if($startts > time()) {
-		return array('status' => 'error',
-		             'errorcode' => 38,
-		             'errormsg' => 'reservation has not started');
-	}
-
-	// check for overlap
-	$max = getMaxOverlap($user['id']);
-	if(checkOverlap($startts, $end, $max, $requestid)) {
-		return array('status' => 'error',
-		             'errorcode' => 41,
-		             'errormsg' => 'overlapping reservation restriction',
-		             'maxoverlap' => $max);
-	}
-
-	// check for computer being available for extended time?
-	$timeToNext = timeToNextReservation($request);
-	$movedall = 1;
-	if($timeToNext > -1) {
-		foreach($request["reservations"] as $res) {
-			if(! moveReservationsOffComputer($res["computerid"])) {
-				$movedall = 0;
-				break;
-			}
-		}
-	}
-	if(! $movedall) {
-		$timeToNext = timeToNextReservation($request);
-		if($timeToNext >= 15)
-			$timeToNext -= 15;
-		$oldendts = datetimeToUnix($request['end']);
-		// reservation immediately after this one, cannot extend
-		if($timeToNext < 15) {
+	if($typeid = getResourceTypeID($type)) {
+		if(! $groupid = getResourceGroupID("$type/$name")) {
 			return array('status' => 'error',
-			             'errorcode' => 42,
-			             'errormsg' => 'cannot extend due to another reservation immediately after this one');
+			             'errorcode' => 74,
+			             'errormsg' => 'resource group does not exist');
 		}
-		// check that requested extension < $timeToNext
-		elseif((($end - $oldendts) / 60) > $timeToNext) {
-			$maxend = $oldendts + ($timeToNext * 60);
-			return array('status' => 'error',
-			             'errorcode' => 43,
-			             'errormsg' => 'cannot extend by requested amount due to another reservation',
-			             'maxend' => $maxend);
+		$np = getNodePrivileges($nodeid, 'resources');
+		$cnp = getNodeCascadePrivileges($nodeid, 'resources'); 
+		$key = "$type/$name/$groupid";
+		if(array_key_exists($key, $np['resources']) &&
+			(in_array('block', $np['resources'][$key]) ||
+		   ! array_key_exists($key, $cnp['resources'])))
+			$privs = $np['resources'][$key];
+		elseif(array_key_exists($key, $cnp['resources']) &&
+		   array_key_exists($key, $np['resources'])) {
+			$allprivs = array_merge($cnp['resources'][$key], $np['resources'][$key]);
+			$privs = array_unique($allprivs);
 		}
+		elseif(array_key_exists($key, $cnp['resources']))
+			$privs = $cnp['resources'][$key];
+		else
+			$privs = array();
+		return array('status' => 'success',
+		             'privileges' => $privs);
 	}
-	$rc = isAvailable(getImages(), $request['reservations'][0]["imageid"],
-	                  $request['reservations'][0]['imagerevisionid'],
-	                  $startts, $end, $requestid);
-	// conflicts with scheduled maintenance
-	if($rc == -2) {
-		addChangeLogEntry($request["logid"], NULL, unixToDatetime($end),
-		                  $request['start'], NULL, NULL, 0);
-		return array('status' => 'error',
-		             'errorcode' => 46,
-		             'errormsg' => 'requested time is during a maintenance window');
-	}
-	// concurrent license overlap
-	elseif($rc == -1) {
-		addChangeLogEntry($request["logid"], NULL, unixToDatetime($end),
-		                  $request['start'], NULL, NULL, 0);
-		return array('status' => 'error',
-		             'errorcode' => 44,
-		             'errormsg' => 'concurrent license restriction');
-	}
-	// could not extend for some other reason
-	elseif($rc == 0) {
-		addChangeLogEntry($request["logid"], NULL, unixToDatetime($end),
-		                  $request['start'], NULL, NULL, 0);
+	else {
 		return array('status' => 'error',
-		             'errorcode' => 45,
-		             'errormsg' => 'cannot extend at this time');
+		             'errorcode' => 71,
+		             'errormsg' => 'Invalid resource type');
 	}
-	// success
-	updateRequest($requestid);
-	return array('status' => 'success');
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn XMLRPCendRequest($requestid)
+/// \fn XMLRPCaddResourceGroupPriv($name, $type, $nodeid, $permissions)
 ///
-/// \param $requestid - id of a request
+/// \param $name - the name of the resource group
+/// \param $type - the resource group type
+/// \param $nodeid - the ID of the node in the privilege tree
+/// \param $permissions - a colon (:) delimited list of privileges to add
 ///
 /// \return an array with at least one index named 'status' which will have
 /// one of these values\n
 /// \b error - error occurred; there will be 2 additional elements in the array:
 /// \li \b errorcode - error number\n
 /// \li \b errormsg - error string\n
 ///
-/// \b success - request was successfully ended\n
+/// \b success - privileges were successfully added
 ///
-/// \brief ends/deletes a request\n
+/// \brief add privileges for a resource group at a particular node in the
+/// privilege tree
 ///
 ////////////////////////////////////////////////////////////////////////////////
-function XMLRPCendRequest($requestid) {
-	global $user;
-	$requestid = processInputData($requestid, ARG_NUMERIC);
-	$userRequests = getUserRequests('all', $user['id']);
-	$found = 0;
-	foreach($userRequests as $req) {
-		if($req['id'] == $requestid) {
-			$request = getRequestInfo($requestid);
-			$found = 1;
-			break;
-		}
-	}
-	if(! $found)
-		return array('status' => 'error',
-		             'errorcode' => 1,
-		             'errormsg' => 'unknown requestid');
-
-	deleteRequest($request);
-	return array('status' => 'success');
+function XMLRPCaddResourceGroupPriv($name, $type, $nodeid, $permissions) {
+	return _XMLRPCchangeResourceGroupPriv_sub('add', $name, $type, $nodeid,
+	                                          $permissions);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn XMLRPCgetRequestIds()
+/// \fn XMLRPCremoveResourceGroupPriv($name, $type, $nodeid, $permissions)
+///
+/// \param $name - the name of the resource group
+/// \param $type - the resource type
+/// \param $nodeid - the ID of the node in the privilege tree
+/// \param $permissions - a colon (:) delimited list of privileges to remove
 ///
 /// \return an array with at least one index named 'status' which will have
 /// one of these values\n
 /// \b error - error occurred; there will be 2 additional elements in the array:
 /// \li \b errorcode - error number\n
 /// \li \b errormsg - error string\n
 ///
-/// \b success - request was successfully ended; there will be an additional
-/// element whose index is 'requests' which is an array of arrays, each having
-/// these elements (or empty if no existing requests):\n
-/// \li \b requestid - id of the request\n
-/// \li \b imageid - id of the image\n
-/// \li \b imagename - name of the image\n
-/// \li \b start - unix timestamp of start time\n
-/// \li \b end - unix timestamp of end time
+/// \b success - privileges were successfully removed
 ///
-/// \brief gets information about all of user's requests\n
+/// \brief remove privileges for a resource group from a node in the privilege
+/// tree
 ///
 ////////////////////////////////////////////////////////////////////////////////
-function XMLRPCgetRequestIds() {
+function XMLRPCremoveResourceGroupPriv($name, $type, $nodeid, $permissions) {
+	return _XMLRPCchangeResourceGroupPriv_sub('remove', $name, $type, $nodeid,
+	                                          $permissions);
+}
+
+##################################################################################
+###
+### fn _XMLRPCchangeResourceGroupPriv_sub($mode, $name, $type, $nodeid,
+###                                       $permissions)
+###
+### param $mode - 'add' or 'remove'
+### param $name - the name of the resource group
+### param $type - the resource type
+### param $nodeid - the ID of the node in the privilege tree
+### param $permissions - a colon (:) delimited list of privileges to remove
+###
+### return an array with at least one index named 'status' which will have
+### one of these values\n
+### error - error occurred; there will be 2 additional elements in the array:
+### * errorcode - error number\n
+### * errormsg - error string\n
+###
+### success - privileges were successfully added or removed
+###
+### brief internal function to be called from XMLRPCremoveResourceGroupPriv and
+### XMLRPCaddResourceGroupPriv - adds or removes privileges for a resource group
+### from a node in the privilege tree
+###
+################################################################################
+function _XMLRPCchangeResourceGroupPriv_sub($mode, $name, $type, $nodeid,
+                                            $permissions) {
+	require_once(".ht-inc/privileges.php");
 	global $user;
-	$requests = getUserRequests("all");
-	if(empty($requests))
-		return array('status' => 'success', 'requests' => array());
-	$ret = array();
-	foreach($requests as $req) {
-		$start = datetimeToUnix($req['start']);
-		$end = datetimeToUnix($req['end']);
-		$tmp = array('requestid' => $req['id'],
-		             'imageid' => $req['imageid'],
-		             'imagename' => $req['prettyimage'],
-		             'start' => $start,
-		             'end' => $end);
-		array_push($ret, $tmp);
+
+	if(! is_numeric($nodeid)) {
+		return array('status' => 'error',
+		             'errorcode' => 78,
+		             'errormsg' => 'Invalid nodeid specified');
 	}
-	return array('status' => 'success', 'requests' => $ret);
+
+	if(! checkUserHasPriv("resourceGrant", $user['id'], $nodeid)) {
+		return array('status' => 'error',
+		             'errorcode' => 61,
+		             'errormsg' => 'Unable to remove resource group privileges on this node');
+	}
+
+	$resourcetypes = getTypes('resources');
+	if(! in_array($type, $resourcetypes['resources'])) {
+		return array('status' => 'error',
+		             'errorcode' => 71,
+		             'errormsg' => 'Invalid resource type');
+	}
+
+	$groupid = getResourceGroupID("$type/$name");
+	if(is_null($groupid)) {
+		return array('status' => 'error',
+		             'errorcode' => 74,
+		             'errormsg' => 'resource group does not exist');
+	}
+
+	$changeperms = explode(':', $permissions);
+	$allperms = getResourcePrivs();
+	$diff = array_diff($changeperms, $allperms);
+	if(count($diff)) {
+		return array('status' => 'error',
+		             'errorcode' => 66,
+		             'errormsg' => 'Invalid or missing permissions list supplied');
+	}
+
+	$nocheckperms = array('block', 'cascade', 'available');
+	$checkperms = array_diff($changeperms, $nocheckperms);
+
+	$groupdata = getResourceGroups($type, $groupid);
+	if(count($checkperms) &&
+	   ! array_key_exists($groupdata[$groupid]["ownerid"], $user["groups"])) {
+		return array('status' => 'error',
+		             'errorcode' => 79,
+		             'errormsg' => 'Unable to modify privilege set for resource group');
+	}
+
+	$key = "$type/$name/$groupid";
+	$cnp = getNodeCascadePrivileges($nodeid, "resources");
+	$np = getNodePrivileges($nodeid, 'resources');
+	if(array_key_exists($key, $cnp['resources']) &&
+	   (! array_key_exists($key, $np['resources']) ||
+	   ! in_array('block', $np['resources'][$key]))) {
+		$intersect = array_intersect($cnp['resources'][$key], $changeperms);
+		if(count($intersect)) {
+			return array('status' => 'error',
+			             'errorcode' => 80,
+			             'errormsg' => 'Unable to modify privileges cascaded to this node');
+		}
+	}
+
+	if($mode == 'remove') {
+		$diff = array_diff($np['resources'][$key], $changeperms);
+		if(count($diff) == 1 && in_array("cascade", $diff))
+			$changeperms[] = 'cascade';
+	}
+
+	if($mode == 'add')
+		updateResourcePrivs("$groupid", $nodeid, $changeperms, array());
+	elseif($mode == 'remove')
+		updateResourcePrivs("$groupid", $nodeid, array(), $changeperms);
+	return array('status' => 'success');
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn XMLRPCblockAllocation($imageid, $start, $end, $numMachines,
-///                           $usergroupid, $ignoreprivileges)
+/// \fn XMLRPCgetUserGroups($groupType, $affiliationid)
 ///
-/// \param $imageid - id of the image to be used
-/// \param $start - mysql datetime for the start time (i.e. machines should be
-/// prep'd and ready by this time)
-/// \param $end - mysql datetime for the end time
-/// \param $numMachines - number of computers to allocate
-/// \param $usergroupid - id of user group for checking user access to machines
-/// \param $ignoreprivileges (optional, default=0) - 0 (false) or 1 (true) - set
-/// to 1 to select computers from any that are mapped to be able to run the
-/// image; set to 0 to only select computers from ones that are both mapped and
-/// that users in the usergroup assigned to this block allocation have been
-/// granted access to through the privilege tree
+/// \param $groupType - (optional, default=0) specify 0 for all groups, 1 for
+/// only custom groups, 2 for only courseroll groups
+/// \param $affiliationid - (optional, default=0) specifiy an affiliationid to
+/// limit returned groups to only those matching the affiliation; pass 0 for
+/// all affiliations
 ///
-/// \return an array with blockTimesid as an index with the value of the newly
-/// created block time and at least one other index named 'status' which will
-/// have one of these values:\n
+/// \return an array with two indices, one named 'status' which will have a
+/// value of 'success', the other named 'groups' which will be an array of
+/// arrays, each one having the following keys:\n
+/// \li \b id\n
+/// \li \b name\n
+/// \li \b groupaffiliation\n
+/// \li \b groupaffiliationid\n
+/// \li \b ownerid\n
+/// \li \b owner\n
+/// \li \b affiliation\n
+/// \li \b editgroupid\n
+/// \li \b editgroup\n
+/// \li \b editgroupaffiliationid\n
+/// \li \b editgroupaffiliation\n
+/// \li \b custom\n
+/// \li \b courseroll\n
+/// \li \b initialmaxtime\n
+/// \li \b maxextendtime\n
+/// \li \b overlapResCount\n
+///
+/// \brief builds a list of user groups
+///
+////////////////////////////////////////////////////////////////////////////////
+function XMLRPCgetUserGroups($groupType=0, $affiliationid=0) {
+	global $user;
+	$groupType = processInputData($groupType, ARG_NUMERIC, 0, 0);
+	$affiliationid = processInputData($affiliationid, ARG_NUMERIC, 0, 0);
+
+	$groups = getUserGroups($groupType, $affiliationid);
+
+	// Filter out any groups to which the user does not have access.
+	$usergroups = array();
+	foreach($groups as $id => $group) {
+		if($group['ownerid'] == $user['id'] || 
+		   (array_key_exists("editgroupid", $group) &&
+		   array_key_exists($group['editgroupid'], $user["groups"])) || 
+		   (array_key_exists($id, $user["groups"]))) {
+			array_push($usergroups, $group);
+		}
+	}
+	return array("status" => "success",
+	             "groups" => $usergroups);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \fn XMLRPCgetUserGroupAttributes($name, $affiliation)
+///
+/// \param $name - name of user group
+/// \param $affiliation - affiliation of user group
+///
+/// \return an array with at least one index named 'status' which will have
+/// one of these values:\n
 /// \b error - error occurred; there will be 2 additional elements in the array:
 /// \li \b errorcode - error number
 /// \li \b errormsg - error string
 ///
-/// \b success - blockTimesid was processed; there will be two additional
-/// elements in this case:
-/// \li \b allocated - total number of desired allocations that have been
-/// processed
-/// \li \b unallocated - total number of desired allocations that have not been
-/// processed
-///
-/// \b warning - there was a non-fatal issue that occurred while processing
-/// the call; there will be four additional elements in this case:
-/// \li \b warningcode - warning number
-/// \li \b warningmsg - warning string
-/// \li \b allocated - total number of desired allocations that have been
-/// processed
-/// \li \b unallocated - total number of desired allocations that have not been
-/// processed
-///
-/// note that status may be warning, but allocated may be 0 indicating there
-/// were no errors that occurred, but there simply were not any machines
-/// available
+/// \b success - there will be six additional elements in this case:
+/// \li \b owner - user that will be the owner of the group in
+///                username\@affiliation form
+/// \li \b managingGroup - user group that can manage membership of this one in
+///                        groupname\@affiliation form
+/// \li \b initialMaxTime - (minutes) max initial time users in this group can
+///                         select for length of reservations
+/// \li \b totalMaxTime - (minutes) total length users in the group can have for
+///                       a reservation (including all extensions)
+/// \li \b maxExtendTime - (minutes) max length of time users can request as an
+///                        extension to a reservation at a time
+/// \li \b overlapResCount - maximum allowed number of overlapping reservations
+/// allowed for users in this group
 ///
-/// \brief creates and processes a block allocation according to the passed
-/// in criteria\n
+/// \brief gets information about a user group
 ///
 ////////////////////////////////////////////////////////////////////////////////
-function XMLRPCblockAllocation($imageid, $start, $end, $numMachines,
-                               $usergroupid, $ignoreprivileges=0) {
-	global $user, $xmlrpcBlockAPIUsers;
-	if(! in_array($user['id'], $xmlrpcBlockAPIUsers)) {
-		return array('status' => 'error',
-		             'errorcode' => 34,
-		             'errormsg' => 'access denied for managing block allocations');
-	}
-	$ownerid = getUserlistID('vclreload@Local');
-	$name = "API:$start";
-	$managementnodes = getManagementNodes('future');
-	if(empty($managementnodes)) {
+function XMLRPCgetUserGroupAttributes($name, $affiliation) {
+	global $user;
+	if(! in_array('groupAdmin', $user['privileges'])) {
 		return array('status' => 'error',
-		             'errorcode' => 12,
-		             'errormsg' => 'could not allocate a management node to handle block allocation');
+		             'errorcode' => 16,
+		             'errormsg' => 'access denied for managing groups');
 	}
-	$mnid = array_rand($managementnodes);
-	$query = "INSERT INTO blockRequest "
-	       .        "(name, "
-	       .        "imageid, "
-	       .        "numMachines, "
-	       .        "groupid, "
-	       .        "repeating, "
-	       .        "ownerid, "
-	       .        "admingroupid, "
-	       .        "managementnodeid, "
-	       .        "expireTime, "
-	       .        "status) "
-	       . "VALUES "
-	       .        "('$name', "
-	       .        "$imageid, "
-	       .        "$numMachines, "
-	       .        "$usergroupid, "
-	       .        "'list', "
-	       .        "$ownerid, "
-	       .        "0, "
-	       .        "$mnid, "
-	       .        "'$end', "
-	       .        "'accepted')";
-	doQuery($query, 101);
-	$brid = dbLastInsertID();
-	$query = "INSERT INTO blockTimes "
-	       .        "(blockRequestid, "
-	       .        "start, "
-	       .        "end) "
-	       . "VALUES "
-	       .        "($brid, "
-	       .        "'$start', "
-	       .        "'$end')";
-	doQuery($query, 101);
-	$btid = dbLastInsertID();
-	$return = XMLRPCprocessBlockTime($btid, $ignoreprivileges);
-	$return['blockTimesid'] = $btid;
-	return $return;
+	$validate = array('name' => $name,
+	                  'affiliation' => $affiliation);
+	$rc = validateAPIgroupInput($validate, 1);
+	if($rc['status'] == 'error')
+		return $rc;
+	$query = "SELECT ug.id, "
+	       .        "ug.ownerid, "
+	       .        "CONCAT(u.unityid, '@', a.name) AS owner, "
+	       .        "ug.editusergroupid AS editgroupid, "
+	       .        "eug.name AS editgroup, "
+	       .        "eug.affiliationid AS editgroupaffiliationid, "
+	       .        "euga.name AS editgroupaffiliation, "
+	       .        "ug.initialmaxtime, "
+	       .        "ug.totalmaxtime, "
+	       .        "ug.maxextendtime, "
+	       .        "ug.overlapResCount "
+	       . "FROM usergroup ug "
+	       . "LEFT JOIN user u ON (ug.ownerid = u.id) "
+	       . "LEFT JOIN affiliation a ON (u.affiliationid = a.id) "
+	       . "LEFT JOIN usergroup eug ON (ug.editusergroupid = eug.id) "
+	       . "LEFT JOIN affiliation euga ON (eug.affiliationid = euga.id) "
+	       . "WHERE ug.id = {$rc['id']}";
+	$qh = doQuery($query, 101);
+	if(! $row = mysql_fetch_assoc($qh)) {
+		return array('status' => 'error',
+		             'errorcode' => 18,
+		             'errormsg' => 'user group with submitted name and affiliation does not exist');
+	}
+	// if not owner and not member of managing group, no access
+	if($user['id'] != $row['ownerid'] && 
+	   ! array_key_exists($row['editgroupid'], $user['groups'])) {
+		return array('status' => 'error',
+		             'errorcode' => 69,
+		             'errormsg' => 'access denied to user group with submitted name and affiliation');
+	}
+	$ret = array('status' => 'success',
+	             'owner' => $row['owner'],
+	             'managingGroup' => "{$row['editgroup']}@{$row['editgroupaffiliation']}",
+	             'initialMaxTime' => $row['initialmaxtime'],
+	             'totalMaxTime' => $row['totalmaxtime'],
+	             'maxExtendTime' => $row['maxextendtime'],
+	             'overlapResCount' => $row['overlapResCount']);
+	return $ret;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn XMLRPCprocessBlockTime($blockTimesid, $ignoreprivileges)
+/// \fn XMLRPCaddUserGroup($name, $affiliation, $owner, $managingGroup,
+///                        $initialMaxTime, $totalMaxTime, $maxExtendTime,
+///                        $custom)
 ///
-/// \param $blockTimesid - id from the blockTimes table
-/// \param $ignoreprivileges (optional, default=0) - 0 (false) or 1 (true) - set
-/// to 1 to select computers from any that are mapped to be able to run the
-/// image; set to 0 to only select computers from ones that are both mapped and
-/// that users in the usergroup assigned to this block allocation have been
-/// granted access to through the privilege tree
+/// \param $name - name of user group
+/// \param $affiliation - affiliation of user group
+/// \param $owner - user that will be the owner of the group in
+///                 username\@affiliation form
+/// \param $managingGroup - user group that can manage membership of this one
+/// \param $initialMaxTime - (minutes) max initial time users in this group can
+///                          select for length of reservations
+/// \param $totalMaxTime - (minutes) total length users in the group can have
+///                        for a reservation (including all extensions)
+/// \param $maxExtendTime - (minutes) max length of time users can request as an
+///                         extension to a reservation at a time
+/// \param $custom - (optional, default=1) set custom flag for user group; if
+///                set to 0, $owner and $managingGroup will be ignored and group
+///                membership will be managed via authentication protocol
 ///
 /// \return an array with at least one index named 'status' which will have
 /// one of these values:\n
 /// \b error - error occurred; there will be 2 additional elements in the array:
-/// \li \b errorcode - error number
+/// \li \b errorcode - error number\n
 /// \li \b errormsg - error string
 ///
-/// \b completed - blockTimesid was previously successfully processed\n
-/// \b success - blockTimesid was processed; there will be two additional
-/// elements in this case:
-/// \li \b allocated - total number of desired allocations that have been
-/// processed
-/// \li \b unallocated - total number of desired allocations that have not been
-/// processed
+/// \b success - user group was successfully created
 ///
-/// \b warning - there was a non-fatal issue that occurred while processing
-/// the call; there will be four additional elements in this case:
-/// \li \b warningcode - warning number
-/// \li \b warningmsg - warning string
-/// \li \b allocated - total number of desired allocations that have been
-/// processed
-/// \li \b unallocated - total number of desired allocations that have not been
-/// processed
+/// \brief creates a new user group with the specified parameters
 ///
-/// note that status may be warning, but allocated may be 0 indicating there
-/// were no errors that occurred, but there simply were not any machines
-/// available
+////////////////////////////////////////////////////////////////////////////////
+function XMLRPCaddUserGroup($name, $affiliation, $owner, $managingGroup,
+                            $initialMaxTime, $totalMaxTime, $maxExtendTime,
+                            $custom=1) {
+	global $user;
+	if(! in_array('groupAdmin', $user['privileges'])) {
+		return array('status' => 'error',
+		             'errorcode' => 16,
+		             'errormsg' => 'access denied for managing groups');
+	}
+	$validate = array('name' => $name,
+	                  'affiliation' => $affiliation,
+	                  'owner' => $owner,
+	                  'managingGroup' => $managingGroup,
+	                  'initialMaxTime' => $initialMaxTime,
+	                  'totalMaxTime' => $totalMaxTime,
+	                  'maxExtendTime' => $maxExtendTime,
+	                  'custom' => $custom);
+	$rc = validateAPIgroupInput($validate, 0);
+	if($rc['status'] == 'error')
+		return $rc;
+	if($custom != 0 && $custom != 1)
+		$custom = 1;
+	if(! $custom)
+		$rc['managingGroupID'] = NULL;
+	$data = array('type' => 'user',
+	              'owner' => $owner,
+	              'name' => $name,
+	              'affiliationid' => $rc['affiliationid'],
+	              'editgroupid' => $rc['managingGroupID'],
+	              'initialmax' => $initialMaxTime,
+	              'totalmax' => $totalMaxTime,
+	              'maxextend' => $maxExtendTime,
+	              'overlap' => 0,
+	              'custom' => $custom);
+	if(! addGroup($data)) {
+		return array('status' => 'error',
+		             'errorcode' => 26,
+		             'errormsg' => 'failure while adding group to database');
+	}
+	return array('status' => 'success');
+}
+
+////////////////////////////////////////////////////////////////////////////////
 ///
-/// \brief processes a block allocation for the blockTimes entry associated
-/// with blockTimesid\n
+/// \fn XMLRPCeditUserGroup($name, $affiliation, $newName, $newAffiliation,
+///                         $newOwner, $newManagingGroup, $newInitialMaxTime,
+///                         $newTotalMaxTime, $newMaxExtendTime)
+///
+/// \param $name - name of user group
+/// \param $affiliation - affiliation of user group
+/// \param $newName - new name for user group
+/// \param $newAffiliation - new affiliation for user group
+/// \param $newOwner - (optional, default='') user that will be the owner of
+///                    the group in username\@affiliation form
+/// \param $newManagingGroup - (optional, default='') user group that can
+///                            manage membership of this one
+/// \param $newInitialMaxTime - (optional, default='') (minutes) max initial
+///                             time users in this group can select for length
+///                             of reservations
+/// \param $newTotalMaxTime - (optional, default='') (minutes) total length
+///                           users in the group can have for a reservation
+///                           (including all extensions)
+/// \param $newMaxExtendTime - (optional, default='') (minutes) max length of
+///                            time users can request as an extension to a
+///                            reservation at a time
+///
+/// \return an array with at least one index named 'status' which will have
+/// one of these values:\n
+/// \b error - error occurred; there will be 2 additional elements in the array:
+/// \li \b errorcode - error number
+/// \li \b errormsg - error string
+///
+/// \b success - user group was successfully updated
+///
+/// \brief modifies attributes of a user group\n
+/// \b NOTE: an empty string may be passed for any of the new* fields to leave
+/// that item unchanged
 ///
 ////////////////////////////////////////////////////////////////////////////////
-function XMLRPCprocessBlockTime($blockTimesid, $ignoreprivileges=0) {
-	global $requestInfo, $user, $xmlrpcBlockAPIUsers;
-	if(! in_array($user['id'], $xmlrpcBlockAPIUsers)) {
+function XMLRPCeditUserGroup($name, $affiliation, $newName, $newAffiliation,
+                             $newOwner='', $newManagingGroup='',
+                             $newInitialMaxTime='', $newTotalMaxTime='',
+                             $newMaxExtendTime='') {
+	global $user, $mysql_link_vcl;
+	if(! in_array('groupAdmin', $user['privileges'])) {
 		return array('status' => 'error',
-		             'errorcode' => 34,
-		             'errormsg' => 'access denied for managing block allocations');
+		             'errorcode' => 16,
+		             'errormsg' => 'access denied for managing groups');
 	}
-	$return = array('status' => 'success');
-	$query = "SELECT bt.start, "
-	       .        "bt.end, "
-	       .        "br.imageid, "
-	       .        "br.numMachines, "
-	       .        "br.groupid, "
-	       .        "br.expireTime "
-	       . "FROM blockRequest br, "
-	       .      "blockTimes bt "
-	       . "WHERE bt.blockRequestid = br.id AND "
-	       .       "bt.id = $blockTimesid";
+
+	$updates = array();
+
+	# validate group exists and new values other than newName and newAffiliation
+	#   are valid
+	$validate = array('name' => $name,
+	                  'affiliation' => $affiliation);
+	if(get_magic_quotes_gpc())
+		$newOwner = stripslashes($newOwner);
+	if(! empty($newOwner))
+		$validate['owner'] = $newOwner;
+	if(! empty($newManagingGroup))
+		$validate['managingGroup'] = $newManagingGroup;
+	if(! empty($newInitialMaxTime)) {
+		$validate['initialMaxTime'] = $newInitialMaxTime;
+		$updates[] = "initialmaxtime = $newInitialMaxTime";
+	}
+	if(! empty($newTotalMaxTime)) {
+		$validate['totalMaxTime'] = $newTotalMaxTime;
+		$updates[] = "totalmaxtime = $newTotalMaxTime";
+	}
+	if(! empty($newMaxExtendTime)) {
+		$validate['maxExtendTime'] = $newMaxExtendTime;
+		$updates[] = "maxextendtime = $newMaxExtendTime";
+	}
+	$rc = validateAPIgroupInput($validate, 1);
+	if($rc['status'] == 'error')
+		return $rc;
+
+	# get info about group
+	$query = "SELECT ownerid "
+	       .        "affiliationid, "
+	       .        "custom, "
+	       .        "courseroll "
+	       . "FROM usergroup "
+	       . "WHERE id = {$rc['id']}";
 	$qh = doQuery($query, 101);
-	if(! $rqdata = mysql_fetch_assoc($qh)) {
+	if(! $row = mysql_fetch_assoc($qh)) {
 		return array('status' => 'error',
-		             'errorcode' => 8,
-		             'errormsg' => 'unknown blockTimesid');
+		             'errorcode' => 18,
+		             'errormsg' => 'user group with submitted name and affiliation does not exist');
 	}
-	if(datetimeToUnix($rqdata['expireTime']) < time()) {
+	// if custom and not owner or custom/courseroll and no federated user group access, no access to edit group
+	if(($row['custom'] == 1 && $user['id'] != $row['ownerid']) ||
+	   (($row['custom'] == 0 || $row['courseroll'] == 1) &&
+	   ! checkUserHasPerm('Manage Federated User Groups (global)') &&
+	   (! checkUserHasPerm('Manage Federated User Groups (affiliation only)') ||
+	   $row['affiliationid'] != $user['affiliationid']))) {
 		return array('status' => 'error',
-		             'errorcode' => 9,
-		             'errormsg' => 'expired block allocation');
+		             'errorcode' => 32,
+		             'errormsg' => 'access denied to modify attributes for user group with submitted name and affiliation');
 	}
 
-	$images = getImages(0, $rqdata['imageid']);
-	if(empty($images)) {
-		return array('status' => 'error',
-		             'errorcode' => 10,
-		             'errormsg' => 'invalid image associated with block allocation');
-	}
-
-	$unixstart = datetimeToUnix($rqdata['start']);
-	$unixend = datetimeToUnix($rqdata['end']);
-	$revisionid = getProductionRevisionid($rqdata['imageid']);
-	$imgLoadTime = getImageLoadEstimate($rqdata['imageid']);
-	if($imgLoadTime == 0)
-		$imgLoadTime = $images[$rqdata['imageid']]['reloadtime'] * 60;
-	$vclreloadid = getUserlistID('vclreload@Local');
-	$groupmembers = getUserGroupMembers($rqdata['groupid']);
-	$userids = array_keys($groupmembers);
-
-	# add any computers from future reservations users in the group made
-	if(! empty($groupmembers)) {
-		## find reservations by users
-		$allids = implode(',', $userids);
-		$query = "SELECT rq.id AS reqid, "
-		       .        "UNIX_TIMESTAMP(rq.start) AS start, "
-		       .        "rq.userid "
-		       . "FROM request rq, "
-		       .      "reservation rs "
-		       . "WHERE rs.requestid = rq.id AND "
-		       .       "rq.userid IN ($allids) AND "
-		       .       "rq.start < '{$rqdata['end']}' AND "
-		       .       "rq.end > '{$rqdata['start']}' AND "
-		       .       "rs.imageid = {$rqdata['imageid']} AND "
-		       .       "rs.computerid NOT IN (SELECT computerid "
-		       .                             "FROM blockComputers "
-		       .                             "WHERE blockTimeid = $blockTimesid)";
-		$qh = doQuery($query);
-		$donereqids = array();
-		$blockCompVals = array();
-		$checkstartbase = $unixstart - $imgLoadTime - 300;
-		$reloadstartbase = unixToDatetime($checkstartbase);
-		$rows = mysql_num_rows($qh);
-		while($row = mysql_fetch_assoc($qh)) {
-			if(array_key_exists($row['reqid'], $donereqids))
-				continue;
-			$donereqids[$row['reqid']] = 1;
-			if($row['start'] < datetimeToUnix($rqdata['start'])) {
-				$checkstart = $row['start'] - $imgLoadTime - 300;
-				$reloadstart = unixToDatetime($checkstart);
-				$reloadend = unixToDatetime($row['start']);
-			}
-			else {
-				$checkstart = $checkstartbase;
-				$reloadstart = $reloadstartbase;
-				$reloadend = $rqdata['start'];
-			}
-			# check to see if computer is available for whole block
-			$rc = isAvailable($images, $rqdata['imageid'], $revisionid, $checkstart,
-			                  $unixend, $row['reqid'], $row['userid'],
-			                  $ignoreprivileges, 0, '', '', 1);
-			// if not available for whole block, just skip this one
-			if($rc < 1)
-				continue;
-			$compid = $requestInfo['computers'][0];
-			# create reload reservation
-			$reqid = simpleAddRequest($compid, $rqdata['imageid'], $revisionid,
-			                          $reloadstart, $reloadend, 19, $vclreloadid);
-			if($reqid == 0)
-				continue;
-			# add to blockComputers
-			$blockCompVals[] = "($blockTimesid, $compid, {$rqdata['imageid']}, $reqid)";
-			# process any subimages
-			for($key = 1; $key < count($requestInfo['computers']); $key++) {
-				$subimageid = $requestInfo['images'][$key];
-				$subrevid = getProductionRevisionid($subimageid);
-				$compid = $requestInfo['computers'][$key];
-				$mgmtnodeid = $requestInfo['mgmtnodes'][$key];
-				$blockCompVals[] = "($blockTimesid, $compid, $subimageid, $reqid)";
-
-				$query = "INSERT INTO reservation "
-						 .        "(requestid, "
-						 .        "computerid, "
-						 .        "imageid, "
-						 .        "imagerevisionid, "
-						 .        "managementnodeid) "
-						 . "VALUES "
-						 .       "($reqid, "
-						 .       "$compid, "
-						 .       "$subimageid, "
-						 .       "$subrevid, "
-						 .       "$mgmtnodeid)";
-				doQuery($query, 101);
-			}
-		}
-		if(count($blockCompVals)) {
-			$blockComps = implode(',', $blockCompVals);
-			$query = "INSERT INTO blockComputers "
-			       .        "(blockTimeid, computerid, imageid, reloadrequestid) "
-			       . "VALUES $blockComps";
-			doQuery($query);
-		}
-	}
-
-	# check to see if all computers have been allocated
-	$query = "SELECT COUNT(computerid) AS allocated "
-	       . "FROM blockComputers "
-	       . "WHERE blockTimeid = $blockTimesid";
-	$qh = doQuery($query, 101);
-	if(! $row = mysql_fetch_assoc($qh)) {
-		return array('status' => 'error',
-		             'errorcode' => 15,
-		             'errormsg' => 'failure to communicate with database');
-	}
-	$compCompleted = $row['allocated'];
-	if(array_key_exists('subimages', $images[$rqdata['imageid']]))
-		$compsPerAlloc = 1 + count($images[$rqdata['imageid']]['subimages']);
-	else
-		$compsPerAlloc = 1;
-	$toallocate = ($rqdata['numMachines'] * $compsPerAlloc) - $compCompleted;
-	if($toallocate == 0) {
-		if(count($blockCompVals)) {
-			return array('status' => 'success',
-			             'allocated' => $rqdata['numMachines'],
-			             'unallocated' => 0);
+	# validate that newName and newAffiliation are valid
+	if(($name != $newName || $affiliation != $newAffiliation) &&
+	   (! empty($newName) || ! empty($newAffiliation))) {
+		$validate = array('name' => $name,
+		                  'affiliation' => $affiliation);
+		if(! empty($newName)) {
+			if(get_magic_quotes_gpc())
+				$newName = stripslashes($newName);
+			$validate['name'] = $newName;
+			$tmp = mysql_real_escape_string($newName);
+			$updates[] = "name = '$tmp'";
 		}
-		return array('status' => 'completed');
-	}
-	$reqToAlloc = $toallocate / $compsPerAlloc;
-
-	if(! $ignoreprivileges) {
-		# get userids in user group
-		if(empty($groupmembers)) {
-			return array('status' => 'error',
-			             'errorcode' => 11,
-			             'errormsg' => 'empty user group and ignoreprivileges set to 0');
+		if(! empty($newAffiliation))
+			$validate['affiliation'] = $newAffiliation;
+		$rc2 = validateAPIgroupInput($validate, 0);
+		if($rc2['status'] == 'error') {
+			if($rc2['errorcode'] == 27) {
+				$rc2['errorcode'] = 31;
+				$rc2['errormsg'] = 'existing user group with new form of name@affiliation';
+			}
+			return $rc2;
 		}
-		# make length of $userids match $reqToAlloc by duplicating or trimming some users
-		while($reqToAlloc > count($userids))
-			$userids = array_merge($userids, $userids);
-		if($reqToAlloc < count($userids))
-			$userids = array_splice($userids, 0, $reqToAlloc);
-	}
-
-	# staggering: stagger start times for this round (ie, don't worry about
-	#   previous processing of this block time) such that there is 1 minute
-	#   between the start times for each allocation
-	$stagExtra = $reqToAlloc * 60;
-
-	# determine estimated load time
-	$loadtime = $imgLoadTime + (10 * 60); # add 10 minute fudge factor
-	if((time() + $loadtime + $stagExtra) > $unixstart) {
-		$return['status'] = 'warning';
-		$return['warningcode'] = 13;
-		$return['warningmsg'] = 'possibly insufficient time to load machines';
+		if(! empty($newAffiliation))
+			$updates[] = "affiliationid = {$rc2['affiliationid']}";
 	}
-	$start = unixToDatetime($unixstart - $loadtime);
-
-	$userid = 0;
-	$allocated = 0;
-	$blockCompVals = array();
-	# FIXME (maybe) - if some subset of users in the user group have available
-	# computers, but others do not, $allocated will be less than the desired
-	# number of machines; however, calling this function enough times will
-	# result in enough machines being allocated because they will continue to be
-	# allocated based on the ones with machines available; this seems like odd
-	# behavior
-	$stagCnt = 0;
-	$stagTime = 60;        # stagger reload reservations by 1 min
-	if($imgLoadTime > 840) // if estimated load time is > 14 min
-		$stagTime = 120;    #    stagger reload reservations by 2 min 
-	for($i = 0; $i < $reqToAlloc; $i++) {
-		$stagunixstart = $unixstart - $loadtime - ($stagCnt * $stagTime);
-		$stagstart = unixToDatetime($stagunixstart);
-		if(! $ignoreprivileges)
-			$userid = array_pop($userids);
-		# use end of block time to find available computers, but...
-		$rc = isAvailable($images, $rqdata['imageid'], $revisionid, $stagunixstart,
-		                  $unixend, 0, $userid, $ignoreprivileges);
-		if($rc < 1)
-			continue;
-
-		$compid = $requestInfo['computers'][0];
-		# ...use start of block time as end of reload reservation
-		$reqid = simpleAddRequest($compid, $rqdata['imageid'], $revisionid,
-		                          $stagstart, $rqdata['start'], 19, $vclreloadid);
-		if($reqid == 0)
-			continue;
-
-		$stagCnt++;
-		$allocated++;
-		$blockCompVals[] = "($blockTimesid, $compid, {$rqdata['imageid']}, $reqid)";
-
-		# process any subimages
-		for($key = 1; $key < count($requestInfo['computers']); $key++) {
-			$subimageid = $requestInfo['images'][$key];
-			$subrevid = getProductionRevisionid($subimageid);
-			$compid = $requestInfo['computers'][$key];
-			$mgmtnodeid = $requestInfo['mgmtnodes'][$key];
-			$blockCompVals[] = "($blockTimesid, $compid, $subimageid, $reqid)";
 
-			$query = "INSERT INTO reservation "
-					 .        "(requestid, "
-					 .        "computerid, "
-					 .        "imageid, "
-					 .        "imagerevisionid, "
-					 .        "managementnodeid) "
-					 . "VALUES "
-					 .       "($reqid, "
-					 .       "$compid, "
-					 .       "$subimageid, "
-					 .       "$subrevid, "
-					 .       "$mgmtnodeid)";
-			doQuery($query, 101);
+	if($row['custom']) {
+		if(! empty($newOwner)) {
+			$newownerid = getUserlistID(mysql_real_escape_string($newOwner));
+			$updates[] = "ownerid = $newownerid";
 		}
-		semUnlock();
-		$blockComps = implode(',', $blockCompVals);
-		$query = "INSERT INTO blockComputers "
-		       .        "(blockTimeid, computerid, imageid, reloadrequestid) "
-		       . "VALUES $blockComps";
-		doQuery($query, 101);
-		$blockCompVals = array();
+		if(! empty($newManagingGroup))
+			$updates[] = "editusergroupid = {$rc['managingGroupID']}";
 	}
-	if($allocated == 0) {
-		$return['status'] = 'warning';
-		$return['warningcode'] = 14;
-		$return['warningmsg'] = 'unable to allocate any machines';
+	$sets = implode(',', $updates);
+	if(count($updates) == 0) {
+		return array('status' => 'error',
+		             'errorcode' => 33,
+		             'errormsg' => 'no new values submitted');
 	}
-	$return['allocated'] = ($compCompleted / $compsPerAlloc) + $allocated;
-	$return['unallocated'] = $rqdata['numMachines'] - $return['allocated'];
-	return $return;
+	$query = "UPDATE usergroup "
+	       . "SET $sets "
+	       . "WHERE id = {$rc['id']}";
+	doQuery($query, 101);
+	return array('status' => 'success');
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn XMLRPCaddUserGroup($name, $affiliation, $owner, $managingGroup,
-///                        $initialMaxTime, $totalMaxTime, $maxExtendTime,
-///                        $custom)
+/// \fn XMLRPCremoveUserGroup($name, $affiliation)
 ///
 /// \param $name - name of user group
 /// \param $affiliation - affiliation of user group
-/// \param $owner - user that will be the owner of the group in
-///                 username\@affiliation form
-/// \param $managingGroup - user group that can manage membership of this one
-/// \param $initialMaxTime - (minutes) max initial time users in this group can
-///                          select for length of reservations
-/// \param $totalMaxTime - (minutes) total length users in the group can have
-///                        for a reservation (including all extensions)
-/// \param $maxExtendTime - (minutes) max length of time users can request as an
-///                         extension to a reservation at a time
-/// \param $custom (optional, default=1) - set custom flag for user group; if
-///                set to 0, $owner and $managingGroup will be ignored and group
-///                membership will be managed via authentication protocol
 ///
 /// \return an array with at least one index named 'status' which will have
 /// one of these values:\n
 /// \b error - error occurred; there will be 2 additional elements in the array:
 /// \li \b errorcode - error number
 /// \li \b errormsg - error string
 ///
-/// \b success - user group was successfully created
+/// \b success - user group was successfully removed
 ///
-/// \brief creates a new user group with the specified parameters\n
+/// \brief removes a user group along with all of its privileges
 ///
 ////////////////////////////////////////////////////////////////////////////////
-function XMLRPCaddUserGroup($name, $affiliation, $owner, $managingGroup,
-                            $initialMaxTime, $totalMaxTime, $maxExtendTime,
-                            $custom=1) {
-	global $user;
+function XMLRPCremoveUserGroup($name, $affiliation) {
+	global $user, $mysql_link_vcl;
 	if(! in_array('groupAdmin', $user['privileges'])) {
 		return array('status' => 'error',
 		             'errorcode' => 16,
-		             'errormsg' => 'access denied for managing user groups');
+		             'errormsg' => 'access denied for managing groups');
 	}
 	$validate = array('name' => $name,
-	                  'affiliation' => $affiliation,
-	                  'owner' => $owner,
-	                  'managingGroup' => $managingGroup,
-	                  'initialMaxTime' => $initialMaxTime,
-	                  'totalMaxTime' => $totalMaxTime,
-	                  'maxExtendTime' => $maxExtendTime,
-	                  'custom' => $custom);
-	$rc = validateAPIgroupInput($validate, 0);
+	                  'affiliation' => $affiliation);
+	$rc = validateAPIgroupInput($validate, 1);
 	if($rc['status'] == 'error')
 		return $rc;
-	if($custom != 0 && $custom != 1)
-		$custom = 1;
-	$data = array('type' => 'user',
-	              'owner' => $owner,
-	              'name' => $name,
-	              'affiliationid' => $rc['affiliationid'],
-	              'editgroupid' => $rc['managingGroupID'],
-	              'initialmax' => $initialMaxTime,
-	              'totalmax' => $totalMaxTime,
-	              'maxextend' => $maxExtendTime,
-	              'overlap' => 0,
-	              'custom' => $custom);
-	if(! addGroup($data)) {
+	$query = "SELECT ownerid, "
+	       .        "affiliationid, "
+	       .        "custom, "
+	       .        "courseroll "
+	       . "FROM usergroup "
+	       . "WHERE id = {$rc['id']}";
+	$qh = doQuery($query, 101);
+	if(! $row = mysql_fetch_assoc($qh)) {
 		return array('status' => 'error',
-		             'errorcode' => 26,
-		             'errormsg' => 'failure while adding group to database');
+		             'errorcode' => 18,
+		             'errormsg' => 'user group with submitted name and affiliation does not exist');
 	}
-	return array('status' => 'success');
-}
 
-////////////////////////////////////////////////////////////////////////////////
-///
-/// \fn XMLRPCgetResourceGroups($type)
-///
-/// \param $type - the resource group type
-///
-/// \return an array with at least one index named 'status' which will have
-/// one of these values\n
-/// \b error - error occurred; there will be 2 additional elements in the array:
-/// \li \b errorcode - error number\n
-/// \li \b errormsg - error string\n
-///
-/// \b success - an 'groups' element will contain a list of groups
-///         of the given type.\n
-///
-/// \brief get a list of resource groups of a particular type\n
-///
-////////////////////////////////////////////////////////////////////////////////
-function XMLRPCgetResourceGroups($type){
-    global $user;
-    $resources = getUserResources(array("groupAdmin"), array("manageGroup"), 1);
-    if(array_key_exists($type, $resources)){
-        return array(
-            'status' => 'success',
-            'groups' => $resources[$type]);
-    } else {
-        return array(
-            'status' => 'error',
-            'errorcode' => 29,
-            'errormsg' => 'invalid resource group type');
-    }
+	// if custom and not owner or custom/courseroll and no federated user group access, no access to delete group
+	if(($row['custom'] == 1 && $user['id'] != $row['ownerid']) ||
+	   (($row['custom'] == 0 || $row['courseroll'] == 1) &&
+	   ! checkUserHasPerm('Manage Federated User Groups (global)') &&
+	   (! checkUserHasPerm('Manage Federated User Groups (affiliation only)') ||
+	   $row['affiliationid'] != $user['affiliationid']))) {
+		return array('status' => 'error',
+		             'errorcode' => 29,
+		             'errormsg' => 'access denied to delete user group with submitted name and affiliation');
+	}
+	if(checkForGroupUsage($rc['id'], 'user')) {
+		return array('status' => 'error',
+		             'errorcode' => 72,
+		             'errormsg' => 'group currently in use and cannot be removed');
+	}
+	$query = "DELETE FROM usergroup "
+	       . "WHERE id = {$rc['id']}";
+	doQuery($query, 101);
+	# validate something deleted
+	if(mysql_affected_rows($mysql_link_vcl) == 0) {
+		return array('status' => 'error',
+		             'errorcode' => 30,
+		             'errormsg' => 'failure while deleting group from database');
+	}
+	return array('status' => 'success');
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn XMLRPCremoveResourceGroup($name, $type)
+/// \fn XMLRPCdeleteUserGroup($name, $affiliation)
 ///
-/// \param $name - the name of the resource group
-/// \param $type - the resource group type
+/// \param $name - name of user group
+/// \param $affiliation - affiliation of user group
 ///
 /// \return an array with at least one index named 'status' which will have
-/// one of these values\n
+/// one of these values:\n
 /// \b error - error occurred; there will be 2 additional elements in the array:
-/// \li \b errorcode - error number\n
-/// \li \b errormsg - error string\n
-///
-/// \b success - the resource group was removed\n
-///
-/// \brief remove a resource group\n
-///
-////////////////////////////////////////////////////////////////////////////////
-function XMLRPCremoveResourceGroup($name, $type){
-    global $user;
-    if(! in_array("groupAdmin", $user['privileges'])){
-        return array('status' => 'error',
-                     'errorcode' => 16,
-                     'errormsg' => 'access denied for managing user groups');
-    }
-
-    if($groupid = getResourceGroupID("$type/$name")){
-        $userresources = getUserResources(
-            array("groupAdmin"),
-            array("manageGroup"), 1);
-        if(array_key_exists($type, $userresources)){
-            if(array_key_exists($groupid, $userresources[$type])){
-                $query = "DELETE FROM resourcegroup "
-                       . "WHERE id = $groupid";
-                doQuery($query, 315);
-                return array('status' => 'success');
-            }
-        }
-    }
-    return array('status' => 'error',
-                 'errorcode' => 39,
-                 'errormsg' => 'invalid resource group name');
-}
-
-////////////////////////////////////////////////////////////////////////////////
-///
-/// \fn XMLRPCremoveUserGroup($name, $affiliation)
+/// \li \b errorcode - error number
+/// \li \b errormsg - error string
 ///
-/// \param $name - the name of the resource group
-/// \param $affiliation - the affiliation of the user group
+/// \b success - user group was successfully removed
 ///
-/// \return an array with at least one index named 'status' which will have
-/// one of these values\n
-/// \b error - error occurred; there will be 2 additional elements in the array:
-/// \li \b errorcode - error number\n
-/// \li \b errormsg - error string\n
+/// \brief alias for XMLRPCremoveUserGroup
 ///
-/// \b success - the user group was removed\n
-///
-/// \brief remove a user group\n
-///
-////////////////////////////////////////////////////////////////////////////////
-function XMLRPCremoveUserGroup($name, $affiliation){
-    global $user;
-
-    if(! in_array("groupAdmin", $user['privileges'])){
-        return array('status' => 'error',
-                     'errorcode' => 16,
-                     'errormsg' => 'access denied for managing user groups');
-    }
-
-    $validate = array(
-        'name' => $name,
-        'affiliation' => $affiliation);
-
-    $rc = validateAPIgroupInput($validate, 1);
-    if($rc['status'] == 'error')
-        return $rc;
-
-    $groups = getUserGroups();
-    $groupid = $rc['id'];
-    if(array_key_exists($groupid, $groups)){
-        $group = $groups[$groupid];
-        if($group['ownerid'] == $user['id'] ||
-                (array_key_exists("editgroupid", $group) &&
-                array_key_exists($group['editgroupid'], $user["groups"])) || 
-                (array_key_exists($groupid, $user["groups"]))){
-            $query = "DELETE FROM usergroup "
-                   . "WHERE id = $groupid";
-            doQuery($query, 315);
-            return array('status' => 'success');
-        }
-    }
-    return array(
-        'status' => 'error',
-        'errorcode' => 17,
-        'errormsg' => 'access denied for editing specified group');
-}
-
 ////////////////////////////////////////////////////////////////////////////////
-///
-/// \fn XMLRPCaddResourceGroup($name, $managingGroup, $type)
-///
-/// \param $name - the name of the resource group
-/// \param $managingGroup - the name of the managing group
-/// \param $type - the type of resource group
-///
-/// \return an array with at least one index named 'status' which will have
-/// one of these values\n
-/// \b error - error occurred; there will be 2 additional elements in the array:
-/// \li \b errorcode - error number\n
-/// \li \b errormsg - error string\n
-///
-/// \b success - the resource group was added\n
-///
-/// \brief add a resource group\n
-///
-////////////////////////////////////////////////////////////////////////////////
-function XMLRPCaddResourceGroup($name, $managingGroup, $type){
-    global $user;
-    if(! in_array("groupAdmin", $user['privileges'])){
-        return array('status' => 'error',
-                     'errorcode' => 16,
-                     'errormsg' => 'access denied for managing user groups');
-    }
-
-    $validate = array(
-        'managingGroup' => $managingGroup);
-    
-    $rc = validateAPIgroupInput($validate, 0);
-    if($rc['status'] == 'error')
-        return $rc;
-
-    if($typeid = getResourceTypeID($type)){
-        if(checkForGroupName($name, 'resource', '', $typeid)){
-            return array('status' => 'error',
-                         'errorcode' => 28,
-                         'errormsg' => 'resource group already exists');
-        }
-        $data = array(
-            'type' => $type,
-            'ownergroup' => $rc['managingGroupID'],
-            'resourcetypeid' => $typeid,
-            'name' => $name);
-        if(! addGroup($data)){
-            return array('status' => 'error',
-                         'errorcode' => 26,
-                         'errormsg' => 'failure while adding group to database');
-        }
-    } else {
-        return array('status' => 'error',
-                     'errorcode' => 68,
-                     'errormsg' => 'invalid resource type');
-    }
-    return array('status' => 'success');
+function XMLRPCdeleteUserGroup($name, $affiliation) {
+	# This was the original function. All other functions use 'remove' rather
+	# than 'delete'. The function was renamed to XMLRPCremoveUserGroup. This was
+	# kept for compatibility reasons
+	return XMLRPCremoveUserGroup($name, $affiliation);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn XMLRPCgetUserGroupAttributes($name, $affiliation)
+/// \fn XMLRPCgetUserGroupMembers($name, $affiliation)
 ///
 /// \param $name - name of user group
 /// \param $affiliation - affiliation of user group
 ///
 /// \return an array with at least one index named 'status' which will have
 /// one of these values:\n
 /// \b error - error occurred; there will be 2 additional elements in the array:
 /// \li \b errorcode - error number
 /// \li \b errormsg - error string
 ///
-/// \b success - there will be five additional elements in this case:
-/// \li \b owner - user that will be the owner of the group in
-///                username\@affiliation form
-/// \li \b managingGroup - user group that can manage membership of this one in
-///                        groupname\@affiliation form
-/// \li \b initialMaxTime - (minutes) max initial time users in this group can
-///                         select for length of reservations
-/// \li \b totalMaxTime - (minutes) total length users in the group can have for
-///                       a reservation (including all extensions)
-/// \li \b maxExtendTime - (minutes) max length of time users can request as an
-///                        extension to a reservation at a time
+/// \b success - there will be one additional element in this case:
+/// \li \b members - array of members of the group in username\@affiliation form
 ///
-/// \brief gets information about a user group\n
+/// \brief gets members of a user group\n
+/// \b NOTE: it is possible to have a group with no members in which case
+/// success will be returned with an empty array for members
 ///
 ////////////////////////////////////////////////////////////////////////////////
-function XMLRPCgetUserGroupAttributes($name, $affiliation) {
+function XMLRPCgetUserGroupMembers($name, $affiliation) {
 	global $user;
 	if(! in_array('groupAdmin', $user['privileges'])) {
 		return array('status' => 'error',
 		             'errorcode' => 16,
-		             'errormsg' => 'access denied for managing user groups');
+		             'errormsg' => 'access denied for managing groups');
 	}
 	$validate = array('name' => $name,
 	                  'affiliation' => $affiliation);
 	$rc = validateAPIgroupInput($validate, 1);
 	if($rc['status'] == 'error')
 		return $rc;
-	$query = "SELECT ug.id, "
-	       .        "ug.ownerid, "
-	       .        "CONCAT(u.unityid, '@', a.name) AS owner, "
-	       .        "ug.editusergroupid AS editgroupid, "
-	       .        "eug.name AS editgroup, "
-	       .        "eug.affiliationid AS editgroupaffiliationid, "
-	       .        "euga.name AS editgroupaffiliation, "
-	       .        "ug.initialmaxtime, "
-	       .        "ug.totalmaxtime, "
-	       .        "ug.maxextendtime, "
-	       .        "ug.overlapResCount "
-	       . "FROM usergroup ug "
-	       . "LEFT JOIN user u ON (ug.ownerid = u.id) "
-	       . "LEFT JOIN affiliation a ON (u.affiliationid = a.id) "
-	       . "LEFT JOIN usergroup eug ON (ug.editusergroupid = eug.id) "
-	       . "LEFT JOIN affiliation euga ON (eug.affiliationid = euga.id) "
-	       . "WHERE ug.id = {$rc['id']}";
+	$query = "SELECT ownerid, "
+	       .        "editusergroupid AS editgroupid, "
+	       .        "affiliationid, "
+	       .        "custom, "
+	       .        "courseroll "
+	       . "FROM usergroup "
+	       . "WHERE id = {$rc['id']}";
 	$qh = doQuery($query, 101);
 	if(! $row = mysql_fetch_assoc($qh)) {
 		return array('status' => 'error',
 		             'errorcode' => 18,
 		             'errormsg' => 'user group with submitted name and affiliation does not exist');
 	}
-	# if not owner and not member of managing group, no access
-	if($user['id'] != $row['ownerid'] && 
-	   ! array_key_exists($row['editgroupid'], $user['groups'])) {
+	// if custom and not owner and not member of managing group or 
+	//    custom/courseroll and no federated user group access, no access to delete group
+	if(($row['custom'] == 1 && $user['id'] != $row['ownerid'] &&
+	   ! array_key_exists($row['editgroupid'], $user['groups'])) ||
+	   (($row['custom'] == 0 || $row['courseroll'] == 1) &&
+	   ! checkUserHasPerm('Manage Federated User Groups (global)') &&
+	   (! checkUserHasPerm('Manage Federated User Groups (affiliation only)') ||
+	   $row['affiliationid'] != $user['affiliationid']))) {
 		return array('status' => 'error',
 		             'errorcode' => 28,
 		             'errormsg' => 'access denied to user group with submitted name and affiliation');
 	}
+	$query = "SELECT CONCAT(u.unityid, '@', a.name) AS member "
+	       . "FROM usergroupmembers ugm, "
+	       .      "user u, "
+	       .      "affiliation a "
+	       . "WHERE ugm.usergroupid = {$rc['id']} AND "
+	       .       "ugm.userid = u.id AND "
+	       .       "u.affiliationid = a.id";
+	$qh = doQuery($query, 101);
+	$members = array();
+	while($row = mysql_fetch_assoc($qh))
+		$members[] = $row['member'];
 	return array('status' => 'success',
-	             'owner' => $row['owner'],
-	             'managingGroup' => "{$row['editgroup']}@{$row['editgroupaffiliation']}",
-	             'initialMaxTime' => $row['initialmaxtime'],
-	             'totalMaxTime' => $row['totalmaxtime'],
-	             'maxExtendTime' => $row['maxextendtime']);
+	             'members' => $members);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn XMLRPCdeleteUserGroup($name, $affiliation)
+/// \fn XMLRPCaddUsersToGroup($name, $affiliation, $users)
 ///
 /// \param $name - name of user group
 /// \param $affiliation - affiliation of user group
+/// \param $users - array of users in username\@affiliation form to be added to
+///                 the group
 ///
 /// \return an array with at least one index named 'status' which will have
 /// one of these values:\n
 /// \b error - error occurred; there will be 2 additional elements in the array:
 /// \li \b errorcode - error number
 /// \li \b errormsg - error string
 ///
-/// \b success - user group was successfully deleted
+/// \b success - users successfully added to the group\n
+/// \b warning - there was a non-fatal issue that occurred while processing
+/// the call; there will be three additional elements in this case:
+/// \li \b warningcode - warning number
+/// \li \b warningmsg - warning string
+/// \li \b failedusers - array of users in username\@affiliation form that could
+///                      not be added
 ///
-/// \brief deletes a user group along with all of its privileges\n
+/// \brief adds users to a group
 ///
 ////////////////////////////////////////////////////////////////////////////////
-function XMLRPCdeleteUserGroup($name, $affiliation) {
-	global $user, $mysql_link_vcl;
+function XMLRPCaddUsersToGroup($name, $affiliation, $users) {
+	global $user;
 	if(! in_array('groupAdmin', $user['privileges'])) {
 		return array('status' => 'error',
 		             'errorcode' => 16,
-		             'errormsg' => 'access denied for managing user groups');
+		             'errormsg' => 'access denied for managing groups');
 	}
 	$validate = array('name' => $name,
 	                  'affiliation' => $affiliation);
 	$rc = validateAPIgroupInput($validate, 1);
 	if($rc['status'] == 'error')
 		return $rc;
-	$query = "SELECT ownerid "
+	$query = "SELECT ownerid, "
+	       .        "editusergroupid AS editgroupid "
 	       . "FROM usergroup "
 	       . "WHERE id = {$rc['id']}";
 	$qh = doQuery($query, 101);
 	if(! $row = mysql_fetch_assoc($qh)) {
 		return array('status' => 'error',
 		             'errorcode' => 18,
 		             'errormsg' => 'user group with submitted name and affiliation does not exist');
 	}
-	# if not owner no access to delete group
-	if($user['id'] != $row['ownerid']) {
+	// if not owner and not member of managing group, no access
+	if($user['id'] != $row['ownerid'] && 
+	   ! array_key_exists($row['editgroupid'], $user['groups'])) {
 		return array('status' => 'error',
-		             'errorcode' => 29,
-		             'errormsg' => 'access denied to delete user group with submitted name and affiliation');
+		             'errorcode' => 28,
+		             'errormsg' => 'access denied to user group with submitted name and affiliation');
 	}
-	$query = "DELETE FROM usergroup "
-			 . "WHERE id = {$rc['id']}";
-	doQuery($query, 101);
-	# validate something deleted
-	if(mysql_affected_rows($mysql_link_vcl) == 0) {
-		return array('status' => 'error',
-		             'errorcode' => 30,
-		             'errormsg' => 'failure while deleting group from database');
+	$fails = array();
+	foreach($users as $_user) {
+		if(empty($_user))
+			continue;
+		if(get_magic_quotes_gpc())
+			$_user = stripslashes($_user);
+		$esc_user = mysql_real_escape_string($_user);
+		if(validateUserid($_user) == 1)
+			addUserGroupMember($esc_user, $rc['id']);
+		else
+			$fails[] = $_user;
+	}
+	if(count($fails)) {
+		$cnt = 'some';
+		$code = 34;
+		if(count($fails) == count($users)) {
+			$cnt = 'all submitted';
+			$code = 35;
+		}
+		return array('status' => 'warning',
+		             'failedusers' => $fails,
+		             'warningcode' => $code,
+		             'warningmsg' => "failed to add $cnt users to user group");
 	}
 	return array('status' => 'success');
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn XMLRPCeditUserGroup($name, $affiliation, $newName, $newAffiliation,
-///                         $newOwner, $newManagingGroup, $newInitialMaxTime,
-///                         $newTotalMaxTime, $newMaxExtendTime)
+/// \fn XMLRPCremoveUsersFromGroup($name, $affiliation, $users)
 ///
 /// \param $name - name of user group
 /// \param $affiliation - affiliation of user group
-/// \param $newName - new name for user group
-/// \param $newAffiliation - new affiliation for user group
-/// \param $newOwner - (optional, default='') user that will be the owner of
-///                    the group in username\@affiliation form
-/// \param $newManagingGroup - (optional, default='') user group that can
-///                            manage membership of this one
-/// \param $newInitialMaxTime - (optional, default='') (minutes) max initial
-///                             time users in this group can select for length
-///                             of reservations
-/// \param $newTotalMaxTime - (optional, default='') (minutes) total length
-///                           users in the group can have for a reservation
-///                           (including all extensions)
-/// \param $newMaxExtendTime - (optional, default='') (minutes) max length of
-///                            time users can request as an extension to a
-///                            reservation at a time
+/// \param $users - array of users in username\@affiliation form to be removed
+///                 from the group
 ///
 /// \return an array with at least one index named 'status' which will have
 /// one of these values:\n
 /// \b error - error occurred; there will be 2 additional elements in the array:
 /// \li \b errorcode - error number
 /// \li \b errormsg - error string
 ///
-/// \b success - user group was successfully updated
+/// \b success - users successfully removed from the group\n
+/// \b warning - there was a non-fatal issue that occurred while processing
+/// the call; there will be three additional elements in this case:
+/// \li \b warningcode - warning number
+/// \li \b warningmsg - warning string
+/// \li \b failedusers - array of users in username\@affiliation form that could
+///                      not be removed
 ///
-/// \brief modifies attributes of a user group\n
-/// \b NOTE: an empty string may be passed for any of the new* fields to leave
-/// that item unchanged\n
+/// \brief removes users from a group
 ///
 ////////////////////////////////////////////////////////////////////////////////
-function XMLRPCeditUserGroup($name, $affiliation, $newName, $newAffiliation,
-                             $newOwner='', $newManagingGroup='',
-                             $newInitialMaxTime='', $newTotalMaxTime='',
-                             $newMaxExtendTime='') {
-	global $user, $mysql_link_vcl;
+function XMLRPCremoveUsersFromGroup($name, $affiliation, $users) {
+	global $user, $findAffilFuncs;
 	if(! in_array('groupAdmin', $user['privileges'])) {
 		return array('status' => 'error',
 		             'errorcode' => 16,
-		             'errormsg' => 'access denied for managing user groups');
+		             'errormsg' => 'access denied for managing groups');
 	}
-
-	$updates = array();
-
-	# validate group exists and new values other than newName and newAffiliation
-	#   are valid
 	$validate = array('name' => $name,
 	                  'affiliation' => $affiliation);
-	if(! empty($newOwner))
-		$validate['owner'] = $newOwner;
-	if(! empty($newManagingGroup))
-		$validate['managingGroup'] = $newManagingGroup;
-	if(! empty($newInitialMaxTime)) {
-		$validate['initialMaxTime'] = $newInitialMaxTime;
-		$updates[] = "initialmaxtime = $newInitialMaxTime";
-	}
-	if(! empty($newTotalMaxTime)) {
-		$validate['totalMaxTime'] = $newTotalMaxTime;
-		$updates[] = "totalmaxtime = $newTotalMaxTime";
-	}
-	if(! empty($newMaxExtendTime)) {
-		$validate['maxExtendTime'] = $newMaxExtendTime;
-		$updates[] = "maxextendtime = $newMaxExtendTime";
-	}
 	$rc = validateAPIgroupInput($validate, 1);
 	if($rc['status'] == 'error')
 		return $rc;
-
-	# get info about group
-	$query = "SELECT ownerid "
+	$query = "SELECT ownerid, "
+	       .        "editusergroupid AS editgroupid "
 	       . "FROM usergroup "
 	       . "WHERE id = {$rc['id']}";
 	$qh = doQuery($query, 101);
 	if(! $row = mysql_fetch_assoc($qh)) {
 		return array('status' => 'error',
 		             'errorcode' => 18,
 		             'errormsg' => 'user group with submitted name and affiliation does not exist');
 	}
-	# if not owner no access to edit group attributes
-	if($user['id'] != $row['ownerid']) {
+	// if not owner and not member of managing group, no access
+	if($user['id'] != $row['ownerid'] && 
+	   ! array_key_exists($row['editgroupid'], $user['groups'])) {
 		return array('status' => 'error',
-		             'errorcode' => 32,
-		             'errormsg' => 'access denied to modify attributes for user group with submitted name and affiliation');
+		             'errorcode' => 28,
+		             'errormsg' => 'access denied to user group with submitted name and affiliation');
 	}
-
-	# validate that newName and newAffiliation are valid
-	if(! empty($newName) || ! empty($newAffiliation)) {
-		$validate = array('name' => $name,
-		                  'affiliation' => $affiliation);
-		if(! empty($newName)) {
-			$validate['name'] = $newName;
-			$tmp = mysql_real_escape_string($newName);
-			$updates[] = "name = '$tmp'";
+	$fails = array();
+	foreach($users as $_user) {
+		if(empty($_user))
+			continue;
+		if(get_magic_quotes_gpc())
+			$_user = stripslashes($_user);
+		$esc_user = mysql_real_escape_string($_user);
+		# check that affiliation of user can be determined because getUserlistID
+		#   will abort if it cannot find it
+		$affilok = 0;
+		foreach($findAffilFuncs as $func) {
+			if($func($_user, $dump))
+				$affilok = 1;
 		}
-		if(! empty($newAffiliation))
-			$validate['affiliation'] = $newAffiliation;
-		$rc2 = validateAPIgroupInput($validate, 0);
-		if($rc2['status'] == 'error') {
-			if($rc2['errorcode'] == 27) {
-				$rc2['errorcode'] = 31;
-				$rc2['errormsg'] = 'existing user group with new form of name@affiliation';
-			}
-			return $rc2;
+		if(! $affilok) {
+			$fails[] = $_user;
+			continue;
 		}
-		if(! empty($newAffiliation))
-			$updates[] = "affiliationid = {$rc2['affiliationid']}";
-	}
-
-	if(! empty($newOwner)) {
-		$newownerid = getUserlistID(mysql_real_escape_string($newOwner));
-		$updates[] = "ownerid = $newownerid";
-	}
-	if(! empty($newManagingGroup)) {
-		$updates[] = "editusergroupid = {$rc['managingGroupID']}";
+		$userid = getUserlistID($esc_user, 1);
+		if(is_null($userid))
+			$fails[] = $_user;
+		else
+			deleteUserGroupMember($userid, $rc['id']);
 	}
-	$sets = implode(',', $updates);
-	if(count($updates) == 0) {
-		return array('status' => 'error',
-		             'errorcode' => 33,
-		             'errormsg' => 'no new values submitted');
+	if(count($fails)) {
+		$cnt = 'some';
+		$code = 36;
+		if(count($fails) == count($users)) {
+			$cnt = 'any';
+			$code = 37;
+		}
+		return array('status' => 'warning',
+		             'failedusers' => $fails,
+		             'warningcode' => $code,
+		             'warningmsg' => "failed to remove $cnt users from user group");
 	}
-	$query = "UPDATE usergroup "
-	       . "SET $sets "
-	       . "WHERE id = {$rc['id']}";
-	doQuery($query, 101);
 	return array('status' => 'success');
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn XMLRPCgetUserGroupMembers($name, $affiliation)
+/// \fn XMLRPCgetResourceGroups($type)
 ///
-/// \param $name - name of user group
-/// \param $affiliation - affiliation of user group
+/// \param $type - the resource group type
 ///
 /// \return an array with at least one index named 'status' which will have
-/// one of these values:\n
+/// one of these values\n
 /// \b error - error occurred; there will be 2 additional elements in the array:
-/// \li \b errorcode - error number
-/// \li \b errormsg - error string
+/// \li \b errorcode - error number\n
+/// \li \b errormsg - error string\n
 ///
-/// \b success - there will be one additional element in this case:
-/// \li \b members - array of members of the group in username\@affiliation form
+/// \b success - a 'groups' element will contain an array of groups of the given
+/// type\n
 ///
-/// \brief gets members of a user group\n
-/// \b Note: it is possible to have a group with no members in which case
-/// success will be returned with an empty array for members\n
+/// \brief get a list of resource groups of a particular type
 ///
 ////////////////////////////////////////////////////////////////////////////////
-function XMLRPCgetUserGroupMembers($name, $affiliation) {
+function XMLRPCgetResourceGroups($type) {
 	global $user;
-	if(! in_array('groupAdmin', $user['privileges'])) {
-		return array('status' => 'error',
-		             'errorcode' => 16,
-		             'errormsg' => 'access denied for managing user groups');
-	}
-	$validate = array('name' => $name,
-	                  'affiliation' => $affiliation);
-	$rc = validateAPIgroupInput($validate, 1);
-	if($rc['status'] == 'error')
-		return $rc;
-	$query = "SELECT ownerid, "
-	       .        "editusergroupid AS editgroupid "
-	       . "FROM usergroup "
-	       . "WHERE id = {$rc['id']}";
-	$qh = doQuery($query, 101);
-	if(! $row = mysql_fetch_assoc($qh)) {
-		return array('status' => 'error',
-		             'errorcode' => 18,
-		             'errormsg' => 'user group with submitted name and affiliation does not exist');
+	$resources = getUserResources(array("groupAdmin"), array("manageGroup"), 1);
+	if(array_key_exists($type, $resources)) {
+		return array('status' => 'success',
+		             'groups' => $resources[$type]);
 	}
-	# if not owner and not member of managing group, no access
-	if($user['id'] != $row['ownerid'] && 
-	   ! array_key_exists($row['editgroupid'], $user['groups'])) {
+	else {
 		return array('status' => 'error',
-		             'errorcode' => 28,
-		             'errormsg' => 'access denied to user group with submitted name and affiliation');
+		             'errorcode' => 73,
+		             'errormsg' => 'invalid resource group type');
 	}
-	$query = "SELECT CONCAT(u.unityid, '@', a.name) AS member "
-	       . "FROM usergroupmembers ugm, "
-	       .      "user u, "
-	       .      "affiliation a "
-	       . "WHERE ugm.usergroupid = {$rc['id']} AND "
-	       .       "ugm.userid = u.id AND "
-	       .       "u.affiliationid = a.id";
-	$qh = doQuery($query, 101);
-	$members = array();
-	while($row = mysql_fetch_assoc($qh))
-		$members[] = $row['member'];
-	return array('status' => 'success',
-	             'members' => $members);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn XMLRPCaddUsersToGroup($name, $affiliation, $users)
+/// \fn XMLRPCaddResourceGroup($name, $managingGroup, $type)
 ///
-/// \param $name - name of user group
-/// \param $affiliation - affiliation of user group
-/// \param $users - array of users in username\@affiliation form to be added to
-///                 the group
+/// \param $name - the name of the resource group
+/// \param $managingGroup - the name of the managing group
+/// \param $type - the type of resource group
 ///
 /// \return an array with at least one index named 'status' which will have
-/// one of these values:\n
+/// one of these values\n
 /// \b error - error occurred; there will be 2 additional elements in the array:
-/// \li \b errorcode - error number
-/// \li \b errormsg - error string
-///
-/// \b success - users successfully added to the group
+/// \li \b errorcode - error number\n
+/// \li \b errormsg - error string\n
 ///
-/// \b warning - there was a non-fatal issue that occurred while processing
-/// the call; there will be three additional elements in this case:
-/// \li \b warningcode - warning number
-/// \li \b warningmsg - warning string
-/// \li \b failedusers - array of users in username\@affiliation form that could
-///                      not be added
+/// \b success - the resource group was added
 ///
-/// \brief adds users to a group\n
+/// \brief add a resource group
 ///
 ////////////////////////////////////////////////////////////////////////////////
-function XMLRPCaddUsersToGroup($name, $affiliation, $users) {
+function XMLRPCaddResourceGroup($name, $managingGroup, $type) {
 	global $user;
-	if(! in_array('groupAdmin', $user['privileges'])) {
+	if(! in_array("groupAdmin", $user['privileges'])) {
 		return array('status' => 'error',
 		             'errorcode' => 16,
-		             'errormsg' => 'access denied for managing user groups');
+		             'errormsg' => 'access denied for managing groups');
 	}
-	$validate = array('name' => $name,
-	                  'affiliation' => $affiliation);
-	$rc = validateAPIgroupInput($validate, 1);
+
+	$validate = array('managingGroup' => $managingGroup);
+
+	$rc = validateAPIgroupInput($validate, 0);
 	if($rc['status'] == 'error')
 		return $rc;
-	$query = "SELECT ownerid, "
-	       .        "editusergroupid AS editgroupid "
-	       . "FROM usergroup "
-	       . "WHERE id = {$rc['id']}";
-	$qh = doQuery($query, 101);
-	if(! $row = mysql_fetch_assoc($qh)) {
-		return array('status' => 'error',
-		             'errorcode' => 18,
-		             'errormsg' => 'user group with submitted name and affiliation does not exist');
+
+	if($typeid = getResourceTypeID($type)) {
+		if(checkForGroupName($name, 'resource', '', $typeid)) {
+			return array('status' => 'error',
+			             'errorcode' => 76,
+			             'errormsg' => 'resource group already exists');
+		}
+		if(get_magic_quotes_gpc())
+			$name = stripslashes($name);
+		if(! preg_match('/^[-a-zA-Z0-9_\. ]{3,30}$/', $name)) {
+			return array('status' => 'error',
+			             'errorcode' => 87,
+			             'errormsg' => 'Name must be between 3 and 30 characters and can only contain letters, numbers, spaces, and these characters: - . _');
+		}
+		$name = mysql_real_escape_string($name);
+		$data = array('type' => 'resource',
+		              'ownergroup' => $rc['managingGroupID'],
+		              'resourcetypeid' => $typeid,
+		              'name' => $name);
+		if(! addGroup($data)) {
+			return array('status' => 'error',
+			             'errorcode' => 26,
+			             'errormsg' => 'failure while adding group to database');
+		}
 	}
-	# if not owner and not member of managing group, no access
-	if($user['id'] != $row['ownerid'] && 
-	   ! array_key_exists($row['editgroupid'], $user['groups'])) {
+	else {
 		return array('status' => 'error',
-		             'errorcode' => 28,
-		             'errormsg' => 'access denied to user group with submitted name and affiliation');
-	}
-	$fails = array();
-	foreach($users as $_user) {
-		if(empty($_user))
-			continue;
-		$esc_user = mysql_real_escape_string($_user);
-		if(validateUserid($esc_user) == 1)
-			addUserGroupMember($esc_user, $rc['id']);
-		else
-			$fails[] = $_user;
-	}
-	if(count($fails)) {
-		$cnt = 'some';
-		$code = 34;
-		if(count($fails) == count($users)) {
-			$cnt = 'all submitted';
-			$code = 35;
-		}
-		return array('status' => 'warning',
-		             'failedusers' => $fails,
-		             'warningcode' => $code,
-		             'warningmsg' => "failed to add $cnt users to user group");
+		             'errorcode' => 68,
+		             'errormsg' => 'invalid resource type');
 	}
 	return array('status' => 'success');
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn XMLRPCremoveUsersFromGroup($name, $affiliation, $users)
+/// \fn XMLRPCremoveResourceGroup($name, $type)
 ///
-/// \param $name - name of user group
-/// \param $affiliation - affiliation of user group
-/// \param $users - array of users in username\@affiliation form to be removed
-///                 from the group
+/// \param $name - the name of the resource group
+/// \param $type - the resource group type
 ///
 /// \return an array with at least one index named 'status' which will have
-/// one of these values:\n
+/// one of these values\n
 /// \b error - error occurred; there will be 2 additional elements in the array:
+/// \li \b errorcode - error number\n
+/// \li \b errormsg - error string\n
+///
+/// \b success - the resource group was removed\n
+///
+/// \brief remove a resource group
+///
+////////////////////////////////////////////////////////////////////////////////
+function XMLRPCremoveResourceGroup($name, $type) {
+	global $user;
+	if(! in_array("groupAdmin", $user['privileges'])) {
+		return array('status' => 'error',
+		             'errorcode' => 16,
+		             'errormsg' => 'access denied for managing groups');
+	}
+
+	if($groupid = getResourceGroupID("$type/$name")) {
+		$userresources = getUserResources(array("groupAdmin"),
+		                                  array("manageGroup"), 1);
+		if(array_key_exists($type, $userresources)) {
+			if(array_key_exists($groupid, $userresources[$type])) {
+				if(checkForGroupUsage($groupid, 'resource')) {
+					return array('status' => 'error',
+					             'errorcode' => 72,
+					             'errormsg' => 'group currently in use and cannot be removed');
+				}
+				$query = "DELETE FROM resourcegroup "
+				       . "WHERE id = $groupid";
+				doQuery($query, 315);
+				return array('status' => 'success');
+			}
+			else
+				return array('status' => 'error',
+				             'errorcode' => 75,
+				             'errormsg' => 'access denied to specified resource group');
+		}
+	}
+	return array('status' => 'error',
+	             'errorcode' => 83,
+	             'errormsg' => 'invalid resource group name');
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \fn XMLRPCblockAllocation($imageid, $start, $end, $numMachines,
+///                           $usergroupid, $ignoreprivileges)
+///
+/// \param $imageid - id of the image to be used
+/// \param $start - mysql datetime for the start time (i.e. machines should be
+/// prep'd and ready by this time)
+/// \param $end - mysql datetime for the end time
+/// \param $numMachines - number of computers to allocate
+/// \param $usergroupid - id of user group for checking user access to machines
+/// \param $ignoreprivileges  - (optional, default=0) 0 (false) or 1 (true) - set
+/// to 1 to select computers from any that are mapped to be able to run the
+/// image; set to 0 to only select computers from ones that are both mapped and
+/// that users in the usergroup assigned to this block allocation have been
+/// granted access to through the privilege tree
+///
+/// \return an array with blockTimesid as an index with the value of the newly
+/// created block time and at least one other index named 'status' which will
+/// have one of these values:\n
+/// \b error - error occurred; there will be 2 additional elements in the
+/// array:
 /// \li \b errorcode - error number
 /// \li \b errormsg - error string
 ///
-/// \b success - users successfully removed from the group
+/// \b success - blockTimesid was processed; there will be two additional
+/// elements in this case:
+/// \li \b allocated - total number of desired allocations that have been
+/// processed
+/// \li \b unallocated - total number of desired allocations that have not been
+/// processed
 ///
 /// \b warning - there was a non-fatal issue that occurred while processing
-/// the call; there will be three additional elements in this case:
+/// the call; there will be four additional elements in this case:
 /// \li \b warningcode - warning number
 /// \li \b warningmsg - warning string
-/// \li \b failedusers - array of users in username\@affiliation form that could
-///                      not be removed
+/// \li \b allocated - total number of desired allocations that have been
+/// processed
+/// \li \b unallocated - total number of desired allocations that have not been
+/// processed\n\n
 ///
-/// \brief removes users from a group\n
+/// \b NOTE: status may be warning, but allocated may be 0 indicating there
+/// were no errors that occurred, but there simply were not any machines
+/// available
 ///
-////////////////////////////////////////////////////////////////////////////////
-function XMLRPCremoveUsersFromGroup($name, $affiliation, $users) {
-	global $user, $findAffilFuncs;
-	if(! in_array('groupAdmin', $user['privileges'])) {
+/// \brief creates and processes a block allocation according to the passed
+/// in criteria
+///
+////////////////////////////////////////////////////////////////////////////////
+function XMLRPCblockAllocation($imageid, $start, $end, $numMachines,
+                               $usergroupid, $ignoreprivileges=0) {
+	global $user, $xmlrpcBlockAPIUsers;
+	if(! in_array($user['id'], $xmlrpcBlockAPIUsers)) {
 		return array('status' => 'error',
-		             'errorcode' => 16,
-		             'errormsg' => 'access denied for managing user groups');
+		             'errorcode' => 34,
+		             'errormsg' => 'access denied for managing block allocations');
 	}
-	$validate = array('name' => $name,
-	                  'affiliation' => $affiliation);
-	$rc = validateAPIgroupInput($validate, 1);
-	if($rc['status'] == 'error')
-		return $rc;
-	$query = "SELECT ownerid, "
-	       .        "editusergroupid AS editgroupid "
-	       . "FROM usergroup "
-	       . "WHERE id = {$rc['id']}";
-	$qh = doQuery($query, 101);
-	if(! $row = mysql_fetch_assoc($qh)) {
+
+	# valid $imageid
+	$resources = getUserResources(array("imageAdmin", "imageCheckOut"));
+	$resources["image"] = removeNoCheckout($resources["image"]);
+	if(! array_key_exists($imageid, $resources['image'])) {
 		return array('status' => 'error',
-		             'errorcode' => 18,
-		             'errormsg' => 'user group with submitted name and affiliation does not exist');
+		             'errorcode' => 3,
+		             'errormsg' => "access denied to $imageid");
 	}
-	# if not owner and not member of managing group, no access
-	if($user['id'] != $row['ownerid'] && 
-	   ! array_key_exists($row['editgroupid'], $user['groups'])) {
+
+	# validate $start and $end
+	$dtreg = '([0-9]{4})-([0-9]{2})-([0-9]{2}) ([0-9]{2}):([0-9]{2}):([0-9]{2})';
+	$startts = datetimeToUnix($start);
+	$endts = datetimeToUnix($end);
+	$maxend = datetimeToUnix("2038-01-01 00:00:00");
+	if(! preg_match("/^$dtreg$/", $start) || $startts < 0 ||
+	   $startts > $maxend) {
 		return array('status' => 'error',
-		             'errorcode' => 28,
-		             'errormsg' => 'access denied to user group with submitted name and affiliation');
+		             'errorcode' => 4,
+		             'errormsg' => "received invalid input for start");
 	}
-	$fails = array();
-	foreach($users as $_user) {
-		if(empty($_user))
-			continue;
-		$esc_user = mysql_real_escape_string($_user);
-		# check that affiliation of user can be determined because getUserlistID
-		#   will abort if it can't find it
-		$affilok = 0;
-		foreach($findAffilFuncs as $func) {
-			if($func($_user, $dump))
-				$affilok = 1;
-		}
-		if(! $affilok) {
-			$fails[] = $_user;
-			continue;
-		}
-		$userid = getUserlistID($esc_user, 1);
-		if(is_null($userid))
-			$fails[] = $_user;
-		else
-			deleteUserGroupMember($userid, $rc['id']);
+	if(! preg_match("/^$dtreg$/", $end) || $endts < 0 ||
+	   $endts > $maxend) {
+		return array('status' => 'error',
+		             'errorcode' => 36,
+		             'errormsg' => "received invalid input for end");
 	}
-	if(count($fails)) {
-		$cnt = 'some';
-		$code = 36;
-		if(count($fails) == count($users)) {
-			$cnt = 'any';
-			$code = 37;
-		}
-		return array('status' => 'warning',
-		             'failedusers' => $fails,
-		             'warningcode' => $code,
-		             'warningmsg' => "failed to remove $cnt users from user group");
+
+	# validate $numMachines
+	if(! is_numeric($numMachines) || $numMachines < MIN_BLOCK_MACHINES ||
+	   $numMachines > MAX_BLOCK_MACHINES) {
+		return array('status' => 'error',
+		             'errorcode' => 64,
+		             'errormsg' => 'The submitted number of seats must be between ' . MIN_BLOCK_MACHINES . ' and ' . MAX_BLOCK_MACHINES . '.');
 	}
-	return array('status' => 'success');
+
+	# validate $usergroupid
+	$groups = getUserGroups();
+	if(! array_key_exists($usergroupid, $groups)) {
+		return array('status' => 'error',
+		             'errorcode' => 67,
+		             'errormsg' => 'Submitted user group does not exist');
+	}
+
+	# validate ignoreprivileges
+	if(! is_numeric($ignoreprivileges) ||
+	   $ignoreprivileges < 0 ||
+		$ignoreprivileges > 1) {
+		return array('status' => 'error',
+		             'errorcode' => 86,
+		             'errormsg' => 'ignoreprivileges must be 0 or 1');
+	}
+
+	$ownerid = getUserlistID('vclreload@Local');
+	$name = "API:$start";
+	$managementnodes = getManagementNodes('future');
+	if(empty($managementnodes)) {
+		return array('status' => 'error',
+		             'errorcode' => 12,
+		             'errormsg' => 'could not allocate a management node to handle block allocation');
+	}
+	$mnid = array_rand($managementnodes);
+	$query = "INSERT INTO blockRequest "
+	       .        "(name, "
+	       .        "imageid, "
+	       .        "numMachines, "
+	       .        "groupid, "
+	       .        "repeating, "
+	       .        "ownerid, "
+	       .        "admingroupid, "
+	       .        "managementnodeid, "
+	       .        "expireTime, "
+	       .        "status) "
+	       . "VALUES "
+	       .        "('$name', "
+	       .        "$imageid, "
+	       .        "$numMachines, "
+	       .        "$usergroupid, "
+	       .        "'list', "
+	       .        "$ownerid, "
+	       .        "0, "
+	       .        "$mnid, "
+	       .        "'$end', "
+	       .        "'accepted')";
+	doQuery($query, 101);
+	$brid = dbLastInsertID();
+	$query = "INSERT INTO blockTimes "
+	       .        "(blockRequestid, "
+	       .        "start, "
+	       .        "end) "
+	       . "VALUES "
+	       .        "($brid, "
+	       .        "'$start', "
+	       .        "'$end')";
+	doQuery($query, 101);
+	$btid = dbLastInsertID();
+	$query = "INSERT INTO blockWebDate "
+	       .        "(blockRequestid, "
+	       .        "start, "
+	       .        "end, "
+	       .        "days) "
+	       . "VALUES "
+	       .        "($brid, "
+	       .        "'$start', "
+	       .        "'$end', "
+	       .        "0)";
+	doQuery($query);
+	$sh = date('g', $startts);
+	$smi = date('i', $startts);
+	$sme = date('a', $startts);
+	$eh = date('g', $startts);
+	$emi = date('i', $startts);
+	$eme = date('a', $startts);
+	$query = "INSERT INTO blockWebTime "
+	       .        "(blockRequestid, "
+	       .        "starthour, "
+	       .        "startminute, "
+	       .        "startmeridian, "
+	       .        "endhour, "
+	       .        "endminute, "
+	       .        "endmeridian, "
+	       .        "`order`) "
+	       . "VALUES "
+	       .        "($brid, "
+	       .        "$sh,"
+	       .        "$smi,"
+	       .        "'$sme',"
+	       .        "$eh,"
+	       .        "$emi,"
+	       .        "'$eme',"
+	       .        "0)";
+	doQuery($query);
+	$return = XMLRPCprocessBlockTime($btid, $ignoreprivileges);
+	$return['blockTimesid'] = $btid;
+	return $return;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn XMLRPCautoCapture($requestid)
+/// \fn XMLRPCprocessBlockTime($blockTimesid, $ignoreprivileges)
 ///
-/// \param $requestid - id of request to be captured
+/// \param $blockTimesid - id from the blockTimes table
+/// \param $ignoreprivileges - (optional, default=0) 0 (false) or 1 (true) - set
+/// to 1 to select computers from any that are mapped to be able to run the
+/// image; set to 0 to only select computers from ones that are both mapped and
+/// that users in the usergroup assigned to this block allocation have been
+/// granted access to through the privilege tree
 ///
 /// \return an array with at least one index named 'status' which will have
 /// one of these values:\n
 /// \b error - error occurred; there will be 2 additional elements in the array:
 /// \li \b errorcode - error number
 /// \li \b errormsg - error string
 ///
-/// \b success - image was successfully set to be captured
+/// \b completed - blockTimesid was previously successfully processed\n
+/// \b success - blockTimesid was processed; there will be two additional
+/// elements in this case:
+/// \li \b allocated - total number of desired allocations that have been
+/// processed
+/// \li \b unallocated - total number of desired allocations that have not been
+/// processed
 ///
-/// \brief creates entries in appropriate tables to capture an image and sets
-/// the request state to image\n
+/// \b warning - there was a non-fatal issue that occurred while processing
+/// the call; there will be four additional elements in this case:
+/// \li \b warningcode - warning number
+/// \li \b warningmsg - warning string
+/// \li \b allocated - total number of desired allocations that have been
+/// processed
+/// \li \b unallocated - total number of desired allocations that have not been
+/// processed\n\n
+///
+/// \b NOTE: status may be warning, but allocated may be 0 indicating there
+/// were no errors that occurred, but there simply were not any machines
+/// available
+///
+/// \brief processes a block allocation for the blockTimes entry associated
+/// with blockTimesid
 ///
 ////////////////////////////////////////////////////////////////////////////////
-function XMLRPCautoCapture($requestid) {
-	global $user, $xmlrpcBlockAPIUsers;
+function XMLRPCprocessBlockTime($blockTimesid, $ignoreprivileges=0) {
+	global $requestInfo, $user, $xmlrpcBlockAPIUsers;
 	if(! in_array($user['id'], $xmlrpcBlockAPIUsers)) {
 		return array('status' => 'error',
-		             'errorcode' => 47,
-		             'errormsg' => 'access denied to XMLRPCautoCapture');
+		             'errorcode' => 34,
+		             'errormsg' => 'access denied for managing block allocations');
 	}
-	$query = "SELECT id FROM request WHERE id = $requestid";
-	$qh = doQuery($query, 101);
-	if(! mysql_num_rows($qh)) {
+
+	# validate $blockTimesid
+	if(! is_numeric($blockTimesid)) {
 		return array('status' => 'error',
-		             'errorcode' => 52,
-		             'errormsg' => 'specified request does not exist');
+		             'errorcode' => 77,
+		             'errormsg' => 'Invalid blockTimesid specified');
 	}
-	$reqData = getRequestInfo($requestid);
-	# check state of reservation
-	if($reqData['stateid'] != 14 || $reqData['laststateid'] != 8) {
+
+	# validate ignoreprivileges
+	if(! is_numeric($ignoreprivileges) ||
+	   $ignoreprivileges < 0 ||
+		$ignoreprivileges > 1) {
 		return array('status' => 'error',
-		             'errorcode' => 51,
-		             'errormsg' => 'reservation not in valid state');
+		             'errorcode' => 86,
+		             'errormsg' => 'ignoreprivileges must be 0 or 1');
 	}
-	# check that not a cluster reservation
-	if(count($reqData['reservations']) > 1) {
+
+	$return = array('status' => 'success');
+	$query = "SELECT bt.start, "
+	       .        "bt.end, "
+	       .        "br.imageid, "
+	       .        "br.numMachines, "
+	       .        "br.groupid, "
+	       .        "br.expireTime "
+	       . "FROM blockRequest br, "
+	       .      "blockTimes bt "
+	       . "WHERE bt.blockRequestid = br.id AND "
+	       .       "bt.id = $blockTimesid";
+	$qh = doQuery($query, 101);
+	if(! $rqdata = mysql_fetch_assoc($qh)) {
 		return array('status' => 'error',
-		             'errorcode' => 48,
-		             'errormsg' => 'cannot image a cluster reservation');
+		             'errorcode' => 8,
+		             'errormsg' => 'unknown blockTimesid');
 	}
-	require_once(".ht-inc/images.php");
-	$imageid = $reqData['reservations'][0]['imageid'];
-	$imageData = getImages(0, $imageid);
-	$captime = unixToDatetime(time());
-	$comments = "start: {$reqData['start']}<br>"
-	          . "end: {$reqData['end']}<br>"
-	          . "computer: {$reqData['reservations'][0]['reservedIP']}<br>"
-	          . "capture time: $captime";
-	# create new revision if requestor is owner and not a kickstart image
-	if($imageData[$imageid]['installtype'] != 'kickstart' &&
-	   $reqData['userid'] == $imageData[$imageid]['ownerid']) {
-		$rc = updateExistingImage($requestid, $reqData['userid'], $comments, 1);
-		if($rc == 0) {
-			return array('status' => 'error',
-			             'errorcode' => 49,
-			             'errormsg' => 'error encountered while attempting to create new revision');
-		}
+	if(datetimeToUnix($rqdata['expireTime']) < time()) {
+		return array('status' => 'error',
+		             'errorcode' => 9,
+		             'errormsg' => 'expired block allocation');
 	}
-	# create a new image if requestor is not owner or a kickstart image
-	else {
-		$ownerdata = getUserInfo($reqData['userid'], 1, 1);
-		$desc = "This is an autocaptured image.<br>"
-		      . "captured from image: {$reqData['reservations'][0]['prettyimage']}<br>"
-		      . "captured on: $captime<br>"
-		      . "owner: {$ownerdata['unityid']}@{$ownerdata['affiliation']}<br>";
-		$connectmethods = getImageConnectMethods($imageid, $reqData['reservations'][0]['imagerevisionid']);
-		$data = array('requestid' => $requestid,
-		              'description' => $desc,
-		              'usage' => '',
-		              'owner' => "{$ownerdata['unityid']}@{$ownerdata['affiliation']}",
-		              'prettyname' => "Autocaptured ({$ownerdata['unityid']} - $requestid)",
-		              'minram' => 64,
-		              'minprocnumber' => 1,
-		              'minprocspeed' => 500,
-		              'minnetwork' => 10,
-		              'maxconcurrent' => '',
-		              'checkuser' => 1,
-		              'rootaccess' => 1,
-		              'sysprep' => 1,
-		              'comments' => $comments,
-		              'connectmethodids' => implode(',', array_keys($connectmethods)));
-		$rc = submitAddImage($data, 1);
-		if($rc == 0) {
-			return array('status' => 'error',
-			             'errorcode' => 50,
-			             'errormsg' => 'error encountered while attempting to create image');
-		}
-	}
-	return array('status' => 'success');
-}
 
-////////////////////////////////////////////////////////////////////////////////
-///
-/// \fn XMLRPCdeployServer($imageid, $start, $end, $admingroup, $logingroup,
-///                        $ipaddr, $macaddr, $monitored, $foruser)
-///
-/// \param $imageid - id of an image
-/// \param $start - "now" or unix timestamp for start of reservation; will
-/// use a floor function to round down to the nearest 15 minute increment
-/// for actual reservation
-/// \param $end - "indefinite" or unix timestamp for end of reservation; will
-/// use a floor function to round up to the nearest 15 minute increment
-/// for actual reservation
-/// \param $admingroup - (optional, default='') admin user group for reservation
-/// \param $logingroup - (optional, default='') login user group for reservation
-/// \param $ipaddr - (optional, default='') IP address to use for public IP of
-/// server
-/// \param $macaddr - (optional, default='') MAC address to use for public NIC
-/// of server
-/// \param $monitored - (optional, default=0) whether or not the server should
-/// be monitored - CURRENTLY, THIS IS UNSUPPORTED
-/// \param $foruser - (optional) login to be used when setting up the account
-/// on the reserved machine - CURRENTLY, THIS IS UNSUPPORTED
-///
-/// \return an array with at least one index named '\b status' which will have
-/// one of these values:\n
-/// \b error - error occurred; there will be 2 additional elements in the array:
-/// \li \b errorcode - error number\n
-/// \li \b errormsg - error string\n
-///
-/// \b notavailable - no computers were available for the request\n
-/// \b success - there will be an additional element in the array:
-/// \li \b requestid - identifier that should be passed to later calls when
-/// acting on the request
-///
-/// \brief tries to make a server request\n
-///
-////////////////////////////////////////////////////////////////////////////////
-function XMLRPCdeployServer($imageid, $start, $end, $admingroup='',
-                            $logingroup='', $ipaddr='', $macaddr='',
-                            $monitored=0, $foruser='') {
-	global $user, $remoteIP;
-	if(! in_array("serverProfileAdmin", $user["privileges"])) {
-		return array('status' => 'error',
-		             'errorcode' => 60,
-		             'errormsg' => "access denied to deploy server");
-	}
-	$imageid = processInputData($imageid, ARG_NUMERIC);
-	$resources = getUserResources(array("imageAdmin", "imageCheckOut"));
-	$images = removeNoCheckout($resources["image"]);
-	$extraimages = getServerProfileImages($user['id']);
-	if(! array_key_exists($imageid, $images) &&
-	   ! array_key_exists($imageid, $extraimages)) {
+	$images = getImages(0, $rqdata['imageid']);
+	if(empty($images)) {
 		return array('status' => 'error',
-		             'errorcode' => 3,
-		             'errormsg' => "access denied to $imageid");
+		             'errorcode' => 10,
+		             'errormsg' => 'invalid image associated with block allocation');
 	}
-	if($admingroup != '' || $logingroup != '')
-		$usergroups = getUserEditGroups($user['id']);
-	if($admingroup != '') {
-		$admingroup = processInputData($admingroup, ARG_STRING);
-		if(preg_match('@', $admingroup)) {
-			$tmp = explode('@', $admingroup);
-			$escadmingroup = mysql_real_escape_string($tmp[0]);
-			$affilid = getAffiliationID(mysql_real_escape_string($tmp[1]));
-			if(is_null($affilid)) {
-				return array('status' => 'error',
-				             'errorcode' => 51,
-				             'errormsg' => "unknown affiliation for admin user group: {$tmp[1]}");
+
+	$unixstart = datetimeToUnix($rqdata['start']);
+	$unixend = datetimeToUnix($rqdata['end']);
+	$revisionid = getProductionRevisionid($rqdata['imageid']);
+	$imgLoadTime = getImageLoadEstimate($rqdata['imageid']);
+	if($imgLoadTime == 0)
+		$imgLoadTime = $images[$rqdata['imageid']]['reloadtime'] * 60;
+	$vclreloadid = getUserlistID('vclreload@Local');
+	$groupmembers = getUserGroupMembers($rqdata['groupid']);
+	$userids = array_keys($groupmembers);
+
+	# add any computers from future reservations users in the group made
+	if(! empty($groupmembers)) {
+		## find reservations by users
+		$allids = implode(',', $userids);
+		$query = "SELECT rq.id AS reqid, "
+		       .        "UNIX_TIMESTAMP(rq.start) AS start, "
+		       .        "rq.userid "
+		       . "FROM request rq, "
+		       .      "reservation rs "
+		       . "WHERE rs.requestid = rq.id AND "
+		       .       "rq.userid IN ($allids) AND "
+		       .       "rq.start < '{$rqdata['end']}' AND "
+		       .       "rq.end > '{$rqdata['start']}' AND "
+		       .       "rs.imageid = {$rqdata['imageid']} AND "
+		       .       "rs.computerid NOT IN (SELECT computerid "
+		       .                             "FROM blockComputers "
+		       .                             "WHERE blockTimeid = $blockTimesid)";
+		$qh = doQuery($query);
+		$donereqids = array();
+		$blockCompVals = array();
+		$checkstartbase = $unixstart - $imgLoadTime - 300;
+		$reloadstartbase = unixToDatetime($checkstartbase);
+		$rows = mysql_num_rows($qh);
+		while($row = mysql_fetch_assoc($qh)) {
+			if(array_key_exists($row['reqid'], $donereqids))
+				continue;
+			$donereqids[$row['reqid']] = 1;
+			if($row['start'] < datetimeToUnix($rqdata['start'])) {
+				$checkstart = $row['start'] - $imgLoadTime - 300;
+				$reloadstart = unixToDatetime($checkstart);
+				$reloadend = unixToDatetime($row['start']);
 			}
-		}
-		else {
-			$escadmingroup = mysql_real_escape_string($admingroup);
-			$affilid = DEFAULT_AFFILID;
-		}
-		$query = "SELECT id "
-		       . "FROM usergroup "
-		       . "WHERE name = '$escadmingroup' AND "
-		       .       "affiliationid = $affilid";
-		$qh = doQuery($query, 300);
-		if($row = mysql_fetch_assoc($qh))
-			$admingroupid = $row['id'];
-		else {
-			return array('status' => 'error',
-			             'errorcode' => 52,
-			             'errormsg' => "unknown admin user group: $admingroup");
-		}
-		if(! array_key_exists($admingroupid, $usergroups)) {
-			return array('status' => 'error',
-			             'errorcode' => 53,
-			             'errormsg' => "access denied to admin user group: $admingroup");
-		}
-	}
-	else
-		$admingroupid = '';
-	if($logingroup != '') {
-		$logingroup = processInputData($logingroup, ARG_STRING);
-		if(preg_match('@', $logingroup)) {
-			$tmp = explode('@', $logingroup);
-			$esclogingroup = mysql_real_escape_string($tmp[0]);
-			$affilid = getAffiliationID(mysql_real_escape_string($tmp[1]));
-			if(is_null($affilid)) {
-				return array('status' => 'error',
-				             'errorcode' => 54,
-				             'errormsg' => "unknown affiliation for login user group: {$tmp[1]}");
+			else {
+				$checkstart = $checkstartbase;
+				$reloadstart = $reloadstartbase;
+				$reloadend = $rqdata['start'];
+			}
+			# check to see if computer is available for whole block
+			$rc = isAvailable($images, $rqdata['imageid'], $revisionid, $checkstart,
+			                  $unixend, $row['reqid'], $row['userid'],
+			                  $ignoreprivileges, 0, '', '', 1);
+			// if not available for whole block, just skip this one
+			if($rc < 1)
+				continue;
+			$compid = $requestInfo['computers'][0];
+			# create reload reservation
+			$reqid = simpleAddRequest($compid, $rqdata['imageid'], $revisionid,
+			                          $reloadstart, $reloadend, 19, $vclreloadid);
+			if($reqid == 0)
+				continue;
+			# add to blockComputers
+			$blockCompVals[] = "($blockTimesid, $compid, {$rqdata['imageid']}, $reqid)";
+			# process any subimages
+			for($key = 1; $key < count($requestInfo['computers']); $key++) {
+				$subimageid = $requestInfo['images'][$key];
+				$subrevid = getProductionRevisionid($subimageid);
+				$compid = $requestInfo['computers'][$key];
+				$mgmtnodeid = $requestInfo['mgmtnodes'][$key];
+				$blockCompVals[] = "($blockTimesid, $compid, $subimageid, $reqid)";
+
+				$query = "INSERT INTO reservation "
+				       .        "(requestid, "
+				       .        "computerid, "
+				       .        "imageid, "
+				       .        "imagerevisionid, "
+				       .        "managementnodeid) "
+				       . "VALUES "
+				       .       "($reqid, "
+				       .       "$compid, "
+				       .       "$subimageid, "
+				       .       "$subrevid, "
+				       .       "$mgmtnodeid)";
+				doQuery($query, 101);
 			}
 		}
-		else {
-			$esclogingroup = mysql_real_escape_string($logingroup);
-			$affilid = DEFAULT_AFFILID;
-		}
-		$query = "SELECT id "
-		       . "FROM usergroup "
-		       . "WHERE name = '$esclogingroup' AND "
-		       .       "affiliationid = $affilid";
-		$qh = doQuery($query, 300);
-		if($row = mysql_fetch_assoc($qh))
-			$logingroupid = $row['id'];
-		else {
-			return array('status' => 'error',
-			             'errorcode' => 55,
-			             'errormsg' => "unknown login user group: $logingroup");
-		}
-		if(! array_key_exists($logingroupid, $usergroups)) {
-			return array('status' => 'error',
-			             'errorcode' => 56,
-			             'errormsg' => "access denied to login user group: $logingroup");
+		if(count($blockCompVals)) {
+			$blockComps = implode(',', $blockCompVals);
+			$query = "INSERT INTO blockComputers "
+			       .        "(blockTimeid, computerid, imageid, reloadrequestid) "
+			       . "VALUES $blockComps";
+			doQuery($query);
 		}
 	}
-	else
-		$logingroupid = '';
-	$ipaddr = processInputData($ipaddr, ARG_STRING);
-	$ipaddrArr = explode('.', $ipaddr);
-	if($ipaddr != '' && (! preg_match('/^(([0-9]){1,3}\.){3}([0-9]){1,3}$/', $ipaddr) ||
-		$ipaddrArr[0] < 1 || $ipaddrArr[0] > 255 ||
-		$ipaddrArr[1] < 0 || $ipaddrArr[1] > 255 ||
-		$ipaddrArr[2] < 0 || $ipaddrArr[2] > 255 ||
-		$ipaddrArr[3] < 0 || $ipaddrArr[3] > 255)) {
-		return array('status' => 'error',
-		             'errorcode' => 57,
-		             'errormsg' => "Invalid IP address. Must be w.x.y.z with each of "
-		                         . "w, x, y, and z being between 1 and 255 (inclusive)");
-	}
-	$macaddr = processInputData($macaddr, ARG_STRING);
-	if($macaddr != '' && ! preg_match('/^(([A-Fa-f0-9]){2}:){5}([A-Fa-f0-9]){2}$/', $macaddr)) {
-		return array('status' => 'error',
-		             'errorcode' => 58,
-		             'errormsg' => "Invalid MAC address.  Must be XX:XX:XX:XX:XX:XX "
-		                         . "with each pair of XX being from 00 to FF (inclusive)");
-	}
-	$monitored = processInputData($monitored, ARG_NUMERIC);
-	if($monitored != 0 && $monitored != 1)
-		$monitored = 0;
-	$start = processInputData($start, ARG_STRING, 1);
-	$end = processInputData($end, ARG_STRING, 1);
-	#$foruser = processInputData($foruser, ARG_STRING, 1);
 
-	# validate $start
-	if($start != 'now' && ! is_numeric($start)) {
+	# check to see if all computers have been allocated
+	$query = "SELECT COUNT(computerid) AS allocated "
+	       . "FROM blockComputers "
+	       . "WHERE blockTimeid = $blockTimesid";
+	$qh = doQuery($query, 101);
+	if(! $row = mysql_fetch_assoc($qh)) {
 		return array('status' => 'error',
-		             'errorcode' => 4,
-		             'errormsg' => "received invalid input for start");
+		             'errorcode' => 15,
+		             'errormsg' => 'failure to communicate with database');
 	}
-	# validate $end
-	if($end != 'indefinite' && ! is_numeric($end)) {
-		return array('status' => 'error',
-		             'errorcode' => 59,
-		             'errormsg' => "received invalid input for end");
+	$compCompleted = $row['allocated'];
+	if(array_key_exists('subimages', $images[$rqdata['imageid']]))
+		$compsPerAlloc = 1 + count($images[$rqdata['imageid']]['subimages']);
+	else
+		$compsPerAlloc = 1;
+	$toallocate = ($rqdata['numMachines'] * $compsPerAlloc) - $compCompleted;
+	if($toallocate == 0) {
+		if(count($blockCompVals)) {
+			return array('status' => 'success',
+			             'allocated' => $rqdata['numMachines'],
+			             'unallocated' => 0);
+		}
+		return array('status' => 'completed');
 	}
+	$reqToAlloc = $toallocate / $compsPerAlloc;
 
-	$nowfuture = 'future';
-	if($start == 'now') {
-		$start = unixFloor15(time());
-		$nowfuture = 'now';
-	}
-	else
-		if($start < (time() - 30))
+	if(! $ignoreprivileges) {
+		# get userids in user group
+		if(empty($groupmembers)) {
 			return array('status' => 'error',
-			             'errorcode' => 5,
-			             'errormsg' => "start time is in the past");
-	if($end == 'indefinite')
-		$end = datetimeToUnix("2038-01-01 00:00:00");
-	elseif($end % (15 * 60))
-		$end = unixFloor15($end) + (15 * 60);
-
-	$max = getMaxOverlap($user['id']);
-	if(checkOverlap($start, $end, $max)) {
-		return array('status' => 'error',
-		             'errorcode' => 7,
-		             'errormsg' => "reservation overlaps with another one you "
-		                         . "have, and you are allowed $max "
-		                         . "overlapping reservations at a time");
+			             'errorcode' => 11,
+			             'errormsg' => 'empty user group and ignoreprivileges set to 0');
+		}
+		# make length of $userids match $reqToAlloc by duplicating or trimming some users
+		while($reqToAlloc > count($userids))
+			$userids = array_merge($userids, $userids);
+		if($reqToAlloc < count($userids))
+			$userids = array_splice($userids, 0, $reqToAlloc);
 	}
 
-	$images = getImages();
-	$revisionid = getProductionRevisionid($imageid);
-	$rc = isAvailable($images, $imageid, $revisionid, $start, $end,
-	                  0, 0, 0, 0, $ipaddr, $macaddr);
-	if($rc < 1) {
-		addLogEntry($nowfuture, unixToDatetime($start), 
-		            unixToDatetime($end), 0, $imageid);
-		return array('status' => 'notavailable');
-	}
-	$return['requestid']= addRequest();
-	$query = "UPDATE reservation "
-	       . "SET remoteIP = '$remoteIP' "
-	       . "WHERE requestid = {$return['requestid']}";
-	doQuery($query);
-	$fields = array('requestid');
-	$values = array($return['requestid']);
-	if($ipaddr != '') {
-		$fields[] = 'fixedIP';
-		$values[] = "'$ipaddr'";
-	}
-	if($macaddr != '') {
-		$fields[] = 'fixedMAC';
-		$values[] = "'$macaddr'";
-	}
-	if($admingroupid != 0) {
-		$fields[] = 'admingroupid';
-		$values[] = $admingroupid;
+	# staggering: stagger start times for this round (ie, do not worry about
+	#   previous processing of this block time) such that there is 1 minute
+	#   between the start times for each allocation
+	$stagExtra = $reqToAlloc * 60;
+
+	# determine estimated load time
+	$loadtime = $imgLoadTime + (10 * 60); # add 10 minute fudge factor
+	if((time() + $loadtime + $stagExtra) > $unixstart) {
+		$return['status'] = 'warning';
+		$return['warningcode'] = 13;
+		$return['warningmsg'] = 'possibly insufficient time to load machines';
 	}
-	if($logingroupid != 0) {
-		$fields[] = 'logingroupid';
-		$values[] = $logingroupid;
+	$start = unixToDatetime($unixstart - $loadtime);
+
+	$userid = 0;
+	$allocated = 0;
+	$blockCompVals = array();
+	# FIXME (maybe) - if some subset of users in the user group have available
+	# computers, but others do not, $allocated will be less than the desired
+	# number of machines; however, calling this function enough times will
+	# result in enough machines being allocated because they will continue to be
+	# allocated based on the ones with machines available; this seems like odd
+	# behavior
+	$stagCnt = 0;
+	$stagTime = 60;        # stagger reload reservations by 1 min
+	if($imgLoadTime > 840) // if estimated load time is > 14 min
+		$stagTime = 120;    #    stagger reload reservations by 2 min 
+	for($i = 0; $i < $reqToAlloc; $i++) {
+		$stagunixstart = $unixstart - $loadtime - ($stagCnt * $stagTime);
+		$stagstart = unixToDatetime($stagunixstart);
+		if(! $ignoreprivileges)
+			$userid = array_pop($userids);
+		# use end of block time to find available computers, but...
+		$rc = isAvailable($images, $rqdata['imageid'], $revisionid, $stagunixstart,
+		                  $unixend, 0, $userid, $ignoreprivileges);
+		if($rc < 1)
+			continue;
+
+		$compid = $requestInfo['computers'][0];
+		# ...use start of block time as end of reload reservation
+		$reqid = simpleAddRequest($compid, $rqdata['imageid'], $revisionid,
+		                          $stagstart, $rqdata['start'], 19, $vclreloadid);
+		if($reqid == 0)
+			continue;
+
+		$stagCnt++;
+		$allocated++;
+		$blockCompVals[] = "($blockTimesid, $compid, {$rqdata['imageid']}, $reqid)";
+
+		# process any subimages
+		for($key = 1; $key < count($requestInfo['computers']); $key++) {
+			$subimageid = $requestInfo['images'][$key];
+			$subrevid = getProductionRevisionid($subimageid);
+			$compid = $requestInfo['computers'][$key];
+			$mgmtnodeid = $requestInfo['mgmtnodes'][$key];
+			$blockCompVals[] = "($blockTimesid, $compid, $subimageid, $reqid)";
+
+			$query = "INSERT INTO reservation "
+			       .        "(requestid, "
+			       .        "computerid, "
+			       .        "imageid, "
+			       .        "imagerevisionid, "
+			       .        "managementnodeid) "
+			       . "VALUES "
+			       .       "($reqid, "
+			       .       "$compid, "
+			       .       "$subimageid, "
+			       .       "$subrevid, "
+			       .       "$mgmtnodeid)";
+			doQuery($query, 101);
+		}
+		semUnlock();
+		$blockComps = implode(',', $blockCompVals);
+		$query = "INSERT INTO blockComputers "
+		       .        "(blockTimeid, computerid, imageid, reloadrequestid) "
+		       . "VALUES $blockComps";
+		doQuery($query, 101);
+		$blockCompVals = array();
 	}
-	if($monitored != 0) {
-		$fields[] = 'monitored';
-		$values[] = 1;
+	if($allocated == 0) {
+		$return['status'] = 'warning';
+		$return['warningcode'] = 14;
+		$return['warningmsg'] = 'unable to allocate any machines';
 	}
-	$allfields = implode(',', $fields);
-	$allvalues = implode(',', $values);
-	$query = "INSERT INTO serverrequest ($allfields) VALUES ($allvalues)";
-	doQuery($query, 101);
-	$return['status'] = 'success';
+	$return['allocated'] = ($compCompleted / $compsPerAlloc) + $allocated;
+	$return['unallocated'] = $rqdata['numMachines'] - $return['allocated'];
 	return $return;
 }
-
-////////////////////////////////////////////////////////////////////////////////
-///
-/// \fn XMLRPCtest($string)
-///
-/// \param $string - a string
-///
-/// \return an array with 3 indices:\n
-/// \b status - will be 'success'\n
-/// \b message - will be 'RPC call worked successfully'\n
-/// \b string - contents of $string (after being sanatized)
-///
-/// \brief this is a test function that call be called when getting XML RPC
-/// calls to this site to work\n
-///
-////////////////////////////////////////////////////////////////////////////////
-function XMLRPCtest($string) {
-	$string = processInputData($string, ARG_STRING);
-	return array('status' => 'success',
-	             'message' => 'RPC call worked successfully',
-	             'string' => $string);
-}
 ?>

Vulnerable code: 	if($data['editgroupid'] == 0 || $data['editgroupid'] == '')
		$data['editgroupid'] = 'NULL';
		print "	    <script type=\"dojo/method\" event=onClick>\n";
		print "	    <script type=\"dojo/method\" event=onClick>\n";
		print "	    <script type=\"dojo/method\" event=onClick>\n";
		print "	<script type=\"dojo/method\" event=onClick>\n";
		print "	<script type=\"dojo/method\" event=onClick>\n";
		print "	<script type=\"dojo/method\" event=onClick>\n";
	print "	  <script type=\"dojo/connect\" event=onCancel>\n";
	print "	  <script type=\"dojo/method\" event=onClick>\n";
	print "	  <script type=\"dojo/method\" event=onClick>\n";
	print "	  <script type=\"dojo/connect\" event=onCancel>\n";
	print "	  <script type=\"dojo/method\" event=onClick>\n";
	print "	  <script type=\"dojo/method\" event=onClick>\n";
	print "	  <script type=\"dojo/connect\" event=onCancel>\n";
	$privs = array("computerAdmin","mgmtNodeAdmin",  "imageAdmin", "scheduleAdmin");
	print "	  <script type=\"dojo/method\" event=onClick>\n";
	print "	  <script type=\"dojo/method\" event=onClick>\n";
	print "	<script type=\"dojo/connect\" event=onKeyPress args=\"e\">\n";
	print "	  <script type=\"dojo/method\" event=onClick>\n";
	print "	  <script type=\"dojo/method\" event=onClick>\n";
	print "	  <script type=\"dojo/method\" event=onClick>\n";
	print "	  <script type=\"dojo/method\" event=onClick>\n";
	print "	<script type=\"dojo/connect\" event=onKeyPress args=\"e\">\n";
	print "	  <script type=\"dojo/method\" event=onClick>\n";
	print "	  <script type=\"dojo/method\" event=onClick>\n";
	print "	<script type=\"dojo/connect\" event=_setup>\n";
	print "	Manage User Group Permissions\n";
	print "	<script type=\"dojo/method\" event=onClick>\n";
	print "		selectUserGroup('$cont');\n";
	print "	</script>\n";
	print "	Copy Permissions\n";
	print "	<script type=\"dojo/method\" event=onClick>\n";
	print "		copyUserGroupPrivs('$cont');\n";
	print "	</script>\n";
	print "	Save Selected Permissions\n";
	print "	<script type=\"dojo/method\" event=onClick>\n";
	print "		saveUserGroupPrivs('$cont');\n";
	print "	</script>\n";
		$text .= "	    <script type=\"dojo/method\" event=onClick>";
		$text .= "	    <script type=\"dojo/method\" event=onClick>";
		$text .= "	    <script type=\"dojo/method\" event=onClick>";
		$text .= "	<script type=\"dojo/method\" event=onClick>";
		$text .= "	<script type=\"dojo/method\" event=onClick>";
	$nodeInfo = getNodeInfo($parent);
	if(! preg_match('/^[-A-Za-z0-9_. ]+$/', $newnode)) {
		$text = "You can only use letters, numbers, spaces,<br>"
		      . "dashes(-), dots(.), and underscores(_).";
		print "dojo.byId('addChildNodeStatus').innerHTML = '$text';";
	if(count($privs))
	updateUserOrGroupPrivs($user["id"], $nodeid, $privs, array(), "user");
	# check if node matching new name already exists at parent
	       .       "name = '$newname'";
		$msg = "A sibling node of that name currently exists";
	       . "SET name = '$newname' " 
				print "<font color=red>$userid not found in any known systems</font><br>\n";
		($usergroup == 2 &&
		($usergroup == 2 &&
/// getNodeCascadePrivileges
		$query = "SELECT g.name AS name, "
		while($row = mysql_fetch_assoc($qh))
	//print "alert('node: $node; newuser: $newuser; newpriv: $newpriv; newprivval: $newprivval');";
	// if $newprivval is true and $newuser already has $newpriv
	//   cascaded to it, do nothing
	$newusergrp = getUserGroupName($newusergrpid);
	//print "alert('node: $node; newuser:grp $newuser;grp newpriv: $newpriv; newprivval: $newprivval');";
	// if $newprivval is true and $newusergrp already has $newpriv
	//   cascaded to it, do nothing
	//print "alert('node: $node; resourcegrp: $resourcegrp; newpriv: $newpriv; newprivval: $newprivval');";
	// if $newprivval is true and $resourcegrp already has $newpriv
	//   cascaded to it, do nothing
	$tmpArr = explode('/', $resourcegrp);
	updateResourcePrivs($tmpArr[2], $node, $adds, $removes);
	# FIXME validate newgroupid
	$resourcesgroups = getUserResources($privs, array("manageGroup"), 1);
	if(! array_key_exists($newgroupid, $resourcesgroups['image']) &&
	   ! array_key_exists($newgroupid, $resourcesgroups['computer']) &&
	   ! array_key_exists($newgroupid, $resourcesgroups['managementnode']) &&
	   ! array_key_exists($newgroupid, $resourcesgroups['schedule']) &&
	   ! array_key_exists($newgroupid, $resourcesgroups['serverprofile'])) {
			(! in_array("block", $privs["usergroups"][$groupname]['privs']) && 
/// \fn getResourceGroups($type)
/// \return an array of resource group names whose index values are the ids;
/// the names are the resource type and group name combined as 'type/name'
function getResourceGroups($type="") {
				$return[$index] = addslashes($value);
			$return = addslashes($return);
/// \fn getUserGroupID($name, $affilid)
function getUserGroupID($name, $affilid=DEFAULT_AFFILID) {
	if($row = mysql_fetch_row($qh)) {
	}
		while($row = mysql_fetch_assoc($qh)) {
			if($row["name"] == "block" || $row["name"] == "cascade")
				continue;
		}
function getResourceGroupID($groupdname) {
	list($type, $name) = explode('/', $groupdname);
    xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCremoveNode", "xmlRPChandler");
    xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCnodeExists", "xmlRPChandler");
    xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCaddResourceGroupPriv", "xmlRPChandler");
    xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCremoveResourceGroupPriv", "xmlRPChandler");
    xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCgetResourceGroupPrivs", "xmlRPChandler");
    xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCaddUserGroupPriv", "xmlRPChandler");
    xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCremoveUserGroupPriv", "xmlRPChandler");
    xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCgetUserGroupPrivs", "xmlRPChandler");
    xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCaddResourceGroup", "xmlRPChandler");
    xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCgetResourceGroups", "xmlRPChandler");
    xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCremoveResourceGroup", "xmlRPChandler");
    xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCremoveUserGroup", "xmlRPChandler");
    xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCremoveImageFromGroup", "xmlRPChandler");
    xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCgetGroupImages", "xmlRPChandler");
    xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCaddImageGroupToComputerGroup", "xmlRPChandler");
    xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCremoveImageGroupFromComputerGroup", "xmlRPChandler");
	print "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n";
///                  they it does not exist
		$esc_affiliation = mysql_real_escape_string($items['affiliation']);
		$affilid = getAffiliationID($esc_affiliation);
		$esc_name = mysql_real_escape_string($items['name']);
		$doesexist = checkForGroupName($esc_name, 'user', '', $affilid);
		if(! validateUserid(mysql_real_escape_string($items['owner']))) {
		$esc_mgName = mysql_real_escape_string($parts[0]);
		$esc_mgAffil = mysql_real_escape_string($parts[1]);
		$mgaffilid = getAffiliationID($esc_mgAffil);
		if(! checkForGroupName($esc_mgName, 'user', '', $mgaffilid)) {
		$items['managingGroupID'] = getUserGroupID($esc_mgName, $mgaffilid);
/// HTTP headers do not need to be passed\n
/// \brief gets the images to which the user has access\n
        $notes = getImageNotes($key);
        $tmp = array('id' => $key,
                     'name' => $val,
                     'description' => $notes['description'],
                     'usage' => $notes['usage']);
/// \brief tries to make a request\n
/// \brief tries to make a request with the specified ending time\n
/// \fn XMLRPCgetRequestStatus($requestid)
/// \param $requestid - id of a request
/// \b ready - request is ready\n
/// \b failed - request failed to load properly\n
/// \b timedout - request timed out (user didn't connect before timeout
/// expired)\n
/// \b loading - request is still loading; there will be an additional element
/// in the array:
/// \li \b time - the estimated wait time (in minutes) for loading to complete\n
///
/// \b future - start time of request is in the future\n
/// \brief determines and returns the status of the request\n
function XMLRPCgetRequestStatus($requestid) {
	global $user;
	$requestid = processInputData($requestid, ARG_NUMERIC);
	$userRequests = getUserRequests('all', $user['id']);
	$found = 0;
	foreach($userRequests as $req) {
		if($req['id'] == $requestid) {
			$request = $req;
			$found = 1;
			break;
		}
	if(! $found)
		             'errorcode' => 1,
		             'errormsg' => 'unknown requestid');

	$now = time();
	# request is ready
	if(requestIsReady($request))
		return array('status' => 'ready');
	# request failed
	elseif($request["currstateid"] == 5)
		return array('status' => 'failed');
	# other cases where the reservation start time has been reached
	elseif(datetimeToUnix($request["start"]) < $now) {
		# request has timed out
		if($request["currstateid"] == 12 ||
		   $request["currstateid"] == 11 ||
		   ($request["currstateid"] == 14 &&
		   $request["laststateid"] == 11)) {
			return array('status' => 'timedout');
		# computer is loading
			$imageid = $request['imageid'];
			$images = getImages(0, $imageid);
			$remaining = 1;
			$computers = getComputers(0, 0, $request['computerid']);
			if(isComputerLoading($request, $computers)) {
				if(datetimeToUnix($request["daterequested"]) >=
					datetimeToUnix($request["start"]))
					$startload = datetimeToUnix($request["daterequested"]);
				else
					$startload = datetimeToUnix($request["start"]);
				$imgLoadTime = getImageLoadEstimate($imageid);
				if($imgLoadTime == 0)
					$imgLoadTime = $images[$imageid]['reloadtime'] * 60;
				$tmp = ($imgLoadTime - ($now - $startload)) / 60;
				$remaining = sprintf("%d", $tmp) + 1;
				if($remaining < 1) {
					$remaining = 1;
				}
			return array('status' => 'loading', 'time' => $remaining);
	# reservation is in the future
		return array('status' => 'future');
}

////////////////////////////////////////////////////////////////////////////////
///
/// \fn XMLRPCgetUserGroups()
///
/// \return an array listing all of the groups to which the given user
/// has read or write access. Each usergroup will be an array with the 
/// following keys:\n
/// id\n
/// name\n
/// groupaffiliation\n
/// groupaffiliationid\n
/// ownerid\n
/// owner\n
/// affiliation\n
/// editgroupid\n
/// editgroup\n
/// editgroupaffiliationid\n
/// editgroupaffiliation\n
/// custom\n
/// courseroll\n
/// initialmaxtime\n
/// maxextendtime\n
/// overlapResCount\n
///
/// \brief builds a list of user groups\n
///
////////////////////////////////////////////////////////////////////////////////
function XMLRPCgetUserGroups($groupType=0, $affiliationid=0) {
    global $user;
    $groups = getUserGroups($groupType, $affiliationid);

    // Filter out any groups to which the user does not have access.
    $usergroups = array();
    foreach($groups as $id => $group){
        if($group['ownerid'] == $user['id'] || 
            (array_key_exists("editgroupid", $group) &&
            array_key_exists($group['editgroupid'], $user["groups"])) || 
            (array_key_exists($id, $user["groups"]))){
            array_push($usergroups, $group);
        }
    }
    return array(
            "status" => "success",
            "groups" => $usergroups);
}


////////////////////////////////////////////////////////////////////////////////
///
/// \fn XMLRPCgetRequestConnectData($requestid, $remoteIP)
///
/// \param $requestid - id of a request
/// \param $remoteIP - ip address of connecting user's computer
///
/// \return an array with at least one index named '\b status' which will have
/// one of these values\n
/// \b error - error occurred; there will be 2 additional elements in the array:
/// \li \b errorcode - error number\n
/// \li \b errormsg - error string\n
///
/// \b ready - request is ready; there will be 3 additional elements in the
/// array:
/// \li \b serverIP - address of the reserved machine
/// \li \b user - user to use when connecting to the machine
/// \li \b password - password to use when connecting to the machine
///
/// \b notready - request is not ready for connection
///
/// \brief if request is ready, adds the connecting user's computer to the
/// request and returns info about how to connect to the computer\n
///
////////////////////////////////////////////////////////////////////////////////
function XMLRPCgetRequestConnectData($requestid, $remoteIP) {
	global $user;
	$requestid = processInputData($requestid, ARG_NUMERIC);
	$remoteIP = processInputData($remoteIP, ARG_STRING, 1);
	if(! preg_match('/^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/', $remoteIP, $matches) ||
	   $matches[1] < 1 || $matches[1] > 223 ||
	   $matches[2] > 255 ||
		$matches[3] > 255 ||
		$matches[4] > 255) {
		             'errorcode' => 2,
		             'errormsg' => 'invalid IP address');
	$userRequests = getUserRequests('all', $user['id']);
	$found = 0;
	foreach($userRequests as $req) {
		if($req['id'] == $requestid) {
			$request = $req;
			$found = 1;
			break;
		}
	if(! $found)
		             'errorcode' => 1,
		             'errormsg' => 'unknown requestid');
	// FIXME - add support for cluster requests
	if(requestIsReady($request)) {
		$requestData = getRequestInfo($requestid);
		$query = "UPDATE reservation "
		       . "SET remoteIP = '$remoteIP' "
        $connectport = $requestData["reservations"][0]["connectport"];
        $connectMethods = getImageConnectMethodTexts(
                $requestData["reservations"][0]["imageid"],
                $requestData["reservations"][0]["imagerevisionid"]);
        foreach($connectMethods as $key => $cm){
            $connecttext = $cm["connecttext"];
            $connecttext = preg_replace("/#userid#/", $thisuser, $connecttext); 
            $connecttext = preg_replace("/#password#/", $passwd, $connecttext); 
            $connecttext = preg_replace("/#connectIP#/", $serverIP, $connecttext); 
            $connecttext = preg_replace("/#connectport#/", $connectport, $connecttext); 
            $connectMethods[$key]["connecttext"] = $connecttext;
        }
                     'password' => $passwd,
                     'connectport' => $connectport,
                     'connectMethods' => $connectMethods);
/// started needs to be extended, delete the request and submit a new one\n
/// \fn XMLRPCremoveImageFromGroup($name, $imageid)
/// \param $name - the name of an imageGroup
/// \param $imageid - the id of an image
/// \b success - image was removed from the group\n
/// \brief removes an image from a resource group\n
///
////////////////////////////////////////////////////////////////////////////////
function XMLRPCremoveImageFromGroup($name, $imageid){
    $groups = getUserResources(array("imageAdmin"), array("manageGroup"), 1);
    
    if($groupid = getResourceGroupID("image/$name")){
        if(!array_key_exists($groupid, $groups['image'])){
            return array('status' => 'error',
                         'errorcode' => 46,
                         'errormsg' => 'Unable to access image group');
        }
        $resources = getUserResources(array("imageAdmin"), array("manageGroup"));
        if(!array_key_exists($imageid, $resources['image'])){
            return array('status' => 'error',
                         'errorcode' => 47,
                         'errormsg' => 'Unable to access image');
        }

        $allimages = getImages();
        $query = "DELETE FROM resourcegroupmembers "
               . "WHERE resourceid={$allimages[$imageid]['resourceid']} "
               . "AND resourcegroupid=$groupid";
        doQuery($query, 287);
        return array('status' => 'success');
    } else {
        return array('status' => 'error',
                     'errorcode' => 83,
                     'errormsg' => 'invalid resource group name');
    }
/// \fn XMLRPCaddImageToGroup($name, $imageid)
/// \param $name - the name of an imageGroup
/// \param $imageid - the id of an image
/// \b success - image was added to the group\n
/// \brief adds an image to a resource group\n
///
////////////////////////////////////////////////////////////////////////////////
function XMLRPCaddImageToGroup($name, $imageid){
    $groups = getUserResources(array("imageAdmin"), array("manageGroup"), 1);
    
    if($groupid = getResourceGroupID("image/$name")){
        if(!array_key_exists($groupid, $groups['image'])){
            return array('status' => 'error',
                         'errorcode' => 46,
                         'errormsg' => 'Unable to access image group');
        }
        $resources = getUserResources(array("imageAdmin"), array("manageGroup"));
        if(!array_key_exists($imageid, $resources['image'])){
            return array('status' => 'error',
                         'errorcode' => 47,
                         'errormsg' => 'Unable to access image');
        }

        $allimages = getImages();
        $query = "INSERT IGNORE INTO resourcegroupmembers "
               . "(resourceid, resourcegroupid) VALUES "
               . "({$allimages[$imageid]['resourceid']}, $groupid)";
        doQuery($query, 287);
        return array('status' => 'success');
    } else {
        return array('status' => 'error',
                     'errorcode' => 83,
                     'errormsg' => 'invalid resource group name');
    }
/// \b success - returns an array of images\n
/// \brief gets a list of all images in a particular group\n
function XMLRPCgetGroupImages($name){
    if($groupid = getResourceGroupID("image/$name")){
        $membership = getResourceGroupMemberships('image');
        $resources = getUserResources(array("imageAdmin"),
                                      array("manageGroup"));
        $images = array();
        foreach($resources['image'] as $imageid => $image){
            if(array_key_exists($imageid, $membership['image']) &&
                    in_array($groupid, $membership['image'][$imageid])){
                array_push($images, array('id' => $imageid, 'name' => $image));
            }
        }
        return array('status' => 'success',
                     'images' => $images);
    } else {
        return array('status' => 'error',
                     'errorcode' => 83,
                     'errormsg' => 'invalid resource group name');
    }
/// \fn XMLRPCaddImageGroupToComputerGroup($imageGroup, $computerGroup)
/// \param $imageGroup - the name of an imageGroup
/// \param $computerGroup - the name of a computerGroup
/// \b success - successfully mapped an image group to a computer group\n
/// \brief map an image group to a computer group\n
///
////////////////////////////////////////////////////////////////////////////////
function XMLRPCaddImageGroupToComputerGroup($imageGroup, $computerGroup){
    $imageid = getResourceGroupID("image/$imageGroup");
    $compid = getResourceGroupID("computer/$computerGroup");
    if($imageid && $compid){
        $tmp = getUserResources(array("imageAdmin"),
                                array("manageMapping"), 1);
        $imagegroups = $tmp['image'];
        $tmp = getUserResources(array("computerAdmin"),
                                array("manageMapping"), 1);
        $computergroups = $tmp['computer'];

        if(array_key_exists($compid, $computergroups) &&
            array_key_exists($imageid, $imagegroups)){
            $mapping = getResourceMapping("image", "computer",
                                          $imageid,
                                          $compid);
            if(!array_key_exists($imageid, $mapping) ||
                !array_key_exists($compid, $mapping[$imageid])){
                $query = "INSERT INTO resourcemap "
                       .        "(resourcegroupid1, "
			           .        "resourcetypeid1, "
			           .        "resourcegroupid2, "
			           .        "resourcetypeid2) "
			           . "VALUES ($imageid, "
			           .         "13, "
			           .         "$compid, "
			           .         "12)";
                doQuery($query, 101);
            }
            return array('status' => 'success');
        } else {
            return array('status' => 'error',
                         'errorcode' => 84,
                         'errormsg' => 'cannot access computer and/or image group');
        }
    } else {
        return array('status' => 'error',
                     'errorcode' => 83,
                     'errormsg' => 'invalid resource group name');
    }
/// \fn XMLRPCremoveImageGroupFromComputerGroup($imageGroup, $computerGroup)
/// \b success - successfully removed the mapping from an 
///     image group to a computer group\n
///
/// \brief remove the mapping of an image group to a computer group\n
///
////////////////////////////////////////////////////////////////////////////////
function XMLRPCremoveImageGroupFromComputerGroup($imageGroup, $computerGroup){
    $imageid = getResourceGroupID("image/$imageGroup");
    $compid = getResourceGroupID("computer/$computerGroup");
    if($imageid && $compid){
        $tmp = getUserResources(array("imageAdmin"),
                                array("manageMapping"), 1);
        $imagegroups = $tmp['image'];
        $tmp = getUserResources(array("computerAdmin"),
                                array("manageMapping"), 1);
        $computergroups = $tmp['computer'];

        if(array_key_exists($compid, $computergroups) &&
            array_key_exists($imageid, $imagegroups)){
            $mapping = getResourceMapping("image", "computer",
                                          $imageid,
                                          $compid);
            if(array_key_exists($imageid, $mapping) &&
                array_key_exists($compid, $mapping[$imageid])){
                $query = "DELETE FROM resourcemap "
					   . "WHERE resourcegroupid1 = $imageid AND "
					   .       "resourcetypeid1 = 13 AND "
					   .       "resourcegroupid2 = $compid AND "
					   .       "resourcetypeid2 = 12";
			    doQuery($query, 101);
            }
            return array('status' => 'success');
        } else {
            return array('status' => 'error',
                         'errorcode' => 84,
                         'errormsg' => 'cannot access computer and/or image group');
        }
    } else {
        return array('status' => 'error',
                     'errorcode' => 83,
                     'errormsg' => 'invalid resource group name');
    }

/// \fn XMLRPCgetNodes($root)
/// \param $root - (optional) the ID of the node forming the root of the hierarchy
/// \b success - returns an array of nodes\n
///
/// \brief gets a list of all nodes in the privilege tree\n
///
////////////////////////////////////////////////////////////////////////////////
function XMLRPCgetNodes($root=NULL){
    global $user;
    if(in_array("nodeAdmin", $user['privileges'])){
        $topNodes = $root ? getChildNodes($root) : getChildNodes();
        $nodes = array();
        $stack = array();
        foreach($topNodes as $id => $node){
            $node['id'] = $id;
            array_push($nodes, $node);
            array_push($stack, $node);
        } 
        while(count($stack)){
            $item = array_shift($stack);
            $children = getChildNodes($item['id']);
            foreach($children as $id => $node){
                $node['id'] = $id;
                array_push($nodes, $node);
                array_push($stack, $node);
            }
        }
        return array(
            'status' => 'success',
            'nodes' => $nodes);
    } else {
        return array(
            'status' => 'error',
            'errorcode' => 56,
            'errormsg' => 'User cannot access node content');
    }
/// \fn XMLRPCnodeExists($nodeName, $parentNode)
/// \param $nodeName - the name of a node
/// \param $parentNode - the ID of the parent node
/// \b success - returns an 'exists' element set to either 1 or 0\n
/// \brief indicates whether a node with that name already exists at this
///     location in the privilege tree\n
///
////////////////////////////////////////////////////////////////////////////////
function XMLRPCnodeExists($nodeName, $parentNode){
    global $user;
    if(in_array("nodeAdmin", $user['privileges'])){
        // does a node with this name already exist?
        $query = "SELECT id "
               . "FROM privnode "
               . "WHERE name = '$nodeName' AND parent = $parentNode";
        $qh = doQuery($query, 335);
        if(mysql_num_rows($qh)){
            return array('status' => 'success', 'exists' => TRUE);
        } else {
            return array('status' => 'success', 'exists' => FALSE);
        }
    } else {
        return array(
            'status' => 'error',
            'errorcode' => 56,
            'errormsg' => 'User cannot access node content');
    }
/// \fn XMLRPCremoveNode($nodeID)
/// \param $nodeID - the ID of a node
/// \b success - node was successfully deleted
/// \brief delete a node in the privilege tree\n
///
////////////////////////////////////////////////////////////////////////////////
function XMLRPCremoveNode($nodeID){
    require_once(".ht-inc/privileges.php");
    global $user;
    if(!in_array("nodeAdmin", $user['privileges'])){
        return array(
            'status' => 'error',
            'errorcode' => 56,
            'errormsg' => 'User cannot administer nodes');
    }
    if(!checkUserHasPriv("nodeAdmin", $user['id'], $nodeID)){
        return array(
            'status' => 'error',
            'errorcode' => 57,
            'errormsg' => 'User cannot edit this node');
    }
    $nodes = recurseGetChildren($nodeID);
    array_push($nodes, $nodeID);
    $deleteNodes = implode(',', $nodes);
    $query = "DELETE FROM privnode "
           . "WHERE id IN ($deleteNodes)";
    doQuery($query, 345);
    return array(
            'status' => 'success');
/// \brief add a node to the privilege tree as a child of the
///     specified parent node\n
///
////////////////////////////////////////////////////////////////////////////////
function XMLRPCaddNode($nodeName, $parentNode){
    require_once(".ht-inc/privileges.php");
    global $user;
    if(in_array("nodeAdmin", $user['privileges'])){
        if(!$parentNode){
            $topNodes = getChildNodes();
            $keys = array_keys($topNodes);
            $parentNode = array_shift($keys);
        }

        if(!preg_match("/^[-A-Za-z0-9_\. ]+$/", $nodeName)){
            return array('status' => 'error',
                         'errorcode' => 48,
                         'errormsg' => 'Invalid node name');
        }

        if(checkUserHasPriv("nodeAdmin", $user['id'], $parentNode)){
            $nodeInfo = getNodeInfo($parentNode);
            $query = "SELECT id "
                   . "FROM privnode "
                   . "WHERE name = '$nodeName' AND parent = $parentNode";
            $qh = doQuery($query, 335);
            if(mysql_num_rows($qh)){
                return array('status' => 'error',
                             'errorcode' => 50,
                             'errormsg' => 'A node of that name already exists under ' . $nodeInfo['name']);
            }
            $query = "INSERT IGNORE INTO privnode "
                   .        "(parent, name) "
                   . "VALUES "
                   .        "($parentNode, '$nodeName')";
            doQuery($query, 337);
            $qh = doQuery("SELECT LAST_INSERT_ID() FROM privnode", 101);
            if(!$row = mysql_fetch_row($qh)){
                return array('status' => 'error',
                             'errorcode' => 51,
                             'errormsg' => 'Could not add node to database');
            }
            $nodeid = $row[0];
            return array('status' => 'success',
                         'nodeid' => $nodeid);
        } else {
            return array('status' => 'error',
                         'errorcode' => 49,
                         'errormsg' => 'Unable to add node at this location');
        }
    } else {
        return array(
            'status' => 'error',
            'errorcode' => 56,
            'errormsg' => 'User cannot access node content');
    }
}
/// \fn XMLRPCremoveResourceGroupPriv($name, $type, $nodeid, $permissions)
/// \param $name - the name of the resource group
/// \param $type - the resource type
/// \param $nodeid - the ID of the node in the privilege tree
/// \param $permissions - a colon (:) delimited list of privileges to remove
/// \b success - privileges were successfully removed
/// \brief remove privileges from a resource group in the privilege
///      node tree\n
///
////////////////////////////////////////////////////////////////////////////////
function XMLRPCremoveResourceGroupPriv($name, $type, $nodeid, $permissions){
    require_once(".ht-inc/privileges.php");
    global $user;

    if(! checkUserHasPriv("resourceGrant", $user['id'], $nodeid)){
        return array('status' => 'error',
                     'errorcode' => 53,
                     'errormsg' => 'Unable to remove group privileges on this node');
    }
    if($typeid = getResourceTypeID($type)){
        if(!checkForGroupName($name, 'resource', '', $typeid)){
            return array('status' => 'error',
                         'errorcode' => 28,
                         'errormsg' => 'resource group does not exist');
        }
        $perms = explode(':', $permissions);
        updateResourcePrivs("$type/$name", $nodeid, array(), $perms);
        return array('status' => 'success');
    } else {
        return array('status' => 'error',
                     'errorcode' => 56,
                     'errormsg' => 'Invalid resource type');
    }
/// \b success - an additional element is returned:
/// \li \b privileges - a list of privileges available
/// \brief get a list of privileges for a user group at a particular
///     location in the privilege tree\n
function XMLRPCgetUserGroupPrivs($name, $affiliation, $nodeid){
    require_once(".ht-inc/privileges.php");
    global $user;
    if(! checkUserHasPriv("userGrant", $user['id'], $nodeid)){
        return array('status' => 'error',
                     'errorcode' => 53,
                     'errormsg' => 'Unable to add resource group to this node');
    }
    $privileges = array();
    $nodePrivileges = getNodePrivileges($nodeid, 'usergroups');
    $cascadedNodePrivileges = getNodeCascadePrivileges($nodeid, 'usergroups'); 
    $cngp = $cascadedNodePrivileges['usergroups'];
    $ngp = $nodePrivileges['usergroups'];
    if(array_key_exists($name, $cngp)){
        foreach($cngp[$name]['privs'] as $p){
            if(! array_key_exists($name, $ngp) ||
                    ! in_array("block", $ngp[$name]['privs'])){
                array_push($privileges, $p);
            }
        }
    }
    if(array_key_exists($name, $ngp)){
        foreach($ngp[$name]['privs'] as $p){
            if($p != "block"){
                array_push($privileges, $p);
            }
        }
    }

    return array(
        'status' => 'success',
        'privileges' => array_unique($privileges));
}
////////////////////////////////////////////////////////////////////////////////
///
/// \fn XMLRPCgetResourceGroupPrivs($name, $type, $nodeid)
///
/// \param $name - the name of the resource group
/// \param $type - the resource group type
/// \param $nodeid - the ID of the node in the privilege tree
///
/// \return an array with at least one index named 'status' which will have
/// one of these values\n
/// \b error - error occurred; there will be 2 additional elements in the array:
/// \li \b errorcode - error number\n
/// \li \b errormsg - error string\n
///
/// \b success - an additional element is returned:
/// \li \b privileges - a list of privileges available
///
/// \brief get a list of privileges for a resource group at a particular
///     location in the privilege tree\n
///
////////////////////////////////////////////////////////////////////////////////
function XMLRPCgetResourceGroupPrivs($name, $type, $nodeid){
    require_once(".ht-inc/privileges.php");
    global $user;

    if(! checkUserHasPriv("resourceGrant", $user['id'], $nodeid)){
        return array('status' => 'error',
                     'errorcode' => 53,
                     'errormsg' => 'Unable to add resource group to this node');
    }

    if($typeid = getResourceTypeID($type)){
        if(!checkForGroupName($name, 'resource', '', $typeid)){
            return array('status' => 'error',
                         'errorcode' => 28,
                         'errormsg' => 'resource group does not exist');
        }
        $nodePrivileges = getNodePrivileges($nodeid, 'resources');
        $nodePrivileges = getNodeCascadePrivileges($nodeid, 'resources', $nodePrivileges); 
        foreach($nodePrivileges['resources'] as $resource => $privs){
            if(strstr($resource, "$type/$name")){
                return array(
                    'status' => 'success',
                    'privileges' => $privs);
            }
        }
        return array(
            'status' => 'error',
            'errorcode' => 29,
            'errormsg' => 'could not find resource name in privilege list');
    } else {
        return array('status' => 'error',
                     'errorcode' => 56,
                     'errormsg' => 'Invalid resource type');
    }
/// \fn XMLRPCaddResourceGroupPriv($name, $type, $nodeid, $permissions)
/// \param $name - the name of the resource group
/// \param $type - the resource group type
/// \brief add privileges for a resource group at a particular
///     location in the privilege tree\n
///
////////////////////////////////////////////////////////////////////////////////
function XMLRPCaddResourceGroupPriv($name, $type, $nodeid, $permissions){
    require_once(".ht-inc/privileges.php");
    global $user;

    if(! checkUserHasPriv("resourceGrant", $user['id'], $nodeid)){
        return array('status' => 'error',
                     'errorcode' => 53,
                     'errormsg' => 'Unable to add resource group to this node');
    }

    if($typeid = getResourceTypeID($type)){
        if(!checkForGroupName($name, 'resource', '', $typeid)){
            return array('status' => 'error',
                         'errorcode' => 28,
                         'errormsg' => 'resource group does not exist');
        }
        $perms = explode(':', $permissions);
        updateResourcePrivs("$type/$name", $nodeid, $perms, array());
        return array('status' => 'success');
    } else {
        return array('status' => 'error',
                     'errorcode' => 56,
                     'errormsg' => 'Invalid resource type');
    }
}


////////////////////////////////////////////////////////////////////////////////
///
/// \fn XMLRPCremoveResourceGroupPriv($name, $type, $nodeid, $permissions)
///
/// \param $name - the name of the resource group
/// \param $type - the resource group type
/// \param $nodeid - the ID of the node in the privilege tree
/// \param $permissions - a colon (:) delimited list of privileges to remove
///
/// \return an array with at least one index named 'status' which will have
/// one of these values\n
/// \b error - error occurred; there will be 2 additional elements in the array:
/// \li \b errorcode - error number\n
/// \li \b errormsg - error string\n
///
/// \b success - privileges were successfully removed
///
/// \brief remove privileges for a resource group at a particular
///     location in the privilege tree\n
function XMLRPCremoveUserGroupPriv($name, $affiliation, $nodeid, $permissions){
    require_once(".ht-inc/privileges.php");
    if(! checkUserHasPriv("userGrant", $user['id'], $nodeid)){
                     'errorcode' => 53,
                     'errormsg' => 'Unable to remove group privileges on this node');
    }
    $groupid = $rc['id'];
    $groupname = "$name@$affiliation";
    $perms = explode(':', $permissions);
    $usertypes = getTypes('users');
    array_push($usertypes["users"], "block");
    $cascadePrivs = getNodeCascadePriviliges($nodeid, "usergroups");
    $removegroupprivs = array();
    if(array_key_exists($groupname, $cascadePrivs['usergroups'])){
        foreach($perms as $permission){
            if(in_array($permission, $cascadePrivs['usergroups'][$groupname]['privs'])){
                array_push($removegroupprivs, $permission);
            }
        }
        $diff = array_diff($cascadePrivs['usergroups'][$groupname], $removegroupprivs);
        if(count($diff) == 1 && in_array("cascade", $diff)){
            array_push($removegroupprivs, "cascade");
	}
	}
    if(empty($removegroupprivs)){
                     'errorcode' => 53,
                     'errormsg' => 'Invalid or missing permissions list supplied');
    updateUserOrGroupPrivs($groupid, $nodeid, array(), $removegroupprivs, "group");
    return array('status' => 'success');
/// \fn XMLRPCaddUserGroupPriv($name, $affiliation, $nodeid, $permissions)
/// \param $permissions - a colon (:) delimited list of privileges to add
/// \b success - privileges were successfully added
/// \brief add privileges for a user group at a particular
///     location in the privilege tree\n
function XMLRPCaddUserGroupPriv($name, $affiliation, $nodeid, $permissions){
    require_once(".ht-inc/privileges.php");
    global $user;
    if(! checkUserHasPriv("userGrant", $user['id'], $nodeid)){
                     'errorcode' => 52,
                     'errormsg' => 'Unable to add a user group to this node');
    $groupid = $rc['id'];
    $perms = explode(':', $permissions);
    $usertypes = getTypes('users');
    array_push($usertypes["users"], "block");
	$newgroupprivs = array();
	foreach($usertypes["users"] as $type) {
		if(in_array($type, $perms))
			array_push($newgroupprivs, $type);
	}
	if(empty($newgroupprivs) || (count($newgroupprivs) == 1 && 
           in_array("cascade", $newgroupprivs))) {
                    'errorcode' => 53,
                    'errormsg' => 'Invalid or missing permissions list supplied');
    updateUserOrGroupPrivs($groupid, $nodeid, $newgroupprivs, array(), "group");
	return array('status' => 'success');
}
/// \fn XMLRPCsetRequestEnding($requestid, $end)
/// \param $requestid - id of a request
/// \param $end - unix timestamp for end of reservation; will be rounded up to
/// the nearest 15 minute increment
/// \b success - request was successfully extended\n
/// \brief modifies the end time of an active request; if a request that has not
/// started needs to be modifed, delete the request and submit a new one\n
function XMLRPCsetRequestEnding($requestid, $end) {
	$requestid = processInputData($requestid, ARG_NUMERIC);
	$userRequests = getUserRequests('all', $user['id']);
	$found = 0;
	foreach($userRequests as $req) {
		if($req['id'] == $requestid) {
			$request = getRequestInfo($requestid);
			$found = 1;
			break;
		}
	if(! $found)
		             'errorcode' => 1,
		             'errormsg' => 'unknown requestid');
	// make sure user is a member of the 'Specify End Time' group
	$groupid = getUserGroupID('Specify End Time');
	$members = getUserGroupMembers($groupid);
	if(! $request['serverrequest'] && ! array_key_exists($user['id'], $members)) {
		return array('status' => 'error',
		             'errorcode' => 35,
		             'errormsg' => "access denied to specify end time");
	}

	$end = processInputData($end, ARG_NUMERIC);

	$startts = datetimeToUnix($request['start']);
	if($end % (15 * 60))
		$end= unixFloor15($end) + (15 * 60);

	// check that reservation has started
	if($startts > time()) {
		return array('status' => 'error',
		             'errorcode' => 38,
		             'errormsg' => 'reservation has not started');
	}

	// check for overlap
	$max = getMaxOverlap($user['id']);
	if(checkOverlap($startts, $end, $max, $requestid)) {
		return array('status' => 'error',
		             'errorcode' => 41,
		             'errormsg' => 'overlapping reservation restriction',
		             'maxoverlap' => $max);
	}

	// check for computer being available for extended time?
	$timeToNext = timeToNextReservation($request);
	$movedall = 1;
	if($timeToNext > -1) {
		foreach($request["reservations"] as $res) {
			if(! moveReservationsOffComputer($res["computerid"])) {
				$movedall = 0;
				break;
			}
		}
	}
	if(! $movedall) {
		$timeToNext = timeToNextReservation($request);
		if($timeToNext >= 15)
			$timeToNext -= 15;
		$oldendts = datetimeToUnix($request['end']);
		// reservation immediately after this one, cannot extend
		if($timeToNext < 15) {
			             'errorcode' => 42,
			             'errormsg' => 'cannot extend due to another reservation immediately after this one');
		// check that requested extension < $timeToNext
		elseif((($end - $oldendts) / 60) > $timeToNext) {
			$maxend = $oldendts + ($timeToNext * 60);
			return array('status' => 'error',
			             'errorcode' => 43,
			             'errormsg' => 'cannot extend by requested amount due to another reservation',
			             'maxend' => $maxend);
	$rc = isAvailable(getImages(), $request['reservations'][0]["imageid"],
	                  $request['reservations'][0]['imagerevisionid'],
	                  $startts, $end, $requestid);
	// conflicts with scheduled maintenance
	if($rc == -2) {
		addChangeLogEntry($request["logid"], NULL, unixToDatetime($end),
		                  $request['start'], NULL, NULL, 0);
		return array('status' => 'error',
		             'errorcode' => 46,
		             'errormsg' => 'requested time is during a maintenance window');
	}
	// concurrent license overlap
	elseif($rc == -1) {
		addChangeLogEntry($request["logid"], NULL, unixToDatetime($end),
		                  $request['start'], NULL, NULL, 0);
		return array('status' => 'error',
		             'errorcode' => 44,
		             'errormsg' => 'concurrent license restriction');
	}
	// could not extend for some other reason
	elseif($rc == 0) {
		addChangeLogEntry($request["logid"], NULL, unixToDatetime($end),
		                  $request['start'], NULL, NULL, 0);
		             'errorcode' => 45,
		             'errormsg' => 'cannot extend at this time');
	// success
	updateRequest($requestid);
	return array('status' => 'success');
/// \fn XMLRPCendRequest($requestid)
/// \param $requestid - id of a request
/// \b success - request was successfully ended\n
/// \brief ends/deletes a request\n
function XMLRPCendRequest($requestid) {
	global $user;
	$requestid = processInputData($requestid, ARG_NUMERIC);
	$userRequests = getUserRequests('all', $user['id']);
	$found = 0;
	foreach($userRequests as $req) {
		if($req['id'] == $requestid) {
			$request = getRequestInfo($requestid);
			$found = 1;
			break;
		}
	}
	if(! $found)
		return array('status' => 'error',
		             'errorcode' => 1,
		             'errormsg' => 'unknown requestid');

	deleteRequest($request);
	return array('status' => 'success');
/// \fn XMLRPCgetRequestIds()
/// \b success - request was successfully ended; there will be an additional
/// element whose index is 'requests' which is an array of arrays, each having
/// these elements (or empty if no existing requests):\n
/// \li \b requestid - id of the request\n
/// \li \b imageid - id of the image\n
/// \li \b imagename - name of the image\n
/// \li \b start - unix timestamp of start time\n
/// \li \b end - unix timestamp of end time
/// \brief gets information about all of user's requests\n
function XMLRPCgetRequestIds() {
	$requests = getUserRequests("all");
	if(empty($requests))
		return array('status' => 'success', 'requests' => array());
	$ret = array();
	foreach($requests as $req) {
		$start = datetimeToUnix($req['start']);
		$end = datetimeToUnix($req['end']);
		$tmp = array('requestid' => $req['id'],
		             'imageid' => $req['imageid'],
		             'imagename' => $req['prettyimage'],
		             'start' => $start,
		             'end' => $end);
		array_push($ret, $tmp);
	return array('status' => 'success', 'requests' => $ret);
/// \fn XMLRPCblockAllocation($imageid, $start, $end, $numMachines,
///                           $usergroupid, $ignoreprivileges)
/// \param $imageid - id of the image to be used
/// \param $start - mysql datetime for the start time (i.e. machines should be
/// prep'd and ready by this time)
/// \param $end - mysql datetime for the end time
/// \param $numMachines - number of computers to allocate
/// \param $usergroupid - id of user group for checking user access to machines
/// \param $ignoreprivileges (optional, default=0) - 0 (false) or 1 (true) - set
/// to 1 to select computers from any that are mapped to be able to run the
/// image; set to 0 to only select computers from ones that are both mapped and
/// that users in the usergroup assigned to this block allocation have been
/// granted access to through the privilege tree
/// \return an array with blockTimesid as an index with the value of the newly
/// created block time and at least one other index named 'status' which will
/// have one of these values:\n
/// \b success - blockTimesid was processed; there will be two additional
/// elements in this case:
/// \li \b allocated - total number of desired allocations that have been
/// processed
/// \li \b unallocated - total number of desired allocations that have not been
/// processed
///
/// \b warning - there was a non-fatal issue that occurred while processing
/// the call; there will be four additional elements in this case:
/// \li \b warningcode - warning number
/// \li \b warningmsg - warning string
/// \li \b allocated - total number of desired allocations that have been
/// processed
/// \li \b unallocated - total number of desired allocations that have not been
/// processed
///
/// note that status may be warning, but allocated may be 0 indicating there
/// were no errors that occurred, but there simply were not any machines
/// available
/// \brief creates and processes a block allocation according to the passed
/// in criteria\n
function XMLRPCblockAllocation($imageid, $start, $end, $numMachines,
                               $usergroupid, $ignoreprivileges=0) {
	global $user, $xmlrpcBlockAPIUsers;
	if(! in_array($user['id'], $xmlrpcBlockAPIUsers)) {
		return array('status' => 'error',
		             'errorcode' => 34,
		             'errormsg' => 'access denied for managing block allocations');
	}
	$ownerid = getUserlistID('vclreload@Local');
	$name = "API:$start";
	$managementnodes = getManagementNodes('future');
	if(empty($managementnodes)) {
		             'errorcode' => 12,
		             'errormsg' => 'could not allocate a management node to handle block allocation');
	$mnid = array_rand($managementnodes);
	$query = "INSERT INTO blockRequest "
	       .        "(name, "
	       .        "imageid, "
	       .        "numMachines, "
	       .        "groupid, "
	       .        "repeating, "
	       .        "ownerid, "
	       .        "admingroupid, "
	       .        "managementnodeid, "
	       .        "expireTime, "
	       .        "status) "
	       . "VALUES "
	       .        "('$name', "
	       .        "$imageid, "
	       .        "$numMachines, "
	       .        "$usergroupid, "
	       .        "'list', "
	       .        "$ownerid, "
	       .        "0, "
	       .        "$mnid, "
	       .        "'$end', "
	       .        "'accepted')";
	doQuery($query, 101);
	$brid = dbLastInsertID();
	$query = "INSERT INTO blockTimes "
	       .        "(blockRequestid, "
	       .        "start, "
	       .        "end) "
	       . "VALUES "
	       .        "($brid, "
	       .        "'$start', "
	       .        "'$end')";
	doQuery($query, 101);
	$btid = dbLastInsertID();
	$return = XMLRPCprocessBlockTime($btid, $ignoreprivileges);
	$return['blockTimesid'] = $btid;
	return $return;
/// \fn XMLRPCprocessBlockTime($blockTimesid, $ignoreprivileges)
/// \param $blockTimesid - id from the blockTimes table
/// \param $ignoreprivileges (optional, default=0) - 0 (false) or 1 (true) - set
/// to 1 to select computers from any that are mapped to be able to run the
/// image; set to 0 to only select computers from ones that are both mapped and
/// that users in the usergroup assigned to this block allocation have been
/// granted access to through the privilege tree
/// \li \b errorcode - error number
/// \b completed - blockTimesid was previously successfully processed\n
/// \b success - blockTimesid was processed; there will be two additional
/// elements in this case:
/// \li \b allocated - total number of desired allocations that have been
/// processed
/// \li \b unallocated - total number of desired allocations that have not been
/// processed
/// \b warning - there was a non-fatal issue that occurred while processing
/// the call; there will be four additional elements in this case:
/// \li \b warningcode - warning number
/// \li \b warningmsg - warning string
/// \li \b allocated - total number of desired allocations that have been
/// processed
/// \li \b unallocated - total number of desired allocations that have not been
/// processed
/// note that status may be warning, but allocated may be 0 indicating there
/// were no errors that occurred, but there simply were not any machines
/// available
/// \brief processes a block allocation for the blockTimes entry associated
/// with blockTimesid\n
function XMLRPCprocessBlockTime($blockTimesid, $ignoreprivileges=0) {
	global $requestInfo, $user, $xmlrpcBlockAPIUsers;
	if(! in_array($user['id'], $xmlrpcBlockAPIUsers)) {
		             'errorcode' => 34,
		             'errormsg' => 'access denied for managing block allocations');
	$return = array('status' => 'success');
	$query = "SELECT bt.start, "
	       .        "bt.end, "
	       .        "br.imageid, "
	       .        "br.numMachines, "
	       .        "br.groupid, "
	       .        "br.expireTime "
	       . "FROM blockRequest br, "
	       .      "blockTimes bt "
	       . "WHERE bt.blockRequestid = br.id AND "
	       .       "bt.id = $blockTimesid";
	if(! $rqdata = mysql_fetch_assoc($qh)) {
		             'errorcode' => 8,
		             'errormsg' => 'unknown blockTimesid');
	if(datetimeToUnix($rqdata['expireTime']) < time()) {
		             'errorcode' => 9,
		             'errormsg' => 'expired block allocation');
	$images = getImages(0, $rqdata['imageid']);
	if(empty($images)) {
		return array('status' => 'error',
		             'errorcode' => 10,
		             'errormsg' => 'invalid image associated with block allocation');
	}

	$unixstart = datetimeToUnix($rqdata['start']);
	$unixend = datetimeToUnix($rqdata['end']);
	$revisionid = getProductionRevisionid($rqdata['imageid']);
	$imgLoadTime = getImageLoadEstimate($rqdata['imageid']);
	if($imgLoadTime == 0)
		$imgLoadTime = $images[$rqdata['imageid']]['reloadtime'] * 60;
	$vclreloadid = getUserlistID('vclreload@Local');
	$groupmembers = getUserGroupMembers($rqdata['groupid']);
	$userids = array_keys($groupmembers);

	# add any computers from future reservations users in the group made
	if(! empty($groupmembers)) {
		## find reservations by users
		$allids = implode(',', $userids);
		$query = "SELECT rq.id AS reqid, "
		       .        "UNIX_TIMESTAMP(rq.start) AS start, "
		       .        "rq.userid "
		       . "FROM request rq, "
		       .      "reservation rs "
		       . "WHERE rs.requestid = rq.id AND "
		       .       "rq.userid IN ($allids) AND "
		       .       "rq.start < '{$rqdata['end']}' AND "
		       .       "rq.end > '{$rqdata['start']}' AND "
		       .       "rs.imageid = {$rqdata['imageid']} AND "
		       .       "rs.computerid NOT IN (SELECT computerid "
		       .                             "FROM blockComputers "
		       .                             "WHERE blockTimeid = $blockTimesid)";
		$qh = doQuery($query);
		$donereqids = array();
		$blockCompVals = array();
		$checkstartbase = $unixstart - $imgLoadTime - 300;
		$reloadstartbase = unixToDatetime($checkstartbase);
		$rows = mysql_num_rows($qh);
		while($row = mysql_fetch_assoc($qh)) {
			if(array_key_exists($row['reqid'], $donereqids))
				continue;
			$donereqids[$row['reqid']] = 1;
			if($row['start'] < datetimeToUnix($rqdata['start'])) {
				$checkstart = $row['start'] - $imgLoadTime - 300;
				$reloadstart = unixToDatetime($checkstart);
				$reloadend = unixToDatetime($row['start']);
			}
			else {
				$checkstart = $checkstartbase;
				$reloadstart = $reloadstartbase;
				$reloadend = $rqdata['start'];
			}
			# check to see if computer is available for whole block
			$rc = isAvailable($images, $rqdata['imageid'], $revisionid, $checkstart,
			                  $unixend, $row['reqid'], $row['userid'],
			                  $ignoreprivileges, 0, '', '', 1);
			// if not available for whole block, just skip this one
			if($rc < 1)
				continue;
			$compid = $requestInfo['computers'][0];
			# create reload reservation
			$reqid = simpleAddRequest($compid, $rqdata['imageid'], $revisionid,
			                          $reloadstart, $reloadend, 19, $vclreloadid);
			if($reqid == 0)
				continue;
			# add to blockComputers
			$blockCompVals[] = "($blockTimesid, $compid, {$rqdata['imageid']}, $reqid)";
			# process any subimages
			for($key = 1; $key < count($requestInfo['computers']); $key++) {
				$subimageid = $requestInfo['images'][$key];
				$subrevid = getProductionRevisionid($subimageid);
				$compid = $requestInfo['computers'][$key];
				$mgmtnodeid = $requestInfo['mgmtnodes'][$key];
				$blockCompVals[] = "($blockTimesid, $compid, $subimageid, $reqid)";

				$query = "INSERT INTO reservation "
						 .        "(requestid, "
						 .        "computerid, "
						 .        "imageid, "
						 .        "imagerevisionid, "
						 .        "managementnodeid) "
						 . "VALUES "
						 .       "($reqid, "
						 .       "$compid, "
						 .       "$subimageid, "
						 .       "$subrevid, "
						 .       "$mgmtnodeid)";
				doQuery($query, 101);
			}
		}
		if(count($blockCompVals)) {
			$blockComps = implode(',', $blockCompVals);
			$query = "INSERT INTO blockComputers "
			       .        "(blockTimeid, computerid, imageid, reloadrequestid) "
			       . "VALUES $blockComps";
			doQuery($query);
		}
	}

	# check to see if all computers have been allocated
	$query = "SELECT COUNT(computerid) AS allocated "
	       . "FROM blockComputers "
	       . "WHERE blockTimeid = $blockTimesid";
	$qh = doQuery($query, 101);
	if(! $row = mysql_fetch_assoc($qh)) {
		return array('status' => 'error',
		             'errorcode' => 15,
		             'errormsg' => 'failure to communicate with database');
	}
	$compCompleted = $row['allocated'];
	if(array_key_exists('subimages', $images[$rqdata['imageid']]))
		$compsPerAlloc = 1 + count($images[$rqdata['imageid']]['subimages']);
	else
		$compsPerAlloc = 1;
	$toallocate = ($rqdata['numMachines'] * $compsPerAlloc) - $compCompleted;
	if($toallocate == 0) {
		if(count($blockCompVals)) {
			return array('status' => 'success',
			             'allocated' => $rqdata['numMachines'],
			             'unallocated' => 0);
		return array('status' => 'completed');
	}
	$reqToAlloc = $toallocate / $compsPerAlloc;

	if(! $ignoreprivileges) {
		# get userids in user group
		if(empty($groupmembers)) {
			return array('status' => 'error',
			             'errorcode' => 11,
			             'errormsg' => 'empty user group and ignoreprivileges set to 0');
		# make length of $userids match $reqToAlloc by duplicating or trimming some users
		while($reqToAlloc > count($userids))
			$userids = array_merge($userids, $userids);
		if($reqToAlloc < count($userids))
			$userids = array_splice($userids, 0, $reqToAlloc);
	}

	# staggering: stagger start times for this round (ie, don't worry about
	#   previous processing of this block time) such that there is 1 minute
	#   between the start times for each allocation
	$stagExtra = $reqToAlloc * 60;

	# determine estimated load time
	$loadtime = $imgLoadTime + (10 * 60); # add 10 minute fudge factor
	if((time() + $loadtime + $stagExtra) > $unixstart) {
		$return['status'] = 'warning';
		$return['warningcode'] = 13;
		$return['warningmsg'] = 'possibly insufficient time to load machines';
	$start = unixToDatetime($unixstart - $loadtime);

	$userid = 0;
	$allocated = 0;
	$blockCompVals = array();
	# FIXME (maybe) - if some subset of users in the user group have available
	# computers, but others do not, $allocated will be less than the desired
	# number of machines; however, calling this function enough times will
	# result in enough machines being allocated because they will continue to be
	# allocated based on the ones with machines available; this seems like odd
	# behavior
	$stagCnt = 0;
	$stagTime = 60;        # stagger reload reservations by 1 min
	if($imgLoadTime > 840) // if estimated load time is > 14 min
		$stagTime = 120;    #    stagger reload reservations by 2 min 
	for($i = 0; $i < $reqToAlloc; $i++) {
		$stagunixstart = $unixstart - $loadtime - ($stagCnt * $stagTime);
		$stagstart = unixToDatetime($stagunixstart);
		if(! $ignoreprivileges)
			$userid = array_pop($userids);
		# use end of block time to find available computers, but...
		$rc = isAvailable($images, $rqdata['imageid'], $revisionid, $stagunixstart,
		                  $unixend, 0, $userid, $ignoreprivileges);
		if($rc < 1)
			continue;

		$compid = $requestInfo['computers'][0];
		# ...use start of block time as end of reload reservation
		$reqid = simpleAddRequest($compid, $rqdata['imageid'], $revisionid,
		                          $stagstart, $rqdata['start'], 19, $vclreloadid);
		if($reqid == 0)
			continue;

		$stagCnt++;
		$allocated++;
		$blockCompVals[] = "($blockTimesid, $compid, {$rqdata['imageid']}, $reqid)";

		# process any subimages
		for($key = 1; $key < count($requestInfo['computers']); $key++) {
			$subimageid = $requestInfo['images'][$key];
			$subrevid = getProductionRevisionid($subimageid);
			$compid = $requestInfo['computers'][$key];
			$mgmtnodeid = $requestInfo['mgmtnodes'][$key];
			$blockCompVals[] = "($blockTimesid, $compid, $subimageid, $reqid)";
			$query = "INSERT INTO reservation "
					 .        "(requestid, "
					 .        "computerid, "
					 .        "imageid, "
					 .        "imagerevisionid, "
					 .        "managementnodeid) "
					 . "VALUES "
					 .       "($reqid, "
					 .       "$compid, "
					 .       "$subimageid, "
					 .       "$subrevid, "
					 .       "$mgmtnodeid)";
			doQuery($query, 101);
		semUnlock();
		$blockComps = implode(',', $blockCompVals);
		$query = "INSERT INTO blockComputers "
		       .        "(blockTimeid, computerid, imageid, reloadrequestid) "
		       . "VALUES $blockComps";
		doQuery($query, 101);
		$blockCompVals = array();
	if($allocated == 0) {
		$return['status'] = 'warning';
		$return['warningcode'] = 14;
		$return['warningmsg'] = 'unable to allocate any machines';
	$return['allocated'] = ($compCompleted / $compsPerAlloc) + $allocated;
	$return['unallocated'] = $rqdata['numMachines'] - $return['allocated'];
	return $return;
/// \fn XMLRPCaddUserGroup($name, $affiliation, $owner, $managingGroup,
///                        $initialMaxTime, $totalMaxTime, $maxExtendTime,
///                        $custom)
/// \param $owner - user that will be the owner of the group in
///                 username\@affiliation form
/// \param $managingGroup - user group that can manage membership of this one
/// \param $initialMaxTime - (minutes) max initial time users in this group can
///                          select for length of reservations
/// \param $totalMaxTime - (minutes) total length users in the group can have
///                        for a reservation (including all extensions)
/// \param $maxExtendTime - (minutes) max length of time users can request as an
///                         extension to a reservation at a time
/// \param $custom (optional, default=1) - set custom flag for user group; if
///                set to 0, $owner and $managingGroup will be ignored and group
///                membership will be managed via authentication protocol
/// \b success - user group was successfully created
/// \brief creates a new user group with the specified parameters\n
function XMLRPCaddUserGroup($name, $affiliation, $owner, $managingGroup,
                            $initialMaxTime, $totalMaxTime, $maxExtendTime,
                            $custom=1) {
	global $user;
		             'errormsg' => 'access denied for managing user groups');
	                  'affiliation' => $affiliation,
	                  'owner' => $owner,
	                  'managingGroup' => $managingGroup,
	                  'initialMaxTime' => $initialMaxTime,
	                  'totalMaxTime' => $totalMaxTime,
	                  'maxExtendTime' => $maxExtendTime,
	                  'custom' => $custom);
	$rc = validateAPIgroupInput($validate, 0);
	if($custom != 0 && $custom != 1)
		$custom = 1;
	$data = array('type' => 'user',
	              'owner' => $owner,
	              'name' => $name,
	              'affiliationid' => $rc['affiliationid'],
	              'editgroupid' => $rc['managingGroupID'],
	              'initialmax' => $initialMaxTime,
	              'totalmax' => $totalMaxTime,
	              'maxextend' => $maxExtendTime,
	              'overlap' => 0,
	              'custom' => $custom);
	if(! addGroup($data)) {
		             'errorcode' => 26,
		             'errormsg' => 'failure while adding group to database');
	return array('status' => 'success');
}
////////////////////////////////////////////////////////////////////////////////
///
/// \fn XMLRPCgetResourceGroups($type)
///
/// \param $type - the resource group type
///
/// \return an array with at least one index named 'status' which will have
/// one of these values\n
/// \b error - error occurred; there will be 2 additional elements in the array:
/// \li \b errorcode - error number\n
/// \li \b errormsg - error string\n
///
/// \b success - an 'groups' element will contain a list of groups
///         of the given type.\n
///
/// \brief get a list of resource groups of a particular type\n
///
////////////////////////////////////////////////////////////////////////////////
function XMLRPCgetResourceGroups($type){
    global $user;
    $resources = getUserResources(array("groupAdmin"), array("manageGroup"), 1);
    if(array_key_exists($type, $resources)){
        return array(
            'status' => 'success',
            'groups' => $resources[$type]);
    } else {
        return array(
            'status' => 'error',
            'errorcode' => 29,
            'errormsg' => 'invalid resource group type');
    }
/// \fn XMLRPCremoveResourceGroup($name, $type)
/// \param $name - the name of the resource group
/// \param $type - the resource group type
/// one of these values\n
/// \li \b errorcode - error number\n
/// \li \b errormsg - error string\n
///
/// \b success - the resource group was removed\n
///
/// \brief remove a resource group\n
///
////////////////////////////////////////////////////////////////////////////////
function XMLRPCremoveResourceGroup($name, $type){
    global $user;
    if(! in_array("groupAdmin", $user['privileges'])){
        return array('status' => 'error',
                     'errorcode' => 16,
                     'errormsg' => 'access denied for managing user groups');
    }

    if($groupid = getResourceGroupID("$type/$name")){
        $userresources = getUserResources(
            array("groupAdmin"),
            array("manageGroup"), 1);
        if(array_key_exists($type, $userresources)){
            if(array_key_exists($groupid, $userresources[$type])){
                $query = "DELETE FROM resourcegroup "
                       . "WHERE id = $groupid";
                doQuery($query, 315);
                return array('status' => 'success');
            }
        }
    }
    return array('status' => 'error',
                 'errorcode' => 39,
                 'errormsg' => 'invalid resource group name');
}

////////////////////////////////////////////////////////////////////////////////
///
/// \fn XMLRPCremoveUserGroup($name, $affiliation)
/// \param $name - the name of the resource group
/// \param $affiliation - the affiliation of the user group
/// \return an array with at least one index named 'status' which will have
/// one of these values\n
/// \b error - error occurred; there will be 2 additional elements in the array:
/// \li \b errorcode - error number\n
/// \li \b errormsg - error string\n
/// \b success - the user group was removed\n
///
/// \brief remove a user group\n
///
////////////////////////////////////////////////////////////////////////////////
function XMLRPCremoveUserGroup($name, $affiliation){
    global $user;

    if(! in_array("groupAdmin", $user['privileges'])){
        return array('status' => 'error',
                     'errorcode' => 16,
                     'errormsg' => 'access denied for managing user groups');
    }

    $validate = array(
        'name' => $name,
        'affiliation' => $affiliation);

    $rc = validateAPIgroupInput($validate, 1);
    if($rc['status'] == 'error')
        return $rc;

    $groups = getUserGroups();
    $groupid = $rc['id'];
    if(array_key_exists($groupid, $groups)){
        $group = $groups[$groupid];
        if($group['ownerid'] == $user['id'] ||
                (array_key_exists("editgroupid", $group) &&
                array_key_exists($group['editgroupid'], $user["groups"])) || 
                (array_key_exists($groupid, $user["groups"]))){
            $query = "DELETE FROM usergroup "
                   . "WHERE id = $groupid";
            doQuery($query, 315);
            return array('status' => 'success');
        }
    }
    return array(
        'status' => 'error',
        'errorcode' => 17,
        'errormsg' => 'access denied for editing specified group');
}

///
/// \fn XMLRPCaddResourceGroup($name, $managingGroup, $type)
///
/// \param $name - the name of the resource group
/// \param $managingGroup - the name of the managing group
/// \param $type - the type of resource group
///
/// \return an array with at least one index named 'status' which will have
/// one of these values\n
/// \b error - error occurred; there will be 2 additional elements in the array:
/// \li \b errorcode - error number\n
/// \li \b errormsg - error string\n
///
/// \b success - the resource group was added\n
///
/// \brief add a resource group\n
///
////////////////////////////////////////////////////////////////////////////////
function XMLRPCaddResourceGroup($name, $managingGroup, $type){
    global $user;
    if(! in_array("groupAdmin", $user['privileges'])){
        return array('status' => 'error',
                     'errorcode' => 16,
                     'errormsg' => 'access denied for managing user groups');
    }

    $validate = array(
        'managingGroup' => $managingGroup);
    
    $rc = validateAPIgroupInput($validate, 0);
    if($rc['status'] == 'error')
        return $rc;

    if($typeid = getResourceTypeID($type)){
        if(checkForGroupName($name, 'resource', '', $typeid)){
            return array('status' => 'error',
                         'errorcode' => 28,
                         'errormsg' => 'resource group already exists');
        }
        $data = array(
            'type' => $type,
            'ownergroup' => $rc['managingGroupID'],
            'resourcetypeid' => $typeid,
            'name' => $name);
        if(! addGroup($data)){
            return array('status' => 'error',
                         'errorcode' => 26,
                         'errormsg' => 'failure while adding group to database');
        }
    } else {
        return array('status' => 'error',
                     'errorcode' => 68,
                     'errormsg' => 'invalid resource type');
    }
    return array('status' => 'success');
/// \fn XMLRPCgetUserGroupAttributes($name, $affiliation)
/// \b success - there will be five additional elements in this case:
/// \li \b owner - user that will be the owner of the group in
///                username\@affiliation form
/// \li \b managingGroup - user group that can manage membership of this one in
///                        groupname\@affiliation form
/// \li \b initialMaxTime - (minutes) max initial time users in this group can
///                         select for length of reservations
/// \li \b totalMaxTime - (minutes) total length users in the group can have for
///                       a reservation (including all extensions)
/// \li \b maxExtendTime - (minutes) max length of time users can request as an
///                        extension to a reservation at a time
/// \brief gets information about a user group\n
function XMLRPCgetUserGroupAttributes($name, $affiliation) {
		             'errormsg' => 'access denied for managing user groups');
	$query = "SELECT ug.id, "
	       .        "ug.ownerid, "
	       .        "CONCAT(u.unityid, '@', a.name) AS owner, "
	       .        "ug.editusergroupid AS editgroupid, "
	       .        "eug.name AS editgroup, "
	       .        "eug.affiliationid AS editgroupaffiliationid, "
	       .        "euga.name AS editgroupaffiliation, "
	       .        "ug.initialmaxtime, "
	       .        "ug.totalmaxtime, "
	       .        "ug.maxextendtime, "
	       .        "ug.overlapResCount "
	       . "FROM usergroup ug "
	       . "LEFT JOIN user u ON (ug.ownerid = u.id) "
	       . "LEFT JOIN affiliation a ON (u.affiliationid = a.id) "
	       . "LEFT JOIN usergroup eug ON (ug.editusergroupid = eug.id) "
	       . "LEFT JOIN affiliation euga ON (eug.affiliationid = euga.id) "
	       . "WHERE ug.id = {$rc['id']}";
	# if not owner and not member of managing group, no access
	if($user['id'] != $row['ownerid'] && 
	   ! array_key_exists($row['editgroupid'], $user['groups'])) {
	             'owner' => $row['owner'],
	             'managingGroup' => "{$row['editgroup']}@{$row['editgroupaffiliation']}",
	             'initialMaxTime' => $row['initialmaxtime'],
	             'totalMaxTime' => $row['totalmaxtime'],
	             'maxExtendTime' => $row['maxextendtime']);
/// \fn XMLRPCdeleteUserGroup($name, $affiliation)
/// \b success - user group was successfully deleted
/// \brief deletes a user group along with all of its privileges\n
function XMLRPCdeleteUserGroup($name, $affiliation) {
	global $user, $mysql_link_vcl;
		             'errormsg' => 'access denied for managing user groups');
	$query = "SELECT ownerid "
	# if not owner no access to delete group
	if($user['id'] != $row['ownerid']) {
		             'errorcode' => 29,
		             'errormsg' => 'access denied to delete user group with submitted name and affiliation');
	$query = "DELETE FROM usergroup "
			 . "WHERE id = {$rc['id']}";
	doQuery($query, 101);
	# validate something deleted
	if(mysql_affected_rows($mysql_link_vcl) == 0) {
		return array('status' => 'error',
		             'errorcode' => 30,
		             'errormsg' => 'failure while deleting group from database');
/// \fn XMLRPCeditUserGroup($name, $affiliation, $newName, $newAffiliation,
///                         $newOwner, $newManagingGroup, $newInitialMaxTime,
///                         $newTotalMaxTime, $newMaxExtendTime)
/// \param $newName - new name for user group
/// \param $newAffiliation - new affiliation for user group
/// \param $newOwner - (optional, default='') user that will be the owner of
///                    the group in username\@affiliation form
/// \param $newManagingGroup - (optional, default='') user group that can
///                            manage membership of this one
/// \param $newInitialMaxTime - (optional, default='') (minutes) max initial
///                             time users in this group can select for length
///                             of reservations
/// \param $newTotalMaxTime - (optional, default='') (minutes) total length
///                           users in the group can have for a reservation
///                           (including all extensions)
/// \param $newMaxExtendTime - (optional, default='') (minutes) max length of
///                            time users can request as an extension to a
///                            reservation at a time
/// \b success - user group was successfully updated
/// \brief modifies attributes of a user group\n
/// \b NOTE: an empty string may be passed for any of the new* fields to leave
/// that item unchanged\n
function XMLRPCeditUserGroup($name, $affiliation, $newName, $newAffiliation,
                             $newOwner='', $newManagingGroup='',
                             $newInitialMaxTime='', $newTotalMaxTime='',
                             $newMaxExtendTime='') {
	global $user, $mysql_link_vcl;
		             'errormsg' => 'access denied for managing user groups');

	$updates = array();

	# validate group exists and new values other than newName and newAffiliation
	#   are valid
	if(! empty($newOwner))
		$validate['owner'] = $newOwner;
	if(! empty($newManagingGroup))
		$validate['managingGroup'] = $newManagingGroup;
	if(! empty($newInitialMaxTime)) {
		$validate['initialMaxTime'] = $newInitialMaxTime;
		$updates[] = "initialmaxtime = $newInitialMaxTime";
	}
	if(! empty($newTotalMaxTime)) {
		$validate['totalMaxTime'] = $newTotalMaxTime;
		$updates[] = "totalmaxtime = $newTotalMaxTime";
	}
	if(! empty($newMaxExtendTime)) {
		$validate['maxExtendTime'] = $newMaxExtendTime;
		$updates[] = "maxextendtime = $newMaxExtendTime";
	}

	# get info about group
	$query = "SELECT ownerid "
	# if not owner no access to edit group attributes
	if($user['id'] != $row['ownerid']) {
		             'errorcode' => 32,
		             'errormsg' => 'access denied to modify attributes for user group with submitted name and affiliation');

	# validate that newName and newAffiliation are valid
	if(! empty($newName) || ! empty($newAffiliation)) {
		$validate = array('name' => $name,
		                  'affiliation' => $affiliation);
		if(! empty($newName)) {
			$validate['name'] = $newName;
			$tmp = mysql_real_escape_string($newName);
			$updates[] = "name = '$tmp'";
		if(! empty($newAffiliation))
			$validate['affiliation'] = $newAffiliation;
		$rc2 = validateAPIgroupInput($validate, 0);
		if($rc2['status'] == 'error') {
			if($rc2['errorcode'] == 27) {
				$rc2['errorcode'] = 31;
				$rc2['errormsg'] = 'existing user group with new form of name@affiliation';
			}
			return $rc2;
		if(! empty($newAffiliation))
			$updates[] = "affiliationid = {$rc2['affiliationid']}";
	}

	if(! empty($newOwner)) {
		$newownerid = getUserlistID(mysql_real_escape_string($newOwner));
		$updates[] = "ownerid = $newownerid";
	}
	if(! empty($newManagingGroup)) {
		$updates[] = "editusergroupid = {$rc['managingGroupID']}";
	$sets = implode(',', $updates);
	if(count($updates) == 0) {
		return array('status' => 'error',
		             'errorcode' => 33,
		             'errormsg' => 'no new values submitted');
	$query = "UPDATE usergroup "
	       . "SET $sets "
	       . "WHERE id = {$rc['id']}";
	doQuery($query, 101);
/// \fn XMLRPCgetUserGroupMembers($name, $affiliation)
/// \param $name - name of user group
/// \param $affiliation - affiliation of user group
/// one of these values:\n
/// \li \b errorcode - error number
/// \li \b errormsg - error string
/// \b success - there will be one additional element in this case:
/// \li \b members - array of members of the group in username\@affiliation form
/// \brief gets members of a user group\n
/// \b Note: it is possible to have a group with no members in which case
/// success will be returned with an empty array for members\n
function XMLRPCgetUserGroupMembers($name, $affiliation) {
	if(! in_array('groupAdmin', $user['privileges'])) {
		return array('status' => 'error',
		             'errorcode' => 16,
		             'errormsg' => 'access denied for managing user groups');
	}
	$validate = array('name' => $name,
	                  'affiliation' => $affiliation);
	$rc = validateAPIgroupInput($validate, 1);
	if($rc['status'] == 'error')
		return $rc;
	$query = "SELECT ownerid, "
	       .        "editusergroupid AS editgroupid "
	       . "FROM usergroup "
	       . "WHERE id = {$rc['id']}";
	$qh = doQuery($query, 101);
	if(! $row = mysql_fetch_assoc($qh)) {
		return array('status' => 'error',
		             'errorcode' => 18,
		             'errormsg' => 'user group with submitted name and affiliation does not exist');
	# if not owner and not member of managing group, no access
	if($user['id'] != $row['ownerid'] && 
	   ! array_key_exists($row['editgroupid'], $user['groups'])) {
		             'errorcode' => 28,
		             'errormsg' => 'access denied to user group with submitted name and affiliation');
	$query = "SELECT CONCAT(u.unityid, '@', a.name) AS member "
	       . "FROM usergroupmembers ugm, "
	       .      "user u, "
	       .      "affiliation a "
	       . "WHERE ugm.usergroupid = {$rc['id']} AND "
	       .       "ugm.userid = u.id AND "
	       .       "u.affiliationid = a.id";
	$qh = doQuery($query, 101);
	$members = array();
	while($row = mysql_fetch_assoc($qh))
		$members[] = $row['member'];
	return array('status' => 'success',
	             'members' => $members);
/// \fn XMLRPCaddUsersToGroup($name, $affiliation, $users)
/// \param $name - name of user group
/// \param $affiliation - affiliation of user group
/// \param $users - array of users in username\@affiliation form to be added to
///                 the group
/// one of these values:\n
/// \li \b errorcode - error number
/// \li \b errormsg - error string
///
/// \b success - users successfully added to the group
/// \b warning - there was a non-fatal issue that occurred while processing
/// the call; there will be three additional elements in this case:
/// \li \b warningcode - warning number
/// \li \b warningmsg - warning string
/// \li \b failedusers - array of users in username\@affiliation form that could
///                      not be added
/// \brief adds users to a group\n
function XMLRPCaddUsersToGroup($name, $affiliation, $users) {
	if(! in_array('groupAdmin', $user['privileges'])) {
		             'errormsg' => 'access denied for managing user groups');
	$validate = array('name' => $name,
	                  'affiliation' => $affiliation);
	$rc = validateAPIgroupInput($validate, 1);
	$query = "SELECT ownerid, "
	       .        "editusergroupid AS editgroupid "
	       . "FROM usergroup "
	       . "WHERE id = {$rc['id']}";
	$qh = doQuery($query, 101);
	if(! $row = mysql_fetch_assoc($qh)) {
		return array('status' => 'error',
		             'errorcode' => 18,
		             'errormsg' => 'user group with submitted name and affiliation does not exist');
	# if not owner and not member of managing group, no access
	if($user['id'] != $row['ownerid'] && 
	   ! array_key_exists($row['editgroupid'], $user['groups'])) {
		             'errorcode' => 28,
		             'errormsg' => 'access denied to user group with submitted name and affiliation');
	}
	$fails = array();
	foreach($users as $_user) {
		if(empty($_user))
			continue;
		$esc_user = mysql_real_escape_string($_user);
		if(validateUserid($esc_user) == 1)
			addUserGroupMember($esc_user, $rc['id']);
		else
			$fails[] = $_user;
	}
	if(count($fails)) {
		$cnt = 'some';
		$code = 34;
		if(count($fails) == count($users)) {
			$cnt = 'all submitted';
			$code = 35;
		}
		return array('status' => 'warning',
		             'failedusers' => $fails,
		             'warningcode' => $code,
		             'warningmsg' => "failed to add $cnt users to user group");
/// \fn XMLRPCremoveUsersFromGroup($name, $affiliation, $users)
/// \param $name - name of user group
/// \param $affiliation - affiliation of user group
/// \param $users - array of users in username\@affiliation form to be removed
///                 from the group
/// one of these values:\n
/// \b success - users successfully removed from the group
/// the call; there will be three additional elements in this case:
/// \li \b failedusers - array of users in username\@affiliation form that could
///                      not be removed
/// \brief removes users from a group\n
////////////////////////////////////////////////////////////////////////////////
function XMLRPCremoveUsersFromGroup($name, $affiliation, $users) {
	global $user, $findAffilFuncs;
	if(! in_array('groupAdmin', $user['privileges'])) {
		             'errorcode' => 16,
		             'errormsg' => 'access denied for managing user groups');
	$validate = array('name' => $name,
	                  'affiliation' => $affiliation);
	$rc = validateAPIgroupInput($validate, 1);
	if($rc['status'] == 'error')
		return $rc;
	$query = "SELECT ownerid, "
	       .        "editusergroupid AS editgroupid "
	       . "FROM usergroup "
	       . "WHERE id = {$rc['id']}";
	$qh = doQuery($query, 101);
	if(! $row = mysql_fetch_assoc($qh)) {
		             'errorcode' => 18,
		             'errormsg' => 'user group with submitted name and affiliation does not exist');
	# if not owner and not member of managing group, no access
	if($user['id'] != $row['ownerid'] && 
	   ! array_key_exists($row['editgroupid'], $user['groups'])) {
		             'errorcode' => 28,
		             'errormsg' => 'access denied to user group with submitted name and affiliation');
	$fails = array();
	foreach($users as $_user) {
		if(empty($_user))
			continue;
		$esc_user = mysql_real_escape_string($_user);
		# check that affiliation of user can be determined because getUserlistID
		#   will abort if it can't find it
		$affilok = 0;
		foreach($findAffilFuncs as $func) {
			if($func($_user, $dump))
				$affilok = 1;
		}
		if(! $affilok) {
			$fails[] = $_user;
			continue;
		}
		$userid = getUserlistID($esc_user, 1);
		if(is_null($userid))
			$fails[] = $_user;
		else
			deleteUserGroupMember($userid, $rc['id']);
	if(count($fails)) {
		$cnt = 'some';
		$code = 36;
		if(count($fails) == count($users)) {
			$cnt = 'any';
			$code = 37;
		}
		return array('status' => 'warning',
		             'failedusers' => $fails,
		             'warningcode' => $code,
		             'warningmsg' => "failed to remove $cnt users from user group");
	return array('status' => 'success');
/// \fn XMLRPCautoCapture($requestid)
/// \param $requestid - id of request to be captured
/// \b success - image was successfully set to be captured
/// \brief creates entries in appropriate tables to capture an image and sets
/// the request state to image\n
function XMLRPCautoCapture($requestid) {
	global $user, $xmlrpcBlockAPIUsers;
		             'errorcode' => 47,
		             'errormsg' => 'access denied to XMLRPCautoCapture');
	$query = "SELECT id FROM request WHERE id = $requestid";
	$qh = doQuery($query, 101);
	if(! mysql_num_rows($qh)) {
		             'errorcode' => 52,
		             'errormsg' => 'specified request does not exist');
	$reqData = getRequestInfo($requestid);
	# check state of reservation
	if($reqData['stateid'] != 14 || $reqData['laststateid'] != 8) {
		             'errorcode' => 51,
		             'errormsg' => 'reservation not in valid state');
	# check that not a cluster reservation
	if(count($reqData['reservations']) > 1) {
		             'errorcode' => 48,
		             'errormsg' => 'cannot image a cluster reservation');
	require_once(".ht-inc/images.php");
	$imageid = $reqData['reservations'][0]['imageid'];
	$imageData = getImages(0, $imageid);
	$captime = unixToDatetime(time());
	$comments = "start: {$reqData['start']}<br>"
	          . "end: {$reqData['end']}<br>"
	          . "computer: {$reqData['reservations'][0]['reservedIP']}<br>"
	          . "capture time: $captime";
	# create new revision if requestor is owner and not a kickstart image
	if($imageData[$imageid]['installtype'] != 'kickstart' &&
	   $reqData['userid'] == $imageData[$imageid]['ownerid']) {
		$rc = updateExistingImage($requestid, $reqData['userid'], $comments, 1);
		if($rc == 0) {
			return array('status' => 'error',
			             'errorcode' => 49,
			             'errormsg' => 'error encountered while attempting to create new revision');
		}
	# create a new image if requestor is not owner or a kickstart image
	else {
		$ownerdata = getUserInfo($reqData['userid'], 1, 1);
		$desc = "This is an autocaptured image.<br>"
		      . "captured from image: {$reqData['reservations'][0]['prettyimage']}<br>"
		      . "captured on: $captime<br>"
		      . "owner: {$ownerdata['unityid']}@{$ownerdata['affiliation']}<br>";
		$connectmethods = getImageConnectMethods($imageid, $reqData['reservations'][0]['imagerevisionid']);
		$data = array('requestid' => $requestid,
		              'description' => $desc,
		              'usage' => '',
		              'owner' => "{$ownerdata['unityid']}@{$ownerdata['affiliation']}",
		              'prettyname' => "Autocaptured ({$ownerdata['unityid']} - $requestid)",
		              'minram' => 64,
		              'minprocnumber' => 1,
		              'minprocspeed' => 500,
		              'minnetwork' => 10,
		              'maxconcurrent' => '',
		              'checkuser' => 1,
		              'rootaccess' => 1,
		              'sysprep' => 1,
		              'comments' => $comments,
		              'connectmethodids' => implode(',', array_keys($connectmethods)));
		$rc = submitAddImage($data, 1);
		if($rc == 0) {
			return array('status' => 'error',
			             'errorcode' => 50,
			             'errormsg' => 'error encountered while attempting to create image');
		}
	}
	return array('status' => 'success');
}
////////////////////////////////////////////////////////////////////////////////
///
/// \fn XMLRPCdeployServer($imageid, $start, $end, $admingroup, $logingroup,
///                        $ipaddr, $macaddr, $monitored, $foruser)
///
/// \param $imageid - id of an image
/// \param $start - "now" or unix timestamp for start of reservation; will
/// use a floor function to round down to the nearest 15 minute increment
/// for actual reservation
/// \param $end - "indefinite" or unix timestamp for end of reservation; will
/// use a floor function to round up to the nearest 15 minute increment
/// for actual reservation
/// \param $admingroup - (optional, default='') admin user group for reservation
/// \param $logingroup - (optional, default='') login user group for reservation
/// \param $ipaddr - (optional, default='') IP address to use for public IP of
/// server
/// \param $macaddr - (optional, default='') MAC address to use for public NIC
/// of server
/// \param $monitored - (optional, default=0) whether or not the server should
/// be monitored - CURRENTLY, THIS IS UNSUPPORTED
/// \param $foruser - (optional) login to be used when setting up the account
/// on the reserved machine - CURRENTLY, THIS IS UNSUPPORTED
///
/// \return an array with at least one index named '\b status' which will have
/// one of these values:\n
/// \b error - error occurred; there will be 2 additional elements in the array:
/// \li \b errorcode - error number\n
/// \li \b errormsg - error string\n
///
/// \b notavailable - no computers were available for the request\n
/// \b success - there will be an additional element in the array:
/// \li \b requestid - identifier that should be passed to later calls when
/// acting on the request
///
/// \brief tries to make a server request\n
///
////////////////////////////////////////////////////////////////////////////////
function XMLRPCdeployServer($imageid, $start, $end, $admingroup='',
                            $logingroup='', $ipaddr='', $macaddr='',
                            $monitored=0, $foruser='') {
	global $user, $remoteIP;
	if(! in_array("serverProfileAdmin", $user["privileges"])) {
		return array('status' => 'error',
		             'errorcode' => 60,
		             'errormsg' => "access denied to deploy server");
	}
	$imageid = processInputData($imageid, ARG_NUMERIC);
	$resources = getUserResources(array("imageAdmin", "imageCheckOut"));
	$images = removeNoCheckout($resources["image"]);
	$extraimages = getServerProfileImages($user['id']);
	if(! array_key_exists($imageid, $images) &&
	   ! array_key_exists($imageid, $extraimages)) {
		             'errorcode' => 3,
		             'errormsg' => "access denied to $imageid");
	if($admingroup != '' || $logingroup != '')
		$usergroups = getUserEditGroups($user['id']);
	if($admingroup != '') {
		$admingroup = processInputData($admingroup, ARG_STRING);
		if(preg_match('@', $admingroup)) {
			$tmp = explode('@', $admingroup);
			$escadmingroup = mysql_real_escape_string($tmp[0]);
			$affilid = getAffiliationID(mysql_real_escape_string($tmp[1]));
			if(is_null($affilid)) {
				return array('status' => 'error',
				             'errorcode' => 51,
				             'errormsg' => "unknown affiliation for admin user group: {$tmp[1]}");
		}
		else {
			$escadmingroup = mysql_real_escape_string($admingroup);
			$affilid = DEFAULT_AFFILID;
		}
		$query = "SELECT id "
		       . "FROM usergroup "
		       . "WHERE name = '$escadmingroup' AND "
		       .       "affiliationid = $affilid";
		$qh = doQuery($query, 300);
		if($row = mysql_fetch_assoc($qh))
			$admingroupid = $row['id'];
		else {
			return array('status' => 'error',
			             'errorcode' => 52,
			             'errormsg' => "unknown admin user group: $admingroup");
		}
		if(! array_key_exists($admingroupid, $usergroups)) {
			return array('status' => 'error',
			             'errorcode' => 53,
			             'errormsg' => "access denied to admin user group: $admingroup");
		}
	}
	else
		$admingroupid = '';
	if($logingroup != '') {
		$logingroup = processInputData($logingroup, ARG_STRING);
		if(preg_match('@', $logingroup)) {
			$tmp = explode('@', $logingroup);
			$esclogingroup = mysql_real_escape_string($tmp[0]);
			$affilid = getAffiliationID(mysql_real_escape_string($tmp[1]));
			if(is_null($affilid)) {
				return array('status' => 'error',
				             'errorcode' => 54,
				             'errormsg' => "unknown affiliation for login user group: {$tmp[1]}");
		else {
			$esclogingroup = mysql_real_escape_string($logingroup);
			$affilid = DEFAULT_AFFILID;
		}
		$query = "SELECT id "
		       . "FROM usergroup "
		       . "WHERE name = '$esclogingroup' AND "
		       .       "affiliationid = $affilid";
		$qh = doQuery($query, 300);
		if($row = mysql_fetch_assoc($qh))
			$logingroupid = $row['id'];
		else {
			return array('status' => 'error',
			             'errorcode' => 55,
			             'errormsg' => "unknown login user group: $logingroup");
		}
		if(! array_key_exists($logingroupid, $usergroups)) {
			return array('status' => 'error',
			             'errorcode' => 56,
			             'errormsg' => "access denied to login user group: $logingroup");
	else
		$logingroupid = '';
	$ipaddr = processInputData($ipaddr, ARG_STRING);
	$ipaddrArr = explode('.', $ipaddr);
	if($ipaddr != '' && (! preg_match('/^(([0-9]){1,3}\.){3}([0-9]){1,3}$/', $ipaddr) ||
		$ipaddrArr[0] < 1 || $ipaddrArr[0] > 255 ||
		$ipaddrArr[1] < 0 || $ipaddrArr[1] > 255 ||
		$ipaddrArr[2] < 0 || $ipaddrArr[2] > 255 ||
		$ipaddrArr[3] < 0 || $ipaddrArr[3] > 255)) {
		return array('status' => 'error',
		             'errorcode' => 57,
		             'errormsg' => "Invalid IP address. Must be w.x.y.z with each of "
		                         . "w, x, y, and z being between 1 and 255 (inclusive)");
	}
	$macaddr = processInputData($macaddr, ARG_STRING);
	if($macaddr != '' && ! preg_match('/^(([A-Fa-f0-9]){2}:){5}([A-Fa-f0-9]){2}$/', $macaddr)) {
		return array('status' => 'error',
		             'errorcode' => 58,
		             'errormsg' => "Invalid MAC address.  Must be XX:XX:XX:XX:XX:XX "
		                         . "with each pair of XX being from 00 to FF (inclusive)");
	}
	$monitored = processInputData($monitored, ARG_NUMERIC);
	if($monitored != 0 && $monitored != 1)
		$monitored = 0;
	$start = processInputData($start, ARG_STRING, 1);
	$end = processInputData($end, ARG_STRING, 1);
	#$foruser = processInputData($foruser, ARG_STRING, 1);
	# validate $start
	if($start != 'now' && ! is_numeric($start)) {
		             'errorcode' => 4,
		             'errormsg' => "received invalid input for start");
	# validate $end
	if($end != 'indefinite' && ! is_numeric($end)) {
		return array('status' => 'error',
		             'errorcode' => 59,
		             'errormsg' => "received invalid input for end");
	$nowfuture = 'future';
	if($start == 'now') {
		$start = unixFloor15(time());
		$nowfuture = 'now';
	}
	else
		if($start < (time() - 30))
			             'errorcode' => 5,
			             'errormsg' => "start time is in the past");
	if($end == 'indefinite')
		$end = datetimeToUnix("2038-01-01 00:00:00");
	elseif($end % (15 * 60))
		$end = unixFloor15($end) + (15 * 60);

	$max = getMaxOverlap($user['id']);
	if(checkOverlap($start, $end, $max)) {
		return array('status' => 'error',
		             'errorcode' => 7,
		             'errormsg' => "reservation overlaps with another one you "
		                         . "have, and you are allowed $max "
		                         . "overlapping reservations at a time");
	$images = getImages();
	$revisionid = getProductionRevisionid($imageid);
	$rc = isAvailable($images, $imageid, $revisionid, $start, $end,
	                  0, 0, 0, 0, $ipaddr, $macaddr);
	if($rc < 1) {
		addLogEntry($nowfuture, unixToDatetime($start), 
		            unixToDatetime($end), 0, $imageid);
		return array('status' => 'notavailable');
	}
	$return['requestid']= addRequest();
	$query = "UPDATE reservation "
	       . "SET remoteIP = '$remoteIP' "
	       . "WHERE requestid = {$return['requestid']}";
	doQuery($query);
	$fields = array('requestid');
	$values = array($return['requestid']);
	if($ipaddr != '') {
		$fields[] = 'fixedIP';
		$values[] = "'$ipaddr'";
	}
	if($macaddr != '') {
		$fields[] = 'fixedMAC';
		$values[] = "'$macaddr'";
	}
	if($admingroupid != 0) {
		$fields[] = 'admingroupid';
		$values[] = $admingroupid;
	if($logingroupid != 0) {
		$fields[] = 'logingroupid';
		$values[] = $logingroupid;
	if($monitored != 0) {
		$fields[] = 'monitored';
		$values[] = 1;
	$allfields = implode(',', $fields);
	$allvalues = implode(',', $values);
	$query = "INSERT INTO serverrequest ($allfields) VALUES ($allvalues)";
	doQuery($query, 101);
	$return['status'] = 'success';

////////////////////////////////////////////////////////////////////////////////
///
/// \fn XMLRPCtest($string)
///
/// \param $string - a string
///
/// \return an array with 3 indices:\n
/// \b status - will be 'success'\n
/// \b message - will be 'RPC call worked successfully'\n
/// \b string - contents of $string (after being sanatized)
///
/// \brief this is a test function that call be called when getting XML RPC
/// calls to this site to work\n
///
////////////////////////////////////////////////////////////////////////////////
function XMLRPCtest($string) {
	$string = processInputData($string, ARG_STRING);
	return array('status' => 'success',
	             'message' => 'RPC call worked successfully',
	             'string' => $string);
}
Fixed code: 	$name = mysql_real_escape_string($name);
		if($data['editgroupid'] == 0 || $data['editgroupid'] == '')
			$data['editgroupid'] = 'NULL';
		print "      <script type=\"dojo/method\" event=onClick>\n";
		print "      <script type=\"dojo/method\" event=onClick>\n";
		print "      <script type=\"dojo/method\" event=onClick>\n";
		print "  <script type=\"dojo/method\" event=onClick>\n";
		print "  <script type=\"dojo/method\" event=onClick>\n";
		print "  <script type=\"dojo/method\" event=onClick>\n";
	print "    <script type=\"dojo/connect\" event=onCancel>\n";
	print "    <script type=\"dojo/method\" event=onClick>\n";
	print "    <script type=\"dojo/method\" event=onClick>\n";
	print "    <script type=\"dojo/connect\" event=onCancel>\n";
	print "    <script type=\"dojo/method\" event=onClick>\n";
	print "    <script type=\"dojo/method\" event=onClick>\n";
	print "    <script type=\"dojo/connect\" event=onCancel>\n";
	$privs = array("computerAdmin", "mgmtNodeAdmin", "imageAdmin",
	               "scheduleAdmin", "serverProfileAdmin");
	print "    <script type=\"dojo/method\" event=onClick>\n";
	print "    <script type=\"dojo/method\" event=onClick>\n";
	print "  <script type=\"dojo/connect\" event=onKeyPress args=\"e\">\n";
	print "    <script type=\"dojo/method\" event=onClick>\n";
	print "    <script type=\"dojo/method\" event=onClick>\n";
	print "    <script type=\"dojo/method\" event=onClick>\n";
	print "    <script type=\"dojo/method\" event=onClick>\n";
	print "  <script type=\"dojo/connect\" event=onKeyPress args=\"e\">\n";
	print "    <script type=\"dojo/method\" event=onClick>\n";
	print "    <script type=\"dojo/method\" event=onClick>\n";
	print "  <script type=\"dojo/connect\" event=_setup>\n";
	print "  Manage User Group Permissions\n";
	print "  <script type=\"dojo/method\" event=onClick>\n";
	print "    selectUserGroup('$cont');\n";
	print "  </script>\n";
	print "  Copy Permissions\n";
	print "  <script type=\"dojo/method\" event=onClick>\n";
	print "    copyUserGroupPrivs('$cont');\n";
	print "  </script>\n";
	print "  Save Selected Permissions\n";
	print "  <script type=\"dojo/method\" event=onClick>\n";
	print "    saveUserGroupPrivs('$cont');\n";
	print "  </script>\n";
		$text .= "      <script type=\"dojo/method\" event=onClick>";
		$text .= "      <script type=\"dojo/method\" event=onClick>";
		$text .= "      <script type=\"dojo/method\" event=onClick>";
		$text .= "  <script type=\"dojo/method\" event=onClick>";
		$text .= "  <script type=\"dojo/method\" event=onClick>";
	$errmsg = '';
	if(! validateNodeName($newnode, $errmsg)) {
		print "dojo.byId('addChildNodeStatus').innerHTML = '$errmsg';";
	$nodeInfo = getNodeInfo($parent);

	if(count($privs)) {
		updateUserOrGroupPrivs($user["id"], $nodeid, $privs, array(), "user");
	}
////////////////////////////////////////////////////////////////////////////////
///
/// \fn validateNodeName($name, &$errmsg)
///
/// \param $name - name for a node
/// \param $errmsg - variable into which an error message will be placed if
/// $name is not valid
///
/// \return 1 if name is okay, 0 if not; if 0, $errmsg is populated with an
/// error message
///
/// \brief validates that a name for a node is okay
///
////////////////////////////////////////////////////////////////////////////////
function validateNodeName($name, &$errmsg) {
	if(preg_match('/^[-A-Za-z0-9_\. ]+$/', $name))
		return 1;
	$errmsg = _("Node names can only contain letters, numbers, spaces,<br>dashes(-), dots(.), and underscores(_).");
	return 0;
}

	$errmsg = '';
	if(! validateNodeName($newname, $errmsg)) {
		$arr = array('error' => 2, 'message' => $errmsg);
		sendJSON($arr);
		return;
	}
	# check if node matching new name already exists at parent
	$_newname = mysql_real_escape_string($newname);
	       .       "name = '$_newname'";
		$msg = _("A sibling node of that name currently exists");
	       . "SET name = '$_newname' " 
				print "<font color=red>$userid not found</font><br>\n";
	   ($usergroup == 2 &&
	   ($usergroup == 2 &&
/// getNodePrivileges
		$query = "SELECT g.id, "
		       .        "g.name, "
		while($row = mysql_fetch_assoc($qh)) {
			# TODO adding the id at the end will fix the bug where blocking cascaded resource
			#   privileges are only blocked at the node and the block is not cascaded to
			#   child nodes
			#$allblockdata[$row['privnodeid']][] = "{$row["type"]}/{$row["name"]}/{$row['id']}";
		}

	if(! validateUserid($newuser)) {
		$text = "Invalid user submitted.";
		print "alert('$text');";
		return;
	}

	$privid = getUserPrivTypeID($newpriv);
	if(is_null($privid)) {
		$text = "Invalid user privilege submitted.";
		print "alert('$text');";
		return;
	}
		// if $newuser already has $newpriv cascaded to it, do nothing

	$newusergrp = getUserGroupName($newusergrpid);
	if($newusergrp === 0) {
		$text = "Invalid user group submitted.";
		print "alert('$text');";
		return;
	}

	$privid = getUserPrivTypeID($newpriv);
	if(is_null($privid)) {
		$text = "Invalid user privilege submitted.";
		print "alert('$text');";
		return;
	}
		// if $newusergrp already has $newpriv cascaded to it, do nothing

	$allprivs = getResourcePrivs();
	if(! in_array($newpriv, $allprivs)) {
		$text = "Invalid resource privilege submitted.";
		print "alert('$text');";
		return;
	}

	$resourcetypes = getTypes('resources');
	$types = implode('|', $resourcetypes['resources']);
	if(! preg_match("@($types)/([^/]+)/([0-9]+)@", $resourcegrp, $matches)) {
		$text = "Invalid resource group submitted.";
		print "alert('$text');";
		return;
	}

	$type = $matches[1];
	$groupid = $matches[3];

	$groupdata = getResourceGroups($type, $groupid);
	if(empty($groupdata)) {
		$text = "Invalid resource group submitted.";
		print "alert('$text');";
		return;
	}

	// if $type is administer, manageGroup, or manageMapping, and it is not
	# checked, and the user is not in the resource owner group, don't allow
	# the change
	if($newpriv != "block" && $newpriv != "cascade" && $newpriv != "available" &&
	   ! array_key_exists($groupdata[$groupid]["ownerid"], $user["groups"])) {
		$text = "You do not have rights to modify the submitted privilege for the submitted group.";
		print "alert('$text');";
		return;
	}
		// if $resourcegrp already has $newpriv cascaded to it, do nothing
	updateResourcePrivs($groupid, $node, $adds, $removes);

	$newgroup = getUserGroupName($newgroupid);
	if($newgroup === 0) {
		$text = "Invalid user group submitted.";
		print "alert('$text');";
		return;
	}

	$resourcegroups = getUserResources($privs, array("manageGroup"), 1);

	$groupdata = getResourceGroups('', $newgroupid);

	if(empty($groupdata)) {
		$text = "Invalid resource group submitted.";
		print "addResourceGroupPaneHide(); ";
		print "alert('$text');";
		return;
	}
	list($newtype, $tmp) = explode('/', $groupdata[$newgroupid]['name']);
	if(! array_key_exists($newgroupid, $resourcegroups[$newtype])) {
		   (! in_array("block", $privs["usergroups"][$groupname]['privs']) && 
		$type = mysql_real_escape_string($type);
		$type = mysql_real_escape_string($type);
/// \fn getResourceGroups($type, $id)
/// \param $id - (optional) id of a resource group
/// \return an array of resource groups where each key is a group id and each
/// value is an array with these elements:\n
/// \b name - type and name of group combined as type/name\n
/// \b ownerid - id of owning user group\n
/// \b owner - name of owning user group
function getResourceGroups($type='', $id='') {
	if(! empty($id))
		$query .= "AND g.id = $id ";

				$return[$index] = mysql_real_escape_string($value);
			$return = mysql_real_escape_string($return);
/// \fn getUserGroupID($name, $affilid, $noadd)
/// \param $noadd - (optional, defaults to 0) set to 1 to return NULL if group
/// does not exist instead of adding it to table
function getUserGroupID($name, $affilid=DEFAULT_AFFILID, $noadd=0) {
	if($row = mysql_fetch_row($qh))
	elseif($noadd)
		return NULL;
		while($row = mysql_fetch_assoc($qh))
function getResourceGroupID($groupname) {
	list($type, $name) = explode('/', $groupname);
	$type = mysql_real_escape_string($type);
	$name = mysql_real_escape_string($name);
	$name = mysql_real_escape_string($name);
	xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCremoveNode", "xmlRPChandler");
	xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCnodeExists", "xmlRPChandler");
	xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCaddResourceGroupPriv", "xmlRPChandler");
	xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCremoveResourceGroupPriv", "xmlRPChandler");
	xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCgetResourceGroupPrivs", "xmlRPChandler");
	xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCaddUserGroupPriv", "xmlRPChandler");
	xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCremoveUserGroupPriv", "xmlRPChandler");
	xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCgetUserGroupPrivs", "xmlRPChandler");
	xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCaddResourceGroup", "xmlRPChandler");
	xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCgetResourceGroups", "xmlRPChandler");
	xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCremoveResourceGroup", "xmlRPChandler");
	xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCremoveUserGroup", "xmlRPChandler");
	xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCremoveImageFromGroup", "xmlRPChandler");
	xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCgetGroupImages", "xmlRPChandler");
	xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCaddImageGroupToComputerGroup", "xmlRPChandler");
	xmlrpc_server_register_method($xmlrpc_handle, "XMLRPCremoveImageGroupFromComputerGroup", "xmlRPChandler");
	print "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?" . ">\n"; # splitting the ? and > makes vim syntax highlighting work correctly
///                  it does not exist
		$affilid = getAffiliationID($items['affiliation']);
		$doesexist = checkForGroupName($items['name'], 'user', '', $affilid);
			$esc_name = mysql_real_escape_string($items['name']);
		if(! validateUserid($items['owner'])) {
		$mgaffilid = getAffiliationID($parts[1]);
		if(is_null($mgaffilid) ||
		   ! checkForGroupName($parts[0], 'user', '', $mgaffilid)) {
		$items['managingGroupID'] = getUserGroupID($parts[0], $mgaffilid);
/// HTTP headers do not need to be passed
////////////////////////////////////////////////////////////////////////////////
///
/// \fn XMLRPCtest($string)
///
/// \param $string - a string
///
/// \return an array with 3 indices:\n
/// \b status - will be 'success'\n
/// \b message - will be 'RPC call worked successfully'\n
/// \b string - contents of $string (after being sanatized)
///
/// \brief this is a test function that call be called when getting XML RPC
/// calls to this site to work
///
////////////////////////////////////////////////////////////////////////////////
function XMLRPCtest($string) {
	$string = processInputData($string, ARG_STRING);
	return array('status' => 'success',
	             'message' => 'RPC call worked successfully',
	             'string' => $string);
}

/// \brief gets the images to which the user has access
		$notes = getImageNotes($key);
		$tmp = array('id' => $key,
		             'name' => $val,
		             'description' => $notes['description'],
		             'usage' => $notes['usage']);
/// \brief tries to make a request
/// \brief tries to make a request with the specified ending time
/// \fn XMLRPCdeployServer($imageid, $start, $end, $admingroup, $logingroup,
///                        $ipaddr, $macaddr, $monitored, $foruser, $name)
/// \param $imageid - id of an image
/// \param $start - "now" or unix timestamp for start of reservation; will
/// use a floor function to round down to the nearest 15 minute increment
/// for actual reservation
/// \param $end - "indefinite" or unix timestamp for end of reservation; will
/// use a floor function to round up to the nearest 15 minute increment
/// for actual reservation
/// \param $admingroup - (optional, default='') admin user group for reservation
/// \param $logingroup - (optional, default='') login user group for reservation
/// \param $ipaddr - (optional, default='') IP address to use for public IP of
/// server
/// \param $macaddr - (optional, default='') MAC address to use for public NIC
/// of server
/// \param $monitored - (optional, default=0) whether or not the server should
/// be monitored - CURRENTLY, THIS IS UNSUPPORTED
/// \param $foruser - (optional) login to be used when setting up the account
/// on the reserved machine - CURRENTLY, THIS IS UNSUPPORTED
/// \param $name - (optional) name for reservation
/// \b notavailable - no computers were available for the request\n
/// \b success - there will be an additional element in the array:
/// \li \b requestid - identifier that should be passed to later calls when
/// acting on the request
/// \brief tries to make a server request
function XMLRPCdeployServer($imageid, $start, $end, $admingroup='',
                            $logingroup='', $ipaddr='', $macaddr='',
                            $monitored=0, $foruser='', $name='') {
	global $user, $remoteIP;
	if(! in_array("serverProfileAdmin", $user["privileges"])) {
		return array('status' => 'error',
		             'errorcode' => 60,
		             'errormsg' => "access denied to deploy server");
	$imageid = processInputData($imageid, ARG_NUMERIC);
	$resources = getUserResources(array("imageAdmin", "imageCheckOut"));
	$images = removeNoCheckout($resources["image"]);
	$extraimages = getServerProfileImages($user['id']);
	if(! array_key_exists($imageid, $images) &&
	   ! array_key_exists($imageid, $extraimages)) {
		             'errorcode' => 3,
		             'errormsg' => "access denied to $imageid");
	}
	if($admingroup != '') {
		$admingroup = processInputData($admingroup, ARG_STRING);
		if(get_magic_quotes_gpc())
			$admingroup = stripslashes($admingroup);
		if(preg_match('/@/', $admingroup)) {
			$tmp = explode('@', $admingroup);
			$escadmingroup = mysql_real_escape_string($tmp[0]);
			$affilid = getAffiliationID($tmp[1]);
			if(is_null($affilid)) {
				return array('status' => 'error',
				             'errorcode' => 51,
				             'errormsg' => "unknown affiliation for admin user group: {$tmp[1]}");
			}
			$escadmingroup = mysql_real_escape_string($admingroup);
			$affilid = DEFAULT_AFFILID;
		}
		$admingroupid = getUserGroupID($escadmingroup, $affilid, 1);
		if(is_null($admingroupid)) {
			return array('status' => 'error',
			             'errorcode' => 52,
			             'errormsg' => "unknown admin user group: $admingroup");
		}
	}
	else
		$admingroupid = '';
	if($logingroup != '') {
		$logingroup = processInputData($logingroup, ARG_STRING);
		if(get_magic_quotes_gpc())
			$logingroup = stripslashes($logingroup);
		if(preg_match('/@/', $logingroup)) {
			$tmp = explode('@', $logingroup);
			$esclogingroup = mysql_real_escape_string($tmp[0]);
			$affilid = getAffiliationID($tmp[1]);
			if(is_null($affilid)) {
				return array('status' => 'error',
				             'errorcode' => 54,
				             'errormsg' => "unknown affiliation for login user group: {$tmp[1]}");
		}
		else {
			$esclogingroup = mysql_real_escape_string($logingroup);
			$affilid = DEFAULT_AFFILID;
		}
		$logingroupid = getUserGroupID($esclogingroup, $affilid, 1);
		if(is_null($logingroupid)) {
			return array('status' => 'error',
			             'errorcode' => 55,
			             'errormsg' => "unknown login user group: $logingroup");
		$logingroupid = '';
	$ipaddr = processInputData($ipaddr, ARG_STRING);
	$ipaddrArr = explode('.', $ipaddr);
	if($ipaddr != '' && (! preg_match('/^(([0-9]){1,3}\.){3}([0-9]){1,3}$/', $ipaddr) ||
		$ipaddrArr[0] < 1 || $ipaddrArr[0] > 255 ||
		$ipaddrArr[1] < 0 || $ipaddrArr[1] > 255 ||
		$ipaddrArr[2] < 0 || $ipaddrArr[2] > 255 ||
		$ipaddrArr[3] < 0 || $ipaddrArr[3] > 255)) {
		return array('status' => 'error',
		             'errorcode' => 57,
		             'errormsg' => "Invalid IP address. Must be w.x.y.z with each of "
		                         . "w, x, y, and z being between 1 and 255 (inclusive)");
	}
	$macaddr = processInputData($macaddr, ARG_STRING);
	if($macaddr != '' && ! preg_match('/^(([A-Fa-f0-9]){2}:){5}([A-Fa-f0-9]){2}$/', $macaddr)) {
		return array('status' => 'error',
		             'errorcode' => 58,
		             'errormsg' => "Invalid MAC address.  Must be XX:XX:XX:XX:XX:XX "
		                         . "with each pair of XX being from 00 to FF (inclusive)");
	}
	$monitored = processInputData($monitored, ARG_NUMERIC);
	if($monitored != 0 && $monitored != 1)
		$monitored = 0;
	$start = processInputData($start, ARG_STRING, 1);
	$end = processInputData($end, ARG_STRING, 1);
	#$foruser = processInputData($foruser, ARG_STRING, 1);
	$name = processInputData($name, ARG_STRING);
	if(get_magic_quotes_gpc())
		$name = stripslashes($name);
	if(! preg_match('/^([-a-zA-Z0-9_\. ]){0,255}$/', $name)) {
		             'errorcode' => 58,
						 'errormsg' => "Invalid name. Can only contain letters, numbers, "
		                         . "spaces, dashes(-), underscores(_), and periods(.) "
		                         . "and be up to 255 characters long");
	$name = mysql_real_escape_string($name);

	# validate $start
	if($start != 'now' && ! is_numeric($start)) {
		return array('status' => 'error',
		             'errorcode' => 4,
		             'errormsg' => "received invalid input for start");
	# validate $end
	if($end != 'indefinite' && ! is_numeric($end)) {
		             'errorcode' => 59,
		             'errormsg' => "received invalid input for end");
	}
	$nowfuture = 'future';
	if($start == 'now') {
		$start = unixFloor15(time());
		$nowfuture = 'now';
	}
	else
		if($start < (time() - 30))
			return array('status' => 'error',
			             'errorcode' => 5,
			             'errormsg' => "start time is in the past");
	if($end == 'indefinite')
		$end = datetimeToUnix("2038-01-01 00:00:00");
	elseif($end % (15 * 60))
		$end = unixFloor15($end) + (15 * 60);
	elseif($end < ($start + 900))
		return array('status' => 'error',
		             'errorcode' => 88,
		             'errormsg' => "end time must be at least 15 minutes after start time");

	$max = getMaxOverlap($user['id']);
	if(checkOverlap($start, $end, $max)) {
		return array('status' => 'error',
		             'errorcode' => 7,
		             'errormsg' => "reservation overlaps with another one you "
		                         . "have, and you are allowed $max "
		                         . "overlapping reservations at a time");
	}

	$images = getImages();
	$revisionid = getProductionRevisionid($imageid);
	$rc = isAvailable($images, $imageid, $revisionid, $start, $end,
	                  0, 0, 0, 0, $ipaddr, $macaddr);
	if($rc < 1) {
		addLogEntry($nowfuture, unixToDatetime($start), 
		            unixToDatetime($end), 0, $imageid);
		return array('status' => 'notavailable');
	}
	$return['requestid']= addRequest();
	$query = "UPDATE reservation "
	       . "SET remoteIP = '$remoteIP' "
	       . "WHERE requestid = {$return['requestid']}";
	doQuery($query);
	$fields = array('requestid');
	$values = array($return['requestid']);
	if($name != '') {
		$fields[] = 'name';
		$values[] = "'$name'";
	}
	if($ipaddr != '') {
		$fields[] = 'fixedIP';
		$values[] = "'$ipaddr'";
	}
	if($macaddr != '') {
		$fields[] = 'fixedMAC';
		$values[] = "'$macaddr'";
	}
	if($admingroupid != 0) {
		$fields[] = 'admingroupid';
		$values[] = $admingroupid;
	}
	if($logingroupid != 0) {
		$fields[] = 'logingroupid';
		$values[] = $logingroupid;
	}
	if($monitored != 0) {
		$fields[] = 'monitored';
		$values[] = 1;
	}
	$allfields = implode(',', $fields);
	$allvalues = implode(',', $values);
	$query = "INSERT INTO serverrequest ($allfields) VALUES ($allvalues)";
	doQuery($query, 101);
	$return['status'] = 'success';
	return $return;
}

////////////////////////////////////////////////////////////////////////////////
///
/// \fn XMLRPCgetRequestIds()
///
/// \return an array with at least one index named 'status' which will have
/// one of these values\n
/// \b error - error occurred; there will be 2 additional elements in the array:
/// \li \b errorcode - error number\n
/// \li \b errormsg - error string\n
///
/// \b success - request was successfully ended; there will be an additional
/// element whose index is 'requests' which is an array of arrays, each having
/// these elements (or empty if no existing requests):\n
/// \li \b requestid - id of the request\n
/// \li \b imageid - id of the image\n
/// \li \b imagename - name of the image\n
/// \li \b start - unix timestamp of start time\n
/// \li \b end - unix timestamp of end time\n
/// \li \b OS - name of OS used in image\n
/// \li \b isserver - 0 or 1 - whether or not this is a server reservation\n
/// \li \b state - current state of reservation\n
/// \li \b servername - only included if isserver == 1 - name of the reservation
///
/// \brief gets information about all of user's requests
///
////////////////////////////////////////////////////////////////////////////////
function XMLRPCgetRequestIds() {
	global $user;
	$requests = getUserRequests("all");
	if(empty($requests))
		return array('status' => 'success', 'requests' => array());
	$states = getStates();
	$ret = array();
	foreach($requests as $req) {
		$start = datetimeToUnix($req['start']);
		$end = datetimeToUnix($req['end']);
		$tmp = array('requestid' => $req['id'],
		             'imageid' => $req['imageid'],
		             'imagename' => $req['prettyimage'],
		             'start' => $start,
		             'end' => $end,
		             'OS' => $req['OS'],
		             'isserver' => $req['server']);
		if($req['currstateid'] == 14)
			$tmp['state'] = $states[$req['laststateid']];
		else
			$tmp['state'] = $states[$req['currstateid']];
		if($req['server'])
			$tmp['servername'] = $req['servername'];
		array_push($ret, $tmp);
	}
	return array('status' => 'success', 'requests' => $ret);
}

////////////////////////////////////////////////////////////////////////////////
///
/// \fn XMLRPCgetRequestStatus($requestid)
///
/// \param $requestid - id of a request
///
/// \return an array with at least one index named '\b status' which will have
/// one of these values:\n
/// \b error - error occurred; there will be 2 additional elements in the array:
/// \li \b errorcode - error number\n
/// \li \b errormsg - error string\n
///
/// \b ready - request is ready\n
/// \b failed - request failed to load properly\n
/// \b timedout - request timed out (user didn't connect before timeout
/// expired)\n
/// \b loading - request is still loading; there will be an additional element
/// in the array:
/// \li \b time - the estimated wait time (in minutes) for loading to complete\n
///
/// \b future - start time of request is in the future\n
///
/// \brief determines and returns the status of the request
///
////////////////////////////////////////////////////////////////////////////////
function XMLRPCgetRequestStatus($requestid) {
	global $user;
	$requestid = processInputData($requestid, ARG_NUMERIC);
	$userRequests = getUserRequests('all', $user['id']);
	$found = 0;
	foreach($userRequests as $req) {
		if($req['id'] == $requestid) {
			$request = $req;
			$found = 1;
			break;
		}
	}
	if(! $found)
		return array('status' => 'error',
		             'errorcode' => 1,
		             'errormsg' => 'unknown requestid');

	$now = time();
	# request is ready
	if(requestIsReady($request))
		return array('status' => 'ready');
	# request failed
	elseif($request["currstateid"] == 5)
		return array('status' => 'failed');
	# other cases where the reservation start time has been reached
	elseif(datetimeToUnix($request["start"]) < $now) {
		# request has timed out
		if($request["currstateid"] == 12 ||
		   $request["currstateid"] == 11 ||
		   ($request["currstateid"] == 14 &&
		   $request["laststateid"] == 11)) {
			return array('status' => 'timedout');
		}
		# computer is loading
		else {
			$imageid = $request['imageid'];
			$images = getImages(0, $imageid);
			$remaining = 1;
			$computers = getComputers(0, 0, $request['computerid']);
			if(isComputerLoading($request, $computers)) {
				if(datetimeToUnix($request["daterequested"]) >=
					datetimeToUnix($request["start"]))
					$startload = datetimeToUnix($request["daterequested"]);
				else
					$startload = datetimeToUnix($request["start"]);
				$imgLoadTime = getImageLoadEstimate($imageid);
				if($imgLoadTime == 0)
					$imgLoadTime = $images[$imageid]['reloadtime'] * 60;
				$tmp = ($imgLoadTime - ($now - $startload)) / 60;
				$remaining = sprintf("%d", $tmp) + 1;
				if($remaining < 1) {
					$remaining = 1;
				}
			}
			return array('status' => 'loading', 'time' => $remaining);
		}
	}
	# reservation is in the future
	else
		return array('status' => 'future');
}

////////////////////////////////////////////////////////////////////////////////
///
/// \fn XMLRPCgetRequestConnectData($requestid, $remoteIP)
///
/// \param $requestid - id of a request
/// \param $remoteIP - ip address of connecting user's computer
///
/// \return an array with at least one index named '\b status' which will have
/// one of these values\n
/// \b error - error occurred; there will be 2 additional elements in the array:
/// \li \b errorcode - error number\n
/// \li \b errormsg - error string\n
///
/// \b ready - request is ready; there will be 3 additional elements in the
/// array:\n
/// \li \b serverIP - address of the reserved machine
/// \li \b user - user to use when connecting to the machine
/// \li \b password - password to use when connecting to the machine
///
/// \b notready - request is not ready for connection
///
/// \brief if request is ready, adds the connecting user's computer to the
/// request and returns info about how to connect to the computer
///
////////////////////////////////////////////////////////////////////////////////
function XMLRPCgetRequestConnectData($requestid, $remoteIP) {
	global $user;
	$requestid = processInputData($requestid, ARG_NUMERIC);
	$remoteIP = processInputData($remoteIP, ARG_STRING, 1);
	if(! preg_match('/^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/', $remoteIP, $matches) ||
	   $matches[1] < 1 || $matches[1] > 223 ||
	   $matches[2] > 255 ||
	   $matches[3] > 255 ||
	   $matches[4] > 255) {
		return array('status' => 'error',
		             'errorcode' => 2,
		             'errormsg' => 'invalid IP address');
	}
	$userRequests = getUserRequests('all', $user['id']);
	$found = 0;
	foreach($userRequests as $req) {
		if($req['id'] == $requestid) {
			$request = $req;
			$found = 1;
			break;
		}
	}
	if(! $found)
		return array('status' => 'error',
		             'errorcode' => 1,
		             'errormsg' => 'unknown requestid');

	// FIXME - add support for cluster requests
	if(requestIsReady($request)) {
		$requestData = getRequestInfo($requestid);
		$query = "UPDATE reservation "
		       . "SET remoteIP = '$remoteIP' "
		$connectport = $requestData["reservations"][0]["connectport"];
		$connectMethods = getImageConnectMethodTexts(
		$requestData["reservations"][0]["imageid"],
		$requestData["reservations"][0]["imagerevisionid"]);
		foreach($connectMethods as $key => $cm) {
			$connecttext = $cm["connecttext"];
			$connecttext = preg_replace("/#userid#/", $thisuser, $connecttext); 
			$connecttext = preg_replace("/#password#/", $passwd, $connecttext); 
			$connecttext = preg_replace("/#connectIP#/", $serverIP, $connecttext); 
			$connecttext = preg_replace("/#connectport#/", $connectport, $connecttext); 
			$connectMethods[$key]["connecttext"] = $connecttext;
		}
		             'password' => $passwd,
		             'connectport' => $connectport,
		             'connectMethods' => $connectMethods);
/// started needs to be extended, delete the request and submit a new one
/// \fn XMLRPCsetRequestEnding($requestid, $end)
/// \param $requestid - id of a request
/// \param $end - unix timestamp for end of reservation; will be rounded up to
/// the nearest 15 minute increment
/// \b success - request was successfully extended\n
///
/// \brief modifies the end time of an active request; if a request that has not
/// started needs to be modifed, delete the request and submit a new one
////////////////////////////////////////////////////////////////////////////////
function XMLRPCsetRequestEnding($requestid, $end) {
	global $user;

	$requestid = processInputData($requestid, ARG_NUMERIC);
	$userRequests = getUserRequests('all', $user['id']);
	$found = 0;
	foreach($userRequests as $req) {
		if($req['id'] == $requestid) {
			$request = getRequestInfo($requestid);
			$found = 1;
			break;
		}
	}
	if(! $found)
		return array('status' => 'error',
		             'errorcode' => 1,
		             'errormsg' => 'unknown requestid');

	// make sure user is a member of the 'Specify End Time' group
	$groupid = getUserGroupID('Specify End Time');
	$members = getUserGroupMembers($groupid);
	if(! $request['serverrequest'] && ! array_key_exists($user['id'], $members)) {
		return array('status' => 'error',
		             'errorcode' => 35,
		             'errormsg' => "access denied to specify end time");
	}

	$end = processInputData($end, ARG_NUMERIC);

	$maxend = datetimeToUnix("2038-01-01 00:00:00");
	if($end < 0 || $end > $maxend) {
		return array('status' => 'error',
		             'errorcode' => 36,
		             'errormsg' => "received invalid input for end");
	}

	$startts = datetimeToUnix($request['start']);
	if($end % (15 * 60))
		$end= unixFloor15($end) + (15 * 60);

	// check that reservation has started
	if($startts > time()) {
		return array('status' => 'error',
		             'errorcode' => 38,
		             'errormsg' => 'reservation has not started');
	}

	// check for overlap
	$max = getMaxOverlap($user['id']);
	if(checkOverlap($startts, $end, $max, $requestid)) {
		return array('status' => 'error',
		             'errorcode' => 41,
		             'errormsg' => 'overlapping reservation restriction',
		             'maxoverlap' => $max);
	}

	// check for computer being available for extended time?
	$timeToNext = timeToNextReservation($request);
	$movedall = 1;
	if($timeToNext > -1) {
		foreach($request["reservations"] as $res) {
			if(! moveReservationsOffComputer($res["computerid"])) {
				$movedall = 0;
				break;
			}
		}
	}
	if(! $movedall) {
		$timeToNext = timeToNextReservation($request);
		if($timeToNext >= 15)
			$timeToNext -= 15;
		$oldendts = datetimeToUnix($request['end']);
		// reservation immediately after this one, cannot extend
		if($timeToNext < 15) {
			return array('status' => 'error',
			             'errorcode' => 42,
			             'errormsg' => 'cannot extend due to another reservation immediately after this one');
		}
		// check that requested extension < $timeToNext
		elseif((($end - $oldendts) / 60) > $timeToNext) {
			$maxend = $oldendts + ($timeToNext * 60);
			return array('status' => 'error',
			             'errorcode' => 43,
			             'errormsg' => 'cannot extend by requested amount due to another reservation',
			             'maxend' => $maxend);
		}
	}
	$rc = isAvailable(getImages(), $request['reservations'][0]["imageid"],
	                  $request['reservations'][0]['imagerevisionid'],
	                  $startts, $end, $requestid);
	// conflicts with scheduled maintenance
	if($rc == -2) {
		addChangeLogEntry($request["logid"], NULL, unixToDatetime($end),
		                  $request['start'], NULL, NULL, 0);
		return array('status' => 'error',
		             'errorcode' => 46,
		             'errormsg' => 'requested time is during a maintenance window');
	}
	// concurrent license overlap
	elseif($rc == -1) {
		addChangeLogEntry($request["logid"], NULL, unixToDatetime($end),
		                  $request['start'], NULL, NULL, 0);
		return array('status' => 'error',
		             'errorcode' => 44,
		             'errormsg' => 'concurrent license restriction');
	}
	// could not extend for some other reason
	elseif($rc == 0) {
		addChangeLogEntry($request["logid"], NULL, unixToDatetime($end),
		                  $request['start'], NULL, NULL, 0);
		return array('status' => 'error',
		             'errorcode' => 45,
		             'errormsg' => 'cannot extend at this time');
	}
	// success
	updateRequest($requestid);
	return array('status' => 'success');
/// \fn XMLRPCendRequest($requestid)
/// \param $requestid - id of a request
/// \b success - request was successfully ended\n
///
/// \brief ends/deletes a request
////////////////////////////////////////////////////////////////////////////////
function XMLRPCendRequest($requestid) {
	global $user;
	$requestid = processInputData($requestid, ARG_NUMERIC);
	$userRequests = getUserRequests('all', $user['id']);
	$found = 0;
	foreach($userRequests as $req) {
		if($req['id'] == $requestid) {
			$request = getRequestInfo($requestid);
			$found = 1;
			break;
		}
	}
	if(! $found)
		return array('status' => 'error',
		             'errorcode' => 1,
		             'errormsg' => 'unknown requestid');

	deleteRequest($request);
	return array('status' => 'success');
}

////////////////////////////////////////////////////////////////////////////////
///
/// \fn XMLRPCautoCapture($requestid)
///
/// \param $requestid - id of request to be captured
///
/// \return an array with at least one index named 'status' which will have
/// one of these values:\n
/// \b error - error occurred; there will be 2 additional elements in the array:
/// \li \b errorcode - error number
/// \li \b errormsg - error string
///
/// \b success - image was successfully set to be captured
///
/// \brief creates entries in appropriate tables to capture an image and sets
/// the request state to image
///
////////////////////////////////////////////////////////////////////////////////
function XMLRPCautoCapture($requestid) {
	global $user, $xmlrpcBlockAPIUsers;
	if(! in_array($user['id'], $xmlrpcBlockAPIUsers)) {
		return array('status' => 'error',
		             'errorcode' => 47,
		             'errormsg' => 'access denied to XMLRPCautoCapture');
	}
	$query = "SELECT id FROM request WHERE id = $requestid";
	$qh = doQuery($query, 101);
	if(! mysql_num_rows($qh)) {
		return array('status' => 'error',
		             'errorcode' => 52,
		             'errormsg' => 'specified request does not exist');
	}
	$reqData = getRequestInfo($requestid);
	# check state of reservation
	if($reqData['stateid'] != 14 || $reqData['laststateid'] != 8) {
		return array('status' => 'error',
		             'errorcode' => 51,
		             'errormsg' => 'reservation not in valid state');
	}
	# check that not a cluster reservation
	if(count($reqData['reservations']) > 1) {
		return array('status' => 'error',
		             'errorcode' => 48,
		             'errormsg' => 'cannot image a cluster reservation');
	}
	require_once(".ht-inc/images.php");
	$imageid = $reqData['reservations'][0]['imageid'];
	$imageData = getImages(0, $imageid);
	$captime = unixToDatetime(time());
	$comments = "start: {$reqData['start']}<br>"
	          . "end: {$reqData['end']}<br>"
	          . "computer: {$reqData['reservations'][0]['reservedIP']}<br>"
	          . "capture time: $captime";
	# create new revision if requestor is owner and not a kickstart image
	if($imageData[$imageid]['installtype'] != 'kickstart' &&
	   $reqData['userid'] == $imageData[$imageid]['ownerid']) {
		$rc = updateExistingImage($requestid, $reqData['userid'], $comments, 1);
		if($rc == 0) {
			return array('status' => 'error',
			             'errorcode' => 49,
			             'errormsg' => 'error encountered while attempting to create new revision');
		}
	}
	# create a new image if requestor is not owner or a kickstart image
	else {
		$ownerdata = getUserInfo($reqData['userid'], 1, 1);
		$desc = "This is an autocaptured image.<br>"
		      . "captured from image: {$reqData['reservations'][0]['prettyimage']}<br>"
		      . "captured on: $captime<br>"
		      . "owner: {$ownerdata['unityid']}@{$ownerdata['affiliation']}<br>";
		$connectmethods = getImageConnectMethods($imageid, $reqData['reservations'][0]['imagerevisionid']);
		$data = array('requestid' => $requestid,
		              'description' => $desc,
		              'usage' => '',
		              'owner' => "{$ownerdata['unityid']}@{$ownerdata['affiliation']}",
		              'prettyname' => "Autocaptured ({$ownerdata['unityid']} - $requestid)",
		              'minram' => 64,
		              'minprocnumber' => 1,
		              'minprocspeed' => 500,
		              'minnetwork' => 10,
		              'maxconcurrent' => '',
		              'checkuser' => 1,
		              'rootaccess' => 1,
		              'sysprep' => 1,
		              'comments' => $comments,
		              'connectmethodids' => implode(',', array_keys($connectmethods)));
		$rc = submitAddImage($data, 1);
		if($rc == 0) {
			return array('status' => 'error',
			             'errorcode' => 50,
			             'errormsg' => 'error encountered while attempting to create image');
		}
	}
	return array('status' => 'success');
/// \b success - returns an array of images; there will be an additional element
/// in the array with an index of 'images' that is an array of images with
/// each element having the following two keys:\n
/// \li \b id - id of the image\n
/// \li \b name - name of the image
/// \brief gets a list of all images in a particular group
function XMLRPCgetGroupImages($name) {
	if($groupid = getResourceGroupID("image/$name")) {
		$membership = getResourceGroupMemberships('image');
		$resources = getUserResources(array("imageAdmin"), array("manageGroup"));
		$images = array();
		foreach($resources['image'] as $imageid => $image) {
			if(array_key_exists($imageid, $membership['image']) &&
			   in_array($groupid, $membership['image'][$imageid]))
				array_push($images, array('id' => $imageid, 'name' => $image));
		}
		return array('status' => 'success',
		             'images' => $images);
	}
	else {
		return array('status' => 'error',
		             'errorcode' => 83,
		             'errormsg' => 'invalid resource group name');
	}
/// \fn XMLRPCaddImageToGroup($name, $imageid)
/// \param $name - the name of an imageGroup
/// \param $imageid - the id of an image
/// \b success - image was added to the group\n
/// \brief adds an image to a resource group
///
////////////////////////////////////////////////////////////////////////////////
function XMLRPCaddImageToGroup($name, $imageid) {
	if($groupid = getResourceGroupID("image/$name")) {
		$groups = getUserResources(array("imageAdmin"), array("manageGroup"), 1);
		if(! array_key_exists($groupid, $groups['image'])) {
			return array('status' => 'error',
			             'errorcode' => 46,
			             'errormsg' => 'Unable to access image group');
		}
		$resources = getUserResources(array("imageAdmin"), array("manageGroup"));
		if(! array_key_exists($imageid, $resources['image'])) {
			return array('status' => 'error',
			             'errorcode' => 47,
			             'errormsg' => 'Unable to access image');
		}

		$allimages = getImages(0, $imageid);
		$query = "INSERT IGNORE INTO resourcegroupmembers "
		       .        "(resourceid, "
		       .        "resourcegroupid) "
		       . "VALUES "
		       .       "({$allimages[$imageid]['resourceid']}, "
		       .       "$groupid)";
		doQuery($query);
		return array('status' => 'success');
	}
	else {
		return array('status' => 'error',
		             'errorcode' => 83,
		             'errormsg' => 'invalid resource group name');
	}
/// \fn XMLRPCremoveImageFromGroup($name, $imageid)
///
/// \param $name - the name of an imageGroup
/// \param $imageid - the id of an image
///
/// \return an array with at least one index named 'status' which will have
/// one of these values\n
/// \b error - error occurred; there will be 2 additional elements in the array:
/// \li \b errorcode - error number\n
/// \li \b errormsg - error string\n
///
/// \b success - image was removed from the group\n
///
/// \brief removes an image from a resource group
///
////////////////////////////////////////////////////////////////////////////////
function XMLRPCremoveImageFromGroup($name, $imageid) {
	if($groupid = getResourceGroupID("image/$name")) {
		$groups = getUserResources(array("imageAdmin"), array("manageGroup"), 1);
		if(! array_key_exists($groupid, $groups['image'])) {
			return array('status' => 'error',
			             'errorcode' => 46,
			             'errormsg' => 'Unable to access image group');
		}
		$resources = getUserResources(array("imageAdmin"), array("manageGroup"));
		if(! array_key_exists($imageid, $resources['image'])) {
			return array('status' => 'error',
			             'errorcode' => 47,
			             'errormsg' => 'Unable to access image');
		}

		$allimages = getImages(0, $imageid);
		$query = "DELETE FROM resourcegroupmembers "
		       . "WHERE resourceid = {$allimages[$imageid]['resourceid']} AND "
		       .       "resourcegroupid = $groupid";
		doQuery($query);
		return array('status' => 'success');
	}
	else {
		return array('status' => 'error',
		             'errorcode' => 83,
		             'errormsg' => 'invalid resource group name');
	}
}

////////////////////////////////////////////////////////////////////////////////
///
/// \fn XMLRPCaddImageGroupToComputerGroup($imageGroup, $computerGroup)
/// \b success - successfully mapped an image group to a computer group\n
///
/// \brief map an image group to a computer group
///
////////////////////////////////////////////////////////////////////////////////
function XMLRPCaddImageGroupToComputerGroup($imageGroup, $computerGroup) {
	$imageid = getResourceGroupID("image/$imageGroup");
	$compid = getResourceGroupID("computer/$computerGroup");
	if($imageid && $compid) {
		$tmp = getUserResources(array("imageAdmin"),
		                        array("manageMapping"), 1);
		$imagegroups = $tmp['image'];
		$tmp = getUserResources(array("computerAdmin"),
		                        array("manageMapping"), 1);
		$computergroups = $tmp['computer'];

		if(array_key_exists($compid, $computergroups) &&
			array_key_exists($imageid, $imagegroups)) {
			$mapping = getResourceMapping("image", "computer",
			                              $imageid, $compid);
			if(! array_key_exists($imageid, $mapping) ||
			   ! in_array($compid, $mapping[$imageid])) {
				$query = "INSERT INTO resourcemap "
				       .        "(resourcegroupid1, "
				       .        "resourcetypeid1, "
				       .        "resourcegroupid2, "
				       .        "resourcetypeid2) "
				       . "VALUES ($imageid, "
				       .         "13, "
				       .         "$compid, "
				       .         "12)";
				doQuery($query, 101);
			}
			return array('status' => 'success');
		}
		else {
			return array('status' => 'error',
			             'errorcode' => 84,
			             'errormsg' => 'cannot access computer and/or image group');
		}
	}
	else {
		return array('status' => 'error',
		             'errorcode' => 83,
		             'errormsg' => 'invalid resource group name');
	}
/// \fn XMLRPCremoveImageGroupFromComputerGroup($imageGroup, $computerGroup)
/// \param $imageGroup - the name of an imageGroup
/// \param $computerGroup - the name of a computerGroup
/// \b success - successfully removed the mapping from an image group to a
/// computer group\n
///
/// \brief remove the mapping of an image group to a computer group
///
////////////////////////////////////////////////////////////////////////////////
function XMLRPCremoveImageGroupFromComputerGroup($imageGroup, $computerGroup) {
	$imageid = getResourceGroupID("image/$imageGroup");
	$compid = getResourceGroupID("computer/$computerGroup");
	if($imageid && $compid) {
		$tmp = getUserResources(array("imageAdmin"),
		                        array("manageMapping"), 1);
		$imagegroups = $tmp['image'];
		$tmp = getUserResources(array("computerAdmin"),
		                        array("manageMapping"), 1);
		$computergroups = $tmp['computer'];

		if(array_key_exists($compid, $computergroups) &&
			array_key_exists($imageid, $imagegroups)) {
			$mapping = getResourceMapping("image", "computer",
			                              $imageid, $compid);
			if(array_key_exists($imageid, $mapping) &&
			   in_array($compid, $mapping[$imageid])) {
				$query = "DELETE FROM resourcemap "
				       . "WHERE resourcegroupid1 = $imageid AND "
				       .       "resourcetypeid1 = 13 AND "
				       .       "resourcegroupid2 = $compid AND "
				       .       "resourcetypeid2 = 12";
				doQuery($query, 101);
			}
			return array('status' => 'success');
		}
		else {
			return array('status' => 'error',
			             'errorcode' => 84,
			             'errormsg' => 'cannot access computer and/or image group');
		}
	}
	else {
		return array('status' => 'error',
		             'errorcode' => 83,
		             'errormsg' => 'invalid resource group name');
	}
/// \fn XMLRPCgetNodes($root)
/// \param $root - (optional, default=top of tree) the ID of the node forming
/// the root of the hierarchy
/// \b success - returns an array of nodes; there will be an additional element
/// in the array with an index of 'nodes' that is an array of nodes with each
/// element having the following three keys:\n
/// \li \b id - id of the node\n
/// \li \b name - name of the node\n
/// \li \b parent - id of the parent node
/// \brief gets a list of all nodes in the privilege tree
///
////////////////////////////////////////////////////////////////////////////////
function XMLRPCgetNodes($root=NULL) {
	global $user;
	if(in_array("userGrant", $user["privileges"]) ||
		in_array("resourceGrant", $user["privileges"]) ||
		in_array("nodeAdmin", $user["privileges"])) {
		$root = processInputData($root, ARG_NUMERIC);
		$topNodes = $root ? getChildNodes($root) : getChildNodes();
		$nodes = array();
		$stack = array();
		foreach($topNodes as $id => $node) {
			$node['id'] = $id;
			array_push($nodes, $node);
			array_push($stack, $node);
		} 
		while(count($stack)) {
			$item = array_shift($stack);
			$children = getChildNodes($item['id']);
			foreach($children as $id => $node) {
				$node['id'] = $id;
				array_push($nodes, $node);
				array_push($stack, $node);
			}
		}
		return array('status' => 'success',
		             'nodes' => $nodes);
	}
	else {
		return array('status' => 'error',
		             'errorcode' => 70,
		             'errormsg' => 'User cannot access node content');
	}
/// \fn XMLRPCnodeExists($nodeName, $parentNode)
/// \param $nodeName - the name of a node
/// \param $parentNode - the ID of the parent node
/// \b success - returns an 'exists' element set to either 1 or 0\n
///
/// \brief indicates whether a node with that name already exists at this
/// location in the privilege tree
////////////////////////////////////////////////////////////////////////////////
function XMLRPCnodeExists($nodeName, $parentNode) {
	global $user;
	if(! is_numeric($parentNode)) {
		return array('status' => 'error',
		             'errorcode' => 78,
		             'errormsg' => 'Invalid nodeid specified');
	}
	if(in_array("userGrant", $user["privileges"]) ||
		in_array("resourceGrant", $user["privileges"]) ||
		in_array("nodeAdmin", $user["privileges"])) {
		if(get_magic_quotes_gpc())
			$nodeName = stripslashes($nodeName);
		$nodeName = mysql_real_escape_string($nodeName);
		// does a node with this name already exist?
		$query = "SELECT id "
		       . "FROM privnode "
		       . "WHERE name = '$nodeName' AND parent = $parentNode";
		$qh = doQuery($query, 335);
		if(mysql_num_rows($qh))
			return array('status' => 'success', 'exists' => TRUE);
		else
			return array('status' => 'success', 'exists' => FALSE);
	}
	else {
		return array('status' => 'error',
		             'errorcode' => 70,
		             'errormsg' => 'User cannot access node content');
	}
/// \brief add a node to the privilege tree as a child of the specified parent
/// node
///
////////////////////////////////////////////////////////////////////////////////
function XMLRPCaddNode($nodeName, $parentNode) {
	require_once(".ht-inc/privileges.php");
	global $user;
	if(! is_numeric($parentNode)) {
		return array('status' => 'error',
		             'errorcode' => 78,
		             'errormsg' => 'Invalid nodeid specified');
	}
	if(in_array("nodeAdmin", $user['privileges'])) {
		$nodeInfo = getNodeInfo($parentNode);
		if(is_null($tmp)) {
			return array('status' => 'error',
			             'errorcode' => 78,
			             'errormsg' => 'Invalid nodeid specified');
		}
		if(! validateNodeName($nodeName, $tmp)) {
			return array('status' => 'error',
			             'errorcode' => 81,
			             'errormsg' => 'Invalid node name');
		}

		if(checkUserHasPriv("nodeAdmin", $user['id'], $parentNode)) {
			$query = "SELECT id "
			       . "FROM privnode "
			       . "WHERE name = '$nodeName' AND parent = $parentNode";
			$qh = doQuery($query);
			if(mysql_num_rows($qh)) {
				return array('status' => 'error',
				             'errorcode' => 82,
				             'errormsg' => 'A node of that name already exists under ' . $nodeInfo['name']);
			}
			$query = "INSERT IGNORE INTO privnode "
			       .        "(parent, name) "
			       . "VALUES "
			       .        "($parentNode, '$nodeName')";
			doQuery($query);
			$qh = doQuery("SELECT LAST_INSERT_ID() FROM privnode", 101);
			if(! $row = mysql_fetch_row($qh)) {
				return array('status' => 'error',
				             'errorcode' => 85,
				             'errormsg' => 'Could not add node to database');
			}
			$nodeid = $row[0];
			return array('status' => 'success',
			             'nodeid' => $nodeid);
		}
		else {
			return array('status' => 'error',
			             'errorcode' => 49,
			             'errormsg' => 'Unable to add node at this location');
		}
	}
	else {
		return array('status' => 'error',
		             'errorcode' => 70,
		             'errormsg' => 'User cannot access node content');
	}
}
/// \fn XMLRPCremoveNode($nodeID)
/// \param $nodeID - the ID of a node
/// \b success - node was successfully deleted
/// \brief delete a node from the privilege tree
///
////////////////////////////////////////////////////////////////////////////////
function XMLRPCremoveNode($nodeID) {
	require_once(".ht-inc/privileges.php");
	global $user;
	if(! is_numeric($nodeID)) {
		return array('status' => 'error',
		             'errorcode' => 78,
		             'errormsg' => 'Invalid nodeid specified');
	}
	if(! in_array("nodeAdmin", $user['privileges'])) {
		return array('status' => 'error',
		             'errorcode' => 70,
		             'errormsg' => 'User cannot administer nodes');
	}
	if(! checkUserHasPriv("nodeAdmin", $user['id'], $nodeID)) {
		return array('status' => 'error',
		             'errorcode' => 57,
		             'errormsg' => 'User cannot edit this node');
	}
	$nodes = recurseGetChildren($nodeID);
	array_push($nodes, $nodeID);
	$deleteNodes = implode(',', $nodes);
	$query = "DELETE FROM privnode "
	       . "WHERE id IN ($deleteNodes)";
	doQuery($query, 345);
	return array('status' => 'success');
/// \b success - an additional element is returned:\n
/// \li \b privileges - array of privileges assigned at the node
/// \brief get a list of privileges for a user group at a particular node in the
/// privilege tree
function XMLRPCgetUserGroupPrivs($name, $affiliation, $nodeid) {
	require_once(".ht-inc/privileges.php");
	global $user;

	if(! is_numeric($nodeid)) {
		return array('status' => 'error',
		             'errorcode' => 78,
		             'errormsg' => 'Invalid nodeid specified');
	}
	if(! in_array("userGrant", $user["privileges"]) &&
		! in_array("resourceGrant", $user["privileges"]) &&
		! in_array("nodeAdmin", $user["privileges"])) {
		return array('status' => 'error',
		             'errorcode' => 62,
		             'errormsg' => 'Unable to view user group privileges');
	}
	$privileges = array();
	$nodePrivileges = getNodePrivileges($nodeid, 'usergroups');
	$cascadedNodePrivileges = getNodeCascadePrivileges($nodeid, 'usergroups'); 
	$cngp = $cascadedNodePrivileges['usergroups'];
	$ngp = $nodePrivileges['usergroups'];
	if(array_key_exists($name, $cngp)) {
		foreach($cngp[$name]['privs'] as $p) {
			if(! array_key_exists($name, $ngp) ||
			   ! in_array("block", $ngp[$name]['privs']))
				array_push($privileges, $p);
		}
	}
	if(array_key_exists($name, $ngp)) {
		foreach($ngp[$name]['privs'] as $p) {
			if($p != "block")
				array_push($privileges, $p);
		}
	}
	return array('status' => 'success',
	              'privileges' => array_unique($privileges));
/// \fn XMLRPCaddUserGroupPriv($name, $affiliation, $nodeid, $permissions)
/// \param $name - the name of the user group
/// \param $affiliation - the affiliation of the user group
/// \brief add privileges for a user group at a particular node in the
/// privilege tree
function XMLRPCaddUserGroupPriv($name, $affiliation, $nodeid, $permissions) {
	require_once(".ht-inc/privileges.php");
	if(! is_numeric($nodeid)) {
		return array('status' => 'error',
		             'errorcode' => 78,
		             'errormsg' => 'Invalid nodeid specified');
	}

	if(! checkUserHasPriv("userGrant", $user['id'], $nodeid)) {
		             'errorcode' => 52,
		             'errormsg' => 'Unable to add a user group to this node');
	}
	$groupid = $rc['id'];
	#$name = "$name@$affiliation";
	$perms = explode(':', $permissions);
	$usertypes = getTypes('users');
	array_push($usertypes["users"], "block");

	$diff = array_diff($perms, $usertypes['users']);
	if(count($diff) || (count($perms) == 1 && $perms[0] == 'cascade')) {
		             'errorcode' => 66,
		             'errormsg' => 'Invalid or missing permissions list supplied');
	$cnp = getNodeCascadePrivileges($nodeid, "usergroups");
	$np = getNodePrivileges($nodeid, "usergroups", $cnp);

	$diff = array_diff($perms, $np['usergroups'][$name]['privs']);
	if(empty($diff))
		return array('status' => 'success');

	updateUserOrGroupPrivs($groupid, $nodeid, $diff, array(), "group");
	return array('status' => 'success');
/// \fn XMLRPCremoveUserGroupPriv($name, $affiliation, $nodeid,
///                                   $permissions)
/// \param $permissions - a colon (:) delimited list of privileges to remove
/// \b success - privileges were successfully removed
/// \brief remove privileges for a resource group at a particular node in the
/// privilege tree
function XMLRPCremoveUserGroupPriv($name, $affiliation, $nodeid, $permissions) {
	require_once(".ht-inc/privileges.php");
	global $user;

	if(! is_numeric($nodeid)) {
		return array('status' => 'error',
		             'errorcode' => 78,
		             'errormsg' => 'Invalid nodeid specified');
	}
	if(! checkUserHasPriv("userGrant", $user['id'], $nodeid)) {
		             'errorcode' => 65,
		             'errormsg' => 'Unable to remove user group privileges on this node');
	$groupid = $rc['id'];
	#$name = "$name@$affiliation";
	$perms = explode(':', $permissions);
	$usertypes = getTypes('users');
	array_push($usertypes["users"], "block");

	$diff = array_diff($perms, $usertypes['users']);
	if(count($diff)) {
		             'errorcode' => 66,
		             'errormsg' => 'Invalid or missing permissions list supplied');
	$cnp = getNodeCascadePrivileges($nodeid, "usergroups");
	$np = getNodePrivileges($nodeid, "usergroups");

	if(array_key_exists($name, $cnp['usergroups']) &&
	   (! array_key_exists($name, $np['usergroups']) ||
	   ! in_array('block', $np['usergroups'][$name]))) {
		$intersect = array_intersect($cnp['usergroups'][$name]['privs'], $perms);
		if(count($intersect)) {
			return array('status' => 'error',
			             'errorcode' => 80,
			             'errormsg' => 'Unable to modify privileges cascaded to this node');
		}
	}
	$diff = array_diff($np['usergroups'][$name]['privs'], $perms);
	if(count($diff) == 1 && in_array("cascade", $diff))
		array_push($perms, "cascade");
	updateUserOrGroupPrivs($groupid, $nodeid, array(), $perms, "group");
	return array('status' => 'success');
}
/// \fn XMLRPCgetResourceGroupPrivs($name, $type, $nodeid)
/// \param $name - the name of the resource group
/// \param $type - the resource group type
/// \param $nodeid - the ID of the node in the privilege tree
/// \b success - an additional element is returned:\n
/// \li \b privileges - array of privileges assigned at the node
/// \brief get a list of privileges for a resource group at a particular node in
/// the privilege tree
function XMLRPCgetResourceGroupPrivs($name, $type, $nodeid) {
	require_once(".ht-inc/privileges.php");
	if(! is_numeric($nodeid)) {
		return array('status' => 'error',
		             'errorcode' => 78,
		             'errormsg' => 'Invalid nodeid specified');

	if(! in_array("userGrant", $user["privileges"]) &&
		! in_array("resourceGrant", $user["privileges"]) &&
		! in_array("nodeAdmin", $user["privileges"])) {
		             'errorcode' => 63,
		             'errormsg' => 'Unable to view resource group privileges');
	}
	if($typeid = getResourceTypeID($type)) {
		if(! $groupid = getResourceGroupID("$type/$name")) {
			             'errorcode' => 74,
			             'errormsg' => 'resource group does not exist');
		$np = getNodePrivileges($nodeid, 'resources');
		$cnp = getNodeCascadePrivileges($nodeid, 'resources'); 
		$key = "$type/$name/$groupid";
		if(array_key_exists($key, $np['resources']) &&
			(in_array('block', $np['resources'][$key]) ||
		   ! array_key_exists($key, $cnp['resources'])))
			$privs = $np['resources'][$key];
		elseif(array_key_exists($key, $cnp['resources']) &&
		   array_key_exists($key, $np['resources'])) {
			$allprivs = array_merge($cnp['resources'][$key], $np['resources'][$key]);
			$privs = array_unique($allprivs);
		elseif(array_key_exists($key, $cnp['resources']))
			$privs = $cnp['resources'][$key];
		else
			$privs = array();
		return array('status' => 'success',
		             'privileges' => $privs);
	else {
		             'errorcode' => 71,
		             'errormsg' => 'Invalid resource type');
/// \fn XMLRPCaddResourceGroupPriv($name, $type, $nodeid, $permissions)
/// \param $name - the name of the resource group
/// \param $type - the resource group type
/// \param $nodeid - the ID of the node in the privilege tree
/// \param $permissions - a colon (:) delimited list of privileges to add
/// \b success - privileges were successfully added
/// \brief add privileges for a resource group at a particular node in the
/// privilege tree
function XMLRPCaddResourceGroupPriv($name, $type, $nodeid, $permissions) {
	return _XMLRPCchangeResourceGroupPriv_sub('add', $name, $type, $nodeid,
	                                          $permissions);
/// \fn XMLRPCremoveResourceGroupPriv($name, $type, $nodeid, $permissions)
///
/// \param $name - the name of the resource group
/// \param $type - the resource type
/// \param $nodeid - the ID of the node in the privilege tree
/// \param $permissions - a colon (:) delimited list of privileges to remove
/// \b success - privileges were successfully removed
/// \brief remove privileges for a resource group from a node in the privilege
/// tree
function XMLRPCremoveResourceGroupPriv($name, $type, $nodeid, $permissions) {
	return _XMLRPCchangeResourceGroupPriv_sub('remove', $name, $type, $nodeid,
	                                          $permissions);
}

##################################################################################
###
### fn _XMLRPCchangeResourceGroupPriv_sub($mode, $name, $type, $nodeid,
###                                       $permissions)
###
### param $mode - 'add' or 'remove'
### param $name - the name of the resource group
### param $type - the resource type
### param $nodeid - the ID of the node in the privilege tree
### param $permissions - a colon (:) delimited list of privileges to remove
###
### return an array with at least one index named 'status' which will have
### one of these values\n
### error - error occurred; there will be 2 additional elements in the array:
### * errorcode - error number\n
### * errormsg - error string\n
###
### success - privileges were successfully added or removed
###
### brief internal function to be called from XMLRPCremoveResourceGroupPriv and
### XMLRPCaddResourceGroupPriv - adds or removes privileges for a resource group
### from a node in the privilege tree
###
################################################################################
function _XMLRPCchangeResourceGroupPriv_sub($mode, $name, $type, $nodeid,
                                            $permissions) {
	require_once(".ht-inc/privileges.php");

	if(! is_numeric($nodeid)) {
		return array('status' => 'error',
		             'errorcode' => 78,
		             'errormsg' => 'Invalid nodeid specified');

	if(! checkUserHasPriv("resourceGrant", $user['id'], $nodeid)) {
		return array('status' => 'error',
		             'errorcode' => 61,
		             'errormsg' => 'Unable to remove resource group privileges on this node');
	}

	$resourcetypes = getTypes('resources');
	if(! in_array($type, $resourcetypes['resources'])) {
		return array('status' => 'error',
		             'errorcode' => 71,
		             'errormsg' => 'Invalid resource type');
	}

	$groupid = getResourceGroupID("$type/$name");
	if(is_null($groupid)) {
		return array('status' => 'error',
		             'errorcode' => 74,
		             'errormsg' => 'resource group does not exist');
	}

	$changeperms = explode(':', $permissions);
	$allperms = getResourcePrivs();
	$diff = array_diff($changeperms, $allperms);
	if(count($diff)) {
		return array('status' => 'error',
		             'errorcode' => 66,
		             'errormsg' => 'Invalid or missing permissions list supplied');
	}

	$nocheckperms = array('block', 'cascade', 'available');
	$checkperms = array_diff($changeperms, $nocheckperms);

	$groupdata = getResourceGroups($type, $groupid);
	if(count($checkperms) &&
	   ! array_key_exists($groupdata[$groupid]["ownerid"], $user["groups"])) {
		return array('status' => 'error',
		             'errorcode' => 79,
		             'errormsg' => 'Unable to modify privilege set for resource group');
	}

	$key = "$type/$name/$groupid";
	$cnp = getNodeCascadePrivileges($nodeid, "resources");
	$np = getNodePrivileges($nodeid, 'resources');
	if(array_key_exists($key, $cnp['resources']) &&
	   (! array_key_exists($key, $np['resources']) ||
	   ! in_array('block', $np['resources'][$key]))) {
		$intersect = array_intersect($cnp['resources'][$key], $changeperms);
		if(count($intersect)) {
			return array('status' => 'error',
			             'errorcode' => 80,
			             'errormsg' => 'Unable to modify privileges cascaded to this node');
		}
	}

	if($mode == 'remove') {
		$diff = array_diff($np['resources'][$key], $changeperms);
		if(count($diff) == 1 && in_array("cascade", $diff))
			$changeperms[] = 'cascade';
	}

	if($mode == 'add')
		updateResourcePrivs("$groupid", $nodeid, $changeperms, array());
	elseif($mode == 'remove')
		updateResourcePrivs("$groupid", $nodeid, array(), $changeperms);
	return array('status' => 'success');
/// \fn XMLRPCgetUserGroups($groupType, $affiliationid)
/// \param $groupType - (optional, default=0) specify 0 for all groups, 1 for
/// only custom groups, 2 for only courseroll groups
/// \param $affiliationid - (optional, default=0) specifiy an affiliationid to
/// limit returned groups to only those matching the affiliation; pass 0 for
/// all affiliations
/// \return an array with two indices, one named 'status' which will have a
/// value of 'success', the other named 'groups' which will be an array of
/// arrays, each one having the following keys:\n
/// \li \b id\n
/// \li \b name\n
/// \li \b groupaffiliation\n
/// \li \b groupaffiliationid\n
/// \li \b ownerid\n
/// \li \b owner\n
/// \li \b affiliation\n
/// \li \b editgroupid\n
/// \li \b editgroup\n
/// \li \b editgroupaffiliationid\n
/// \li \b editgroupaffiliation\n
/// \li \b custom\n
/// \li \b courseroll\n
/// \li \b initialmaxtime\n
/// \li \b maxextendtime\n
/// \li \b overlapResCount\n
///
/// \brief builds a list of user groups
///
////////////////////////////////////////////////////////////////////////////////
function XMLRPCgetUserGroups($groupType=0, $affiliationid=0) {
	global $user;
	$groupType = processInputData($groupType, ARG_NUMERIC, 0, 0);
	$affiliationid = processInputData($affiliationid, ARG_NUMERIC, 0, 0);

	$groups = getUserGroups($groupType, $affiliationid);

	// Filter out any groups to which the user does not have access.
	$usergroups = array();
	foreach($groups as $id => $group) {
		if($group['ownerid'] == $user['id'] || 
		   (array_key_exists("editgroupid", $group) &&
		   array_key_exists($group['editgroupid'], $user["groups"])) || 
		   (array_key_exists($id, $user["groups"]))) {
			array_push($usergroups, $group);
		}
	}
	return array("status" => "success",
	             "groups" => $usergroups);
}

////////////////////////////////////////////////////////////////////////////////
///
/// \fn XMLRPCgetUserGroupAttributes($name, $affiliation)
///
/// \param $name - name of user group
/// \param $affiliation - affiliation of user group
///
/// \return an array with at least one index named 'status' which will have
/// one of these values:\n
/// \b success - there will be six additional elements in this case:
/// \li \b owner - user that will be the owner of the group in
///                username\@affiliation form
/// \li \b managingGroup - user group that can manage membership of this one in
///                        groupname\@affiliation form
/// \li \b initialMaxTime - (minutes) max initial time users in this group can
///                         select for length of reservations
/// \li \b totalMaxTime - (minutes) total length users in the group can have for
///                       a reservation (including all extensions)
/// \li \b maxExtendTime - (minutes) max length of time users can request as an
///                        extension to a reservation at a time
/// \li \b overlapResCount - maximum allowed number of overlapping reservations
/// allowed for users in this group
/// \brief gets information about a user group
function XMLRPCgetUserGroupAttributes($name, $affiliation) {
	global $user;
	if(! in_array('groupAdmin', $user['privileges'])) {
		             'errorcode' => 16,
		             'errormsg' => 'access denied for managing groups');
	$validate = array('name' => $name,
	                  'affiliation' => $affiliation);
	$rc = validateAPIgroupInput($validate, 1);
	if($rc['status'] == 'error')
		return $rc;
	$query = "SELECT ug.id, "
	       .        "ug.ownerid, "
	       .        "CONCAT(u.unityid, '@', a.name) AS owner, "
	       .        "ug.editusergroupid AS editgroupid, "
	       .        "eug.name AS editgroup, "
	       .        "eug.affiliationid AS editgroupaffiliationid, "
	       .        "euga.name AS editgroupaffiliation, "
	       .        "ug.initialmaxtime, "
	       .        "ug.totalmaxtime, "
	       .        "ug.maxextendtime, "
	       .        "ug.overlapResCount "
	       . "FROM usergroup ug "
	       . "LEFT JOIN user u ON (ug.ownerid = u.id) "
	       . "LEFT JOIN affiliation a ON (u.affiliationid = a.id) "
	       . "LEFT JOIN usergroup eug ON (ug.editusergroupid = eug.id) "
	       . "LEFT JOIN affiliation euga ON (eug.affiliationid = euga.id) "
	       . "WHERE ug.id = {$rc['id']}";
	$qh = doQuery($query, 101);
	if(! $row = mysql_fetch_assoc($qh)) {
		return array('status' => 'error',
		             'errorcode' => 18,
		             'errormsg' => 'user group with submitted name and affiliation does not exist');
	}
	// if not owner and not member of managing group, no access
	if($user['id'] != $row['ownerid'] && 
	   ! array_key_exists($row['editgroupid'], $user['groups'])) {
		return array('status' => 'error',
		             'errorcode' => 69,
		             'errormsg' => 'access denied to user group with submitted name and affiliation');
	}
	$ret = array('status' => 'success',
	             'owner' => $row['owner'],
	             'managingGroup' => "{$row['editgroup']}@{$row['editgroupaffiliation']}",
	             'initialMaxTime' => $row['initialmaxtime'],
	             'totalMaxTime' => $row['totalmaxtime'],
	             'maxExtendTime' => $row['maxextendtime'],
	             'overlapResCount' => $row['overlapResCount']);
	return $ret;
/// \fn XMLRPCaddUserGroup($name, $affiliation, $owner, $managingGroup,
///                        $initialMaxTime, $totalMaxTime, $maxExtendTime,
///                        $custom)
/// \param $name - name of user group
/// \param $affiliation - affiliation of user group
/// \param $owner - user that will be the owner of the group in
///                 username\@affiliation form
/// \param $managingGroup - user group that can manage membership of this one
/// \param $initialMaxTime - (minutes) max initial time users in this group can
///                          select for length of reservations
/// \param $totalMaxTime - (minutes) total length users in the group can have
///                        for a reservation (including all extensions)
/// \param $maxExtendTime - (minutes) max length of time users can request as an
///                         extension to a reservation at a time
/// \param $custom - (optional, default=1) set custom flag for user group; if
///                set to 0, $owner and $managingGroup will be ignored and group
///                membership will be managed via authentication protocol
/// \li \b errorcode - error number\n
/// \b success - user group was successfully created
/// \brief creates a new user group with the specified parameters
////////////////////////////////////////////////////////////////////////////////
function XMLRPCaddUserGroup($name, $affiliation, $owner, $managingGroup,
                            $initialMaxTime, $totalMaxTime, $maxExtendTime,
                            $custom=1) {
	global $user;
	if(! in_array('groupAdmin', $user['privileges'])) {
		return array('status' => 'error',
		             'errorcode' => 16,
		             'errormsg' => 'access denied for managing groups');
	}
	$validate = array('name' => $name,
	                  'affiliation' => $affiliation,
	                  'owner' => $owner,
	                  'managingGroup' => $managingGroup,
	                  'initialMaxTime' => $initialMaxTime,
	                  'totalMaxTime' => $totalMaxTime,
	                  'maxExtendTime' => $maxExtendTime,
	                  'custom' => $custom);
	$rc = validateAPIgroupInput($validate, 0);
	if($rc['status'] == 'error')
		return $rc;
	if($custom != 0 && $custom != 1)
		$custom = 1;
	if(! $custom)
		$rc['managingGroupID'] = NULL;
	$data = array('type' => 'user',
	              'owner' => $owner,
	              'name' => $name,
	              'affiliationid' => $rc['affiliationid'],
	              'editgroupid' => $rc['managingGroupID'],
	              'initialmax' => $initialMaxTime,
	              'totalmax' => $totalMaxTime,
	              'maxextend' => $maxExtendTime,
	              'overlap' => 0,
	              'custom' => $custom);
	if(! addGroup($data)) {
		return array('status' => 'error',
		             'errorcode' => 26,
		             'errormsg' => 'failure while adding group to database');
	}
	return array('status' => 'success');
}

////////////////////////////////////////////////////////////////////////////////
/// \fn XMLRPCeditUserGroup($name, $affiliation, $newName, $newAffiliation,
///                         $newOwner, $newManagingGroup, $newInitialMaxTime,
///                         $newTotalMaxTime, $newMaxExtendTime)
///
/// \param $name - name of user group
/// \param $affiliation - affiliation of user group
/// \param $newName - new name for user group
/// \param $newAffiliation - new affiliation for user group
/// \param $newOwner - (optional, default='') user that will be the owner of
///                    the group in username\@affiliation form
/// \param $newManagingGroup - (optional, default='') user group that can
///                            manage membership of this one
/// \param $newInitialMaxTime - (optional, default='') (minutes) max initial
///                             time users in this group can select for length
///                             of reservations
/// \param $newTotalMaxTime - (optional, default='') (minutes) total length
///                           users in the group can have for a reservation
///                           (including all extensions)
/// \param $newMaxExtendTime - (optional, default='') (minutes) max length of
///                            time users can request as an extension to a
///                            reservation at a time
///
/// \return an array with at least one index named 'status' which will have
/// one of these values:\n
/// \b error - error occurred; there will be 2 additional elements in the array:
/// \li \b errorcode - error number
/// \li \b errormsg - error string
///
/// \b success - user group was successfully updated
///
/// \brief modifies attributes of a user group\n
/// \b NOTE: an empty string may be passed for any of the new* fields to leave
/// that item unchanged
function XMLRPCeditUserGroup($name, $affiliation, $newName, $newAffiliation,
                             $newOwner='', $newManagingGroup='',
                             $newInitialMaxTime='', $newTotalMaxTime='',
                             $newMaxExtendTime='') {
	global $user, $mysql_link_vcl;
	if(! in_array('groupAdmin', $user['privileges'])) {
		             'errorcode' => 16,
		             'errormsg' => 'access denied for managing groups');

	$updates = array();

	# validate group exists and new values other than newName and newAffiliation
	#   are valid
	$validate = array('name' => $name,
	                  'affiliation' => $affiliation);
	if(get_magic_quotes_gpc())
		$newOwner = stripslashes($newOwner);
	if(! empty($newOwner))
		$validate['owner'] = $newOwner;
	if(! empty($newManagingGroup))
		$validate['managingGroup'] = $newManagingGroup;
	if(! empty($newInitialMaxTime)) {
		$validate['initialMaxTime'] = $newInitialMaxTime;
		$updates[] = "initialmaxtime = $newInitialMaxTime";
	}
	if(! empty($newTotalMaxTime)) {
		$validate['totalMaxTime'] = $newTotalMaxTime;
		$updates[] = "totalmaxtime = $newTotalMaxTime";
	}
	if(! empty($newMaxExtendTime)) {
		$validate['maxExtendTime'] = $newMaxExtendTime;
		$updates[] = "maxextendtime = $newMaxExtendTime";
	}
	$rc = validateAPIgroupInput($validate, 1);
	if($rc['status'] == 'error')
		return $rc;

	# get info about group
	$query = "SELECT ownerid "
	       .        "affiliationid, "
	       .        "custom, "
	       .        "courseroll "
	       . "FROM usergroup "
	       . "WHERE id = {$rc['id']}";
	if(! $row = mysql_fetch_assoc($qh)) {
		             'errorcode' => 18,
		             'errormsg' => 'user group with submitted name and affiliation does not exist');
	// if custom and not owner or custom/courseroll and no federated user group access, no access to edit group
	if(($row['custom'] == 1 && $user['id'] != $row['ownerid']) ||
	   (($row['custom'] == 0 || $row['courseroll'] == 1) &&
	   ! checkUserHasPerm('Manage Federated User Groups (global)') &&
	   (! checkUserHasPerm('Manage Federated User Groups (affiliation only)') ||
	   $row['affiliationid'] != $user['affiliationid']))) {
		             'errorcode' => 32,
		             'errormsg' => 'access denied to modify attributes for user group with submitted name and affiliation');
	# validate that newName and newAffiliation are valid
	if(($name != $newName || $affiliation != $newAffiliation) &&
	   (! empty($newName) || ! empty($newAffiliation))) {
		$validate = array('name' => $name,
		                  'affiliation' => $affiliation);
		if(! empty($newName)) {
			if(get_magic_quotes_gpc())
				$newName = stripslashes($newName);
			$validate['name'] = $newName;
			$tmp = mysql_real_escape_string($newName);
			$updates[] = "name = '$tmp'";
		if(! empty($newAffiliation))
			$validate['affiliation'] = $newAffiliation;
		$rc2 = validateAPIgroupInput($validate, 0);
		if($rc2['status'] == 'error') {
			if($rc2['errorcode'] == 27) {
				$rc2['errorcode'] = 31;
				$rc2['errormsg'] = 'existing user group with new form of name@affiliation';
			}
			return $rc2;
		if(! empty($newAffiliation))
			$updates[] = "affiliationid = {$rc2['affiliationid']}";
	if($row['custom']) {
		if(! empty($newOwner)) {
			$newownerid = getUserlistID(mysql_real_escape_string($newOwner));
			$updates[] = "ownerid = $newownerid";
		if(! empty($newManagingGroup))
			$updates[] = "editusergroupid = {$rc['managingGroupID']}";
	$sets = implode(',', $updates);
	if(count($updates) == 0) {
		return array('status' => 'error',
		             'errorcode' => 33,
		             'errormsg' => 'no new values submitted');
	$query = "UPDATE usergroup "
	       . "SET $sets "
	       . "WHERE id = {$rc['id']}";
	doQuery($query, 101);
	return array('status' => 'success');
/// \fn XMLRPCremoveUserGroup($name, $affiliation)
/// \b success - user group was successfully removed
/// \brief removes a user group along with all of its privileges
function XMLRPCremoveUserGroup($name, $affiliation) {
	global $user, $mysql_link_vcl;
		             'errormsg' => 'access denied for managing groups');
	                  'affiliation' => $affiliation);
	$rc = validateAPIgroupInput($validate, 1);
	$query = "SELECT ownerid, "
	       .        "affiliationid, "
	       .        "custom, "
	       .        "courseroll "
	       . "FROM usergroup "
	       . "WHERE id = {$rc['id']}";
	$qh = doQuery($query, 101);
	if(! $row = mysql_fetch_assoc($qh)) {
		             'errorcode' => 18,
		             'errormsg' => 'user group with submitted name and affiliation does not exist');
	// if custom and not owner or custom/courseroll and no federated user group access, no access to delete group
	if(($row['custom'] == 1 && $user['id'] != $row['ownerid']) ||
	   (($row['custom'] == 0 || $row['courseroll'] == 1) &&
	   ! checkUserHasPerm('Manage Federated User Groups (global)') &&
	   (! checkUserHasPerm('Manage Federated User Groups (affiliation only)') ||
	   $row['affiliationid'] != $user['affiliationid']))) {
		return array('status' => 'error',
		             'errorcode' => 29,
		             'errormsg' => 'access denied to delete user group with submitted name and affiliation');
	}
	if(checkForGroupUsage($rc['id'], 'user')) {
		return array('status' => 'error',
		             'errorcode' => 72,
		             'errormsg' => 'group currently in use and cannot be removed');
	}
	$query = "DELETE FROM usergroup "
	       . "WHERE id = {$rc['id']}";
	doQuery($query, 101);
	# validate something deleted
	if(mysql_affected_rows($mysql_link_vcl) == 0) {
		return array('status' => 'error',
		             'errorcode' => 30,
		             'errormsg' => 'failure while deleting group from database');
	}
	return array('status' => 'success');
/// \fn XMLRPCdeleteUserGroup($name, $affiliation)
/// \param $name - name of user group
/// \param $affiliation - affiliation of user group
/// one of these values:\n
/// \li \b errorcode - error number
/// \li \b errormsg - error string
/// \b success - user group was successfully removed
/// \brief alias for XMLRPCremoveUserGroup
function XMLRPCdeleteUserGroup($name, $affiliation) {
	# This was the original function. All other functions use 'remove' rather
	# than 'delete'. The function was renamed to XMLRPCremoveUserGroup. This was
	# kept for compatibility reasons
	return XMLRPCremoveUserGroup($name, $affiliation);
/// \fn XMLRPCgetUserGroupMembers($name, $affiliation)
/// \b success - there will be one additional element in this case:
/// \li \b members - array of members of the group in username\@affiliation form
/// \brief gets members of a user group\n
/// \b NOTE: it is possible to have a group with no members in which case
/// success will be returned with an empty array for members
function XMLRPCgetUserGroupMembers($name, $affiliation) {
		             'errormsg' => 'access denied for managing groups');
	$query = "SELECT ownerid, "
	       .        "editusergroupid AS editgroupid, "
	       .        "affiliationid, "
	       .        "custom, "
	       .        "courseroll "
	       . "FROM usergroup "
	       . "WHERE id = {$rc['id']}";
	// if custom and not owner and not member of managing group or 
	//    custom/courseroll and no federated user group access, no access to delete group
	if(($row['custom'] == 1 && $user['id'] != $row['ownerid'] &&
	   ! array_key_exists($row['editgroupid'], $user['groups'])) ||
	   (($row['custom'] == 0 || $row['courseroll'] == 1) &&
	   ! checkUserHasPerm('Manage Federated User Groups (global)') &&
	   (! checkUserHasPerm('Manage Federated User Groups (affiliation only)') ||
	   $row['affiliationid'] != $user['affiliationid']))) {
	$query = "SELECT CONCAT(u.unityid, '@', a.name) AS member "
	       . "FROM usergroupmembers ugm, "
	       .      "user u, "
	       .      "affiliation a "
	       . "WHERE ugm.usergroupid = {$rc['id']} AND "
	       .       "ugm.userid = u.id AND "
	       .       "u.affiliationid = a.id";
	$qh = doQuery($query, 101);
	$members = array();
	while($row = mysql_fetch_assoc($qh))
		$members[] = $row['member'];
	             'members' => $members);
/// \fn XMLRPCaddUsersToGroup($name, $affiliation, $users)
/// \param $users - array of users in username\@affiliation form to be added to
///                 the group
/// \b success - users successfully added to the group\n
/// \b warning - there was a non-fatal issue that occurred while processing
/// the call; there will be three additional elements in this case:
/// \li \b warningcode - warning number
/// \li \b warningmsg - warning string
/// \li \b failedusers - array of users in username\@affiliation form that could
///                      not be added
/// \brief adds users to a group
function XMLRPCaddUsersToGroup($name, $affiliation, $users) {
	global $user;
		             'errormsg' => 'access denied for managing groups');
	$query = "SELECT ownerid, "
	       .        "editusergroupid AS editgroupid "
	// if not owner and not member of managing group, no access
	if($user['id'] != $row['ownerid'] && 
	   ! array_key_exists($row['editgroupid'], $user['groups'])) {
		             'errorcode' => 28,
		             'errormsg' => 'access denied to user group with submitted name and affiliation');
	$fails = array();
	foreach($users as $_user) {
		if(empty($_user))
			continue;
		if(get_magic_quotes_gpc())
			$_user = stripslashes($_user);
		$esc_user = mysql_real_escape_string($_user);
		if(validateUserid($_user) == 1)
			addUserGroupMember($esc_user, $rc['id']);
		else
			$fails[] = $_user;
	}
	if(count($fails)) {
		$cnt = 'some';
		$code = 34;
		if(count($fails) == count($users)) {
			$cnt = 'all submitted';
			$code = 35;
		}
		return array('status' => 'warning',
		             'failedusers' => $fails,
		             'warningcode' => $code,
		             'warningmsg' => "failed to add $cnt users to user group");
/// \fn XMLRPCremoveUsersFromGroup($name, $affiliation, $users)
/// \param $users - array of users in username\@affiliation form to be removed
///                 from the group
/// \b success - users successfully removed from the group\n
/// \b warning - there was a non-fatal issue that occurred while processing
/// the call; there will be three additional elements in this case:
/// \li \b warningcode - warning number
/// \li \b warningmsg - warning string
/// \li \b failedusers - array of users in username\@affiliation form that could
///                      not be removed
/// \brief removes users from a group
function XMLRPCremoveUsersFromGroup($name, $affiliation, $users) {
	global $user, $findAffilFuncs;
		             'errormsg' => 'access denied for managing groups');
	$query = "SELECT ownerid, "
	       .        "editusergroupid AS editgroupid "
	// if not owner and not member of managing group, no access
	if($user['id'] != $row['ownerid'] && 
	   ! array_key_exists($row['editgroupid'], $user['groups'])) {
		             'errorcode' => 28,
		             'errormsg' => 'access denied to user group with submitted name and affiliation');
	$fails = array();
	foreach($users as $_user) {
		if(empty($_user))
			continue;
		if(get_magic_quotes_gpc())
			$_user = stripslashes($_user);
		$esc_user = mysql_real_escape_string($_user);
		# check that affiliation of user can be determined because getUserlistID
		#   will abort if it cannot find it
		$affilok = 0;
		foreach($findAffilFuncs as $func) {
			if($func($_user, $dump))
				$affilok = 1;
		if(! $affilok) {
			$fails[] = $_user;
			continue;
		$userid = getUserlistID($esc_user, 1);
		if(is_null($userid))
			$fails[] = $_user;
		else
			deleteUserGroupMember($userid, $rc['id']);
	if(count($fails)) {
		$cnt = 'some';
		$code = 36;
		if(count($fails) == count($users)) {
			$cnt = 'any';
			$code = 37;
		}
		return array('status' => 'warning',
		             'failedusers' => $fails,
		             'warningcode' => $code,
		             'warningmsg' => "failed to remove $cnt users from user group");
/// \fn XMLRPCgetResourceGroups($type)
/// \param $type - the resource group type
/// one of these values\n
/// \li \b errorcode - error number\n
/// \li \b errormsg - error string\n
/// \b success - a 'groups' element will contain an array of groups of the given
/// type\n
/// \brief get a list of resource groups of a particular type
function XMLRPCgetResourceGroups($type) {
	$resources = getUserResources(array("groupAdmin"), array("manageGroup"), 1);
	if(array_key_exists($type, $resources)) {
		return array('status' => 'success',
		             'groups' => $resources[$type]);
	else {
		             'errorcode' => 73,
		             'errormsg' => 'invalid resource group type');
/// \fn XMLRPCaddResourceGroup($name, $managingGroup, $type)
/// \param $name - the name of the resource group
/// \param $managingGroup - the name of the managing group
/// \param $type - the type of resource group
/// one of these values\n
/// \li \b errorcode - error number\n
/// \li \b errormsg - error string\n
/// \b success - the resource group was added
/// \brief add a resource group
function XMLRPCaddResourceGroup($name, $managingGroup, $type) {
	if(! in_array("groupAdmin", $user['privileges'])) {
		             'errormsg' => 'access denied for managing groups');

	$validate = array('managingGroup' => $managingGroup);

	$rc = validateAPIgroupInput($validate, 0);

	if($typeid = getResourceTypeID($type)) {
		if(checkForGroupName($name, 'resource', '', $typeid)) {
			return array('status' => 'error',
			             'errorcode' => 76,
			             'errormsg' => 'resource group already exists');
		}
		if(get_magic_quotes_gpc())
			$name = stripslashes($name);
		if(! preg_match('/^[-a-zA-Z0-9_\. ]{3,30}$/', $name)) {
			return array('status' => 'error',
			             'errorcode' => 87,
			             'errormsg' => 'Name must be between 3 and 30 characters and can only contain letters, numbers, spaces, and these characters: - . _');
		}
		$name = mysql_real_escape_string($name);
		$data = array('type' => 'resource',
		              'ownergroup' => $rc['managingGroupID'],
		              'resourcetypeid' => $typeid,
		              'name' => $name);
		if(! addGroup($data)) {
			return array('status' => 'error',
			             'errorcode' => 26,
			             'errormsg' => 'failure while adding group to database');
		}
	else {
		             'errorcode' => 68,
		             'errormsg' => 'invalid resource type');
/// \fn XMLRPCremoveResourceGroup($name, $type)
/// \param $name - the name of the resource group
/// \param $type - the resource group type
/// one of these values\n
/// \li \b errorcode - error number\n
/// \li \b errormsg - error string\n
///
/// \b success - the resource group was removed\n
///
/// \brief remove a resource group
///
////////////////////////////////////////////////////////////////////////////////
function XMLRPCremoveResourceGroup($name, $type) {
	global $user;
	if(! in_array("groupAdmin", $user['privileges'])) {
		return array('status' => 'error',
		             'errorcode' => 16,
		             'errormsg' => 'access denied for managing groups');
	}

	if($groupid = getResourceGroupID("$type/$name")) {
		$userresources = getUserResources(array("groupAdmin"),
		                                  array("manageGroup"), 1);
		if(array_key_exists($type, $userresources)) {
			if(array_key_exists($groupid, $userresources[$type])) {
				if(checkForGroupUsage($groupid, 'resource')) {
					return array('status' => 'error',
					             'errorcode' => 72,
					             'errormsg' => 'group currently in use and cannot be removed');
				}
				$query = "DELETE FROM resourcegroup "
				       . "WHERE id = $groupid";
				doQuery($query, 315);
				return array('status' => 'success');
			}
			else
				return array('status' => 'error',
				             'errorcode' => 75,
				             'errormsg' => 'access denied to specified resource group');
		}
	}
	return array('status' => 'error',
	             'errorcode' => 83,
	             'errormsg' => 'invalid resource group name');
}

////////////////////////////////////////////////////////////////////////////////
///
/// \fn XMLRPCblockAllocation($imageid, $start, $end, $numMachines,
///                           $usergroupid, $ignoreprivileges)
///
/// \param $imageid - id of the image to be used
/// \param $start - mysql datetime for the start time (i.e. machines should be
/// prep'd and ready by this time)
/// \param $end - mysql datetime for the end time
/// \param $numMachines - number of computers to allocate
/// \param $usergroupid - id of user group for checking user access to machines
/// \param $ignoreprivileges  - (optional, default=0) 0 (false) or 1 (true) - set
/// to 1 to select computers from any that are mapped to be able to run the
/// image; set to 0 to only select computers from ones that are both mapped and
/// that users in the usergroup assigned to this block allocation have been
/// granted access to through the privilege tree
///
/// \return an array with blockTimesid as an index with the value of the newly
/// created block time and at least one other index named 'status' which will
/// have one of these values:\n
/// \b error - error occurred; there will be 2 additional elements in the
/// array:
/// \b success - blockTimesid was processed; there will be two additional
/// elements in this case:
/// \li \b allocated - total number of desired allocations that have been
/// processed
/// \li \b unallocated - total number of desired allocations that have not been
/// processed
/// the call; there will be four additional elements in this case:
/// \li \b allocated - total number of desired allocations that have been
/// processed
/// \li \b unallocated - total number of desired allocations that have not been
/// processed\n\n
/// \b NOTE: status may be warning, but allocated may be 0 indicating there
/// were no errors that occurred, but there simply were not any machines
/// available
/// \brief creates and processes a block allocation according to the passed
/// in criteria
///
////////////////////////////////////////////////////////////////////////////////
function XMLRPCblockAllocation($imageid, $start, $end, $numMachines,
                               $usergroupid, $ignoreprivileges=0) {
	global $user, $xmlrpcBlockAPIUsers;
	if(! in_array($user['id'], $xmlrpcBlockAPIUsers)) {
		             'errorcode' => 34,
		             'errormsg' => 'access denied for managing block allocations');

	# valid $imageid
	$resources = getUserResources(array("imageAdmin", "imageCheckOut"));
	$resources["image"] = removeNoCheckout($resources["image"]);
	if(! array_key_exists($imageid, $resources['image'])) {
		             'errorcode' => 3,
		             'errormsg' => "access denied to $imageid");

	# validate $start and $end
	$dtreg = '([0-9]{4})-([0-9]{2})-([0-9]{2}) ([0-9]{2}):([0-9]{2}):([0-9]{2})';
	$startts = datetimeToUnix($start);
	$endts = datetimeToUnix($end);
	$maxend = datetimeToUnix("2038-01-01 00:00:00");
	if(! preg_match("/^$dtreg$/", $start) || $startts < 0 ||
	   $startts > $maxend) {
		             'errorcode' => 4,
		             'errormsg' => "received invalid input for start");
	if(! preg_match("/^$dtreg$/", $end) || $endts < 0 ||
	   $endts > $maxend) {
		return array('status' => 'error',
		             'errorcode' => 36,
		             'errormsg' => "received invalid input for end");

	# validate $numMachines
	if(! is_numeric($numMachines) || $numMachines < MIN_BLOCK_MACHINES ||
	   $numMachines > MAX_BLOCK_MACHINES) {
		return array('status' => 'error',
		             'errorcode' => 64,
		             'errormsg' => 'The submitted number of seats must be between ' . MIN_BLOCK_MACHINES . ' and ' . MAX_BLOCK_MACHINES . '.');

	# validate $usergroupid
	$groups = getUserGroups();
	if(! array_key_exists($usergroupid, $groups)) {
		return array('status' => 'error',
		             'errorcode' => 67,
		             'errormsg' => 'Submitted user group does not exist');
	}

	# validate ignoreprivileges
	if(! is_numeric($ignoreprivileges) ||
	   $ignoreprivileges < 0 ||
		$ignoreprivileges > 1) {
		return array('status' => 'error',
		             'errorcode' => 86,
		             'errormsg' => 'ignoreprivileges must be 0 or 1');
	}

	$ownerid = getUserlistID('vclreload@Local');
	$name = "API:$start";
	$managementnodes = getManagementNodes('future');
	if(empty($managementnodes)) {
		return array('status' => 'error',
		             'errorcode' => 12,
		             'errormsg' => 'could not allocate a management node to handle block allocation');
	}
	$mnid = array_rand($managementnodes);
	$query = "INSERT INTO blockRequest "
	       .        "(name, "
	       .        "imageid, "
	       .        "numMachines, "
	       .        "groupid, "
	       .        "repeating, "
	       .        "ownerid, "
	       .        "admingroupid, "
	       .        "managementnodeid, "
	       .        "expireTime, "
	       .        "status) "
	       . "VALUES "
	       .        "('$name', "
	       .        "$imageid, "
	       .        "$numMachines, "
	       .        "$usergroupid, "
	       .        "'list', "
	       .        "$ownerid, "
	       .        "0, "
	       .        "$mnid, "
	       .        "'$end', "
	       .        "'accepted')";
	doQuery($query, 101);
	$brid = dbLastInsertID();
	$query = "INSERT INTO blockTimes "
	       .        "(blockRequestid, "
	       .        "start, "
	       .        "end) "
	       . "VALUES "
	       .        "($brid, "
	       .        "'$start', "
	       .        "'$end')";
	doQuery($query, 101);
	$btid = dbLastInsertID();
	$query = "INSERT INTO blockWebDate "
	       .        "(blockRequestid, "
	       .        "start, "
	       .        "end, "
	       .        "days) "
	       . "VALUES "
	       .        "($brid, "
	       .        "'$start', "
	       .        "'$end', "
	       .        "0)";
	doQuery($query);
	$sh = date('g', $startts);
	$smi = date('i', $startts);
	$sme = date('a', $startts);
	$eh = date('g', $startts);
	$emi = date('i', $startts);
	$eme = date('a', $startts);
	$query = "INSERT INTO blockWebTime "
	       .        "(blockRequestid, "
	       .        "starthour, "
	       .        "startminute, "
	       .        "startmeridian, "
	       .        "endhour, "
	       .        "endminute, "
	       .        "endmeridian, "
	       .        "`order`) "
	       . "VALUES "
	       .        "($brid, "
	       .        "$sh,"
	       .        "$smi,"
	       .        "'$sme',"
	       .        "$eh,"
	       .        "$emi,"
	       .        "'$eme',"
	       .        "0)";
	doQuery($query);
	$return = XMLRPCprocessBlockTime($btid, $ignoreprivileges);
	$return['blockTimesid'] = $btid;
	return $return;
/// \fn XMLRPCprocessBlockTime($blockTimesid, $ignoreprivileges)
/// \param $blockTimesid - id from the blockTimes table
/// \param $ignoreprivileges - (optional, default=0) 0 (false) or 1 (true) - set
/// to 1 to select computers from any that are mapped to be able to run the
/// image; set to 0 to only select computers from ones that are both mapped and
/// that users in the usergroup assigned to this block allocation have been
/// granted access to through the privilege tree
/// \b completed - blockTimesid was previously successfully processed\n
/// \b success - blockTimesid was processed; there will be two additional
/// elements in this case:
/// \li \b allocated - total number of desired allocations that have been
/// processed
/// \li \b unallocated - total number of desired allocations that have not been
/// processed
/// \b warning - there was a non-fatal issue that occurred while processing
/// the call; there will be four additional elements in this case:
/// \li \b warningcode - warning number
/// \li \b warningmsg - warning string
/// \li \b allocated - total number of desired allocations that have been
/// processed
/// \li \b unallocated - total number of desired allocations that have not been
/// processed\n\n
///
/// \b NOTE: status may be warning, but allocated may be 0 indicating there
/// were no errors that occurred, but there simply were not any machines
/// available
///
/// \brief processes a block allocation for the blockTimes entry associated
/// with blockTimesid
function XMLRPCprocessBlockTime($blockTimesid, $ignoreprivileges=0) {
	global $requestInfo, $user, $xmlrpcBlockAPIUsers;
		             'errorcode' => 34,
		             'errormsg' => 'access denied for managing block allocations');

	# validate $blockTimesid
	if(! is_numeric($blockTimesid)) {
		             'errorcode' => 77,
		             'errormsg' => 'Invalid blockTimesid specified');

	# validate ignoreprivileges
	if(! is_numeric($ignoreprivileges) ||
	   $ignoreprivileges < 0 ||
		$ignoreprivileges > 1) {
		             'errorcode' => 86,
		             'errormsg' => 'ignoreprivileges must be 0 or 1');

	$return = array('status' => 'success');
	$query = "SELECT bt.start, "
	       .        "bt.end, "
	       .        "br.imageid, "
	       .        "br.numMachines, "
	       .        "br.groupid, "
	       .        "br.expireTime "
	       . "FROM blockRequest br, "
	       .      "blockTimes bt "
	       . "WHERE bt.blockRequestid = br.id AND "
	       .       "bt.id = $blockTimesid";
	$qh = doQuery($query, 101);
	if(! $rqdata = mysql_fetch_assoc($qh)) {
		             'errorcode' => 8,
		             'errormsg' => 'unknown blockTimesid');
	if(datetimeToUnix($rqdata['expireTime']) < time()) {
		return array('status' => 'error',
		             'errorcode' => 9,
		             'errormsg' => 'expired block allocation');
	$images = getImages(0, $rqdata['imageid']);
	if(empty($images)) {
		             'errorcode' => 10,
		             'errormsg' => 'invalid image associated with block allocation');

	$unixstart = datetimeToUnix($rqdata['start']);
	$unixend = datetimeToUnix($rqdata['end']);
	$revisionid = getProductionRevisionid($rqdata['imageid']);
	$imgLoadTime = getImageLoadEstimate($rqdata['imageid']);
	if($imgLoadTime == 0)
		$imgLoadTime = $images[$rqdata['imageid']]['reloadtime'] * 60;
	$vclreloadid = getUserlistID('vclreload@Local');
	$groupmembers = getUserGroupMembers($rqdata['groupid']);
	$userids = array_keys($groupmembers);

	# add any computers from future reservations users in the group made
	if(! empty($groupmembers)) {
		## find reservations by users
		$allids = implode(',', $userids);
		$query = "SELECT rq.id AS reqid, "
		       .        "UNIX_TIMESTAMP(rq.start) AS start, "
		       .        "rq.userid "
		       . "FROM request rq, "
		       .      "reservation rs "
		       . "WHERE rs.requestid = rq.id AND "
		       .       "rq.userid IN ($allids) AND "
		       .       "rq.start < '{$rqdata['end']}' AND "
		       .       "rq.end > '{$rqdata['start']}' AND "
		       .       "rs.imageid = {$rqdata['imageid']} AND "
		       .       "rs.computerid NOT IN (SELECT computerid "
		       .                             "FROM blockComputers "
		       .                             "WHERE blockTimeid = $blockTimesid)";
		$qh = doQuery($query);
		$donereqids = array();
		$blockCompVals = array();
		$checkstartbase = $unixstart - $imgLoadTime - 300;
		$reloadstartbase = unixToDatetime($checkstartbase);
		$rows = mysql_num_rows($qh);
		while($row = mysql_fetch_assoc($qh)) {
			if(array_key_exists($row['reqid'], $donereqids))
				continue;
			$donereqids[$row['reqid']] = 1;
			if($row['start'] < datetimeToUnix($rqdata['start'])) {
				$checkstart = $row['start'] - $imgLoadTime - 300;
				$reloadstart = unixToDatetime($checkstart);
				$reloadend = unixToDatetime($row['start']);
			else {
				$checkstart = $checkstartbase;
				$reloadstart = $reloadstartbase;
				$reloadend = $rqdata['start'];
			}
			# check to see if computer is available for whole block
			$rc = isAvailable($images, $rqdata['imageid'], $revisionid, $checkstart,
			                  $unixend, $row['reqid'], $row['userid'],
			                  $ignoreprivileges, 0, '', '', 1);
			// if not available for whole block, just skip this one
			if($rc < 1)
				continue;
			$compid = $requestInfo['computers'][0];
			# create reload reservation
			$reqid = simpleAddRequest($compid, $rqdata['imageid'], $revisionid,
			                          $reloadstart, $reloadend, 19, $vclreloadid);
			if($reqid == 0)
				continue;
			# add to blockComputers
			$blockCompVals[] = "($blockTimesid, $compid, {$rqdata['imageid']}, $reqid)";
			# process any subimages
			for($key = 1; $key < count($requestInfo['computers']); $key++) {
				$subimageid = $requestInfo['images'][$key];
				$subrevid = getProductionRevisionid($subimageid);
				$compid = $requestInfo['computers'][$key];
				$mgmtnodeid = $requestInfo['mgmtnodes'][$key];
				$blockCompVals[] = "($blockTimesid, $compid, $subimageid, $reqid)";

				$query = "INSERT INTO reservation "
				       .        "(requestid, "
				       .        "computerid, "
				       .        "imageid, "
				       .        "imagerevisionid, "
				       .        "managementnodeid) "
				       . "VALUES "
				       .       "($reqid, "
				       .       "$compid, "
				       .       "$subimageid, "
				       .       "$subrevid, "
				       .       "$mgmtnodeid)";
				doQuery($query, 101);
		if(count($blockCompVals)) {
			$blockComps = implode(',', $blockCompVals);
			$query = "INSERT INTO blockComputers "
			       .        "(blockTimeid, computerid, imageid, reloadrequestid) "
			       . "VALUES $blockComps";
			doQuery($query);
	# check to see if all computers have been allocated
	$query = "SELECT COUNT(computerid) AS allocated "
	       . "FROM blockComputers "
	       . "WHERE blockTimeid = $blockTimesid";
	$qh = doQuery($query, 101);
	if(! $row = mysql_fetch_assoc($qh)) {
		             'errorcode' => 15,
		             'errormsg' => 'failure to communicate with database');
	$compCompleted = $row['allocated'];
	if(array_key_exists('subimages', $images[$rqdata['imageid']]))
		$compsPerAlloc = 1 + count($images[$rqdata['imageid']]['subimages']);
	else
		$compsPerAlloc = 1;
	$toallocate = ($rqdata['numMachines'] * $compsPerAlloc) - $compCompleted;
	if($toallocate == 0) {
		if(count($blockCompVals)) {
			return array('status' => 'success',
			             'allocated' => $rqdata['numMachines'],
			             'unallocated' => 0);
		}
		return array('status' => 'completed');
	$reqToAlloc = $toallocate / $compsPerAlloc;
	if(! $ignoreprivileges) {
		# get userids in user group
		if(empty($groupmembers)) {
			             'errorcode' => 11,
			             'errormsg' => 'empty user group and ignoreprivileges set to 0');
		}
		# make length of $userids match $reqToAlloc by duplicating or trimming some users
		while($reqToAlloc > count($userids))
			$userids = array_merge($userids, $userids);
		if($reqToAlloc < count($userids))
			$userids = array_splice($userids, 0, $reqToAlloc);
	# staggering: stagger start times for this round (ie, do not worry about
	#   previous processing of this block time) such that there is 1 minute
	#   between the start times for each allocation
	$stagExtra = $reqToAlloc * 60;

	# determine estimated load time
	$loadtime = $imgLoadTime + (10 * 60); # add 10 minute fudge factor
	if((time() + $loadtime + $stagExtra) > $unixstart) {
		$return['status'] = 'warning';
		$return['warningcode'] = 13;
		$return['warningmsg'] = 'possibly insufficient time to load machines';
	$start = unixToDatetime($unixstart - $loadtime);

	$userid = 0;
	$allocated = 0;
	$blockCompVals = array();
	# FIXME (maybe) - if some subset of users in the user group have available
	# computers, but others do not, $allocated will be less than the desired
	# number of machines; however, calling this function enough times will
	# result in enough machines being allocated because they will continue to be
	# allocated based on the ones with machines available; this seems like odd
	# behavior
	$stagCnt = 0;
	$stagTime = 60;        # stagger reload reservations by 1 min
	if($imgLoadTime > 840) // if estimated load time is > 14 min
		$stagTime = 120;    #    stagger reload reservations by 2 min 
	for($i = 0; $i < $reqToAlloc; $i++) {
		$stagunixstart = $unixstart - $loadtime - ($stagCnt * $stagTime);
		$stagstart = unixToDatetime($stagunixstart);
		if(! $ignoreprivileges)
			$userid = array_pop($userids);
		# use end of block time to find available computers, but...
		$rc = isAvailable($images, $rqdata['imageid'], $revisionid, $stagunixstart,
		                  $unixend, 0, $userid, $ignoreprivileges);
		if($rc < 1)
			continue;

		$compid = $requestInfo['computers'][0];
		# ...use start of block time as end of reload reservation
		$reqid = simpleAddRequest($compid, $rqdata['imageid'], $revisionid,
		                          $stagstart, $rqdata['start'], 19, $vclreloadid);
		if($reqid == 0)
			continue;

		$stagCnt++;
		$allocated++;
		$blockCompVals[] = "($blockTimesid, $compid, {$rqdata['imageid']}, $reqid)";

		# process any subimages
		for($key = 1; $key < count($requestInfo['computers']); $key++) {
			$subimageid = $requestInfo['images'][$key];
			$subrevid = getProductionRevisionid($subimageid);
			$compid = $requestInfo['computers'][$key];
			$mgmtnodeid = $requestInfo['mgmtnodes'][$key];
			$blockCompVals[] = "($blockTimesid, $compid, $subimageid, $reqid)";

			$query = "INSERT INTO reservation "
			       .        "(requestid, "
			       .        "computerid, "
			       .        "imageid, "
			       .        "imagerevisionid, "
			       .        "managementnodeid) "
			       . "VALUES "
			       .       "($reqid, "
			       .       "$compid, "
			       .       "$subimageid, "
			       .       "$subrevid, "
			       .       "$mgmtnodeid)";
			doQuery($query, 101);
		}
		semUnlock();
		$blockComps = implode(',', $blockCompVals);
		$query = "INSERT INTO blockComputers "
		       .        "(blockTimeid, computerid, imageid, reloadrequestid) "
		       . "VALUES $blockComps";
		doQuery($query, 101);
		$blockCompVals = array();
	if($allocated == 0) {
		$return['status'] = 'warning';
		$return['warningcode'] = 14;
		$return['warningmsg'] = 'unable to allocate any machines';
	$return['allocated'] = ($compCompleted / $compsPerAlloc) + $allocated;
	$return['unallocated'] = $rqdata['numMachines'] - $return['allocated'];