index.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. <?php
  2. //die("Error: Disabled");
  3. define('HESTIA_CMD', '/usr/bin/sudo /usr/local/hestia/bin/');
  4. include($_SERVER['DOCUMENT_ROOT']."/inc/helpers.php");
  5. /**
  6. * Displays the error message, checks the proper code and saves a log if needed.
  7. *
  8. * @param int $exit_code
  9. * @param string $message
  10. * @param bool $add_log
  11. * @param string $user
  12. * @return void
  13. */
  14. function api_error($exit_code, $message, bool $add_log = false, $user = 'system') {
  15. $message = trim(is_array($message) ? implode("\n", $message) : $message);
  16. // Add log
  17. if ($add_log) {
  18. $v_real_user_ip = get_real_user_ip();
  19. hst_add_history_log("[$v_real_user_ip] $message", 'API', 'Error', $user);
  20. }
  21. // Print the message with http_code and exit_code
  22. $http_code = ($exit_code >= 100) ? $exit_code : exit_code_to_http_code($exit_code);
  23. header("Hestia-Exit-Code: $exit_code");
  24. http_response_code($http_code);
  25. echo (!preg_match('/^Error:/', $message)) ? "Error: $message" : $message;
  26. exit;
  27. }
  28. /**
  29. * Legacy connection format using hash or user and password.
  30. *
  31. * @param string $hst_hash
  32. * @param string $hst_user
  33. * @param string $hst_password
  34. * @param string $hst_returncode
  35. * @param string $hst_cmd
  36. * @param string $hst_arg1
  37. * @param string $hst_arg2
  38. * @param string $hst_arg3
  39. * @param string $hst_arg4
  40. * @param string $hst_arg5
  41. * @param string $hst_arg6
  42. * @param string $hst_arg7
  43. * @param string $hst_arg8
  44. * @param string $hst_arg9
  45. * @return void
  46. */
  47. function api_legacy($hst_hash, $hst_user, $hst_password, $hst_returncode, $hst_cmd, $hst_arg1, $hst_arg2, $hst_arg3, $hst_arg4, $hst_arg5, $hst_arg6, $hst_arg7, $hst_arg8, $hst_arg9) {
  48. exec(HESTIA_CMD."v-list-sys-config json", $output, $return_var);
  49. $settings = json_decode(implode('', $output), true);
  50. unset($output);
  51. if ($settings['config']['API'] != 'yes') {
  52. echo 'Error: API has been disabled';
  53. exit;
  54. }
  55. if ($settings['config']['API_ALLOWED_IP'] != 'allow-all') {
  56. $ip_list = explode(',', $settings['config']['API_ALLOWED_IP']);
  57. $ip_list[] = '';
  58. if (!in_array(get_real_user_ip(), $ip_list)) {
  59. echo 'Error: IP is not allowed to connect with API';
  60. exit;
  61. }
  62. }
  63. //This exists, so native JSON can be used without the repeating the code twice, so future code changes are easier and don't need to be replicated twice
  64. // Authentication
  65. if (empty($hst_hash)) {
  66. if ($hst_user != 'admin') {
  67. echo 'Error: authentication failed';
  68. exit;
  69. }
  70. $password = $hst_password;
  71. if (!isset($password)) {
  72. echo 'Error: missing authentication';
  73. exit;
  74. }
  75. $v_ip = escapeshellarg(get_real_user_ip());
  76. $output = '';
  77. exec(HESTIA_CMD."v-get-user-salt admin ".$v_ip." json", $output, $return_var);
  78. $pam = json_decode(implode('', $output), true);
  79. $salt = $pam['admin']['SALT'];
  80. $method = $pam['admin']['METHOD'];
  81. if ($method == 'md5') {
  82. $hash = crypt($password, '$1$'.$salt.'$');
  83. }
  84. if ($method == 'sha-512') {
  85. $hash = crypt($password, '$6$rounds=5000$'.$salt.'$');
  86. $hash = str_replace('$rounds=5000', '', $hash);
  87. }
  88. if ($method == 'des') {
  89. $hash = crypt($password, $salt);
  90. }
  91. // Send hash via tmp file
  92. $v_hash = exec('mktemp -p /tmp');
  93. $fp = fopen($v_hash, "w");
  94. fwrite($fp, $hash."\n");
  95. fclose($fp);
  96. // Check user hash
  97. exec(HESTIA_CMD."v-check-user-hash admin ".$v_hash." ".$v_ip, $output, $return_var);
  98. unset($output);
  99. // Remove tmp file
  100. unlink($v_hash);
  101. // Check API answer
  102. if ($return_var > 0) {
  103. echo 'Error: authentication failed';
  104. exit;
  105. }
  106. } else {
  107. $key = '/usr/local/hestia/data/keys/'.basename($hst_hash);
  108. $v_ip = escapeshellarg(get_real_user_ip());
  109. exec(HESTIA_CMD."v-check-api-key ".escapeshellarg($key)." ".$v_ip, $output, $return_var);
  110. unset($output);
  111. // Check API answer
  112. if ($return_var > 0) {
  113. echo 'Error: authentication failed';
  114. exit;
  115. }
  116. }
  117. // Prepare arguments
  118. if (isset($hst_cmd)) $cmd = escapeshellarg($hst_cmd);
  119. if (isset($hst_arg1)) $arg1 = escapeshellarg($hst_arg1);
  120. if (isset($hst_arg2)) $arg2 = escapeshellarg($hst_arg2);
  121. if (isset($hst_arg3)) $arg3 = escapeshellarg($hst_arg3);
  122. if (isset($hst_arg4)) $arg4 = escapeshellarg($hst_arg4);
  123. if (isset($hst_arg5)) $arg5 = escapeshellarg($hst_arg5);
  124. if (isset($hst_arg6)) $arg6 = escapeshellarg($hst_arg6);
  125. if (isset($hst_arg7)) $arg7 = escapeshellarg($hst_arg7);
  126. if (isset($hst_arg8)) $arg8 = escapeshellarg($hst_arg8);
  127. if (isset($hst_arg9)) $arg9 = escapeshellarg($hst_arg9);
  128. // Build query
  129. $cmdquery = HESTIA_CMD.$cmd." ";
  130. if (!empty($arg1)) {
  131. $cmdquery = $cmdquery.$arg1." ";
  132. }
  133. if (!empty($arg2)) {
  134. $cmdquery = $cmdquery.$arg2." ";
  135. }
  136. if (!empty($arg3)) {
  137. $cmdquery = $cmdquery.$arg3." ";
  138. }
  139. if (!empty($arg4)) {
  140. $cmdquery = $cmdquery.$arg4." ";
  141. }
  142. if (!empty($arg5)) {
  143. $cmdquery = $cmdquery.$arg5." ";
  144. }
  145. if (!empty($arg6)) {
  146. $cmdquery = $cmdquery.$arg6." ";
  147. }
  148. if (!empty($arg7)) {
  149. $cmdquery = $cmdquery.$arg7." ";
  150. }
  151. if (!empty($arg8)) {
  152. $cmdquery = $cmdquery.$arg8." ";
  153. }
  154. if (!empty($arg9)) {
  155. $cmdquery = $cmdquery.$arg9;
  156. }
  157. // Check command
  158. if ($cmd == "'v-make-tmp-file'") {
  159. // Used in DNS Cluster
  160. $fp = fopen('/tmp/'.basename($hst_arg2), 'w');
  161. fwrite($fp, $hst_arg1."\n");
  162. fclose($fp);
  163. $return_var = 0;
  164. } else {
  165. // Run normal cmd query
  166. exec($cmdquery, $output, $return_var);
  167. }
  168. if ((!empty($hst_returncode)) && ($hst_returncode == 'yes')) {
  169. echo $return_var;
  170. } else {
  171. if (($return_var == 0) && (empty($output))) {
  172. echo "OK";
  173. } else {
  174. echo implode("\n", $output)."\n";
  175. }
  176. }
  177. exit;
  178. }
  179. /**
  180. * Connection using access key.
  181. *
  182. * @param array{access_key: string, secret_key: string, cmd: string, arg1?: string, arg2?: string, arg3?: string, arg4?: string, arg5?: string, arg6?: string, arg7?: string, arg8?: string, arg9?: string, returncode?: string} $request_data
  183. * @return void
  184. */
  185. function api_connection(array $request_data) {
  186. $v_real_user_ip = get_real_user_ip();
  187. exec(HESTIA_CMD."v-list-sys-config json", $output, $return_var);
  188. $settings = json_decode(implode('', $output), true);
  189. unset($output, $return_var);
  190. // Get the status of api
  191. $api_status = (!empty($settings['config']['API_SYSTEM']) && is_numeric($settings['config']['API_SYSTEM'])) ? $settings['config']['API_SYSTEM'] : 0;
  192. if ($api_status == 0) {
  193. // Check if API is disabled for all users
  194. api_error(E_DISABLED, "API has been disabled");
  195. }
  196. // Check if API access is enabled for the user
  197. if ($settings['config']['API_ALLOWED_IP'] != 'allow-all') {
  198. $ip_list = explode(',', $settings['config']['API_ALLOWED_IP']);
  199. $ip_list[] = '';
  200. if (!in_array($v_real_user_ip, $ip_list) && !in_array('0.0.0.0', $ip_list)) {
  201. api_error(E_FORBIDDEN, "IP is not allowed to connect with API");
  202. }
  203. }
  204. // Get POST Params
  205. $hst_access_key_id = trim($request_data['access_key'] ?? '');
  206. $hst_secret_access_key = trim($request_data['secret_key'] ?? '');
  207. $hst_return = (($request_data['returncode'] ?? 'no') === 'yes') ? 'code' : 'data';
  208. $hst_cmd = trim($request_data['cmd'] ?? '');
  209. $hst_cmd_args = [];
  210. for ($i = 1; $i <= 9; $i++) {
  211. if (isset($request_data["arg{$i}"])) {
  212. $hst_cmd_args["arg{$i}"] = trim($request_data["arg{$i}"]);
  213. }
  214. }
  215. if (empty($hst_cmd)) {
  216. api_error(E_INVALID, "Command not provided");
  217. } else if (!preg_match('/^[a-zA-Z0-9_-]+$/', $hst_cmd)) {
  218. api_error(E_INVALID, "$hst_cmd command invalid");
  219. }
  220. if (empty($hst_access_key_id) || empty($hst_secret_access_key)) {
  221. api_error(E_PASSWORD, "Authentication failed");
  222. }
  223. // Authenticates the key and checks permission to run the script
  224. exec(HESTIA_CMD."v-check-access-key ".escapeshellarg($hst_access_key_id)." ".escapeshellarg($hst_secret_access_key)." ".escapeshellarg($hst_cmd)." ".escapeshellarg($v_real_user_ip)." json", $output, $return_var);
  225. if ($return_var > 0) {
  226. //api_error($return_var, "Key $hst_access_key_id - authentication failed");
  227. api_error($return_var, $output);
  228. }
  229. $key_data = json_decode(implode('', $output), true) ?? [];
  230. unset($output, $return_var);
  231. $key_user = $key_data['USER'];
  232. $user_arg_position = (isset($key_data['USER_ARG_POSITION']) && is_numeric($key_data['USER_ARG_POSITION'])) ? $key_data['USER_ARG_POSITION'] : -1;
  233. # Check if API access is enabled for nonadmin users
  234. if ($key_user != 'admin' && $api_status < 2) {
  235. api_error(E_DISABLED, "API has been disabled");
  236. }
  237. // Checks if the value entered in the "user" argument matches the user of the key
  238. if ($key_user != 'admin' && $user_arg_position > 0 && $hst_cmd_args["arg{$user_arg_position}"] != $key_user) {
  239. api_error(E_FORBIDDEN, "Key $hst_access_key_id - the \"user\" argument doesn\'t match the key\'s user");
  240. }
  241. // Prepare command
  242. $cmdquery = HESTIA_CMD.escapeshellcmd($hst_cmd);
  243. // Prepare arguments
  244. foreach ($hst_cmd_args as $cmd_arg) {
  245. $cmdquery .= " ".escapeshellarg($cmd_arg);
  246. }
  247. // Run cmd query
  248. exec($cmdquery, $output, $cmd_exit_code);
  249. $cmd_output = trim(implode("\n", $output));
  250. unset($output);
  251. header("Hestia-Exit-Code: $cmd_exit_code");
  252. if ($hst_return == 'code') {
  253. echo $cmd_exit_code;
  254. } else {
  255. if ($cmd_exit_code > 0) {
  256. http_response_code(exit_code_to_http_code($cmd_exit_code));
  257. } else {
  258. http_response_code(!empty($cmd_output) ? 200 : 204);
  259. if (!empty($cmd_output) && json_decode($cmd_output, true)) {
  260. header('Content-Type: application/json; charset=utf-8');
  261. }
  262. }
  263. echo $cmd_output;
  264. }
  265. exit;
  266. }
  267. // Get request data
  268. if (isset($_POST['access_key']) || isset($_POST['user']) || isset($_POST['hash'])) {
  269. $request_data = $_POST;
  270. } else if (($json_data = json_decode(file_get_contents("php://input"), true)) != null) {
  271. $request_data = $json_data;
  272. } else {
  273. api_error(405, "Error: data received is null or invalid, check https://docs.hestiacp.com/admin_docs/api.html");
  274. }
  275. // Try to get access key in the hash
  276. if (!isset($request_data['access_key']) && isset($request_data['hash']) && substr_count($request_data['hash'], ':') == 1) {
  277. $hash_parts = explode(':', $request_data['hash']);
  278. if (strlen($hash_parts[0]) == 20 && strlen($hash_parts[1]) == 40) {
  279. $request_data['access_key'] = $hash_parts[0];
  280. $request_data['secret_key'] = $hash_parts[1];
  281. unset($request_data['hash']);
  282. }
  283. }
  284. // Check data format
  285. if (isset($request_data['access_key']) && isset($request_data['secret_key'])) {
  286. api_connection($request_data);
  287. } else if (isset($request_data['user']) || isset($request_data['hash'])) {
  288. $array = array('user', 'password', 'hash', 'returncode', 'cmd', 'arg1', 'arg2', 'arg3', 'arg4', 'arg5', 'arg6', 'arg7', 'arg8', 'arg9');
  289. foreach ($array as $key) {
  290. if (empty($request_data[$key])) {
  291. $request_data[$key] = '';
  292. }
  293. }
  294. api_legacy($request_data['hash'], $request_data['user'], $request_data['password'], $request_data['returncode'], $request_data['cmd'], $request_data['arg1'], $request_data['arg2'], $request_data['arg3'], $request_data['arg4'], $request_data['arg5'], $request_data['arg6'], $request_data['arg7'], $request_data['arg8'], $request_data['arg9']);
  295. } else {
  296. api_error(405, "Error: data received is null or invalid, check https://docs.hestiacp.com/admin_docs/api.html");
  297. }