Files
2021-09-17 12:41:45 +02:00

263 lines
8.7 KiB
PHP

<?php
/**
* PHP class to handle connections with cPanel's UAPI and API2 specifically through cURL requests as seamlessly and simply as possible.
*
* For documentation on cPanel's UAPI:
* @see https://documentation.cpanel.net/display/SDK/UAPI+Functions
*
* For documentation on cPanel's API2:
* @see https://documentation.cpanel.net/display/SDK/Guide+to+cPanel+API+2
*
* Please use UAPI where possible, only use API2 where the equivalent doesn't exist for UAPI
*
* @author Toby New - www.source-control.co.uk
* @copyright 2016 N1ghteyes
* @license license.txt The MIT License (MIT)
* @link https://github.com/N1ghteyes/cpanel-UAPI-php-class
*
* @todo - im sure there is a lot that can be improved here
*/
/**
* Class cPanelAPI
*/
class App_Service_CpanelApi
{
public $version = '2.0';
public $ssl = 1; //Bool - TRUE / FALSE for ssl connection
public $port = 2083; //default for ssl servers.
public $server;
public $maxredirect = 0; //Number of redirects to make, typically 0 is fine. on some shared setups this will need to be increased.
public $user;
public $json = '';
protected $scope; //String - Module we want to use
protected $api;
protected $auth;
protected $pass;
protected $secret;
protected $type;
protected $session;
protected $method;
protected $requestUrl;
protected $eno;
protected $emes;
protected $token = FALSE;
protected $httpMethod = 'GET';
protected $postData = '';
/**
* @param $user
* @param $pass
* @param $server
* @param $secret
*/
function __construct($user, $pass, $server, $secret = FALSE)
{
$this->user = $user;
$this->pass = $pass;
$this->server = $server;
if ($this->secret) {
$this->secret = $secret;
$this->set2Fa();
}
}
/**
* Include the ot class so we can generate a valid token for 2FA
*/
protected function set2Fa()
{
require 'otphp/lib/otphp.php';
$totp = new \OTPHP\TOTP($this->secret);
$this->token = $totp->now();
}
public function __get($name)
{
switch (strtolower($name)) {
case 'get':
$this->httpMethod = 'GET';
break;
case 'post':
$this->httpMethod = 'POST';
break;
case 'api2':
$this->setApi('api2');
break;
case 'uapi':
$this->setApi('uapi');
break;
default:
$this->scope = $name;
}
return $this;
}
/**
* Set the api to use for connections.
* @param $api
* @return $this
* @throws Exception
*/
protected function setApi($api)
{
$this->api = $api;
$this->setMethod();
return $this;
}
/**
* Function to set the method used to communicate with the chosen api.
* @return $this
* @throws Exception
*/
protected function setMethod()
{
switch ($this->api) {
case 'uapi':
$this->method = '/execute/';
break;
case 'api2':
$this->method = '/json-api/cpanel/';
break;
default:
throw new Exception('$this->api is not set or is incorrectly set. The only available options are \'uapi\' or \'api2\'');
}
return $this;
}
/**
* Magic __toSting() method, allows us to return the result as raw json
* @return mixed
*/
public function __toString()
{
return $this->json;
}
/**
* Magic __call method, will translate all function calls to object to API requests
* @param $name - name of the function
* @param $arguments - an array of arguments
* @return mixed
* @throws Exception
*/
public function __call($name, $arguments)
{
if (count($arguments) < 1 || !is_array($arguments[0]))
$arguments[0] = [];
$this->json = $this->APIcall($name, $arguments[0]);
return json_decode($this->json);
}
/**
* @param $name
* @param $arguments
* @return bool|mixed
* @throws Exception
*/
protected function APIcall($name, $arguments)
{
$this->auth = base64_encode($this->user . ":" . $this->pass);
$this->type = $this->ssl == 1 ? "https://" : "http://";
$this->requestUrl = $this->type . $this->server . ':' . $this->port . $this->method;
switch ($this->api) {
case 'uapi':
$this->requestUrl .= ($this->scope != '' ? $this->scope . "/" : '') . $name . '?';
break;
case 'api2':
if ($this->scope == '') {
throw new Exception('Scope must be set.');
}
$this->requestUrl .= '?cpanel_jsonapi_user=' . $this->user . '&cpanel_jsonapi_apiversion=2&cpanel_jsonapi_module=' . $this->scope . '&cpanel_jsonapi_func=' . $name . '&';
break;
default:
throw new Exception('$this->api is not set or is incorrectly set. The only available options are \'uapi\' or \'api2\'');
}
if($this->httpMethod == 'GET') {
$this->requestUrl .= http_build_query($arguments);
}
if($this->httpMethod == 'POST'){
$this->postData = $arguments;
}
return $this->curl_request($this->requestUrl);
}
/**
* @param $url
* @return bool|mixed
*/
protected function curl_request($url)
{
$httpHeaders = array("Authorization: Basic " . $this->auth);
//If we have a token then send that with the request for 2FA.
if ($this->token) {
$httpHeaders[] = "X-CPANEL-OTP: " . $this->token;
}
$ch = curl_init();
if($this->httpMethod == 'POST'){
$httpHeaders[] = "Content-type: multipart/form-data";
curl_setopt($ch,CURLOPT_POSTFIELDS, $this->postData);
}
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPHEADER, $httpHeaders);
curl_setopt($ch, CURLOPT_TIMEOUT, 100020);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
$content = $this->curl_exec_follow($ch, $this->maxredirect);
$this->eno = curl_errno($ch);
$this->emes = curl_error($ch);
curl_close($ch);
return $content;
}
/**
* @param $ch
* @param null $maxredirect
* @return bool|mixed
*/
protected function curl_exec_follow($ch, &$maxredirect = null)
{
// we emulate a browser here since some websites detect
// us as a bot and don't let us do our job
$user_agent = "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.5)" .
" Gecko/20041107 Firefox/1.0";
curl_setopt($ch, CURLOPT_USERAGENT, $user_agent);
$mr = $maxredirect === null ? 5 : intval($maxredirect);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, $mr > 0);
curl_setopt($ch, CURLOPT_MAXREDIRS, $mr);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
return curl_exec($ch);
}
/**
* Function to get the last request made
* @return mixed
*/
public function getLastRequest()
{
return $this->requestUrl;
}
/**
* Function to return the error if there was one, or FALSE if not.
* @return array|bool
*/
public function getError()
{
if (!empty($this->eno)) {
return ['no' => $this->eno, 'message' => $this->emes];
}
return FALSE;
}
}