CVE-2013-0267 (published 2018-02-21T15:29Z)

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.

Severity: MEDIUM
CVSS v2 base score: 6.5 (AV:N/AC:L/Au:S/C:P/I:P/A:P)
CVSS v3 base score: 8.8, HIGH (CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-264
cwe_name: Permissions, Privileges, and Access Controls
description: Weaknesses in this category are related to the management of permissions, privileges, and other security features that are used to perform access control.
cwe_id: CWE-20
cwe_name: Improper Input Validation
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.
extended_description: 
	   Input validation is a frequently-used technique
	   for checking potentially dangerous inputs in order to
	   ensure that the inputs are safe for processing within the
	   code, or when communicating with other components.  When
	   software does not validate input properly, an attacker is
	   able to craft the input in a form that is not expected by
	   the rest of the application. This will lead to parts of the
	   system receiving unintended input, which may result in
	   altered control flow, arbitrary control of a resource, or
	   arbitrary code execution.
	   Input validation is not the only technique for
	   processing input, however.  Other techniques attempt to
	   transform potentially-dangerous input into something safe, such
	   as filtering (CWE-790) - which attempts to remove dangerous
	   inputs - or encoding/escaping (CWE-116), which attempts to
	   ensure that the input is not misinterpreted when it is included
	   in output to another component. Other techniques exist as well
	   (see CWE-138 for more examples.)
	   Input validation can be applied to:
	   
	     raw data - strings, numbers, parameters, file contents, etc.
	     metadata - information about the raw data, such as headers or size
	   
	   Data can be simple or structured.  Structured data
	   can be composed of many nested layers, composed of
	   combinations of metadata and raw data, with other simple or
	   structured data.
	   Many properties of raw data or metadata may need
	   to be validated upon entry into the code, such
	   as:
	   
	     specified quantities such as size, length, frequency, price, rate, number of operations, time, etc.
	     implied or derived quantities, such as the actual size of a file instead of a specified size
	     indexes, offsets, or positions into more complex data structures
	     symbolic keys or other elements into hash tables, associative arrays, etc.
	     well-formedness, i.e. syntactic correctness - compliance with expected syntax 
	     lexical token correctness - compliance with rules for what is treated as a token
	     specified or derived type - the actual type of the input (or what the input appears to be)
	     consistency - between individual data elements, between raw data and metadata, between references, etc.
	     conformance to domain-specific rules, e.g. business logic 
	     equivalence - ensuring that equivalent inputs are treated the same
	     authenticity, ownership, or other attestations about the input, e.g. a cryptographic signature to prove the source of the data
	   
	   Implied or derived properties of data must often
	   be calculated or inferred by the code itself.  Errors in
	   deriving properties may be considered a contributing factor
	   to improper input validation.
	   
	   Note that "input validation" has very different
	   meanings to different people, or within different
	   classification schemes.  Caution must be used when
	   referencing this CWE entry or mapping to it.  For example,
	   some weaknesses might involve inadvertently giving control
	   to an attacker over an input when they should not be able
	   to provide an input at all, but sometimes this is referred
	   to as input validation.
	   Finally, it is important to emphasize that the
	   distinctions between input validation and output escaping
	   are often blurred, and developers must be careful to
	   understand the difference, including how input validation
	   is not always sufficient to prevent vulnerabilities,
	   especially when less stringent data types must be
	   supported, such as free-form text. Consider a SQL injection
	   scenario in which a person's last name is inserted into a
	   query. The name "O'Reilly" would likely pass the validation
	   step since it is a common last name in the English
	   language. However, this valid name cannot be directly
	   inserted into the database because it contains the "'"
	   apostrophe character, which would need to be escaped or
	   otherwise transformed. In this case, removing the
	   apostrophe might reduce the risk of SQL injection, but it
	   would produce incorrect behavior because the wrong name
	   would be recorded.
       
         

Fix information:
repo_url: https://github.com/apache/vcl
commit_details: repo_url: https://github.com/apache/vcl
author: Josh Thompson
author_date: 2013-02-07 19:43:01+00:00
msg: 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
repository: repo_url: https://github.com/apache/vcl
repo_name: apache/vcl
description: Mirror of Apache VCL
repo_language: Perl
owner: apache
file_changes: filename: groups.php
change_type: ModificationType.MODIFY
diff: @@ -1016,6 +1016,7 @@ function processGroupInput($checks=1) {
 ///
 ////////////////////////////////////////////////////////////////////////////////
 function checkForGroupName($name, $type, $id, $extraid) {
+	$name = mysql_real_escape_string($name);
 	if($type == "user")
 		$query = "SELECT id FROM usergroup "
 		       . "WHERE name = '$name' AND "
@@ -1090,9 +1091,9 @@ function updateGroup($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) {

programming_language: Perl
filename: privileges.php
change_type: ModificationType.MODIFY
diff: @@ -100,7 +100,7 @@ function viewNodes() {
 		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";
@@ -109,7 +109,7 @@ function viewNodes() {
 		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";
@@ -118,7 +118,7 @@ function viewNodes() {
 		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";
@@ -186,7 +186,7 @@ function viewNodes() {
 	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";
@@ -236,7 +236,7 @@ function viewNodes() {
 	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";
@@ -296,7 +296,7 @@ function viewNodes() {
 	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";
@@ -312,7 +312,7 @@ function viewNodes() {
 	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";
@@ -355,7 +355,7 @@ function viewNodes() {
 	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";
@@ -363,7 +363,7 @@ function viewNodes() {
 	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";
@@ -378,7 +378,7 @@ function viewNodes() {
 	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";
@@ -423,7 +423,7 @@ function viewNodes() {
 	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";
@@ -431,7 +431,7 @@ function viewNodes() {
 	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";
@@ -446,7 +446,7 @@ function viewNodes() {
 	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";
@@ -466,7 +466,8 @@ function viewNodes() {
 	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) {
@@ -501,7 +502,7 @@ function viewNodes() {
 	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";
@@ -509,7 +510,7 @@ function viewNodes() {
 	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";
@@ -528,7 +529,7 @@ function viewNodes() {
 	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";
@@ -539,7 +540,7 @@ function viewNodes() {
 	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";
@@ -547,7 +548,7 @@ function viewNodes() {
 	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";
@@ -571,7 +572,7 @@ function viewNodes() {
 	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";
@@ -579,7 +580,7 @@ function viewNodes() {
 	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";
@@ -599,7 +600,7 @@ function viewNodes() {
 	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";
@@ -611,7 +612,7 @@ function viewNodes() {
 	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";
@@ -619,7 +620,7 @@ function viewNodes() {
 	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";
@@ -632,7 +633,7 @@ function viewNodes() {
 
 	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";
@@ -648,10 +649,10 @@ function viewNodes() {
 	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";
@@ -680,17 +681,17 @@ function viewNodes() {
 	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";
@@ -732,7 +733,7 @@ function selectNode() {
 		$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>";
@@ -741,7 +742,7 @@ function selectNode() {
 		$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>";
@@ -750,7 +751,7 @@ function selectNode() {
 		$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>";
@@ -859,7 +860,7 @@ function selectNode() {
 	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>";
@@ -919,7 +920,7 @@ function selectNode() {
 	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>";
@@ -994,15 +995,15 @@ function AJsubmitAddChildNode() {
 		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 "
@@ -1033,9 +1034,10 @@ function AJsubmitAddChildNode() {
 		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);";
 }
 
@@ -1059,6 +1061,27 @@ function nodeExists($node) {
 		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()
@@ -1110,22 +1133,29 @@ function AJsubmitRenameNode() {
 		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);
@@ -1234,7 +1264,7 @@ function userLookup() {
 		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;
 			}
 		}
@@ -1602,7 +1632,7 @@ function printUserPrivRow($privname, $rownum, $privs, $types,
 	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
@@ -1740,7 +1770,7 @@ function getUserPrivRowHTML($privname, $rownum, $privs, $types,
 	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
@@ -2173,7 +2203,7 @@ function getNodePrivileges($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
@@ -2223,7 +2253,8 @@ function getNodeCascadePrivileges($node, $type="all", $privs=0) {
 	# 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, "
@@ -2233,10 +2264,15 @@ function getNodeCascadePrivileges($node, $type="all", $privs=0) {
 		       .       "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
@@ -2440,14 +2476,25 @@ function AJchangeUserPrivs() {
 	$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;
@@ -2481,17 +2528,28 @@ function AJchangeUserGroupPrivs() {
 		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;
@@ -2527,14 +2585,47 @@ function AJchangeResourcePrivs() {
 	$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;
@@ -2547,8 +2638,7 @@ function AJchangeResourcePrivs() {
 		$adds = array();
 		$removes = array($newpriv);
 	}
-	$tmpArr = explode('/', $resourcegrp);
-	updateResourcePrivs($tmpArr[2], $node, $adds, $removes);
+	updateResourcePrivs($groupid, $node, $adds, $removes);
 	$_SESSION['dirtyprivs'] = 1;
 }
 
@@ -2616,9 +2706,16 @@ function AJsubmitAddUserGroupPriv() {
 		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");
@@ -2660,13 +2757,19 @@ function AJsubmitAddResourcePriv() {
 	$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');";
@@ -2754,7 +2857,7 @@ function checkUserHasPriv($priv, $uid, $node, $privs=0, $cascadePrivs=0) {
 		   $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;

programming_language: Perl
filename: utils.php
change_type: ModificationType.MODIFY
diff: @@ -2274,6 +2274,7 @@ function updateResourcePrivs($group, $node, $adds, $removes) {
 	else
 		$groupid = getResourceGroupID($group);
 	foreach($adds as $type) {
+		$type = mysql_real_escape_string($type);
 		$query = "INSERT IGNORE INTO resourcepriv ("
 		       .        "resourcegroupid, "
 		       .        "privnodeid, "
@@ -2285,6 +2286,7 @@ function updateResourcePrivs($group, $node, $adds, $removes) {
 		doQuery($query, 377);
 	}
 	foreach($removes as $type) {
+		$type = mysql_real_escape_string($type);
 		$query = "DELETE FROM resourcepriv "
 		       . "WHERE resourcegroupid = $groupid AND "
 		       .       "privnodeid = $node AND "
@@ -2627,18 +2629,22 @@ function getUserGroupPrivTypes() {
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \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, "
@@ -2656,6 +2662,9 @@ function getResourceGroups($type="") {
 	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)) {
@@ -3310,7 +3319,7 @@ function processInputData($data, $type, $addslashes=0, $defaultvalue=NULL) {
 			if(! is_string($value))
 				$return[$index] = $defaultvalue;
 			elseif($addslashes)
-				$return[$index] = addslashes($value);
+				$return[$index] = mysql_real_escape_string($value);
 		}
 		return $return;
 	}
@@ -3319,7 +3328,7 @@ function processInputData($data, $type, $addslashes=0, $defaultvalue=NULL) {
 		if(strlen($return) == 0)
 			$return = $defaultvalue;
 		elseif($addslashes)
-			$return = addslashes($return);
+			$return = mysql_real_escape_string($return);
 	}
 
 	return $return;
@@ -8210,11 +8219,13 @@ function updateGroups($newusergroups, $userid) {
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \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
 ///
@@ -8222,15 +8233,16 @@ function updateGroups($newusergroups, $userid) {
 /// 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, "
@@ -8787,11 +8799,8 @@ 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;
 }
@@ -8970,8 +8979,10 @@ function getReservationLengthCeiling($length) {
 /// \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 "
@@ -8997,6 +9008,7 @@ function getResourceGroupID($groupdname) {
 ///
 ////////////////////////////////////////////////////////////////////////////////
 function getResourceTypeID($name) {
+	$name = mysql_real_escape_string($name);
 	$query = "SELECT id "
 	       . "FROM resourcetype "
 	       . "WHERE name = '$name'";
@@ -9811,24 +9823,24 @@ function xmlrpccall() {
 	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);
@@ -9970,7 +9982,7 @@ function xmlRPCabort($errcode, $query='') {
 ////////////////////////////////////////////////////////////////////////////////
 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";
@@ -10008,7 +10020,7 @@ function printXMLRPCerror($errcode) {
 /// \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
@@ -10058,8 +10070,7 @@ function validateAPIgroupInput($items, $exists) {
 	}
 	# 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,
@@ -10076,8 +10087,7 @@ function validateAPIgroupInput($items, $exists) {
 			                         . '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,
@@ -10089,12 +10099,13 @@ function validateAPIgroupInput($items, $exists) {
 			             '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');
@@ -10108,15 +10119,14 @@ function validateAPIgroupInput($items, $exists) {
 			             '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;
 	}

programming_language: PHP
filename: xmlrpcWrappers.php
change_type: ModificationType.MODIFY
diff: @@ -69,7 +69,7 @@
 ///
 /// \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() {
@@ -82,6 +82,28 @@ function XMLRPCaffiliations() {
 	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()
@@ -90,7 +112,7 @@ function XMLRPCaffiliations() {
 /// \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() {
@@ -98,11 +120,11 @@ function XMLRPCgetImages() {
 	$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;
@@ -132,7 +154,7 @@ function XMLRPCgetImages() {
 /// \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='') {
@@ -228,7 +250,7 @@ function XMLRPCaddRequest($imageid, $start, $length, $foruser='') {
 /// \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='') {
@@ -314,9 +336,27 @@ function XMLRPCaddRequestWithEnding($imageid, $start, $end, $foruser='') {
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \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
@@ -324,195 +364,421 @@ function XMLRPCaddRequestWithEnding($imageid, $start, $end, $foruser='') {
 /// \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
@@ -520,20 +786,20 @@ function XMLRPCgetRequestConnectData($requestid, $remoteIP) {
 				$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');
 }
@@ -554,7 +820,7 @@ function XMLRPCgetRequestConnectData($requestid, $remoteIP) {
 /// \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) {
@@ -678,10 +944,11 @@ function XMLRPCextendRequest($requestid, $extendtime) {
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \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
@@ -689,46 +956,136 @@ function XMLRPCextendRequest($requestid, $extendtime) {
 /// \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
@@ -736,38 +1093,126 @@ function XMLRPCremoveImageFromGroup($name, $imageid){
 /// \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');
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -782,40 +1227,43 @@ function XMLRPCaddImageToGroup($name, $imageid){
 /// \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
@@ -823,56 +1271,93 @@ function XMLRPCgetGroupImages($name){
 /// \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
@@ -883,56 +1368,60 @@ function XMLRPCaddImageGroupToComputerGroup($imageGroup, $computerGroup){
 /// \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
@@ -940,48 +1429,57 @@ function XMLRPCremoveImageGroupFromComputerGroup($imageGroup, $computerGroup){
 /// \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
@@ -989,38 +1487,55 @@ function XMLRPCgetNodes($root=NULL){
 /// \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
@@ -1028,34 +1543,40 @@ function XMLRPCnodeExists($nodeName, $parentNode){
 /// \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');
+	}
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -1073,73 +1594,75 @@ function XMLRPCremoveNode($nodeID){
 ///
 /// \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
@@ -1147,35 +1670,36 @@ function XMLRPCaddNode($nodeName, $parentNode){
 /// \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');
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -1192,22 +1716,30 @@ function XMLRPCremoveResourceGroupPriv($name, $type, $nodeid, $permissions){
 /// \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);
@@ -1215,95 +1747,35 @@ function XMLRPCgetUserGroupPrivs($name, $affiliation, $nodeid){
 	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
 ///
@@ -1315,67 +1787,25 @@ function XMLRPCgetResourceGroupPrivs($name, $type, $nodeid){
 ///
 /// \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);
@@ -1383,43 +1813,40 @@ function XMLRPCremoveUserGroupPriv($name, $affiliation, $nodeid, $permissions){
 	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
@@ -1427,20 +1854,26 @@ function XMLRPCremoveUserGroupPriv($name, $affiliation, $nodeid, $permissions){
 /// \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,
@@ -1449,36 +1882,49 @@ function XMLRPCaddUserGroupPriv($name, $affiliation, $nodeid, $permissions){
 	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
@@ -1486,129 +1932,71 @@ function XMLRPCaddUserGroupPriv($name, $affiliation, $nodeid, $permissions){
 /// \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
@@ -1616,35 +2004,25 @@ function XMLRPCsetRequestEnding($requestid, $end) {
 /// \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
@@ -1652,463 +2030,482 @@ function XMLRPCendRequest($requestid) {
 /// \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
@@ -2116,244 +2513,91 @@ function XMLRPCprocessBlockTime($blockTimesid, $ignoreprivileges=0) {
 /// \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
@@ -2364,77 +2608,74 @@ function XMLRPCaddResourceGroup($name, $managingGroup, $type){
 /// \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
@@ -2442,24 +2683,31 @@ function XMLRPCgetUserGroupAttributes($name, $affiliation) {
 /// \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);
@@ -2468,47 +2716,48 @@ function XMLRPCdeleteUserGroup($name, $affiliation) {
 		             '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
@@ -2516,52 +2765,31 @@ function XMLRPCdeleteUserGroup($name, $affiliation) {
 /// \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);
@@ -2570,300 +2798,405 @@ function XMLRPCeditUserGroup($name, $affiliation, $newName, $newAffiliation,
 		             '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
@@ -2871,346 +3204,305 @@ function XMLRPCremoveUsersFromGroup($name, $affiliation, $users) {
 /// \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);
-}
 ?>

programming_language: PHP