You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@vcl.apache.org by jf...@apache.org on 2013/02/06 21:57:55 UTC

svn commit: r1443208 [2/2] - /vcl/branches/vcl-2.3-bugfixes/web/.ht-inc/xmlrpcWrappers.php

Modified: vcl/branches/vcl-2.3-bugfixes/web/.ht-inc/xmlrpcWrappers.php
URL: http://svn.apache.org/viewvc/vcl/branches/vcl-2.3-bugfixes/web/.ht-inc/xmlrpcWrappers.php?rev=1443208&r1=1443207&r2=1443208&view=diff
==============================================================================
--- vcl/branches/vcl-2.3-bugfixes/web/.ht-inc/xmlrpcWrappers.php (original)
+++ vcl/branches/vcl-2.3-bugfixes/web/.ht-inc/xmlrpcWrappers.php Wed Feb  6 20:57:55 2013
@@ -84,6 +84,28 @@ function XMLRPCaffiliations() {
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
+/// \fn XMLRPCtest($string)
+///
+/// \param $string - a string
+///
+/// \return an array with 3 indices:\n
+/// \b status - will be 'success'\n
+/// \b message - will be 'RPC call worked successfully'\n
+/// \b string - contents of $string (after being sanatized)
+///
+/// \brief this is a test function that call be called when getting XML RPC
+/// calls to this site to work
+///
+////////////////////////////////////////////////////////////////////////////////
+function XMLRPCtest($string) {
+	$string = processInputData($string, ARG_STRING);
+	return array('status' => 'success',
+	             'message' => 'RPC call worked successfully',
+	             'string' => $string);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
 /// \fn XMLRPCgetImages()
 ///
 /// \return an array of image arrays, each with 2 indices:\n
@@ -314,9 +336,27 @@ function XMLRPCaddRequestWithEnding($ima
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \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,137 +364,357 @@ function XMLRPCaddRequestWithEnding($ima
 /// \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
+/// \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;
-				}
-			}
-			return array('status' => 'loading', 'time' => $remaining);
+			$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");
 		}
 	}
-	# reservation is in the future
 	else
-		return array('status' => 'future');
-}
-
-////////////////////////////////////////////////////////////////////////////////
-///
-/// \fn XMLRPCgetUserGroups($groupType, $affiliationid)
-///
-/// \param $groupType - (optional, default=0) specify 0 for all groups, 1 for
-/// only custom groups, 2 for only courseroll groups
-/// \param $affiliationid - (optional, default=0) specifiy an affiliationid to
-/// limit returned groups to only those matching the affiliation; pass 0 for
-/// all affiliations
-///
-/// \return an array with two indices, one named 'status' which will have a
-/// value of 'success', the other named 'groups' which will be an array of
-/// arrays, each one having the following keys:\n
-/// \li \b id\n
-/// \li \b name\n
-/// \li \b groupaffiliation\n
-/// \li \b groupaffiliationid\n
-/// \li \b ownerid\n
-/// \li \b owner\n
-/// \li \b affiliation\n
-/// \li \b editgroupid\n
-/// \li \b editgroup\n
-/// \li \b editgroupaffiliationid\n
-/// \li \b editgroupaffiliation\n
-/// \li \b custom\n
-/// \li \b courseroll\n
-/// \li \b initialmaxtime\n
-/// \li \b maxextendtime\n
-/// \li \b overlapResCount\n
-///
-/// \brief builds a list of user groups
-///
-////////////////////////////////////////////////////////////////////////////////
-function XMLRPCgetUserGroups($groupType=0, $affiliationid=0) {
-	global $user;
-	$groupType = processInputData($groupType, ARG_NUMERIC, 0, 0);
-	$affiliationid = processInputData($affiliationid, ARG_NUMERIC, 0, 0);
-
-	$groups = getUserGroups($groupType, $affiliationid);
-
-	// Filter out any groups to which the user does not have access.
-	$usergroups = array();
-	foreach($groups as $id => $group) {
-		if($group['ownerid'] == $user['id'] || 
-		   (array_key_exists("editgroupid", $group) &&
-		   array_key_exists($group['editgroupid'], $user["groups"])) || 
-		   (array_key_exists($id, $user["groups"]))) {
-			array_push($usergroups, $group);
+		$admingroupid = '';
+	if($logingroup != '') {
+		$logingroup = processInputData($logingroup, ARG_STRING);
+		if(get_magic_quotes_gpc())
+			$logingroup = stripslashes($logingroup);
+		if(preg_match('/@/', $logingroup)) {
+			$tmp = explode('@', $logingroup);
+			$esclogingroup = mysql_real_escape_string($tmp[0]);
+			$affilid = getAffiliationID($tmp[1]);
+			if(is_null($affilid)) {
+				return array('status' => 'error',
+				             'errorcode' => 54,
+				             'errormsg' => "unknown affiliation for login user group: {$tmp[1]}");
+			}
+		}
+		else {
+			$esclogingroup = mysql_real_escape_string($logingroup);
+			$affilid = DEFAULT_AFFILID;
+		}
+		$logingroupid = getUserGroupID($esclogingroup, $affilid, 1);
+		if(is_null($logingroupid)) {
+			return array('status' => 'error',
+			             'errorcode' => 55,
+			             'errormsg' => "unknown login user group: $logingroup");
 		}
 	}
-	return array("status" => "success",
-	             "groups" => $usergroups);
-}
+	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);
 
-////////////////////////////////////////////////////////////////////////////////
-///
+	$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' => 58,
+						 'errormsg' => "Invalid name. Can only contain letters, numbers, "
+		                         . "spaces, dashes(-), underscores(_), and periods(.) "
+		                         . "and be up to 255 characters long");
+	}
+	$name = mysql_real_escape_string($name);
+
+	# validate $start
+	if($start != 'now' && ! is_numeric($start)) {
+		return array('status' => 'error',
+		             'errorcode' => 4,
+		             'errormsg' => "received invalid input for start");
+	}
+	# validate $end
+	if($end != 'indefinite' && ! is_numeric($end)) {
+		return array('status' => 'error',
+		             'errorcode' => 59,
+		             'errormsg' => "received invalid input for end");
+	}
+
+	$nowfuture = 'future';
+	if($start == 'now') {
+		$start = unixFloor15(time());
+		$nowfuture = 'now';
+	}
+	else
+		if($start < (time() - 30))
+			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
@@ -684,10 +944,11 @@ function XMLRPCextendRequest($requestid,
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \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
@@ -695,95 +956,136 @@ function XMLRPCextendRequest($requestid,
 /// \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 removes an image from a resource group
+/// \brief modifies the end time of an active request; if a request that has not
+/// started needs to be modifed, delete the request and submit a new one
 ///
 ////////////////////////////////////////////////////////////////////////////////
-function 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');
+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');
 
-		$allimages = getImages(0, $imageid);
-		$query = "DELETE FROM resourcegroupmembers "
-		       . "WHERE resourceid = {$allimages[$imageid]['resourceid']} AND "
-		       .       "resourcegroupid = $groupid";
-		doQuery($query);
-		return array('status' => 'success');
+	// 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");
 	}
-	else {
+
+	$end = processInputData($end, ARG_NUMERIC);
+
+	$maxend = datetimeToUnix("2038-01-01 00:00:00");
+	if($end < 0 || $end > $maxend) {
 		return array('status' => 'error',
-		             'errorcode' => 83,
-		             'errormsg' => 'invalid resource group name');
+		             'errorcode' => 36,
+		             'errormsg' => "received invalid input for end");
 	}
-}
 
-////////////////////////////////////////////////////////////////////////////////
-///
-/// \fn XMLRPCaddImageToGroup($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 added to the group\n
-///
-/// \brief adds an image to a resource group
-///
-////////////////////////////////////////////////////////////////////////////////
-function XMLRPCaddImageToGroup($name, $imageid) {
-	if($groupid = getResourceGroupID("image/$name")) {
-		$groups = getUserResources(array("imageAdmin"), array("manageGroup"), 1);
-		if(! array_key_exists($groupid, $groups['image'])) {
+	$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' => 46,
-			             'errormsg' => 'Unable to access image group');
+			             'errorcode' => 42,
+			             'errormsg' => 'cannot extend due to another reservation immediately after this one');
 		}
-		$resources = getUserResources(array("imageAdmin"), array("manageGroup"));
-		if(! array_key_exists($imageid, $resources['image'])) {
+		// check that requested extension < $timeToNext
+		elseif((($end - $oldendts) / 60) > $timeToNext) {
+			$maxend = $oldendts + ($timeToNext * 60);
 			return array('status' => 'error',
-			             'errorcode' => 47,
-			             'errormsg' => 'Unable to access image');
+			             'errorcode' => 43,
+			             'errormsg' => 'cannot extend by requested amount due to another reservation',
+			             'maxend' => $maxend);
 		}
-
-		$allimages = getImages(0, $imageid);
-		$query = "INSERT IGNORE INTO resourcegroupmembers "
-		       .        "(resourceid, "
-		       .        "resourcegroupid) "
-		       . "VALUES "
-		       .       "({$allimages[$imageid]['resourceid']}, "
-		       .       "$groupid)";
-		doQuery($query);
-		return array('status' => 'success');
 	}
-	else {
+	$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' => 83,
-		             'errormsg' => 'invalid resource group name');
+		             '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 XMLRPCgetGroupImages($name)
+/// \fn XMLRPCendRequest($requestid)
 ///
-/// \param $name - the name of an imageGroup
+/// \param $requestid - id of a request
 ///
 /// \return an array with at least one index named 'status' which will have
 /// one of these values\n
@@ -791,90 +1093,163 @@ function XMLRPCaddImageToGroup($name, $i
 /// \li \b errorcode - error number\n
 /// \li \b errormsg - error string\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
+/// \b success - request was successfully ended\n
 ///
-/// \brief gets a list of all images in a particular group
+/// \brief ends/deletes a request
 ///
 ////////////////////////////////////////////////////////////////////////////////
-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));
+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;
 		}
-		return array('status' => 'success',
-		             'images' => $images);
-
 	}
-	else {
+	if(! $found)
 		return array('status' => 'error',
-		             'errorcode' => 83,
-		             'errormsg' => 'invalid resource group name');
-	}
+		             'errorcode' => 1,
+		             'errormsg' => 'unknown requestid');
+
+	deleteRequest($request);
+	return array('status' => 'success');
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn XMLRPCaddImageGroupToComputerGroup($imageGroup, $computerGroup)
+/// \fn XMLRPCautoCapture($requestid)
 ///
-/// \param $imageGroup - the name of an imageGroup
-/// \param $computerGroup - the name of a computerGroup
+/// \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
+/// 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
+/// \li \b errorcode - error number
+/// \li \b errormsg - error string
 ///
-/// \b success - successfully mapped an image group to a computer group\n
+/// \b success - image was successfully set to be captured
 ///
-/// \brief map an image group to a computer group
+/// \brief creates entries in appropriate tables to capture an image and sets
+/// the request state to image
 ///
 ////////////////////////////////////////////////////////////////////////////////
-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');
+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');
 		}
-		else {
+	}
+	# 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' => 84,
-			             'errormsg' => 'cannot access computer and/or image group');
+			             'errorcode' => 50,
+			             'errormsg' => 'error encountered while attempting to create image');
+		}
+	}
+	return array('status' => 'success');
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \fn XMLRPCgetGroupImages($name)
+///
+/// \param $name - the name of an imageGroup
+///
+/// \return an array with at least one index named 'status' which will have
+/// one of these values\n
+/// \b error - error occurred; there will be 2 additional elements in the array:
+/// \li \b errorcode - error number\n
+/// \li \b errormsg - error string\n
+///
+/// \b success - returns an array of images; there will be an additional element
+/// in the array with an index of 'images' that is an array of images with
+/// each element having the following two keys:\n
+/// \li \b id - id of the image\n
+/// \li \b name - name of the image
+///
+/// \brief gets a list of all images in a particular group
+///
+////////////////////////////////////////////////////////////////////////////////
+function XMLRPCgetGroupImages($name) {
+	if($groupid = getResourceGroupID("image/$name")) {
+		$membership = getResourceGroupMemberships('image');
+		$resources = getUserResources(array("imageAdmin"), array("manageGroup"));
+
+		$images = array();
+		foreach($resources['image'] as $imageid => $image) {
+			if(array_key_exists($imageid, $membership['image']) &&
+			   in_array($groupid, $membership['image'][$imageid]))
+				array_push($images, array('id' => $imageid, 'name' => $image));
 		}
+		return array('status' => 'success',
+		             'images' => $images);
+
 	}
 	else {
 		return array('status' => 'error',
@@ -885,7 +1260,104 @@ function XMLRPCaddImageGroupToComputerGr
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn XMLRPCremoveImageGroupFromComputerGroup($imageGroup, $computerGroup)
+/// \fn XMLRPCaddImageToGroup($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 added to the group\n
+///
+/// \brief adds an image to a resource group
+///
+////////////////////////////////////////////////////////////////////////////////
+function XMLRPCaddImageToGroup($name, $imageid) {
+	if($groupid = getResourceGroupID("image/$name")) {
+		$groups = getUserResources(array("imageAdmin"), array("manageGroup"), 1);
+		if(! array_key_exists($groupid, $groups['image'])) {
+			return array('status' => 'error',
+			             'errorcode' => 46,
+			             'errormsg' => 'Unable to access image group');
+		}
+		$resources = getUserResources(array("imageAdmin"), array("manageGroup"));
+		if(! array_key_exists($imageid, $resources['image'])) {
+			return array('status' => 'error',
+			             'errorcode' => 47,
+			             'errormsg' => 'Unable to access image');
+		}
+
+		$allimages = getImages(0, $imageid);
+		$query = "INSERT IGNORE INTO resourcegroupmembers "
+		       .        "(resourceid, "
+		       .        "resourcegroupid) "
+		       . "VALUES "
+		       .       "({$allimages[$imageid]['resourceid']}, "
+		       .       "$groupid)";
+		doQuery($query);
+		return array('status' => 'success');
+	}
+	else {
+		return array('status' => 'error',
+		             'errorcode' => 83,
+		             'errormsg' => 'invalid resource group name');
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \fn XMLRPCremoveImageFromGroup($name, $imageid)
+///
+/// \param $name - the name of an imageGroup
+/// \param $imageid - the id of an image
+///
+/// \return an array with at least one index named 'status' which will have
+/// one of these values\n
+/// \b error - error occurred; there will be 2 additional elements in the array:
+/// \li \b errorcode - error number\n
+/// \li \b errormsg - error string\n
+///
+/// \b success - image was removed from the group\n
+///
+/// \brief removes an image from a resource group
+///
+////////////////////////////////////////////////////////////////////////////////
+function XMLRPCremoveImageFromGroup($name, $imageid) {
+	if($groupid = getResourceGroupID("image/$name")) {
+		$groups = getUserResources(array("imageAdmin"), array("manageGroup"), 1);
+		if(! array_key_exists($groupid, $groups['image'])) {
+			return array('status' => 'error',
+			             'errorcode' => 46,
+			             'errormsg' => 'Unable to access image group');
+		}
+		$resources = getUserResources(array("imageAdmin"), array("manageGroup"));
+		if(! array_key_exists($imageid, $resources['image'])) {
+			return array('status' => 'error',
+			             'errorcode' => 47,
+			             'errormsg' => 'Unable to access image');
+		}
+
+		$allimages = getImages(0, $imageid);
+		$query = "DELETE FROM resourcegroupmembers "
+		       . "WHERE resourceid = {$allimages[$imageid]['resourceid']} AND "
+		       .       "resourcegroupid = $groupid";
+		doQuery($query);
+		return array('status' => 'success');
+	}
+	else {
+		return array('status' => 'error',
+		             'errorcode' => 83,
+		             'errormsg' => 'invalid resource group name');
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \fn XMLRPCaddImageGroupToComputerGroup($imageGroup, $computerGroup)
 ///
 /// \param $imageGroup - the name of an imageGroup
 /// \param $computerGroup - the name of a computerGroup
@@ -896,13 +1368,12 @@ function XMLRPCaddImageGroupToComputerGr
 /// \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
+/// \b success - successfully mapped an image group to a computer group\n
 ///
-/// \brief remove the mapping of an image group to a computer group
+/// \brief map an image group to a computer group
 ///
 ////////////////////////////////////////////////////////////////////////////////
-function XMLRPCremoveImageGroupFromComputerGroup($imageGroup, $computerGroup) {
+function XMLRPCaddImageGroupToComputerGroup($imageGroup, $computerGroup) {
 	$imageid = getResourceGroupID("image/$imageGroup");
 	$compid = getResourceGroupID("computer/$computerGroup");
 	if($imageid && $compid) {
@@ -917,13 +1388,17 @@ function XMLRPCremoveImageGroupFromCompu
 			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";
+			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');
@@ -943,10 +1418,10 @@ function XMLRPCremoveImageGroupFromCompu
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn XMLRPCgetNodes($root)
+/// \fn XMLRPCremoveImageGroupFromComputerGroup($imageGroup, $computerGroup)
 ///
-/// \param $root - (optional, default=top of tree) 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
@@ -954,17 +1429,75 @@ function XMLRPCremoveImageGroupFromCompu
 /// \li \b errorcode - error number\n
 /// \li \b errormsg - error string\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
+/// \b success - successfully removed the mapping from an image group to a
+/// computer group\n
 ///
-/// \brief gets a list of all nodes in the privilege tree
+/// \brief remove the mapping of an image group to a computer group
 ///
 ////////////////////////////////////////////////////////////////////////////////
-function XMLRPCgetNodes($root=NULL) {
+function XMLRPCremoveImageGroupFromComputerGroup($imageGroup, $computerGroup) {
+	$imageid = getResourceGroupID("image/$imageGroup");
+	$compid = getResourceGroupID("computer/$computerGroup");
+	if($imageid && $compid) {
+		$tmp = getUserResources(array("imageAdmin"),
+		                        array("manageMapping"), 1);
+		$imagegroups = $tmp['image'];
+		$tmp = getUserResources(array("computerAdmin"),
+		                        array("manageMapping"), 1);
+		$computergroups = $tmp['computer'];
+
+		if(array_key_exists($compid, $computergroups) &&
+			array_key_exists($imageid, $imagegroups)) {
+			$mapping = getResourceMapping("image", "computer",
+			                              $imageid, $compid);
+			if(array_key_exists($imageid, $mapping) &&
+			   in_array($compid, $mapping[$imageid])) {
+				$query = "DELETE FROM resourcemap "
+				       . "WHERE resourcegroupid1 = $imageid AND "
+				       .       "resourcetypeid1 = 13 AND "
+				       .       "resourcegroupid2 = $compid AND "
+				       .       "resourcetypeid2 = 12";
+				doQuery($query, 101);
+			}
+			return array('status' => 'success');
+		}
+		else {
+			return array('status' => 'error',
+			             'errorcode' => 84,
+			             'errormsg' => 'cannot access computer and/or image group');
+		}
+	}
+	else {
+		return array('status' => 'error',
+		             'errorcode' => 83,
+		             'errormsg' => 'invalid resource group name');
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \fn XMLRPCgetNodes($root)
+///
+/// \param $root - (optional, default=top of tree) the ID of the node forming
+/// the root of the hierarchy
+///
+/// \return an array with at least one index named 'status' which will have
+/// one of these values\n
+/// \b error - error occurred; there will be 2 additional elements in the array:
+/// \li \b errorcode - error number\n
+/// \li \b errormsg - error string\n
+///
+/// \b success - returns an array of nodes; there will be an additional element
+/// in the array with an index of 'nodes' that is an array of nodes with each
+/// element having the following three keys:\n
+/// \li \b id - id of the node\n
+/// \li \b name - name of the node\n
+/// \li \b parent - id of the parent node
+///
+/// \brief gets a list of all nodes in the privilege tree
+///
+////////////////////////////////////////////////////////////////////////////////
+function XMLRPCgetNodes($root=NULL) {
 	global $user;
 	if(in_array("userGrant", $user["privileges"]) ||
 		in_array("resourceGrant", $user["privileges"]) ||
@@ -1048,50 +1581,6 @@ function XMLRPCnodeExists($nodeName, $pa
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn XMLRPCremoveNode($nodeID)
-///
-/// \param $nodeID - the ID of a node
-///
-/// \return an array with at least one index named 'status' which will have
-/// one of these values\n
-/// \b error - error occurred; there will be 2 additional elements in the array:
-/// \li \b errorcode - error number\n
-/// \li \b errormsg - error string\n
-///
-/// \b success - node was successfully deleted
-///
-/// \brief delete a node from the privilege tree
-///
-////////////////////////////////////////////////////////////////////////////////
-function XMLRPCremoveNode($nodeID) {
-	require_once(".ht-inc/privileges.php");
-	global $user;
-	if(! is_numeric($nodeID)) {
-		return array('status' => 'error',
-		             'errorcode' => 78,
-		             'errormsg' => 'Invalid nodeid specified');
-	}
-	if(! in_array("nodeAdmin", $user['privileges'])) {
-		return array('status' => 'error',
-		             'errorcode' => 70,
-		             'errormsg' => 'User cannot administer nodes');
-	}
-	if(! checkUserHasPriv("nodeAdmin", $user['id'], $nodeID)) {
-		return array('status' => 'error',
-		             'errorcode' => 57,
-		             'errormsg' => 'User cannot edit this node');
-	}
-	$nodes = recurseGetChildren($nodeID);
-	array_push($nodes, $nodeID);
-	$deleteNodes = implode(',', $nodes);
-	$query = "DELETE FROM privnode "
-	       . "WHERE id IN ($deleteNodes)";
-	doQuery($query, 345);
-	return array('status' => 'success');
-}
-
-////////////////////////////////////////////////////////////////////////////////
-///
 /// \fn XMLRPCaddNode($nodeName, $parentNode)
 ///
 /// \param $nodeName - the name of the new node
@@ -1171,12 +1660,9 @@ function XMLRPCaddNode($nodeName, $paren
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \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
@@ -1184,15 +1670,36 @@ function XMLRPCaddNode($nodeName, $paren
 /// \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 for a resource group from a node in the privilege
-/// tree
+/// \brief delete a node from the privilege tree
 ///
 ////////////////////////////////////////////////////////////////////////////////
-function XMLRPCremoveResourceGroupPriv($name, $type, $nodeid, $permissions) {
-	return _XMLRPCchangeResourceGroupPriv_sub('remove', $name, $type, $nodeid,
-	                                          $permissions);
+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');
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -1265,11 +1772,12 @@ function XMLRPCgetUserGroupPrivs($name, 
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn XMLRPCgetResourceGroupPrivs($name, $type, $nodeid)
+/// \fn XMLRPCaddUserGroupPriv($name, $affiliation, $nodeid, $permissions)
 ///
-/// \param $name - the name of the resource group
-/// \param $type - the resource group type
+/// \param $name - the name of the user group
+/// \param $affiliation - the affiliation of the user group
 /// \param $nodeid - the ID of the node in the privilege tree
+/// \param $permissions - a colon (:) delimited list of privileges to add
 ///
 /// \return an array with at least one index named 'status' which will have
 /// one of these values\n
@@ -1277,14 +1785,13 @@ function XMLRPCgetUserGroupPrivs($name, 
 /// \li \b errorcode - error number\n
 /// \li \b errormsg - error string\n
 ///
-/// \b success - an additional element is returned:\n
-/// \li \b privileges - array of privileges assigned at the node
+/// \b success - privileges were successfully added
 ///
-/// \brief get a list of privileges for a resource group at a particular node in
-/// the privilege tree
+/// \brief add privileges for a user group at a particular node in the
+/// privilege tree
 ///
 ////////////////////////////////////////////////////////////////////////////////
-function XMLRPCgetResourceGroupPrivs($name, $type, $nodeid) {
+function XMLRPCaddUserGroupPriv($name, $affiliation, $nodeid, $permissions) {
 	require_once(".ht-inc/privileges.php");
 	global $user;
 
@@ -1294,70 +1801,41 @@ function XMLRPCgetResourceGroupPrivs($na
 		             'errormsg' => 'Invalid nodeid specified');
 	}
 
-	if(! in_array("userGrant", $user["privileges"]) &&
-		! in_array("resourceGrant", $user["privileges"]) &&
-		! in_array("nodeAdmin", $user["privileges"])) {
+	if(! checkUserHasPriv("userGrant", $user['id'], $nodeid)) {
 		return array('status' => 'error',
-		             'errorcode' => 63,
-		             'errormsg' => 'Unable to view resource group privileges');
+		             'errorcode' => 52,
+		             'errormsg' => 'Unable to add a user group to this node');
 	}
 
-	if($typeid = getResourceTypeID($type)) {
-		if(! $groupid = getResourceGroupID("$type/$name")) {
-			return array('status' => 'error',
-			             'errorcode' => 74,
-			             'errormsg' => 'resource group does not exist');
-		}
-		$np = getNodePrivileges($nodeid, 'resources');
-		$cnp = getNodeCascadePrivileges($nodeid, 'resources'); 
-		$key = "$type/$name/$groupid";
-		if(array_key_exists($key, $np['resources']) &&
-			(in_array('block', $np['resources'][$key]) ||
-		   ! array_key_exists($key, $cnp['resources'])))
-			$privs = $np['resources'][$key];
-		elseif(array_key_exists($key, $cnp['resources']) &&
-		   array_key_exists($key, $np['resources'])) {
-			$allprivs = array_merge($cnp['resources'][$key], $np['resources'][$key]);
-			$privs = array_unique($allprivs);
-		}
-		elseif(array_key_exists($key, $cnp['resources']))
-			$privs = $cnp['resources'][$key];
-		else
-			$privs = array();
-		return array('status' => 'success',
-		             'privileges' => $privs);
-	}
-	else {
+	$validate = array('name' => $name,
+	                  'affiliation' => $affiliation);
+	$rc = validateAPIgroupInput($validate, 1);
+	if($rc['status'] == 'error')
+		return $rc;
+
+	$groupid = $rc['id'];
+	#$name = "$name@$affiliation";
+	$perms = explode(':', $permissions);
+	$usertypes = getTypes('users');
+	array_push($usertypes["users"], "block");
+	array_push($usertypes["users"], "cascade");
+
+	$diff = array_diff($perms, $usertypes['users']);
+	if(count($diff) || (count($perms) == 1 && $perms[0] == 'cascade')) {
 		return array('status' => 'error',
-		             'errorcode' => 71,
-		             'errormsg' => 'Invalid resource type');
+		             'errorcode' => 66,
+		             'errormsg' => 'Invalid or missing permissions list supplied');
 	}
-}
 
-////////////////////////////////////////////////////////////////////////////////
-///
-/// \fn XMLRPCaddResourceGroupPriv($name, $type, $nodeid, $permissions)
-///
-/// \param $name - the name of the resource group
-/// \param $type - the resource group type
-/// \param $nodeid - the ID of the node in the privilege tree
-/// \param $permissions - a colon (:) delimited list of privileges to add
-///
-/// \return an array with at least one index named 'status' which will have
-/// one of these values\n
-/// \b error - error occurred; there will be 2 additional elements in the array:
-/// \li \b errorcode - error number\n
-/// \li \b errormsg - error string\n
-///
-/// \b success - privileges were successfully added
-///
-/// \brief add privileges for a resource group at a particular node in the
-/// privilege tree
-///
-////////////////////////////////////////////////////////////////////////////////
-function XMLRPCaddResourceGroupPriv($name, $type, $nodeid, $permissions) {
-	return _XMLRPCchangeResourceGroupPriv_sub('add', $name, $type, $nodeid,
-	                                          $permissions);
+	$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');
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -1442,12 +1920,11 @@ function XMLRPCremoveUserGroupPriv($name
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn XMLRPCaddUserGroupPriv($name, $affiliation, $nodeid, $permissions)
+/// \fn XMLRPCgetResourceGroupPrivs($name, $type, $nodeid)
 ///
-/// \param $name - the name of the user group
-/// \param $affiliation - the affiliation of the user group
+/// \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
@@ -1455,13 +1932,14 @@ function XMLRPCremoveUserGroupPriv($name
 /// \li \b errorcode - error number\n
 /// \li \b errormsg - error string\n
 ///
-/// \b success - privileges were successfully added
+/// \b success - an additional element is returned:\n
+/// \li \b privileges - array of privileges assigned at the node
 ///
-/// \brief add privileges for a user group at a particular node in the
-/// privilege tree
+/// \brief get a list of privileges for a resource group at a particular node in
+/// the privilege tree
 ///
 ////////////////////////////////////////////////////////////////////////////////
-function XMLRPCaddUserGroupPriv($name, $affiliation, $nodeid, $permissions) {
+function XMLRPCgetResourceGroupPrivs($name, $type, $nodeid) {
 	require_once(".ht-inc/privileges.php");
 	global $user;
 
@@ -1471,50 +1949,80 @@ function XMLRPCaddUserGroupPriv($name, $
 		             'errormsg' => 'Invalid nodeid specified');
 	}
 
-	if(! checkUserHasPriv("userGrant", $user['id'], $nodeid)) {
+	if(! in_array("userGrant", $user["privileges"]) &&
+		! in_array("resourceGrant", $user["privileges"]) &&
+		! in_array("nodeAdmin", $user["privileges"])) {
 		return array('status' => 'error',
-		             'errorcode' => 52,
-		             'errormsg' => 'Unable to add a user group to this node');
+		             'errorcode' => 63,
+		             'errormsg' => 'Unable to view resource group privileges');
 	}
 
-	$validate = array('name' => $name,
-	                  'affiliation' => $affiliation);
-	$rc = validateAPIgroupInput($validate, 1);
-	if($rc['status'] == 'error')
-		return $rc;
-
-	$groupid = $rc['id'];
-	#$name = "$name@$affiliation";
-	$perms = explode(':', $permissions);
-	$usertypes = getTypes('users');
-	array_push($usertypes["users"], "block");
-	array_push($usertypes["users"], "cascade");
-
-	$diff = array_diff($perms, $usertypes['users']);
-	if(count($diff) || (count($perms) == 1 && $perms[0] == 'cascade')) {
+	if($typeid = getResourceTypeID($type)) {
+		if(! $groupid = getResourceGroupID("$type/$name")) {
+			return array('status' => 'error',
+			             'errorcode' => 74,
+			             'errormsg' => 'resource group does not exist');
+		}
+		$np = getNodePrivileges($nodeid, 'resources');
+		$cnp = getNodeCascadePrivileges($nodeid, 'resources'); 
+		$key = "$type/$name/$groupid";
+		if(array_key_exists($key, $np['resources']) &&
+			(in_array('block', $np['resources'][$key]) ||
+		   ! array_key_exists($key, $cnp['resources'])))
+			$privs = $np['resources'][$key];
+		elseif(array_key_exists($key, $cnp['resources']) &&
+		   array_key_exists($key, $np['resources'])) {
+			$allprivs = array_merge($cnp['resources'][$key], $np['resources'][$key]);
+			$privs = array_unique($allprivs);
+		}
+		elseif(array_key_exists($key, $cnp['resources']))
+			$privs = $cnp['resources'][$key];
+		else
+			$privs = array();
+		return array('status' => 'success',
+		             'privileges' => $privs);
+	}
+	else {
 		return array('status' => 'error',
-		             'errorcode' => 66,
-		             'errormsg' => 'Invalid or missing permissions list supplied');
+		             'errorcode' => 71,
+		             'errormsg' => 'Invalid resource type');
 	}
+}
 
-	$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 XMLRPCaddResourceGroupPriv($name, $type, $nodeid, $permissions)
+///
+/// \param $name - the name of the resource group
+/// \param $type - the resource group type
+/// \param $nodeid - the ID of the node in the privilege tree
+/// \param $permissions - a colon (:) delimited list of privileges to add
+///
+/// \return an array with at least one index named 'status' which will have
+/// one of these values\n
+/// \b error - error occurred; there will be 2 additional elements in the array:
+/// \li \b errorcode - error number\n
+/// \li \b errormsg - error string\n
+///
+/// \b success - privileges were successfully added
+///
+/// \brief add privileges for a resource group at a particular node in the
+/// privilege tree
+///
+////////////////////////////////////////////////////////////////////////////////
+function XMLRPCaddResourceGroupPriv($name, $type, $nodeid, $permissions) {
+	return _XMLRPCchangeResourceGroupPriv_sub('add', $name, $type, $nodeid,
+	                                          $permissions);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn XMLRPCsetRequestEnding($requestid, $end)
+/// \fn XMLRPCremoveResourceGroupPriv($name, $type, $nodeid, $permissions)
 ///
-/// \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 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
@@ -1522,427 +2030,352 @@ function XMLRPCaddUserGroupPriv($name, $
 /// \li \b errorcode - error number\n
 /// \li \b errormsg - error string\n
 ///
-/// \b success - request was successfully extended\n
+/// \b success - privileges were successfully removed
 ///
-/// \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 remove privileges for a resource group from a node in the privilege
+/// tree
 ///
 ////////////////////////////////////////////////////////////////////////////////
-function XMLRPCsetRequestEnding($requestid, $end) {
+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;
 
-	$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)
+	if(! is_numeric($nodeid)) {
 		return array('status' => 'error',
-		             'errorcode' => 1,
-		             'errormsg' => 'unknown requestid');
+		             'errorcode' => 78,
+		             'errormsg' => 'Invalid nodeid specified');
+	}
 
-	// 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)) {
+	if(! checkUserHasPriv("resourceGrant", $user['id'], $nodeid)) {
 		return array('status' => 'error',
-		             'errorcode' => 35,
-		             'errormsg' => "access denied to specify end time");
+		             'errorcode' => 61,
+		             'errormsg' => 'Unable to remove resource group privileges on this node');
 	}
 
-	$end = processInputData($end, ARG_NUMERIC);
-
-	$maxend = datetimeToUnix("2038-01-01 00:00:00");
-	if($end < 0 || $end > $maxend) {
+	$resourcetypes = getTypes('resources');
+	if(! in_array($type, $resourcetypes['resources'])) {
 		return array('status' => 'error',
-		             'errorcode' => 36,
-		             'errormsg' => "received invalid input for end");
+		             'errorcode' => 71,
+		             'errormsg' => 'Invalid resource type');
 	}
 
-	$startts = datetimeToUnix($request['start']);
-	if($end % (15 * 60))
-		$end= unixFloor15($end) + (15 * 60);
-
-	// check that reservation has started
-	if($startts > time()) {
+	$groupid = getResourceGroupID("$type/$name");
+	if(is_null($groupid)) {
 		return array('status' => 'error',
-		             'errorcode' => 38,
-		             'errormsg' => 'reservation has not started');
+		             'errorcode' => 74,
+		             'errormsg' => 'resource group does not exist');
 	}
 
-	// check for overlap
-	$max = getMaxOverlap($user['id']);
-	if(checkOverlap($startts, $end, $max, $requestid)) {
+	$changeperms = explode(':', $permissions);
+	$allperms = getResourcePrivs();
+	$diff = array_diff($changeperms, $allperms);
+	if(count($diff)) {
 		return array('status' => 'error',
-		             'errorcode' => 41,
-		             'errormsg' => 'overlapping reservation restriction',
-		             'maxoverlap' => $max);
+		             'errorcode' => 66,
+		             'errormsg' => 'Invalid or missing permissions list supplied');
 	}
 
-	// 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;
-			}
-		}
+	$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');
 	}
-	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);
+
+	$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' => 43,
-			             'errormsg' => 'cannot extend by requested amount due to another reservation',
-			             'maxend' => $maxend);
+			             'errorcode' => 80,
+			             'errormsg' => 'Unable to modify privileges cascaded to this node');
 		}
 	}
-	$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');
+
+	if($mode == 'remove') {
+		$diff = array_diff($np['resources'][$key], $changeperms);
+		if(count($diff) == 1 && in_array("cascade", $diff))
+			$changeperms[] = 'cascade';
 	}
-	// success
-	updateRequest($requestid);
+
+	if($mode == 'add')
+		updateResourcePrivs("$groupid", $nodeid, $changeperms, array());
+	elseif($mode == 'remove')
+		updateResourcePrivs("$groupid", $nodeid, array(), $changeperms);
 	return array('status' => 'success');
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn XMLRPCendRequest($requestid)
-///
-/// \param $requestid - id of a request
+/// \fn XMLRPCgetUserGroups($groupType, $affiliationid)
 ///
-/// \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
+/// \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
 ///
-/// \b success - request was successfully ended\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 ends/deletes a request
+/// \brief builds a list of user groups
 ///
 ////////////////////////////////////////////////////////////////////////////////
-function XMLRPCendRequest($requestid) {
+function XMLRPCgetUserGroups($groupType=0, $affiliationid=0) {
 	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;
+	$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);
 		}
 	}
-	if(! $found)
-		return array('status' => 'error',
-		             'errorcode' => 1,
-		             'errormsg' => 'unknown requestid');
-
-	deleteRequest($request);
-	return array('status' => 'success');
+	return array("status" => "success",
+	             "groups" => $usergroups);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn XMLRPCgetRequestIds()
+/// \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
+/// 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
+/// \li \b errorcode - error number
+/// \li \b errormsg - error string
 ///
-/// \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
+/// \b success - there will be six additional elements in this case:
+/// \li \b owner - user that will be the owner of the group in
+///                username\@affiliation form
+/// \li \b managingGroup - user group that can manage membership of this one in
+///                        groupname\@affiliation form
+/// \li \b initialMaxTime - (minutes) max initial time users in this group can
+///                         select for length of reservations
+/// \li \b totalMaxTime - (minutes) total length users in the group can have for
+///                       a reservation (including all extensions)
+/// \li \b maxExtendTime - (minutes) max length of time users can request as an
+///                        extension to a reservation at a time
+/// \li \b overlapResCount - maximum allowed number of overlapping reservations
+/// allowed for users in this group
 ///
-/// \brief gets information about all of user's requests
+/// \brief gets information about a user group
 ///
 ////////////////////////////////////////////////////////////////////////////////
-function XMLRPCgetRequestIds() {
+function XMLRPCgetUserGroupAttributes($name, $affiliation) {
 	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);
+	if(! in_array('groupAdmin', $user['privileges'])) {
+		return array('status' => 'error',
+		             'errorcode' => 16,
+		             'errormsg' => 'access denied for managing groups');
 	}
-	return array('status' => 'success', 'requests' => $ret);
+	$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 XMLRPCblockAllocation($imageid, $start, $end, $numMachines,
-///                           $usergroupid, $ignoreprivileges)
+/// \fn XMLRPCaddUserGroup($name, $affiliation, $owner, $managingGroup,
+///                        $initialMaxTime, $totalMaxTime, $maxExtendTime,
+///                        $custom)
 ///
-/// \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 $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 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:\n
+/// \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
+/// \li \b errormsg - error string
 ///
-/// \b success - blockTimesid was processed; there will be two additional
-/// elements in this case:\n
-/// \li \b allocated - total number of desired allocations that have been
-/// processed\n
-/// \li \b unallocated - total number of desired allocations that have not been
-/// processed\n
-///
-/// \b warning - there was a non-fatal issue that occurred while processing
-/// the call; there will be four additional elements in this case:\n
-/// \li \b warningcode - warning number\n
-/// \li \b warningmsg - warning string\n
-/// \li \b allocated - total number of desired allocations that have been
-/// processed\n
-/// \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
+/// \b success - user group was successfully created
 ///
-/// \brief creates and processes a block allocation according to the passed
-/// in criteria
+/// \brief creates a new user group with the specified parameters
 ///
 ////////////////////////////////////////////////////////////////////////////////
-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');
-	}
-
-	# valid $imageid
-	$resources = getUserResources(array("imageAdmin", "imageCheckOut"));
-	$resources["image"] = removeNoCheckout($resources["image"]);
-	if(! array_key_exists($imageid, $resources['image'])) {
-		return array('status' => 'error',
-		             'errorcode' => 3,
-		             'errormsg' => "access denied to $imageid");
-	}
-
-	# validate $start and $end
-	$dtreg = '([0-9]{4})-([0-9]{2})-([0-9]{2}) ([0-9]{2}):([0-9]{2}):([0-9]{2})';
-	$startts = datetimeToUnix($start);
-	$endts = datetimeToUnix($end);
-	$maxend = datetimeToUnix("2038-01-01 00:00:00");
-	if(! preg_match("/^$dtreg$/", $start) || $startts < 0 ||
-	   $startts > $maxend) {
-		return array('status' => 'error',
-		             'errorcode' => 4,
-		             'errormsg' => "received invalid input for start");
-	}
-	if(! preg_match("/^$dtreg$/", $end) || $endts < 0 ||
-	   $endts > $maxend) {
-		return array('status' => 'error',
-		             'errorcode' => 36,
-		             'errormsg' => "received invalid input for end");
-	}
-
-	# validate $numMachines
-	if(! is_numeric($numMachines) || $numMachines < MIN_BLOCK_MACHINES ||
-	   $numMachines > MAX_BLOCK_MACHINES) {
-		return array('status' => 'error',
-		             'errorcode' => 64,
-		             'errormsg' => 'The submitted number of seats must be between ' . MIN_BLOCK_MACHINES . ' and ' . MAX_BLOCK_MACHINES . '.');
-	}
-
-	# validate $usergroupid
-	$groups = getUserGroups();
-	if(! array_key_exists($usergroupid, $groups)) {
-		return array('status' => 'error',
-		             'errorcode' => 67,
-		             'errormsg' => 'Submitted user group does not exist');
-	}
-
-	# validate ignoreprivileges
-	if(! is_numeric($ignoreprivileges) ||
-	   $ignoreprivileges < 0 ||
-		$ignoreprivileges > 1) {
+function XMLRPCaddUserGroup($name, $affiliation, $owner, $managingGroup,
+                            $initialMaxTime, $totalMaxTime, $maxExtendTime,
+                            $custom=1) {
+	global $user;
+	if(! in_array('groupAdmin', $user['privileges'])) {
 		return array('status' => 'error',
-		             'errorcode' => 86,
-		             'errormsg' => 'ignoreprivileges must be 0 or 1');
+		             'errorcode' => 16,
+		             'errormsg' => 'access denied for managing groups');
 	}
-
-	$ownerid = getUserlistID('vclreload@Local');
-	$name = "API:$start";
-	$managementnodes = getManagementNodes('future');
-	if(empty($managementnodes)) {
+	$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' => 12,
-		             'errormsg' => 'could not allocate a management node to handle block allocation');
+		             'errorcode' => 26,
+		             'errormsg' => 'failure while adding group to database');
 	}
-	$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);

[... 2462 lines stripped ...]