The Official cPanel Blog

Troubleshooting the XML-API

Bookmark and Share
user-pic

We have had a flurry of integration tickets over the past week, which is awesome to see.  Most of these tickets have come down to a couple of basic problems.  The first of which is due to some confusion about how to utilize HTTP to call  an API function.The second problem appears to stem from a lack of information API troubleshooting tools that come with cPanel/WHM.

The first of our common problems stem from some basic confusion about how to utilize HTTP to call our API functions.  The second of problem seems to come from a lack of information about how to use the tools that are available within cPanel and WHM for troubleshooting these API calls.

So, there are numerous things that need to be understood about using HTTP when working with our XML-API.  The first point that needs to be made is that the GET HTTP call is limited to 2048 characters.  The RFC itself does not specify any maximum size for a GET request; however, as this limitation has been imposed by browser manufacturers, HTTP servers and clients have implemented it as well.  This limit affects XML-API by limiting the number of characters that in can be passed via GET.  If the number of characters exceeds 2048 (think of SSL certificates), the string will be truncated and the call will fail.  This is why it is very important to always use POST with the XML API.

The second issue that I’ve been noticing is that people don’t encode the data that they are passing to the XML-API.  In most cases, this is fine; however, I’ve had quite a few tickets where the developer  is having issues with this.  When passing something like an email account over HTTP via GET, it will have user@domain.com inside of the HTTP request.  This is an issue because the @ symbol is reserved for specific uses, namely redirects or authentication.  If you are using an HTTP client that does not automatically encode GET requests, the URL will be truncated.  If you are using PHP for your interactions, the built-in url_encode function works really well for this.  When developing in perl the URI::Escape module works quite well.

When you run across issues with the XML-API, there are a few ways of troubleshooting them.  The first is the most obvious:  look at the error_log (located at /usr/local/cpanel/logs/error_log).  If there is an error with the call itself, it should show up in this log.  Of course, this won’t help much with errors inside of your application.  For troubleshooting those, the easiest log to look at is the access_log at /usr/local/cpanel/logs/access_log.  This will show every URL called within cPanel using a format similar to Apache’s ‘combined’ log style. 

A completely undocumented log that is really useful is the “log-http-requests” cpanel.config option.  To enable this option:

Open /var/cpanel/cpanel.config in your preferred text editor.
Add the following line to the file:
log-http-requests=1
Restart cPanel:
/etc/init.d/cpanel restart

After doing this, if you run tail-f /usr/local/cpanel/logs/incoming_http_requests.log you will see something like
]15421][10/29/2009:17:14:21 -0000][getreq 0]: [POST /xml-api/addip HTTP/1.1
[15421][10/29/2009:17:14:21 -0000][headerparser 0]:Host: 127.0.0.1:2087
[15421][10/29/2009:17:14:21 -0000][headerparser 0]:Accept: */*
[15421][10/29/2009:17:14:21 -0000][headerparser 0]:Authorization: Basic *censored*
[15421][10/29/2009:17:14:21 -0000][headerparser 0]:Content-Type: application/x-www-form-urlencoded
[15421][10/29/2009:17:14:21 -0000][headerparser 0]:Content-Length: 34
[15421][10/29/2009:17:14:21 -0000][killconnection]
[15421][10/29/2009:17:14:21 -0000][killconnection exit]

This allows you to ensure that the proper data is being sent to the server.

Another cool feature that we’ve added in newer builds of cPanel 11.25 (build # 41121 and higher) is the ability to log POSTDATA sent to the server.  To enable this simply add log-http-requests-postdata=1 to /var/cpanel/cpanel.config and restart the service.  This will add another line of output to the incoming_http_requests.log file displaying the POSTDATA passed to the server.

If you really run into a lot of trouble with the XML-API, I suggest using the XML-API PHP class or working with the API.  This class does encoding, POST requests, and even provides some useful debugging information.

Spotlight On: The cPanel Interface - Icons, Groups and Variables

Bookmark and Share
user-pic

This past week we have been working on documenting how to add icons and groups to cPanel without using our plugin generator.  Writing DynamicUI files is a direct and flexible approach to adding custom groups, icons and extra logic not available through the plugin generator (boolean ExpVar checks, etc).  During the course of documenting this, we realized that we also needed to document the variables available within the cPanel interface.  These are referred to as ExpVar variables.  These variable expressions internally parse the given reference and return the appropriate module output or dataset value. Example returns from ExpVar variables include information like configuration information, account limits and language tags.  Very handy for creating your own cPanel interface.

Adding Icons and Groups to the cPanel Interface - This document is used for learning how to customize icons & groups within cPanel.  Icons and Groups can be customized on a per-reseller or per-branding package basis.

DynamicUI Reference - This table explains all of the variables available within the DynamicUI system in a handy small reference-chart format.

ExpVar Reference - This table explains all of the variables available within the cPanel interface.  These can be used in DynamicUI keys, inside of &gt;cpanelif&lt; tags and inside of <cpanel print=””> tags.

XML API PHP class version 1.0

Bookmark and Share
user-pic

Some of the reasons why there hasn’t been many updates lately include our cPanel conference, the cPanel/WHM 11.25.0 release, and the release of the XML-API PHP class 1.0.  If you are not familiar with the XML-API PHP class, it makes interacting with the XML-API much more simple by implementing functions that allow you to call your remote cPanel server without ever having to touch cURL or simplexml.  This works by implementing a function for every XML-API call.  For example:


$xmlapi = new xmlapi($ip, “root”, $root_pass);
$zones = $xmlapi->listzones();

The code above will make $zones a simplexml object that contains all of the zones on the server.  This combined with proper data handling (encoding), the ability to return associative arrays, simplexml objects, raw JSON or raw XML makes developing PHP applications for the XML-API rather simple.

The biggest change in this version is the fact that the class is full documented, allowing you to access a quick reference for looking up these functions and how they work.

This version is a complete re-write from the previous version.  The (partial) change log is below:

 

  • Added in 11.25 functions
  • Changed the constructor to allow for either the "DEFINE" config setting method or using parameters
  • Removed used of the gui setting
  • Added fopen support
  • Added auto detection for fopen or curl (uses curl by default)
  • Added ability to return in multiple formats: associative array, simplexml, xml, json
  • Added PHP Documentor documentation for all necessary functions
  • Changed submission from GET to POST
  • Change api1 and api2 query to use XML-API fast mode
  • added authentication failure detection for 11.24
  • add array checking where array parameters are taken in

If you are interested in working with the XML-API PHP Class, you can download it here.  If you want to provide feedback, bugs, or comments you can do so from the developer’s discussion forum

It should be noted that this is not an supported class, but rather released as reference code.  If you need assistance with the class, the forum is the proper place to ask for help.

Spotlight On: WHM Plugins & cPanel PHP

Bookmark and Share
user-pic

If you haven’t noticed, we’ve been working on overhauling our development documentation pretty heavily.  We have a ton of great ways to integrate with cPanel but until recently, no information on how to use them.  Starting this week, every week I will be posting about what new articles we’ve added to this documentation along with why you should know about these features.

Past couple of weeks I personally have been trying to focus on how to extend the cPanel interface.  How to add new interfaces to it and different ways of creating these interfaces.  Specifically I have added two sections; WHM Plugins and cPanel PHP.

WHM Plugins
- These are extension to the cPanel interface and probably about the most straight-forward integration that cPanel provides.  These are just regular CGI scripts with a couple of comments to control how they work

cPanel PHP - In this document we talk about how to use PHP in the cPanel interface along with how PHP and cpanel’s APIs can live together in a single file.  This is also another “simple” form of integration that is worth a read for anyone looking to develop custom applications for their users to use.

Creating cPanel CGI Scripts

Bookmark and Share
user-pic

cPanel and CGI

One of the most flexible ways of working within cPanel is by creating CGI applications.  Of course, this isn’t the most efficient method, however sometimes efficiency isn’t your best bet when you’re in a rush.  You can use a standard CGI script within cPanel, you are limited by our APIs, such as what perl version to use.

First thing to note, is that these CGI scripts  have to be placed in /usr/local/cpanel/base/3rdparty/ with normal CGI permissions (have to be executable by the user, cannot be globally writable, etc).  Once placed here it can be accessed via $IP:2083/3rdparty/scriptnamed.cgi.  If you place them anywhere else within the cPanel document root, they will be offered up for download rather than executable.

When these CGI scripts are executed, they are executed as the user, so limited permissions apply.

What if you want to actually access cPanel’s APIs from within this CGI script?  What do you do?  There are a few different ways of handling this.  First you can use the JSON API if you just want to handle the interaction via javascript (you’re already authenticated, why not?).  The other option is using the Cpanel::XML module to make XML-API calls without the HTTP interface at all.  With the introduction of XML API’s Fast Mode we included a function called cpanel_fast_exec.  This is essentially a perl interface into API1 and API2 that can be accessed outside of cPanel.  The data passed into this is the same as the parameters used inside of fast mode.  For example, if you wanted to get a list of email accounts, you would call the following:

my $xml = Cpanel::XML::cpanel_exec_fast(
        {
                'cpanel_xmlapi_module' => 'Email',
                'cpanel_xmlapi_func' => 'listpopswithdisk',
        }
);

$xml = XMLin($xml);



Now $xml is a hash reference containing all the information returned by the call.

To download the example of this in use, click here. 

WHM Plugins

Bookmark and Share
user-pic

We really have no information available on how to write WHM Plugins.  I have had 3 people ask me in the past 48 hours on how to write them, so I thought I might want to consolidate and post this knowledge.  A WHM plugin is merely a simple CGI application that has a couple of special comments in it to handle how it is displayed.  Any CGI language can be used here, however only perl will allow you access to some special functions that make permission handling much easier.

All WHM plugins must be placed at /usr/local/cpanel/whostmgr/docroot/cgi and must be prefixed with addon_ and end with .cgi.  These must be owned by root:root and be globally readable/executable (755), so don’t store any access credentials in these scripts - have them load from other files that are root-readable only.

As I mentioned earlier, there are a couple of special comments that need to be placed inside of WHM Addons.  The first one of these is the WHMADDON comment which sets how the plugin will be displayed in WHM:

#WHMADDON:appname:Display Name

Where it says appname it should be replaced with the actual file name of the application excluding addon_ and .cgi, so if you have addon_test.cgi, this would contain “test”.  Display Name refers to what well be displayed under the “Plugins” header of WHM.  An example of how a #WHMADDON should look for an application named “Sample Test App” would be:

 

#WHMADDON:test:Sample Test App

ACLs

There are two parts to ACLs with WHM Plugins, that control who can display it and who can access it.  The ACLS comment controls who will see the ACL in the Plugins section of WHM.  Then there is actually enforcing the ACL which is done via the Whostmgr::ACLS perl module.  If you are not familiar with what ACLs mean in the context of WHM, these refer to the permissions that the reseller has to various aspects in WHM such as the ability to create accounts or edit DNS zones.  These are indicated by a string like “list-accts” or “all”, you can view a list of these ACLs in /usr/local/cpanel/Whostmgr/ACLS.pm.  You can see what permissions a reseller has by looking at the /var/cpanel/resellers file.

Using the ACLS comment is pretty straight forward.  You simply add #ACLS:<acl name>, this only controls which users will see the plugin in the WHM Plugins section.  If this is not set it will be viewable by ALL resellers.

#ACLS:list-accts


With this ACL in place, only reseller accounts that have access to the list-accts ACL will be able to view this plugin.

The other type of ACL actually enforces permissions.  When this is not set, any reseller can visit cgi/addon_APPNAME.cgi and execute the application with root permissions, so this is a very critical step in WHM Plugin development. 

Inside of our product, we provide a module for performing this type of check called Whostmgr::ACLS.  This module has various functions relating to how ACLs work, but the only function we are concerned with is the checkacl() function.  This operates by being passed an acl name and returning 1 or 0 depending on whether the user has access to this ACL or not.  So if passed the “all” ACL (which is indicative of “root” access) and a reseller without root access tried to access the addon script, it would return 0.  This module requires some setup, since it is located outside of perl’s normal include path, /usr/local/cpanel has to be added to the include path using “use lib”.  Also this module has a constructor called “init_acls()” that has to be called.  As an example, here’s a chunk of code that will check for the “all” (root) acl and print ‘access denied” if the reseller accessing the script does not have permission to do so.

use lib '/usr/local/cpanel/';

use Whostmgr::ACLS ();

Whostmgr::ACLS::init_acls();



if ( !Whostmgr::ACLS::checkacl( 'list-accts' ) ) {

    print "Access Denied";

    exit;

}


Of course, this will only work when this is run inside of a perl script.  Inside of a PHP script /var/cpanel/resellers will have to be parsed manually.  If someone asks nicely, I may write this for you.

That should cover the basics of writing WHM plugins, there are of course other finer aspects of WHM plugins that can be gone into, however this covers the entire concept.

 

API basics and how to call API1 functions

Bookmark and Share
user-pic

I have been noticing several people challenged with calling cPanel functions via our various ways of hooking into our APIs. Unfortunately, this isn't as cut and dry as just calling a function within a programming language.  Various factors, such as whether the call is being made from within cPanel or from a remote system, affect how this needs to be done.

To help you understand this, I will begin covering these topics in a series of posts on cPanel's various functions and how they work. In this first post of the series, I will discuss the basics of cPanel's APIs, and how to call API1 functions.

The basics

Before we can even begin going over how to call cPanel's API functions, we need to discuss the various API types.

For the most part, cPanel's API is divided into two sub-systems; API1 and API2. The difference between these is in how they are called and how they return data.

API1 will normally print data to the cPanel interface.  This works well when the functions are called via cpanel tags (covered later in this article), but won't return much useful data when called via the XML API, livePHP or CGI scripts. 

API2, on the other hand, is a much more robust system, capable of returning complex data structures that can be parsed into templates contained within a cp tag. API2 calls, as they do actually return data, will always return relevant information when called via the XML API, livePHP or CGI scripts.

One of my favorite features of API2 is that it uses named-based parameters that translate well into URL parameters.



Calling API1 functions

API1 functions can be called via a tag. As previously mentioned, these print data to the cPanel interface. cpanel tags use the following format:


<cpanel Module="function( params )">



So, if you wanted to call Ftp::ftpservername(), which is used to print out which FTP server is being used, it would be called with the following syntax:

<cpanel Ftp="ftpservername()">

Now, you will not want all functions to actually display data. For example, Mysql::adddb is generally something that you do not want printing data, for security reasons. Instead you want to check for error handling.

Any data being printed from this function can be suppressed in the browser via HTML comments, like so:

<!--Module="function()"-->


Sending input to the API1 function

So, in order to pass data to an API function, we will need a way to pull in the data.

Inside of cPanel's HTML parsing, we have access to certain variables. The main variables that you need to be aware of are $FORM and $CPERROR.

$FORM is merely a variable that is populated with either GET or POST data passed to the page from the browser. This variable is how cPanel passes data around from page to page. To access it, you call the $FORM variable the same way that you would call a hash in Perl ($FORM{'element'}).

So, for example, if you had a page called add_mysql_db that was passed the following: 


add_mysql_db.html?db=dbname


It would contain the following:


<cpanel Mysql="addb( $FORM{'db'} )">

The <cpanelif> tag

Along with the cpanel tag, there is also support for some really basic logic within this system, via the <cpanelif> tag. This tag allows for checking of basic boolean logic to see if a variable is set; if true, it will send whatever is contained between the <cpanelif> tags to the browser.  <cpanelif> tags cannot be nested in any way.

To call cpanelif, you will do something like the following:

<cpanelif $VAR{''}>

HTML CODE HERE

</cpanelif>

This is useful when you want to display an error message within cPanel's interface.  These are populated into the $CPERROR{$context} variable. 

Unfortunately, context is defined on the back-end on a per-module basis, so generally $context will be the same as whatever module you are calling.  In the case of our previous MySQL example, if we wanted to check for an error message, we would want to do the following:


    <cpanel Mysql="adddb( $FORM{'db'} )">

    <cpanelif $CPERROR{'mysql'}>

      ERROR: $CPERROR{'mysql'}

    </cpanelif>

Of course, you would want to also want to do !$CPERROR{'mysql'} around any success messages to make sure you don't end up with "ERROR: errormsg This was successful".

These are the basics of how to call API1 via .html files. You can always use the XML API to call these functions as well, but because this is API1, you may not get any useful data.

 

Using WHM remote authentication

Bookmark and Share
user-pic

One thing that I have noticed while working with other people developing software that interacts with WHM’s XML API is that they always use basic HTTP authentication. It is okay to use basic authentication, but it is held to the same security restrictions in place for people using browsers. When working with cPanel in a remote fashion, having to work around these restrictions is unnecessary. Inside of our DNS clustering system, we developed a solution for just this problem called WHM Remote Access Keys or “WHM auth”.

The way that WHM auth works is by passing a key inside of the HTTP headers to cpsrvd (cPanel’s HTTP daemon). Your access key can be accessed and regenerated via Setup Remote Access Keys in WHM, or viewed via the file system at ~/.accesshash. These work both for root and resellers with support for cPanel users coming in the future.

When sending a WHM auth header to WHM, you’ll need to add the following as an HTTP header:


Authorization: WHM $user:$hash

where $hash is your access hash, stripped of all new lines.

When working with this functionality inside of scripts, it’s generally easiest to use an HTTP library for adding these headers. For example, if you wanted to use WHM auth inside of PHP & curl, you would simply add the following to the curl object before query:

$hash = “81a ….. 0af”;		# Set up the Hash
$hash = preg_replace(‘(/r|/n)’, “”, $hash); # Strip newlines from the hash
$auth_header[0] = “Authorization: WHM $username:$hash”; # set up the Header Array
$curl_setopt($curl, CURLOPT_HTTPHEADER, $auth_header); # tell curl to use the header array

Of course, not everyone wants to use PHP for handling remote interactions, and I personally would not feel proper discussing how to authenticate to WHM without talking about Perl and LWP.

As always with Perl, there is more than one way to do it, so we will simply discuss the most simple. When calling LWP’s get function, you simply make the second argument a hash named “Authorization” with a value of something similar to

WHM $user:$hash.
my $access_hash = “81a … 0af”;	# set up the accesshash
$access_hash =~ s/(\n|\r)//g;	# Remove newlines from the accesshash
my $auth_string = “WHM $user:$access_hash”;	# create the authentication string
$response = $lwp->get( $url, Authorization => $auth_string ); # send auth header with req

At this point, you can treat $response like a normal HTTP::Response object.

cPanel 11.25 changes that will affect integrated software

Bookmark and Share
user-pic

Inside of cPanel 11.25, there are numerous new security features being implemented. These changes can break both cPanel plugins and remote management applications (like billing systems) that integrate with cPanel. Luckily, the changes are all optional; however, I would hate to see addons preventing people from enabling new features &mdash; like session tokens, which help prevent XSRF attacks. So, stripped from an email I sent out to third-party developers earlier this week, here are some details regarding these changes.

Security tokens

The first of these changes is the inclusion of security tokens.With  this optional feature of 11.25, URLs will now contain <em>cpsess</em>, which has been put in place to help mitigate XSRF attacks.

Absolute URLs will no longer be allowed; you will need to ensure that you are using relative URLs within your product. For non-browser systems that interact with cPanel/WHM and webmail using Basic HTTP authentication, these security measures may be bypassed by ensuring that no session cookies are sent with the request. To enable this setting, go to the Tweak Settings screen in WHM and locate this option:

Require security tokens for all interfaces. This will greatly improve the security of cPanel and WHM against XSRF attacks, but may break integration with other systems, login applications, billing software and third party themes.

Click Save.

Source IP check

The second change that you should be aware of is Source IP Check, which is a security question verification interface inside of cPanel.

This feature will prompt users to define questions on their first login after enabling the feature (which is set to Off by default). Then, users will be prompted to answer the security questions on future logins from new IP addresses.

To configure this, go to $ip:2087/securitypolicy_enable and check the "Limit logins to verified IP addresses" option. It is important to note that this will affect XML API requests unless the Apply security policies to XML-API requests option is disabled.

This change should mostly affect XML API users. To solve other problems, we have modified the Source IP Check feature so that we enable the last login IP when the feature is enabled and no white list is found. This should reduce user annoyance a bit, and it prevents every frame from showing the security questions screen.

Blank referer checks

There are also some changes to the way cPanel handles blank referer checks, which make them more accurate. Basically, if a page is sent with a blank referer inside of an existing session, it will trigger an XSRF prevention page. Yet again, this is not affected inside of sessions that do not use cookies and are authenticated via either HTTP auth or WHM auth.

Testing and support

In order to test your software with these new features, you will need to update your software to the latest beta by editing /etc/cpudpate.conf and changing the CPANEL= line to read CPANEL=beta. These changes should also be available within the EDGE tree by 8/10/09.

 

Writing an FTP password trap in Perl

Bookmark and Share
user-pic

Password traps are probably the type of plugin that generates the most support requests at the moment. When creating a password trap, it is important that you use a Function Hook rather than a Custom Event Handler. The reason for this is that function hooks are executed as root, while custom event handlers are executed as the user. If you are not familiar with function hooks, please read the Documentation.

Function hooks reside as scripts in the subdirectories of /usr/local/cpanel/hooks/. Inside of the hooks/ directory are subdirectories for each module of cPanel, such as ftp/ or email/. When an API call is made, the corresponding module's subdirectory is checked for a script. If one exists, the script is passed XML data that contains both the parameters passed to the API call and information about the user. The XML data looks something like this:

	<cpanelevent>
<errors></errors>
<event>FUNCTION NAME</event>
<module>MODULE NAME</module>
<params>
<param0></param0>
<param1></param1>
<param2></param2>
...
</params>
</cpanelevent>
<CPDATA>
<BWLIMIT>unlimited</BWLIMIT>
.. Data from /var/cpanel/users/USERNAME
<USER>cptest</USER>
</CPDATA>


As you can see from the example, each piece of information is held between tags called containers. In this example, the container merely correlates to data contained within the /var/cpanel/users/ directory. The parameters used to call the function are contained within the container.

In order to read this data in in Perl, we will need to have the following bit of code:

my $xml; 
while()
{
$xml .= $_;
}


This piece of code will assign data to the $xml variable, reading one line at a time. Once the data is contained within the variable, we'll need to transform it into a hash reference using XML::Simple's XMLin function.

my $call_info = XMLin($xml);

Once the information you need has been transformed into a hash, you can access the parameters like any normal hash. In the case of cpanelevent, it looks like the following:

{
'params' => {
'param5' => 'public_html/wierdo',
'param4' => {},
'param0' => 'wierdo',
'param2' => 'public_html/wierdo',
'param3' => 'unlimited',
'param1' => 'Tm$qgIAwrH4A'
},
'errors' => {},
'event' => 'addftp',
'module' => 'ftp'
}


As you can see from the example, each parameter is named using the "param" prefix. These parameters will show up on any API1 call. To make sorting this information easier, we will need to define what information we want to save into a couple of scalar variables. For example:

my $system_user = $api_call->{'CPDATA'}->{'USER'}; 
my $ftp_user = $api_call->{'cpanelevent'}->{'params'}->{'param0'};
my $ftp_password = $api_call->{'cpanelevent'}->{'params'}->{'param1'};


At this point, we've worked out how to access all that data that we need. Now, we'll need to figure out where to log it. For the purposes of this article, we'll use
YAML::Syck. YAML is fast, extremely lightweight, and YAML::Syck can already be found on every cPanel system. But before we can really begin working with YAML::Syck, we'll need to work on our data structure. The following example should work well:

{
system_user => {
ftp_user => ftp_password,
ftp_user2 => ftp_password2
},
system_user2 => {
anotheruser => anotherpassword
}
}

At this point we'll need to load the YAML data from a file (if it exists):

my $datastore = [];
if (-e "/root/ftp_accounts") {
$datastore = LoadFile("/root/ftp_accounts");
}

And then add the data to the hash:

$datastore->{ $system_user }->{ $ftp_user } = $ftp_password; 

At this point, our data structure has been set up and is loading from a file, so all we have to do is save it to the file when it completes adding the data to the hash.

DumpFile("/root/ftp_accounts", $datastore);

Now that the script is finished, we'll simply need to upload it, because this password trap affects cPanel's FTP module, /usr/local/cpanel/hooks/ftp/addftp and /usr/local/cpanel/hooks/ftp/passwdftp. Once this has been done, /root/ftp_accounts will contain a complete list of FTP accounts on the system after they have been created or had their passwords changed.

If you wish to download the information in this tutorial, it is available here.

Mostly the XML API is used for account management; however, there are other features in it that simplify system administration. The most obvious of these functions is the loadavg call.

Last year, I wrote a class for working with the XML API from PHP. This class returns SimpleXML objects for each XML API call made. This makes development of remote cPanel interactions extremely simple.

I'm going to go over how to build a quick-and-dirty multi-server load average monitoring system in PHP using this class.

For this script, we'll store access credentials in an XML file class named monitor.xml. The reason for using XML is so that we can store this data in an easy-to-read/modify format (even programmatically). This is far simpler than using an array of associative arrays for doing this, as access hashes tend to be large.

This is the schema that I have decided upon using:

<monitor>
<server>
<ip>...</ip>
<accesshash>..</accesshash>
<user>..</user>
</server>
<server>
..
</server>
</monitor>

If you are not familiar with access hashes, these correspond to the hash stored in either ~/.accesshash or Setup Remote Access Keys in WHM.

To load this data structure inside of our script, we want to do the following:


$conf = simplexml_load_file("monitor.xml");

Once the configuration of this script has been set in place, we can start on the actual logic of this script, which in this case will consist (mostly) of a loop over all of the servers in the XML.


foreach ( $conf->server as $server ) {
# Logic Here
}

With the configuration and iteration worked out, we will want to include and instantiate the XML API class. This class only takes one parameter in its constructor: the host of the server it's managing.


$conf = simplexml_load_file("monitor.xml");
include("xmlapi.php.inc");
foreach ( $conf->server as $server ) {
$xmlapi = new xmlapi( $server->ip );
}

Next, we will want to set up how to authenticate to the XML API.

Generally speaking, WHM Auth should always be used for any type of automation for numerous reasons, namely because it does not have the same security restrictions as Basic Auth or Cookie Auth. The only reason this shouldn't be done is client-side applications where you may not necessarily know the username and password.

To do this with the XML API PHP class, you should use the hash_auth function, which will set the appropriate headers.


$conf = simplexml_load_file("monitor.xml");
include("xmlapi.php.inc");
foreach ( $conf->server as $server ) {
$xmlapi = new xmlapi( $server->ip );
$xmlapi->hash_auth( $server->user, $server->accesshash);
}

Once this has been set up, we are ready to run whatever commands we need to run. Using this class, we can call loadavg by just calling the loadavg method within the XML API class.


$conf = simplexml_load_file("monitor.xml");
include("xmlapi.php.inc");
foreach ( $conf->server as $server ) {
$xmlapi = new xmlapi( $server->ip );
$xmlapi->hash_auth( $server->user, $server->accesshash);
$loadavg = $xmlapi->loadavg;
}

At this point, $loadavg will contain the loadavg information, similar to what's in /proc/cpuinfo, but in a SimpleXML format. All we have to do now is display this data:


$conf = simplexml_load_file("monitor.xml");
include("xmlapi.php.inc");
foreach ( $conf->server as $server ) {
$xmlapi = new xmlapi( $server->ip );
$xmlapi->hash_auth( $server->user, $server->accesshash);
$loadavg = $xmlapi->loadavg;
print "<br>" . $server->ip . ": " . $loadavg->one . ", " . $loadavg->five . ", " . $loadavg->fifteen;
}

We are ready to run this script. Once executed, you should see something like the following for each server in monitor.xml:


127.0.0.1: 0.00, 0.00, 0.00


Now that this has been written, there is a HUGE security issue within this script that needs to be addressed. The following line is our offending code:


$conf = simplexml_load_file("monitor.xml");

This is an issue, as it is loading monitor.xml from within a document root (for example, $USERHOME/public_html/monitor.xml). This means that anyone could download this file and then authenticate to your server's WHM account. Instead, this file will need to be stored in a secure location outside of the document root, such as ~/monitor.xml.

The other concern with this is that this file should always have permissions of 400, never readable by other users. This script should only be executed as suPHP or off of a shared hosting system, so that it cannot be read by other users on the system.