<?php
namespace vps\proxmox;
/*

Proxmox VE APIv2 (PVE2) Client - PHP Class

Copyright (c) 2012-2014 Nathan Sullivan

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

*/



use RuntimeException;

class PVE2_Exception extends RuntimeException {}

class PVE2_API {
    protected $hostname;
    protected $username;
    protected $realm;
    protected $password;
    protected $port;
    protected $verify_ssl;

    protected $login_ticket = null;
    protected $login_ticket_timestamp = null;
    protected $cluster_node_list = null;

    public function __construct ($hostname, $username, $realm, $password) {



        $this->hostname   = $hostname;
        $this->username   = $username;
        $this->realm      = $realm;
        $this->password   = $password;

        $this->verify_ssl = false;
    }

    /*
     * 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, "{$this->hostname}/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);
        curl_setopt($prox_ch, CURLOPT_SSL_VERIFYHOST, false);

       // print_r($login_postfields_string);

        $login_ticket = curl_exec($prox_ch);

        $login_request_info = curl_getinfo($prox_ch);
      //  echo curl_error($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;
        }
    }

    # Sets the PVEAuthCookie
    # Attetion, after using this the user is logged into the web interface aswell!
    # Use with care, and DO NOT use with root, it may harm your system
    public function setCookie() {
        if (!$this->check_login_ticket()) {
            throw new PVE2_Exception("Not logged into Proxmox host. No Login access ticket found or ticket expired.", 3);
        }

        setrawcookie("PVEAuthCookie", $this->login_ticket['ticket'], 0, "/");
    }

    /*
     * 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, "{$this->hostname}/api2/json{$action_path}");
        curl_setopt($prox_ch, CURLOPT_SSL_VERIFYHOST, false);
        $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);
     //   echo $action_response;

        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 = isset($split_action_response[1]) ? $split_action_response[1] : '';


        $action_response_array = json_decode($body_response, true);

        $split_headers = explode("\r\n", $header_response);
       // print_r($split_headers);
        $split_http_response_line = explode(" ", $split_headers[0]);
        if(isset($split_http_response_line[1]) && $split_http_response_line[1] == '500'){
            return false;
        }

        /* 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)) {
            return $action_response_array;
        } 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");
        $node_list = $node_list['data'];
        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.
}

?>