Interested? - HOW TO: Create your own remote access features in PHP

webignition

Well-Known Member
Jan 22, 2005
1,876
2
166
EDIT 0 - Nav Links
I first posted to see if there was an interest in this HOW TO and, seeing that there was, posted the HOW TO somewhere further down the page. For ease of navigation, use the following links to jump straight to the meaty bits:

Part 1 of 3 : Introductions and problem definition
Part 2 of 3 : The solution in lot of pieces
Part 3 of 3 : Putting it all together

Preface (an introduction to set the scene)
WHM/cPanel provides a very nice API for 'remotely' accessing certain WHM or cPanel features. In this case 'remotely' means either accessing the features of one server from another or accessing WHM or cPanel features from scripts running as normal users. These can be referred to as 'remote access features'

Used correctly, the remote access features offer you (relatively) unlimited functionality within your own scripts. This can be accomplished by mimicking the functionality of the supplied PHP remote access features so as to get your own custom written scripts to securely execute as root, therefore letting you read from and write to a plethora of system-related files.

Since WHM/cPanel effectively provides a GUI that (very nicely) performs operations on a number of system-related files (a somewhat over-simplification, but roughly right), the ability to do so yourself passes the power or WHM/cPanel to your own scripts. This is very good for integrating powerful features into your own website (your own website can do anything WHM/cPanel does and therefore presents the ultimate branding experience).

Why a HOW TO?
Forum users, particularly those new to WHM/cPanel, often ask the following types of questions:

- how do the remote access features work?
- what can be accomplished through the remote access features?
- why are the remote access features so limited in their functionality?

Often it is not easy to give detailed answers to such questions, either due to a lack of time or due to not wanting to get dragged down into trying to help people fix their own scripts.

Since I've derived my own method for using the remote access features to securely run PHP scripts as root, I thought I'd write a HOW TO on the subject so as to:

- generally answer the above types of questions
- provide an example-based method, built up from first principles, to allow people to expand on the supplied remote access functions in their own way
- reduce the need for people to repeatedly ask the above questions

Is there an interest?
I think people would be interested in knowing how to do this, and I also think that it would be beneficial to pool such experiences, so that the best and most secure methods can be shared and used to a greater good.

However, it will take me a fair amount of time and effort to put what is in my head into a decent HOW TO format suitable for these forums.

For now, I'd just like to find out if anyone is interested in me putting together a HOW TO on the topic. If so, I'll get to it. If not, I won't. Comments would therefore be greatly appreciated!

EDIT 1
The HOW TO is being prepared at the moment. I'll post it as soon as it's ready. Time permitting, that may be today. If not, it may be in a couple of days at most.

EDIT 2
The HOW TO has now been added in three rather long posts. I'll be the first to admit that it's a fair amount to read, but hopefully it covers things in sufficient detail to actually make sense.
 
Last edited:

lamp

Well-Known Member
Dec 22, 2003
111
0
166
There is interest from here!

Thanks webignition; good initiative.

Lamp
 

webignition

Well-Known Member
Jan 22, 2005
1,876
2
166
Part 1 of 3 : Introductions and problem definition

Preface (an introduction to set the scene)
WHM/cPanel provides a very nice API for 'remotely' accessing certain WHM or cPanel features. In this case 'remotely' means either accessing the features of one server from another or accessing WHM or cPanel features from scripts running as normal users. These can be referred to as 'remote access features'

Used correctly, the remote access features offer you (relatively) unlimited functionality within your own scripts. This can be accomplished by mimicking the functionality of the supplied PHP remote access features so as to get your own custom written scripts to securely execute as root, therefore letting you read from and write to a plethora of system-related files.

Since WHM/cPanel effectively provides a GUI that (very nicely) performs operations on a number of system-related files (a somewhat over-simplification, but roughly right), the ability to do so yourself passes the power or WHM/cPanel to your own scripts. This is very good for integrating powerful features into your own website (your own website can do anything WHM/cPanel does and therefore presents the ultimate branding experience).

Intended audience (what is expected to be in your head for this to make sense)
Creating your own PHP scripts that execute as root after being called through remote access features is not something you should attempt casually.

Firstly, you will need a solid knowledge of PHP. Unlike a common PHP development environment, you don't have the option of giving something a go and debugging based on PHP errors. Generally such a method is not the best, but in the worst case you could use it and you won't harm anyone but yourself. If you whimsically create a PHP script that can be run as root and don't take care to make sure everything is 100% right when performing certain operations, you could break your server in ways that would appear, to an outside observer, to be thorougly impressive. Sadly though you would be thoroughly unimpressed in a range of vivid and colourful ways.

Secondly, you must be familiar with the relevant security considerations. The PHP scripts themselves, when tested in a safe environment, shouldn't themselves present any major security concerns as they will only do what you tell them to and won't be able to be run by any user other than root. However in most cases, if not all, user-supplied data will be used in conjunction with the scripts. Any insecurities present in your own PHP coding could, under normal circumstances, present a few tricky issues. However when the buggy script then has root powers, you're opening yourself up to a world of misery if you're careless.

In short, a very good understanding of PHP and a good understanding of how to santise user-supplied data is a recommended must. It is not a goal of this HOW TO to cover such topics in any sufficient detail - no spoon-feeding here. That said, this HOW TO is written with the aim of putting the information across in a clear and straightforward fashion without using an over-complex jargon above that which is absolutely necessary.

You should also have a good understanding of how the remote access features work, preferably acquired through actually having used them.

Overview (a summary of what we hope to achieve)
This method is related to the function-based PHP remote access features (as opposed to the class-based alternative).

Taking a look at /usr/local/cpanel/Cpanel/Accounting.php.inc, you'll see two things. Firstly, you'll see that some basic account-related functions can be called from within your own PHP scripts. Secondly, you'll see that there is an astounding similarity between all but one of the listed functions.

The similarity is that all functions apart from whmreq() contain a line similar to the following:

Code:
$result = whmreq("/url",$host,$user,$accesshash,$usessl)
A good look through /usr/local/cpanel/Cpanel/Accounting.php.inc will make you realise that the function whmreq() lets you execute a script located at "/url" as the user '$user'.

This HOW TO aims to cover (with respect to the above):

- where "/url" is
- where to put your own scripts and how to modify "/url" to point to them
- how to add further functions to /usr/local/cpanel/Cpanel/Accounting.php.inc to remotely call your own scripts
- how to use this added functionality within your own local PHP scripts to achieve anything meaningful

How all the above can be achieved will be presented by developing a solution to a real situation:

Problem
I want to let new users register online for new accounts. During registration, the user should supply the domain name they want to use. I want the user to be immediately informed if the chosen domain name is already being used by another account.

Assumptions
A list of all domain names in use on a given server is contained within /etc/localdomains. This is not entirely true, however for the purpose of this example it is sufficient. A perfect solution would also check other files that also contain lists of domain names in use on a given server.

Required task
To read the contents of /etc/localdomains and supply the contents in a meaningful fashion to a PHP script running as a normal user.

Obvious obstacles
The ownership and permissions for /etc/localdomains prevent it being accessed by normal users.
 

webignition

Well-Known Member
Jan 22, 2005
1,876
2
166
Part 2 of 3 : The solution in lot of pieces

Solution
We need to create a script that will execute as root. The script needs to be initiated by a remote access call and will pass data back to a locally running function which will then bear the burden and do what it needs to do.

Creating a PHP script that will run as root
Observing how some existing process works is very useful when you want to mimick some aspect of that function.

My attention turned to MailWatch, which runs from https://hostname:2087/3rdparty/mailwatch/, otherwise known as http://WHM/3rdparty/mailwatch. Very nice to know, as all it's scripts are owned by root:root and have 0644 permissions.

Taking a look from SSH, you'll see that the actual directory is /usr/local/cpanel/whostmgr/docroot/3rdparty/mailwatch. This directory has 0755 permissions and is owned by root:root.

Combining these two facts, all we need to do is:

i) Create /usr/local/cpanel/whostmgr/docroot/3rdparty/example/ with 0755 permissions and owned by root:root.
ii) Create a script called example.php within the above directory, with 0644 permissions and owned by root:root.
iii) Add some coding to /usr/local/cpanel/whostmgr/docroot/3rdparty/example/example.php

For testing purposes, try adding the following to /usr/local/cpanel/whostmgr/docroot/3rdparty/example/example.php:

PHP:
<?php
  // $fp = @fopen("/etc/localdomains");
  // EDIT: changed line above to line below. Left original typo in place for forum continuity!
  $fp = @fopen("/etc/localdomains", "r");

  if ($fp) {
    echo "/etc/localdomains opened successfully!";
    fclose($fp);
  } else {
    echo "Failed to open /etc/localdomains";
  }
?>
Try browsing to https://hostname:2087/3rdparty/example/example.php. Assuming that /etc/localdomains exists, you should only encounter the output "Failed to open /etc/localdomains" if you don't have permission to access /etc/localdomains (assuming that PHP is otherwise working as it should). You should get the other string output and should see that our example script has the power to do useful things.

Note: Scripts don't have to be located in /usr/local/cpanel/whostmgr/docroot/3rdparty/ - /usr/local/cpanel/whostmgr/docroot/ works just fine, but is a spot messy. /usr/local/cpanel/whostmgr/docroot/3rdparty/example/ keeps things in order a little better.

Great, we have a script that can be run as root. But browsing to https://hostname:2087/3rdparty/example/example.php is hardly good, since we'll still have to login to WHM as root to do so.

Adding functionality to /usr/local/cpanel/Cpanel/Accounting.php.inc to call your own scripts
Firstly, /usr/local/cpanel/Cpanel/Accounting.php.inc is an annoyance to type or to even copy and paste. From now on, I'll refer to Accounting.php.inc.

Note 1: In my own implementation, I copied Accounting.php.inc to /home/example/lib/ so that I could play around with it. This gives convenient access to the user 'example' without exposing Accounting.php.inc to WWW users.

Note 2: I didn't actually modify Accounting.php.inc, preferring to keep the original safe and clean and merely included it within another script that contains my custom remote access functions. This keeps you work separate from cPanels's work, which makes it easier to figure out which is which.

Note 3: I'd recommend following notes 1 and 2 as it is, again, a fraction neater. However, for the purpose of consitency, I will refer only to Accounting.php.inc and the modification of it. In reality you'll have to actually modify either your local copy of it or apply modifications to a separate script that merely includes Accounting.php.inc.

Right, notes aside, let's get to the important bit: how to add functionality to Accounting.php.inc so as to call /usr/local/cpanel/whostmrg/docroot/3rdparty/example/example.php.

Let's do this with an example of two PHP snippets. The first is a function copied directly from the original Accounting.php.inc, the second is a function that is created for our needs and added to Accounting.php.inc:

PHP:
function killacct ($host,$user,$accesshash,$usessl,$killuser) {
  $result = whmreq("/scripts/killacct?user=${killuser}&nohtml=1",$host,$user,$accesshash,$usessl);
  if ($cpanelaccterr != "") { return; }
  return $result;
}
PHP:
function whmListDomains($host,$user,$accesshash,$usessl) {
  // Get list of domains, both in db and in /etc/localdomains
  $s_domains = whmreq("/3rdparty/example/example.php",$host,$user,$accesshash,$usessl);
  if ($cpanelaccterr != "") {
    return;
  }
		
  // Get domains into an array and trim
  return explode("\n", trim($s_domains));
}
This is assuming that the following variables are pre-defined:
$host: "localhost" or the IP of the machine you want to talk to
$user: "root"
$accesshash: root's accesshash (see WHM >> Cluster/Remote Access >> Setup Remote Access Key)
$usessl: peferably 1, but 0 if you have issues with this working

Basically, the above variables should contain whatever you normally use to get remote access features to work. You have done that already, haven't you?

Coding styles aside, both of these functions use the function whmreq() to call a remote script and return, processed or not, the output of the script. In the case of our function, the output is returned as an array just to make things a little easier for the receiving local PHP function, as an array is always a nice way to store a list of things.

Notice that without any changes to /usr/local/cpanel/whostmgr/docroot/3rdparty/example/example.php, our function, based on it's name, is next to useless. Since the task of using PHP to open a file and return the contents as a string is not so taxing, it won't be covered at this stage. Let's assume you can do that. If you can't, you shouldn't be reading this.

For now, this demomnstrates how example.php can be be run as root via being called by a function that resides in the PHP script of a 'normal' user. In other words, this is how you effectively get a normal user to run scripts as root.

In reality, the normal user is not running scripts as root but is, through an API, getting a local function to retrieve data from a script that runs as root. However the end result is the same: the contents of root-owned file can be retrieved by a normal user.
 
Last edited:

webignition

Well-Known Member
Jan 22, 2005
1,876
2
166
Part 3 of 3 : Putting it all together

Putting it all together in a way that can be used by your local PHP scripts
I've mentioned a few bits and pieces and those with the time to disect it all will be able to stop here. However nothing works better than a good fully-featured example. Examples can be copied and seen to work and then modified for the task at hand.

Our example uses three scripts:

1. the root script, i.e. the one that runs as root as does the things you otherwise can't
2. the calling script, i.e. Accounting.php.inc, the one that gets the root script to do something
3. the controlling script, i.e. the one that forms part of your website and which uses Accounting.php.inc to do things

Let's see all these three scripts, including their locations and how they're used.

1. the root script

Location:
/usr/local/cpanel/whostmgr/docroot/3rdparty/example/example.php

Ownership/permissions:
/usr/local/cpanel/whostmgr/docroot/3rdparty/example - root:root 0755
/usr/local/cpanel/whostmgr/docroot/3rdparty/example/example.php - root:root 0644

Contents:
PHP:
<?php
  // Limit access to 'root'
  if ($_SERVER['REMOTE_USER'] != "root") {
    echo "You are not root";
    exit();
  }
	
  // Try and open /etc/localdomains
  $fp = @fopen("/etc/localdomains", "r");
  if (!fp) {
    echo "Failed to read /etc/localdomains";
    exit();
  }
	
  // Read /etc/localdomains
  $s_localdomains = fread($fp, filesize("/etc/localdomains"));
  fclose($fp);
	
  // Output content for the benefit of the calling script
  echo $s_localdomains;
  
  // Exit for neatness
  exit();
?>

2. the calling script

Location:
/home/example/lib/whm.php

Ownership/permissions:
example:example 0644

i.e. standard 'normal user' ownership for phpsuexec environments and pretty standard permissions
Uploading the script normally via a standard FTP client should give you the desired ownwership and permissions, so there's no need to specifically set these.

Contents:
PHP:
<?php
  // Include dependencies
  require_once('accesshash.php');      // Contains definitions and values for $host, $user, $accesshash and $usessl
  require_once('Accounting.inc.php');  // This would be a local copy, but the original could also be used
	
  function whmListDomains($host,$user,$accesshash,$usessl) {
    // Get list of domains in /etc/localdomains
      $s_domains = whmreq("/3rdparty/webignition/listdomains.php",$host,$user,$accesshash,$usessl);
      if ($cpanelaccterr != "") {
        return;
      }
		
      // Get domains into an array and trim
      return explode("\n", trim($s_domains));
  }
?>
Notes:
In my implementation, I renamed $host and $user to $whm_host and $whm_user. With a variable named $user, I encountered problems when calling the above function from the controlling script. Whatever the reason, it was messing around with the PHP session I was using to store the data of the user of my site who was logged in and doing things. I then renamed $host to $whm_host for consistency, although a variable named $host presented no technical issues.

3. the controlling script

Location:
/home/example/public_html/controller.php

Ownership/permissions:
example:example 0644

i.e. standard 'normal user' ownership for phpsuexec environments and pretty standard permissions
Uploading the script normally via a standard FTP client should give you the desired ownwership and permissions, so there's no need to specifically set these.

Contents:
PHP:
<?php
  // Include dependencies
  require_once('whm.php');

  function accountDomainTaken($s_domain)
  {
    // Check for domains on the server
    global $host;
    global $user;
    global $accesshash;
    global $usessl;
 
    $a_domains = whmListDomains($host,$user,$accesshash,$usessl);
		
    return in_array($s_domain, $a_domains);
  }

  /*
    Include plenty of code here for generating a string containing the representation of a domain name.
    In reality, this would most likely come from user input and should be severly sanitised!
  /*

  $domain = "example.com";

  if (accountDomainTaken($domain)) {
    echo "The domain name ".$domain." is already in use on this server";
  } else {
    echo "The domain name ".$domain." is free to use";
  }
?>
A brief summary of the above process

1. A user, through a web interface, enters a domain name and the data is passed to the controlling script
2. The controlling script makes use of the calling script and uses the calling script's function to check if a domain name is taken
3. The calling script uses remote access features to call the root script that retrieves the useful data from a file otherwise inaccessible to the user 'example'

Omissions and errors
The above PHP coding is a severly butchered-down form of what I am actually using. Through removing the forest to reveal only a couple of trees, I may have pruned the odd branch off that should not have been.

Therefore don't take the above PHP code as absolute as it may not be. It's just an example. Copy the idea, not the code.

That said, I'll correct my own typing errors if people are kind enough to point them out.

Conclusions (what have actually done here?)
The above ideas, presented in the rough chronological order in which they were developed, should hopefully have demonstrated how we can solve the problem defined at the beginning.

A certain level of prior knowledge is required to build such scripts securely, and I'll leave the curiosities of this to you. The root script does include the line if ($_SERVER['REMOTE_USER'] != "root") only as a means to demonstrate that some form of security is required. This level of security is for example purposes and minimal at best. Please make it better for your needs.

Regardless of the level of PHP knowledge required, the principles should be clearly explained so that you should at least have an understanding of how the process runs even if you don't fully understand what the PHP scripts themselves do. The principle is the good thing, the code is not. Once you have the principle, the code will follow.

What do if it doesn't work for you
Please please please don't simply try and PM me or even post for help. Sit down, look through what you've done and use whatever debugging process you prefer to find the cause of the problem. You won't learn anything if you are spoon-fed.

Some problems may arise due to inaccuracies in this HOW TO. However, the code may be inaccurate but the principle is sound. Again, follow the principle and not the code.

Nevertheless if you do have problems and can't figure out why, please don't PM me for help. I simply don't have time! Please do post problems and if I have the time I'll see what I can make of them, as will others.
 

lamp

Well-Known Member
Dec 22, 2003
111
0
166
Thanks for the HOW TO.

Only one thing... the following doesn't seem to work:

Try browsing to https://hostname:2087/3rdparty/example/example.php. Assuming that /etc/localdomains exists, you should only encounter the output "Failed to open /etc/localdomains" if you don't have permission to access /etc/localdomains (assuming that PHP is otherwise working as it should). You should get the other string output and should see that our example script has the power to do useful things.
My setup:

ls -al /etc/ | grep localdomains
-rw-r--r-- 1 root root 90377 Nov 10 15:07 localdomains

l[/usr/local/cpanel/whostmgr/docroot/3rdparty]# s -al | grep example
drwxr-xr-x 2 root root 4096 Nov 11 12:35 example/

[/usr/local/cpanel/whostmgr/docroot/3rdparty/example]# ls -al | grep example.php
-rw-r--r-- 1 root root 189 Nov 11 12:24 example.php

Point browser to https://WHM/3rdparty/example/example.php
Result: Failed to open /etc/localdomains

Do I have to "tweak" some security settings or do anything fancy to php?

Your thoughts are appreciated.

Lamp
 

webignition

Well-Known Member
Jan 22, 2005
1,876
2
166
lamp said:
ls -al /etc/ | grep localdomains
-rw-r--r-- 1 root root 90377 Nov 10 15:07 localdomains
I'm not going to claim I'm an expert, but those permissions look fine to me. Slightly different from mine, but still fine.

The example.php that you created should be running as root. And as long as /etc/localdomains is owned by root and has sufficient privileges for the owner to read it, it should work.

Therefore my only conclusion would be that, for some odd reason, your example.php isn't running as root.

For me, all scripts in a similar location run as root and so I assumed this would be the case across the board. Perhaps it isn't?

I guess you could create a more complex script that takes longer to execute so that, whilst executing, you could run ps -aux and see if it indeed does run as root and then take things from there.

Does that make sense?
 

lamp

Well-Known Member
Dec 22, 2003
111
0
166
webignition said:
Therefore my only conclusion would be that, for some odd reason, your example.php isn't running as root.
Hi webignition,

I followed your advice and modified the php script to sleep for 20 seconds before trying to open /etc/localdomains. While the script slept, I did a ps -aux and found the following:

root 9680 0.2 0.1 9048 3332 ? S 14:01 0:00 /usr/local/cpanel/3rdparty/bin/php /usr/local/cpanel/whostmgr/docroot/3rdparty/example/example.php

As such, the script is running as root... but it cannot open the /etc/localdomains file for some reason. Maybe the permissions on the /etc/localdomains file are not correct?

I'll dig deeper and maybe a light bulb will come on... Strange.

Thanks again for the HOW-TO.

Lamp
 

lamp

Well-Known Member
Dec 22, 2003
111
0
166
Webignition,

I think you forgot to add "r" to fopen in the 2nd part of your post...

It should read:

$fp = @fopen("/etc/localdomains", "r");

You wrote it correctly in the last part.

Lamp
 

webignition

Well-Known Member
Jan 22, 2005
1,876
2
166
lamp said:
Found the problem!

Mistyped my php file.

Lamp
Glad to hear it was something straightforward.

I recall that you were originally looking to get a method to read /etc/shadow to for an authentication script. Hopefully if you can read /etc/localdomains as shown in this example then you should be able to do the same for /etc/shadow.

Let me know how it goes.
 

webignition

Well-Known Member
Jan 22, 2005
1,876
2
166
lamp said:
Webignition,

I think you forgot to add "r" to fopen in the 2nd part of your post...

It should read:

$fp = @fopen("/etc/localdomains", "r");

You wrote it correctly in the last part.

Lamp
Thanks for pointing that out. I've corrected that in the post, commented out the old line and added a comment saying what was changed so that this post makes sense.
 

lamp

Well-Known Member
Dec 22, 2003
111
0
166
webignition said:
Glad to hear it was something straightforward.

I recall that you were originally looking to get a method to read /etc/shadow to for an authentication script. Hopefully if you can read /etc/localdomains as shown in this example then you should be able to do the same for /etc/shadow.

Let me know how it goes.

Works like a charm.

I implemented password verification program first in perl program and now in java.

Thanks for all of your help... you were the missing piece of the puzzle!

Lamp
 

wzd

Well-Known Member
Dec 16, 2005
120
1
168
South Africa
cPanel Access Level
Root Administrator
Am i off the scent?

I havent had a chance to sift through everything but could someone elaborate why we are trying to gain remote access for non-priviledged users? If this is to provide accounting functions such as new user registrations, remote password modifications, billing purposes etc...

http://www.whmautopilot.com/
http://www.whmxtra.com/

These two items seem to do the job quite well. Not very costly but will save a lot of blood and time and effort to develop everything from scratch.

Let me know if i'm completely off the scent.

Otherwise it looks very interesting, keep up the good work! :)
 

maxstudios

Member
Dec 1, 2003
16
0
151
something that is required.

Hi.

even though i think that addon scripts (that over a preiod of time costs a lot) are most of the time worth while. i also think that us doing something ourselves can produce a more professional and personal approach to a service.

from what i can gather from this how-to is its trying to explain the virtues of creating your own api to intergrate with cpanel. it costs nothing but a little time.. and you have what you want at the end of it..

but i must admit that it would be nice for cpanel to actualy provide a complete api for all their users to create their own control panel or use what is already there

if anyone would like to submit a completed api i for one would be interested in what can be done without spending money
 

webignition

Well-Known Member
Jan 22, 2005
1,876
2
166
wzd said:
I havent had a chance to sift through everything but could someone elaborate why we are trying to gain remote access for non-priviledged users? If this is to provide accounting functions such as new user registrations, remote password modifications, billing purposes etc...

http://www.whmautopilot.com/
http://www.whmxtra.com/

These two items seem to do the job quite well. Not very costly but will save a lot of blood and time and effort to develop everything from scratch.

Let me know if i'm completely off the scent.

Otherwise it looks very interesting, keep up the good work! :)
No, you're not off the scent there.

The entire purpose of this how-to is to explain how you can effectively perform any server-side task through a web interface running under a non-privileged user (what a wordy sentence!).

To say that there are some pretty decent tools already out there would be a grave understatement, however they may not always be best for everyone.

I started on the task merely because I wanted to increase the usability of my sign up process through be able to tell the user when a chosen domain or username is already in use.

Once I got to the bottom of this I realised that the same technique could be applied to doing anything and thought that some other people might find it useful too. So far I know that at least one person has, and hopefully more.