Advertisement
Guest User

Untitled

a guest
Dec 12th, 2022
148
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 12.12 KB | None | 0 0
  1. <?php
  2.  
  3. /*
  4.  
  5. Proxmox VE APIv2 (PVE2) Client - PHP Class
  6.  
  7. Copyright (c) 2012-2014 Nathan Sullivan
  8.  
  9. Permission is hereby granted, free of charge, to any person obtaining a copy of
  10. this software and associated documentation files (the "Software"), to deal in
  11. the Software without restriction, including without limitation the rights to
  12. use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
  13. the Software, and to permit persons to whom the Software is furnished to do so,
  14. subject to the following conditions:
  15.  
  16. The above copyright notice and this permission notice shall be included in all
  17. copies or substantial portions of the Software.
  18.  
  19. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  20. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
  21. FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  22. COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
  23. IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  24. CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  25.  
  26. */
  27.  
  28. namespace Box\Mod\Serviceproxmox;
  29.  
  30. class PVE2_API {
  31. protected $hostname;
  32. protected $username;
  33. protected $realm;
  34. protected $password;
  35. protected $port;
  36. protected $verify_ssl;
  37.  
  38. protected $login_ticket = null;
  39. protected $login_ticket_timestamp = null;
  40. protected $cluster_node_list = null;
  41.  
  42. public function __construct ($hostname, $username, $realm, $password, $port = 8006, $verify_ssl = false) {
  43. if (empty($hostname) || empty($username) || empty($realm) || empty($password) || empty($port)) {
  44. throw new \Exception("Hostname/Username/Realm/Password/Port required for PVE2_API object constructor.", 1);
  45. }
  46. // Check hostname resolves.
  47. if (gethostbyname($hostname) == $hostname && !filter_var($hostname, FILTER_VALIDATE_IP)) {
  48. throw new \Exception("Cannot resolve {$hostname}.", 2);
  49. }
  50. // Check port is between 1 and 65535.
  51. if (!is_int($port) || $port < 1 || $port > 65535) {
  52. throw new \Exception("Port must be an integer between 1 and 65535.", 6);
  53. }
  54. // Check that verify_ssl is boolean.
  55. if (!is_bool($verify_ssl)) {
  56. throw new \Exception("verify_ssl must be boolean.", 7);
  57. }
  58.  
  59. $this->hostname = $hostname;
  60. $this->username = $username;
  61. $this->realm = $realm;
  62. $this->password = $password;
  63. $this->port = $port;
  64. $this->verify_ssl = $verify_ssl;
  65. }
  66.  
  67. /*
  68. * bool login ()
  69. * Performs login to PVE Server using JSON API, and obtains Access Ticket.
  70. */
  71. public function login () {
  72. // Prepare login variables.
  73. $login_postfields = array();
  74. $login_postfields['username'] = $this->username;
  75. $login_postfields['password'] = $this->password;
  76. $login_postfields['realm'] = $this->realm;
  77.  
  78. $login_postfields_string = http_build_query($login_postfields);
  79. unset($login_postfields);
  80.  
  81. // Perform login request.
  82. $prox_ch = curl_init();
  83. curl_setopt($prox_ch, CURLOPT_URL, "https://{$this->hostname}:{$this->port}/api2/json/access/ticket");
  84. curl_setopt($prox_ch, CURLOPT_POST, true);
  85. curl_setopt($prox_ch, CURLOPT_RETURNTRANSFER, true);
  86. curl_setopt($prox_ch, CURLOPT_POSTFIELDS, $login_postfields_string);
  87. curl_setopt($prox_ch, CURLOPT_SSL_VERIFYPEER, $this->verify_ssl);
  88.  
  89. $login_ticket = curl_exec($prox_ch);
  90. $login_request_info = curl_getinfo($prox_ch);
  91.  
  92. curl_close($prox_ch);
  93. unset($prox_ch);
  94. unset($login_postfields_string);
  95.  
  96. if (!$login_ticket) {
  97. // SSL negotiation failed or connection timed out
  98. $this->login_ticket_timestamp = null;
  99. return false;
  100. }
  101.  
  102. $login_ticket_data = json_decode($login_ticket, true);
  103. if ($login_ticket_data == null || $login_ticket_data['data'] == null) {
  104. // Login failed.
  105. // Just to be safe, set this to null again.
  106. $this->login_ticket_timestamp = null;
  107. if ($login_request_info['ssl_verify_result'] == 1) {
  108. throw new \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);
  109. }
  110. return false;
  111. } else {
  112. // Login success.
  113. $this->login_ticket = $login_ticket_data['data'];
  114. // We store a UNIX timestamp of when the ticket was generated here,
  115. // so we can identify when we need a new one expiration-wise later
  116. // on...
  117. $this->login_ticket_timestamp = time();
  118. $this->reload_node_list();
  119. return true;
  120. }
  121. }
  122.  
  123. # Sets the PVEAuthCookie
  124. # Attetion, after using this the user is logged into the web interface aswell!
  125. # Use with care, and DO NOT use with root, it may harm your system
  126. public function setCookie() {
  127. if (!$this->check_login_ticket()) {
  128. throw new \Exception("Not logged into Proxmox host. No Login access ticket found or ticket expired.", 3);
  129. }
  130.  
  131. setrawcookie("PVEAuthCookie", $this->login_ticket['ticket'], 0, "/");
  132. }
  133.  
  134. /*
  135. * bool check_login_ticket ()
  136. * Checks if the login ticket is valid still, returns false if not.
  137. * Method of checking is purely by age of ticket right now...
  138. */
  139. protected function check_login_ticket () {
  140. if ($this->login_ticket == null) {
  141. // Just to be safe, set this to null again.
  142. $this->login_ticket_timestamp = null;
  143. return false;
  144. }
  145. if ($this->login_ticket_timestamp >= (time() + 7200)) {
  146. // Reset login ticket object values.
  147. $this->login_ticket = null;
  148. $this->login_ticket_timestamp = null;
  149. return false;
  150. } else {
  151. return true;
  152. }
  153. }
  154.  
  155. /*
  156. * object action (string action_path, string http_method[, array put_post_parameters])
  157. * This method is responsible for the general cURL requests to the JSON API,
  158. * and sits behind the abstraction layer methods get/put/post/delete etc.
  159. */
  160. private function action ($action_path, $http_method, $put_post_parameters = null) {
  161. // Check if we have a prefixed / on the path, if not add one.
  162. if (substr($action_path, 0, 1) != "/") {
  163. $action_path = "/".$action_path;
  164. }
  165.  
  166. if (!$this->check_login_ticket()) {
  167. throw new \Exception("Not logged into Proxmox host. No Login access ticket found or ticket expired.", 3);
  168. }
  169.  
  170. // Prepare cURL resource.
  171. $prox_ch = curl_init();
  172. curl_setopt($prox_ch, CURLOPT_URL, "https://{$this->hostname}:{$this->port}/api2/json{$action_path}");
  173.  
  174. $put_post_http_headers = array();
  175. $put_post_http_headers[] = "CSRFPreventionToken: {$this->login_ticket['CSRFPreventionToken']}";
  176. // Lets decide what type of action we are taking...
  177. switch ($http_method) {
  178. case "GET":
  179. // Nothing extra to do.
  180. break;
  181. case "PUT":
  182. curl_setopt($prox_ch, CURLOPT_CUSTOMREQUEST, "PUT");
  183.  
  184. // Set "POST" data.
  185. $action_postfields_string = http_build_query($put_post_parameters);
  186. curl_setopt($prox_ch, CURLOPT_POSTFIELDS, $action_postfields_string);
  187. unset($action_postfields_string);
  188.  
  189. // Add required HTTP headers.
  190. curl_setopt($prox_ch, CURLOPT_HTTPHEADER, $put_post_http_headers);
  191. break;
  192. case "POST":
  193. curl_setopt($prox_ch, CURLOPT_POST, true);
  194.  
  195. // Set POST data.
  196. $action_postfields_string = http_build_query($put_post_parameters);
  197. curl_setopt($prox_ch, CURLOPT_POSTFIELDS, $action_postfields_string);
  198. unset($action_postfields_string);
  199.  
  200. // Add required HTTP headers.
  201. curl_setopt($prox_ch, CURLOPT_HTTPHEADER, $put_post_http_headers);
  202. break;
  203. case "DELETE":
  204. curl_setopt($prox_ch, CURLOPT_CUSTOMREQUEST, "DELETE");
  205. // No "POST" data required, the delete destination is specified in the URL.
  206.  
  207. // Add required HTTP headers.
  208. curl_setopt($prox_ch, CURLOPT_HTTPHEADER, $put_post_http_headers);
  209. break;
  210. default:
  211. throw new \Exception("Error - Invalid HTTP Method specified.", 5);
  212. return false;
  213. }
  214.  
  215. curl_setopt($prox_ch, CURLOPT_HEADER, true);
  216. curl_setopt($prox_ch, CURLOPT_RETURNTRANSFER, true);
  217. curl_setopt($prox_ch, CURLOPT_COOKIE, "PVEAuthCookie=".$this->login_ticket['ticket']);
  218. curl_setopt($prox_ch, CURLOPT_SSL_VERIFYPEER, false);
  219.  
  220. $action_response = curl_exec($prox_ch);
  221.  
  222. curl_close($prox_ch);
  223. unset($prox_ch);
  224.  
  225. $split_action_response = explode("\r\n\r\n", $action_response, 2);
  226. $header_response = $split_action_response[0];
  227. $body_response = $split_action_response[1];
  228. $action_response_array = json_decode($body_response, true);
  229.  
  230. $action_response_export = var_export($action_response_array, true);
  231. /*error_log("----------------------------------------------\n" .
  232. "FULL RESPONSE:\n\n{$action_response}\n\nEND FULL RESPONSE\n\n" .
  233. "Headers:\n\n{$header_response}\n\nEnd Headers\n\n" .
  234. "Data:\n\n{$body_response}\n\nEnd Data\n\n" .
  235. "RESPONSE ARRAY:\n\n{$action_response_export}\n\nEND RESPONSE ARRAY\n" .
  236. "----------------------------------------------");*/
  237.  
  238. unset($action_response);
  239. unset($action_response_export);
  240.  
  241. // Parse response, confirm HTTP response code etc.
  242. $split_headers = explode("\r\n", $header_response);
  243. if (substr($split_headers[0], 0, 9) == "HTTP/1.1 ") {
  244. $split_http_response_line = explode(" ", $split_headers[0]);
  245. if ($split_http_response_line[1] == "200") {
  246. if ($http_method == "PUT") {
  247. return true;
  248. } else {
  249. return $action_response_array['data'];
  250. }
  251. } else {
  252. error_log("This API Request Failed.\n" .
  253. "HTTP Response - {$split_http_response_line[1]}\n" .
  254. "HTTP Error - {$split_headers[0]}");
  255. return false;
  256. }
  257. } else {
  258. error_log("Error - Invalid HTTP Response.\n" . var_export($split_headers, true));
  259. return false;
  260. }
  261.  
  262. if (!empty($action_response_array['data'])) {
  263. return $action_response_array['data'];
  264. } else {
  265. error_log("\$action_response_array['data'] is empty. Returning false.\n" .
  266. var_export($action_response_array['data'], true));
  267. return false;
  268. }
  269. }
  270.  
  271. /*
  272. * array reload_node_list ()
  273. * Returns the list of node names as provided by /api2/json/nodes.
  274. * We need this for future get/post/put/delete calls.
  275. * ie. $this->get("nodes/XXX/status"); where XXX is one of the values from this return array.
  276. */
  277. public function reload_node_list () {
  278. $node_list = $this->get("/nodes");
  279. if (count($node_list) > 0) {
  280. $nodes_array = array();
  281. foreach ($node_list as $node) {
  282. $nodes_array[] = $node['node'];
  283. }
  284. $this->cluster_node_list = $nodes_array;
  285. return true;
  286. } else {
  287. error_log(" Empty list of nodes returned in this cluster.");
  288. return false;
  289. }
  290. }
  291.  
  292. /*
  293. * array get_node_list ()
  294. *
  295. */
  296. public function get_node_list () {
  297. // We run this if we haven't queried for cluster nodes as yet, and cache it in the object.
  298. if ($this->cluster_node_list == null) {
  299. if ($this->reload_node_list() === false) {
  300. return false;
  301. }
  302. }
  303.  
  304. return $this->cluster_node_list;
  305. }
  306.  
  307. /*
  308. * bool|int get_next_vmid ()
  309. * Get Last VMID from a Cluster or a Node
  310. * returns a VMID, or false if not found.
  311. */
  312. public function get_next_vmid () {
  313. $vmid = $this->get("/cluster/nextid");
  314. if ($vmid == null) {
  315. return false;
  316. } else {
  317. return $vmid;
  318. }
  319. }
  320.  
  321. /*
  322. * bool|string get_version ()
  323. * Return the version and minor revision of Proxmox Server
  324. */
  325. public function get_version () {
  326. $version = $this->get("/version");
  327. if ($version == null) {
  328. return false;
  329. } else {
  330. return $version['version'];
  331. }
  332. }
  333.  
  334. /*
  335. * object/array? get (string action_path)
  336. */
  337. public function get ($action_path) {
  338. return $this->action($action_path, "GET");
  339. }
  340.  
  341. /*
  342. * bool put (string action_path, array parameters)
  343. */
  344. public function put ($action_path, $parameters = array()) {
  345. return $this->action($action_path, "PUT", $parameters);
  346. }
  347.  
  348. /*
  349. * bool post (string action_path, array parameters)
  350. */
  351. public function post ($action_path, $parameters = array()) {
  352. return $this->action($action_path, "POST", $parameters);
  353. }
  354.  
  355. /*
  356. * bool delete (string action_path)
  357. */
  358. public function delete ($action_path) {
  359. return $this->action($action_path, "DELETE");
  360. }
  361.  
  362. // Logout not required, PVEAuthCookie tokens have a 2 hour lifetime.
  363. }
  364.  
  365. ?>
  366.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement