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/07 20:43:01 UTC

svn commit: r1443684 [2/2] - in /vcl/trunk/web/.ht-inc: groups.php privileges.php utils.php xmlrpcWrappers.php

Modified: vcl/trunk/web/.ht-inc/xmlrpcWrappers.php
URL: http://svn.apache.org/viewvc/vcl/trunk/web/.ht-inc/xmlrpcWrappers.php?rev=1443684&r1=1443683&r2=1443684&view=diff
==============================================================================
--- vcl/trunk/web/.ht-inc/xmlrpcWrappers.php (original)
+++ vcl/trunk/web/.ht-inc/xmlrpcWrappers.php Thu Feb  7 19:43:01 2013
@@ -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() {
@@ -84,13 +84,35 @@ 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
 /// \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, $sta
 /// \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($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,195 +364,421 @@ 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\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 "
+	$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($re
 				$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($re
 /// \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,
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \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,
 /// \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\n
+/// \brief modifies the end time of an active request; if a request that has not
+/// started needs to be modifed, delete the request and submit a new one
 ///
 ////////////////////////////////////////////////////////////////////////////////
-function 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,86 +1093,133 @@ function XMLRPCremoveImageFromGroup($nam
 /// \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 adds an image to a resource group\n
+/// \brief ends/deletes a request
 ///
 ////////////////////////////////////////////////////////////////////////////////
-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 XMLRPCgetGroupImages($name)
+/// \fn XMLRPCautoCapture($requestid)
 ///
-/// \param $name - the name of an imageGroup
+/// \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 - returns an array of images\n
+/// \b success - image was successfully set to be captured
 ///
-/// \brief gets a list of all images in a particular group\n
+/// \brief creates entries in appropriate tables to capture an image and sets
+/// the request state to image
 ///
 ////////////////////////////////////////////////////////////////////////////////
-function XMLRPCgetGroupImages($name){
-    if($groupid = getResourceGroupID("image/$name")){
-        $membership = getResourceGroupMemberships('image');
-        $resources = getUserResources(array("imageAdmin"),
-                                      array("manageGroup"));
-
-        $images = array();
-        foreach($resources['image'] as $imageid => $image){
-            if(array_key_exists($imageid, $membership['image']) &&
-                    in_array($groupid, $membership['image'][$imageid])){
-                array_push($images, array('id' => $imageid, 'name' => $image));
-            }
-        }
-        return array('status' => 'success',
-                     'images' => $images);
-
-    } else {
-        return array('status' => 'error',
-                     'errorcode' => 83,
-                     'errormsg' => 'invalid resource group name');
-    }
+function XMLRPCautoCapture($requestid) {
+	global $user, $xmlrpcBlockAPIUsers;
+	if(! in_array($user['id'], $xmlrpcBlockAPIUsers)) {
+		return array('status' => 'error',
+		             'errorcode' => 47,
+		             'errormsg' => 'access denied to XMLRPCautoCapture');
+	}
+	$query = "SELECT id FROM request WHERE id = $requestid";
+	$qh = doQuery($query, 101);
+	if(! mysql_num_rows($qh)) {
+		return array('status' => 'error',
+		             'errorcode' => 52,
+		             'errormsg' => 'specified request does not exist');
+	}
+	$reqData = getRequestInfo($requestid);
+	# check state of reservation
+	if($reqData['stateid'] != 14 || $reqData['laststateid'] != 8) {
+		return array('status' => 'error',
+		             'errorcode' => 51,
+		             'errormsg' => 'reservation not in valid state');
+	}
+	# check that not a cluster reservation
+	if(count($reqData['reservations']) > 1) {
+		return array('status' => 'error',
+		             'errorcode' => 48,
+		             'errormsg' => 'cannot image a cluster reservation');
+	}
+	require_once(".ht-inc/images.php");
+	$imageid = $reqData['reservations'][0]['imageid'];
+	$imageData = getImages(0, $imageid);
+	$captime = unixToDatetime(time());
+	$comments = "start: {$reqData['start']}<br>"
+	          . "end: {$reqData['end']}<br>"
+	          . "computer: {$reqData['reservations'][0]['reservedIP']}<br>"
+	          . "capture time: $captime";
+	# create new revision if requestor is owner and not a kickstart image
+	if($imageData[$imageid]['installtype'] != 'kickstart' &&
+	   $reqData['userid'] == $imageData[$imageid]['ownerid']) {
+		$rc = updateExistingImage($requestid, $reqData['userid'], $comments, 1);
+		if($rc == 0) {
+			return array('status' => 'error',
+			             'errorcode' => 49,
+			             'errormsg' => 'error encountered while attempting to create new revision');
+		}
+	}
+	# create a new image if requestor is not owner or a kickstart image
+	else {
+		$ownerdata = getUserInfo($reqData['userid'], 1, 1);
+		$desc = "This is an autocaptured image.<br>"
+		      . "captured from image: {$reqData['reservations'][0]['prettyimage']}<br>"
+		      . "captured on: $captime<br>"
+		      . "owner: {$ownerdata['unityid']}@{$ownerdata['affiliation']}<br>";
+		$connectmethods = getImageConnectMethods($imageid, $reqData['reservations'][0]['imagerevisionid']);
+		$data = array('requestid' => $requestid,
+		              'description' => $desc,
+		              'usage' => '',
+		              'owner' => "{$ownerdata['unityid']}@{$ownerdata['affiliation']}",
+		              'prettyname' => "Autocaptured ({$ownerdata['unityid']} - $requestid)",
+		              'minram' => 64,
+		              'minprocnumber' => 1,
+		              'minprocspeed' => 500,
+		              'minnetwork' => 10,
+		              'maxconcurrent' => '',
+		              'checkuser' => 1,
+		              'rootaccess' => 1,
+		              'sysprep' => 1,
+		              'comments' => $comments,
+		              'connectmethodids' => implode(',', array_keys($connectmethods)));
+		$rc = submitAddImage($data, 1);
+		if($rc == 0) {
+			return array('status' => 'error',
+			             'errorcode' => 50,
+			             'errormsg' => 'error encountered while attempting to create image');
+		}
+	}
+	return array('status' => 'success');
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn XMLRPCaddImageGroupToComputerGroup($imageGroup, $computerGroup)
+/// \fn XMLRPCgetGroupImages($name)
 ///
-/// \param $imageGroup - the name of an imageGroup
-/// \param $computerGroup - the name of a computerGroup
+/// \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
@@ -823,59 +1227,43 @@ 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
-///
-/// \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');
-    }
+/// \b success - returns an array of images; there will be an additional element
+/// in the array with an index of 'images' that is an array of images with
+/// each element having the following two keys:\n
+/// \li \b id - id of the image\n
+/// \li \b name - name of the image
+///
+/// \brief gets a list of all images in a particular group
+///
+////////////////////////////////////////////////////////////////////////////////
+function XMLRPCgetGroupImages($name) {
+	if($groupid = getResourceGroupID("image/$name")) {
+		$membership = getResourceGroupMemberships('image');
+		$resources = getUserResources(array("imageAdmin"), array("manageGroup"));
+
+		$images = array();
+		foreach($resources['image'] as $imageid => $image) {
+			if(array_key_exists($imageid, $membership['image']) &&
+			   in_array($groupid, $membership['image'][$imageid]))
+				array_push($images, array('id' => $imageid, 'name' => $image));
+		}
+		return array('status' => 'success',
+		             'images' => $images);
+
+	}
+	else {
+		return array('status' => 'error',
+		             'errorcode' => 83,
+		             'errormsg' => 'invalid resource group name');
+	}
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn XMLRPCremoveImageGroupFromComputerGroup($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
@@ -883,56 +1271,49 @@ 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 - image was added to the group\n
 ///
-/// \brief remove the mapping of an image group to a computer group\n
+/// \brief adds an image to a resource 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) &&
-                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');
-    }
-}
-
-
+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 XMLRPCgetNodes($root)
+/// \fn XMLRPCremoveImageFromGroup($name, $imageid)
 ///
-/// \param $root - (optional) the ID of the node forming the root of the hierarchy
+/// \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
@@ -940,48 +1321,46 @@ function XMLRPCremoveImageGroupFromCompu
 /// \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 - 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 XMLRPCnodeExists($nodeName, $parentNode)
+/// \fn XMLRPCaddImageGroupToComputerGroup($imageGroup, $computerGroup)
 ///
-/// \param $nodeName - the name of a node
-/// \param $parentNode - the ID of the parent node
+/// \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
@@ -989,38 +1368,60 @@ 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 - successfully mapped an image group to a computer group\n
 ///
-/// \brief indicates whether a node with that name already exists at this
-///     location in the privilege tree\n
+/// \brief map an image group to a computer group
 ///
 ////////////////////////////////////////////////////////////////////////////////
-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');
-    }
+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 XMLRPCremoveNode($nodeID)
+/// \fn XMLRPCremoveImageGroupFromComputerGroup($imageGroup, $computerGroup)
 ///
-/// \param $nodeID - the ID of a node
+/// \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
@@ -1028,42 +1429,57 @@ function XMLRPCnodeExists($nodeName, $pa
 /// \li \b errorcode - error number\n
 /// \li \b errormsg - error string\n
 ///
-/// \b success - node was successfully deleted
+/// \b success - successfully removed the mapping from an image group to a
+/// computer group\n
 ///
-/// \brief delete a node in the privilege tree\n
+/// \brief remove the mapping of an image group to a computer group
 ///
 ////////////////////////////////////////////////////////////////////////////////
-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 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 XMLRPCaddNode($nodeName, $parentNode)
+/// \fn XMLRPCgetNodes($root)
 ///
-/// \param $nodeName - the name of the new node
-/// \param $parentNode - the ID of the node parent
+/// \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
@@ -1071,75 +1487,55 @@ function XMLRPCremoveNode($nodeID){
 /// \li \b errorcode - error number\n
 /// \li \b errormsg - error string\n
 ///
-/// \b success - node was successfully added
+/// \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 add a node to the privilege tree as a child of the
-///     specified parent node\n
+/// \brief gets a list of all nodes in the privilege tree
 ///
 ////////////////////////////////////////////////////////////////////////////////
-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');
-    }
+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 XMLRPCremoveResourceGroupPriv($name, $type, $nodeid, $permissions)
+/// \fn XMLRPCnodeExists($nodeName, $parentNode)
 ///
-/// \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 $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
@@ -1147,44 +1543,48 @@ function XMLRPCaddNode($nodeName, $paren
 /// \li \b errorcode - error number\n
 /// \li \b errormsg - error string\n
 ///
-/// \b success - privileges were successfully removed
+/// \b success - returns an 'exists' element set to either 1 or 0\n
 ///
-/// \brief remove privileges from a resource group in the privilege
-///      node tree\n
+/// \brief indicates whether a node with that name already exists at this
+/// location in the privilege tree
 ///
 ////////////////////////////////////////////////////////////////////////////////
-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');
-    }
+function XMLRPCnodeExists($nodeName, $parentNode) {
+	global $user;
+	if(! is_numeric($parentNode)) {
+		return array('status' => 'error',
+		             'errorcode' => 78,
+		             'errormsg' => 'Invalid nodeid specified');
+	}
+	if(in_array("userGrant", $user["privileges"]) ||
+		in_array("resourceGrant", $user["privileges"]) ||
+		in_array("nodeAdmin", $user["privileges"])) {
+		if(get_magic_quotes_gpc())
+			$nodeName = stripslashes($nodeName);
+		$nodeName = mysql_real_escape_string($nodeName);
+		// does a node with this name already exist?
+		$query = "SELECT id "
+		       . "FROM privnode "
+		       . "WHERE name = '$nodeName' AND parent = $parentNode";
+		$qh = doQuery($query, 335);
+		if(mysql_num_rows($qh))
+			return array('status' => 'success', 'exists' => TRUE);
+		else
+			return array('status' => 'success', 'exists' => FALSE);
+	}
+	else {
+		return array('status' => 'error',
+		             'errorcode' => 70,
+		             'errormsg' => 'User cannot access node content');
+	}
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn XMLRPCgetUserGroupPrivs($name, $affiliation, $nodeid)
+/// \fn XMLRPCaddNode($nodeName, $parentNode)
 ///
-/// \param $name - the name of the user group
-/// \param $affiliation - the affiliation of the group
-/// \param $nodeid - the ID of the node in the privilege tree
+/// \param $nodeName - the name of the new node
+/// \param $parentNode - the ID of the node parent
 ///
 /// \return an array with at least one index named 'status' which will have
 /// one of these values\n
@@ -1192,62 +1592,77 @@ function XMLRPCremoveResourceGroupPriv($
 /// \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 - node was successfully added
 ///
-/// \brief get a list of privileges for a user group at a particular
-///     location in the privilege tree\n
+/// \brief add a node to the privilege tree as a child of the specified parent
+/// node
 ///
 ////////////////////////////////////////////////////////////////////////////////
-function XMLRPCgetUserGroupPrivs($name, $affiliation, $nodeid){
-    require_once(".ht-inc/privileges.php");
-    global $user;
-
-    if(! checkUserHasPriv("userGrant", $user['id'], $nodeid)){
-        return array('status' => 'error',
-                     'errorcode' => 53,
-                     'errormsg' => 'Unable to add resource group to this node');
-    }
+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');
+		}
 
-	$validate = array('name' => $name,
-	                  'affiliation' => $affiliation);
-	$rc = validateAPIgroupInput($validate, 1);
-	if($rc['status'] == 'error')
-		return $rc;
+		if(! validateNodeName($nodeName, $tmp)) {
+			return array('status' => 'error',
+			             'errorcode' => 81,
+			             'errormsg' => 'Invalid node name');
+		}
 
-    $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));
+		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 XMLRPCgetResourceGroupPrivs($name, $type, $nodeid)
+/// \fn XMLRPCremoveNode($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
+/// \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
@@ -1255,57 +1670,45 @@ function XMLRPCgetUserGroupPrivs($name, 
 /// \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 - node was successfully deleted
 ///
-/// \brief get a list of privileges for a resource group at a particular
-///     location in the privilege tree\n
+/// \brief delete a node from the privilege tree
 ///
 ////////////////////////////////////////////////////////////////////////////////
-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');
-    }
+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 XMLRPCaddResourceGroupPriv($name, $type, $nodeid, $permissions)
+/// \fn XMLRPCgetUserGroupPrivs($name, $affiliation, $nodeid)
 ///
-/// \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 group
 /// \param $nodeid - the ID of the node in the privilege tree
-/// \param $permissions - a colon (:) delimited list of privileges to add
 ///
 /// \return an array with at least one index named 'status' which will have
 /// one of these values\n
@@ -1313,47 +1716,68 @@ function XMLRPCgetResourceGroupPrivs($na
 /// \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 resource 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 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');
-    }
-}
+function XMLRPCgetUserGroupPrivs($name, $affiliation, $nodeid) {
+	require_once(".ht-inc/privileges.php");
+	global $user;
+
+	if(! is_numeric($nodeid)) {
+		return array('status' => 'error',
+		             'errorcode' => 78,
+		             'errormsg' => 'Invalid nodeid specified');
+	}
 
+	if(! in_array("userGrant", $user["privileges"]) &&
+		! in_array("resourceGrant", $user["privileges"]) &&
+		! in_array("nodeAdmin", $user["privileges"])) {
+		return array('status' => 'error',
+		             'errorcode' => 62,
+		             'errormsg' => 'Unable to view user group privileges');
+	}
+
+	$validate = array('name' => $name,
+	                  'affiliation' => $affiliation);
+	$rc = validateAPIgroupInput($validate, 1);
+	if($rc['status'] == 'error')
+		return $rc;
+
+	$privileges = array();
+	$nodePrivileges = getNodePrivileges($nodeid, 'usergroups');
+	$cascadedNodePrivileges = getNodeCascadePrivileges($nodeid, 'usergroups'); 
+	$cngp = $cascadedNodePrivileges['usergroups'];
+	$ngp = $nodePrivileges['usergroups'];
+	if(array_key_exists($name, $cngp)) {
+		foreach($cngp[$name]['privs'] as $p) {
+			if(! array_key_exists($name, $ngp) ||
+			   ! in_array("block", $ngp[$name]['privs']))
+				array_push($privileges, $p);
+		}
+	}
+	if(array_key_exists($name, $ngp)) {
+		foreach($ngp[$name]['privs'] as $p) {
+			if($p != "block")
+				array_push($privileges, $p);
+		}
+	}
+
+	return array('status' => 'success',
+	              'privileges' => array_unique($privileges));
+}
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
-/// \fn XMLRPCremoveResourceGroupPriv($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 remove
+/// \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
@@ -1361,21 +1785,27 @@ function XMLRPCaddResourceGroupPriv($nam
 /// \li \b errorcode - error number\n
 /// \li \b errormsg - error string\n
 ///
-/// \b success - privileges were successfully removed
+/// \b success - privileges were successfully added
 ///
-/// \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' => 53,
-                     'errormsg' => 'Unable to remove group privileges on this node');
-    }
+		             'errorcode' => 78,
+		             'errormsg' => 'Invalid nodeid specified');
+	}
+
+	if(! checkUserHasPriv("userGrant", $user['id'], $nodeid)) {
+		return array('status' => 'error',
+		             '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
 	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
 /// \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, $
 	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, $
 /// \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) {

[... 2542 lines stripped ...]