65535) { throw new PVE2_Exception("Port must be an integer between 1 and 65535.", 6); } // Check that verify_ssl is boolean. if (!is_bool($verify_ssl)) { throw new PVE2_Exception("verify_ssl must be boolean.", 7); } $this->hostname = $hostname; $this->username = $username; $this->realm = $realm; $this->password = $password; $this->port = $port; $this->verify_ssl = $verify_ssl; } /* * bool login () * Performs login to PVE Server using JSON API, and obtains Access Ticket. */ public function login () { // Prepare login variables. $login_postfields = array(); $login_postfields['username'] = $this->username; $login_postfields['password'] = $this->password; $login_postfields['realm'] = $this->realm; $login_postfields_string = http_build_query($login_postfields); unset($login_postfields); // Perform login request. $prox_ch = curl_init(); curl_setopt($prox_ch, CURLOPT_URL, "https://{$this->hostname}:{$this->port}/api2/json/access/ticket"); curl_setopt($prox_ch, CURLOPT_POST, true); curl_setopt($prox_ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($prox_ch, CURLOPT_POSTFIELDS, $login_postfields_string); curl_setopt($prox_ch, CURLOPT_SSL_VERIFYPEER, $this->verify_ssl); $login_ticket = curl_exec($prox_ch); $login_request_info = curl_getinfo($prox_ch); curl_close($prox_ch); unset($prox_ch); unset($login_postfields_string); if (!$login_ticket) { // SSL negotiation failed or connection timed out $this->login_ticket_timestamp = null; return false; } $login_ticket_data = json_decode($login_ticket, true); if ($login_ticket_data == null || $login_ticket_data['data'] == null) { // Login failed. // Just to be safe, set this to null again. $this->login_ticket_timestamp = null; if ($login_request_info['ssl_verify_result'] == 1) { throw new PVE2_Exception("Invalid SSL cert on {$this->hostname} - check that the hostname is correct, and that it appears in the server certificate's SAN list. Alternatively set the verify_ssl flag to false if you are using internal self-signed certs (ensure you are aware of the security risks before doing so).", 4); } return false; } else { // Login success. $this->login_ticket = $login_ticket_data['data']; // We store a UNIX timestamp of when the ticket was generated here, // so we can identify when we need a new one expiration-wise later // on... $this->login_ticket_timestamp = time(); $this->reload_node_list(); return true; } } /* * bool check_login_ticket () * Checks if the login ticket is valid still, returns false if not. * Method of checking is purely by age of ticket right now... */ protected function check_login_ticket () { if ($this->login_ticket == null) { // Just to be safe, set this to null again. $this->login_ticket_timestamp = null; return false; } if ($this->login_ticket_timestamp >= (time() + 7200)) { // Reset login ticket object values. $this->login_ticket = null; $this->login_ticket_timestamp = null; return false; } else { return true; } } /* * object action (string action_path, string http_method[, array put_post_parameters]) * This method is responsible for the general cURL requests to the JSON API, * and sits behind the abstraction layer methods get/put/post/delete etc. */ private function action ($action_path, $http_method, $put_post_parameters = null) { // Check if we have a prefixed / on the path, if not add one. if (substr($action_path, 0, 1) != "/") { $action_path = "/".$action_path; } if (!$this->check_login_ticket()) { throw new PVE2_Exception("Not logged into Proxmox host. No Login access ticket found or ticket expired.", 3); } // Prepare cURL resource. $prox_ch = curl_init(); curl_setopt($prox_ch, CURLOPT_URL, "https://{$this->hostname}:{$this->port}/api2/json{$action_path}"); $put_post_http_headers = array(); $put_post_http_headers[] = "CSRFPreventionToken: {$this->login_ticket['CSRFPreventionToken']}"; // Lets decide what type of action we are taking... switch ($http_method) { case "GET": // Nothing extra to do. break; case "PUT": curl_setopt($prox_ch, CURLOPT_CUSTOMREQUEST, "PUT"); // Set "POST" data. $action_postfields_string = http_build_query($put_post_parameters); curl_setopt($prox_ch, CURLOPT_POSTFIELDS, $action_postfields_string); unset($action_postfields_string); // Add required HTTP headers. curl_setopt($prox_ch, CURLOPT_HTTPHEADER, $put_post_http_headers); break; case "POST": curl_setopt($prox_ch, CURLOPT_POST, true); // Set POST data. $action_postfields_string = http_build_query($put_post_parameters); curl_setopt($prox_ch, CURLOPT_POSTFIELDS, $action_postfields_string); unset($action_postfields_string); // Add required HTTP headers. curl_setopt($prox_ch, CURLOPT_HTTPHEADER, $put_post_http_headers); break; case "DELETE": curl_setopt($prox_ch, CURLOPT_CUSTOMREQUEST, "DELETE"); // No "POST" data required, the delete destination is specified in the URL. // Add required HTTP headers. curl_setopt($prox_ch, CURLOPT_HTTPHEADER, $put_post_http_headers); break; default: throw new PVE2_Exception("Error - Invalid HTTP Method specified.", 5); return false; } curl_setopt($prox_ch, CURLOPT_HEADER, true); curl_setopt($prox_ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($prox_ch, CURLOPT_COOKIE, "PVEAuthCookie=".$this->login_ticket['ticket']); curl_setopt($prox_ch, CURLOPT_SSL_VERIFYPEER, false); $action_response = curl_exec($prox_ch); curl_close($prox_ch); unset($prox_ch); $split_action_response = explode("\r\n\r\n", $action_response, 2); $header_response = $split_action_response[0]; $body_response = $split_action_response[1]; $action_response_array = json_decode($body_response, true); $action_response_export = var_export($action_response_array, true); error_log("----------------------------------------------\n" . "FULL RESPONSE:\n\n{$action_response}\n\nEND FULL RESPONSE\n\n" . "Headers:\n\n{$header_response}\n\nEnd Headers\n\n" . "Data:\n\n{$body_response}\n\nEnd Data\n\n" . "RESPONSE ARRAY:\n\n{$action_response_export}\n\nEND RESPONSE ARRAY\n" . "----------------------------------------------"); unset($action_response); unset($action_response_export); // Parse response, confirm HTTP response code etc. $split_headers = explode("\r\n", $header_response); if (substr($split_headers[0], 0, 9) == "HTTP/1.1 ") { $split_http_response_line = explode(" ", $split_headers[0]); if ($split_http_response_line[1] == "200") { if ($http_method == "PUT") { return true; } else { return $action_response_array['data']; } } else { error_log("This API Request Failed.\n" . "HTTP Response - {$split_http_response_line[1]}\n" . "HTTP Error - {$split_headers[0]}"); return false; } } else { error_log("Error - Invalid HTTP Response.\n" . var_export($split_headers, true)); return false; } if (!empty($action_response_array['data'])) { return $action_response_array['data']; } else { error_log("\$action_response_array['data'] is empty. Returning false.\n" . var_export($action_response_array['data'], true)); return false; } } /* * array reload_node_list () * Returns the list of node names as provided by /api2/json/nodes. * We need this for future get/post/put/delete calls. * ie. $this->get("nodes/XXX/status"); where XXX is one of the values from this return array. */ public function reload_node_list () { $node_list = $this->get("/nodes"); if (count($node_list) > 0) { $nodes_array = array(); foreach ($node_list as $node) { $nodes_array[] = $node['node']; } $this->cluster_node_list = $nodes_array; return true; } else { error_log(" Empty list of nodes returned in this cluster."); return false; } } /* * array get_node_list () * */ public function get_node_list () { // We run this if we haven't queried for cluster nodes as yet, and cache it in the object. if ($this->cluster_node_list == null) { if ($this->reload_node_list() === false) { return false; } } return $this->cluster_node_list; } /* * bool|int get_next_vmid () * Get Last VMID from a Cluster or a Node * returns a VMID, or false if not found. */ public function get_next_vmid () { $vmid = $this->get("/cluster/nextid"); if ($vmid == null) { return false; } else { return $vmid; } } /* * bool|string get_version () * Return the version and minor revision of Proxmox Server */ public function get_version () { $version = $this->get("/version"); if ($version == null) { return false; } else { return $version['version']; } } /* * object/array? get (string action_path) */ public function get ($action_path) { return $this->action($action_path, "GET"); } /* * bool put (string action_path, array parameters) */ public function put ($action_path, $parameters) { return $this->action($action_path, "PUT", $parameters); } /* * bool post (string action_path, array parameters) */ public function post ($action_path, $parameters) { return $this->action($action_path, "POST", $parameters); } /* * bool delete (string action_path) */ public function delete ($action_path) { return $this->action($action_path, "DELETE"); } // Logout not required, PVEAuthCookie tokens have a 2 hour lifetime. } ?>