PublicAPI does not work in custom API2 function

ruyrocha

Member
Oct 12, 2010
24
0
51
Brasil
Hello team,

I'm using PublicAPI to retrieve user home:

Code:
# cat ohai.pl 
#!/usr/bin/perl

BEGIN{ unshift @INC, "/usr/local/cpanel" }

use Cpanel::PublicAPI ();

my $pubapi = Cpanel::PublicAPI->new();
my $res = $pubapi->whm_api('domainuserdata', "domain=dummy.ruyrocha.com");

print "OHAI - $res->{'userdata'}->{'homedir'}\n";


root@opsbox [~/z]# perl ohai.pl 
OHAI - /home/dummy
root@opsbox [~]# cd
root@opsbox [~]# perl -V | tail -n 8
  Compiled at Jan 27 2012 13:26:43
  @INC:
    /usr/local/lib/perl5/5.8.8/x86_64-linux
    /usr/local/lib/perl5/5.8.8
    /usr/local/lib/perl5/site_perl/5.8.8/x86_64-linux
    /usr/local/lib/perl5/site_perl/5.8.8
    /usr/local/lib/perl5/site_perl
    .
So I wrote a custom module - called BlaBla - which has only one function (xxx) to retrieve user home:

Code:
root@opsbox [/usr/local/cpanel/Cpanel]# cat BlaBla.pm 
package Cpanel::BlaBla;

use Cpanel::PublicAPI ();

sub api2_xxx {
  my %OPTS = @_;

  my $domain = $OPTS{'domain'};

  my $pubapi = Cpanel::PublicAPI->new();
  my $res = $pubapi->whm_api('domainuserdata', "domain=$domain");

  my $homedir = $res->{'userdata'}->{'homedir'};

  return $homedir;
}

sub api2 {
  my $func = shift;

  $API{'xxx'}{'func'}          = 'api2_xxx';
  $API{'xxx'}{'engine'}        = 'hasharray';

  return $API{$func};
}

1;
This module doest not work - function xxx is not found:

Code:
perseverance ~/dev/php $ php -f wd-blabla.php 
URL: http://10.1.1.90:2082/xml-api/cpanel
DATA: domain=dummy.ruyrocha.com&cpanel_xmlapi_user=hgdummy&cpanel_xmlapi_module=BlaBla&cpanel_xmlapi_func=xxx&cpanel_xmlapi_apiversion=2
Authentication Header: Authorization: Basic aGdkdW1teTpGNENad2tYQmpydTM3WXhVUWlRVDl4dXQ=


RESPONSE:
 <?xml version="1.0" ?>
  <cpanelresult>
    <apiversion>2</apiversion>
    <error>Could not find function 'xxx' in module 'BlaBla'</error>
    <func>xxx</func>
    <module>BlaBla</module>
  </cpanelresult>

SimpleXML var_dump:
SimpleXMLElement Object
(
    [apiversion] => 2
    [error] => Could not find function 'xxx' in module 'BlaBla'
    [func] => xxx
    [module] => BlaBla
)
Seems PublicAPI uses Perl 5.6.2 (!?!?!) and cannot find MIME::Base64:

Code:
root@opsbox [/usr/local/cpanel/Cpanel]# tail -f ../logs/error_log 
	Cpanel::LoadModule::loadmodule('BlaBla') called at /usr/local/cpanel/Cpanel/Api2/Exec.pm line 33
	Cpanel::Api2::Exec::api2_preexec('BlaBla', 'xxx') called at cpanel line 880
	main::docpanelaction('HASH(0xfed9920)') called at cpanel line 4921
	main::run_fast_xml_mode() called at cpanel line 409


[2012-01-31 13:46:02 -0200] warn [cpanel] API2: xxx is missing from Cpanel::BlaBla's api2 function.  Perhaps you misspelled xxx. at /usr/local/cpanel/Cpanel/Api2/Exec.pm line 38
	Cpanel::Api2::Exec::api2_preexec('BlaBla', 'xxx') called at cpanel line 880
	main::docpanelaction(HASH(0xfed9920)) called at cpanel line 4921
	main::run_fast_xml_mode() called at cpanel line 409
[2012-01-31 13:48:15 -0200] die [Internal Death while parsing  30973] Can't locate MIME/Base64.pm in @INC (@INC contains: /usr/local/cpanel/perl/_diet /usr/local/cpanel /usr/local/cpanel/perl /usr/local/cpanel/Cpanel/CPAN/overload/__Digest /usr/local/cpanel/Cpanel/CPAN/overload/__Time /usr/local/cpanel/build-tools/stubs /usr/local/cpanel/perl/C220 /usr/local/cpanel/perl/5.6.2/x86_64-linux /usr/local/cpanel/perl/5.6.2/x86_64-linux /usr/local/cpanel/perl/5.6.2 /usr/local/cpanel/perl/site_perl/5.6.2/x86_64-linux /usr/local/cpanel/perl/site_perl/5.6.2/x86_64-linux /usr/local/cpanel/perl/site_perl/5.6.2 /usr/local/cpanel/perl/site_perl/5.6.2/x86_64-linux /usr/local/cpanel/perl/site_perl/5.6.2 /usr/local/cpanel/perl/site_perl . /usr/local/cpanel/perl/5.6.2/x86_64-linux /usr/local/cpanel/perl/5.6.2 /usr/local/cpanel/perl/site_perl/5.6.2/x86_64-linux /usr/local/cpanel/perl/site_perl/5.6.2 /usr/local/cpanel/perl/site_perl .) at /usr/local/cpanel/Cpanel/PublicAPI.pm line 34.
	Cpanel::PublicAPI::BEGIN() called at MIME/Base64.pm line 34
	eval {...} called at MIME/Base64.pm line 34
	require Cpanel/PublicAPI.pm called at /usr/local/cpanel/Cpanel/BlaBla.pm line 3
	Cpanel::BlaBla::BEGIN() called at MIME/Base64.pm line 34
	eval {...} called at MIME/Base64.pm line 34
	require Cpanel/BlaBla.pm called at (eval 4) line 1
	eval ' require Cpanel::BlaBla; Cpanel::BlaBla::BlaBla_init() if UNIVERSAL::can( "Cpanel::BlaBla", "BlaBla_init" ); 
;' called at /usr/local/cpanel/Cpanel/LoadModule.pm line 20
	Cpanel::LoadModule::_modloader('BlaBla') called at /usr/local/cpanel/Cpanel/LoadModule.pm line 24
	Cpanel::LoadModule::loadmodule('BlaBla') called at /usr/local/cpanel/Cpanel/Api2/Exec.pm line 33
	Cpanel::Api2::Exec::api2_preexec('BlaBla', 'xxx') called at cpanel line 880
	main::docpanelaction('HASH(0x1085a680)') called at cpanel line 4921
	main::run_fast_xml_mode() called at cpanel line 409


[2012-01-31 13:48:15 -0200] die [Internal Death while parsing  30973] Can't locate MIME/Base64.pm in @INC (@INC contains: /usr/local/cpanel/perl/_diet /usr/local/cpanel /usr/local/cpanel/perl /usr/local/cpanel/Cpanel/CPAN/overload/__Digest /usr/local/cpanel/Cpanel/CPAN/overload/__Time /usr/local/cpanel/build-tools/stubs /usr/local/cpanel/perl/C220 /usr/local/cpanel/perl/5.6.2/x86_64-linux /usr/local/cpanel/perl/5.6.2/x86_64-linux /usr/local/cpanel/perl/5.6.2 /usr/local/cpanel/perl/site_perl/5.6.2/x86_64-linux /usr/local/cpanel/perl/site_perl/5.6.2/x86_64-linux /usr/local/cpanel/perl/site_perl/5.6.2 /usr/local/cpanel/perl/site_perl/5.6.2/x86_64-linux /usr/local/cpanel/perl/site_perl/5.6.2 /usr/local/cpanel/perl/site_perl . /usr/local/cpanel/perl/5.6.2/x86_64-linux /usr/local/cpanel/perl/5.6.2 /usr/local/cpanel/perl/site_perl/5.6.2/x86_64-linux /usr/local/cpanel/perl/site_perl/5.6.2 /usr/local/cpanel/perl/site_perl .) at /usr/local/cpanel/Cpanel/PublicAPI.pm line 34.
BEGIN failed--compilation aborted at /usr/local/cpanel/Cpanel/PublicAPI.pm line 34.
Compilation failed in require at /usr/local/cpanel/Cpanel/BlaBla.pm line 3.
	Cpanel::BlaBla::BEGIN() called at /usr/local/cpanel/Cpanel/PublicAPI.pm line 3
	eval {...} called at /usr/local/cpanel/Cpanel/PublicAPI.pm line 3
	require Cpanel/BlaBla.pm called at (eval 4) line 1
	eval ' require Cpanel::BlaBla; Cpanel::BlaBla::BlaBla_init() if UNIVERSAL::can( "Cpanel::BlaBla", "BlaBla_init" ); 
;' called at /usr/local/cpanel/Cpanel/LoadModule.pm line 20
	Cpanel::LoadModule::_modloader('BlaBla') called at /usr/local/cpanel/Cpanel/LoadModule.pm line 24
	Cpanel::LoadModule::loadmodule('BlaBla') called at /usr/local/cpanel/Cpanel/Api2/Exec.pm line 33
	Cpanel::Api2::Exec::api2_preexec('BlaBla', 'xxx') called at cpanel line 880
	main::docpanelaction('HASH(0x1085a680)') called at cpanel line 4921
	main::run_fast_xml_mode() called at cpanel line 409


[2012-01-31 13:48:15 -0200] warn [cpanel] API2: xxx is missing from Cpanel::BlaBla's api2 function.  Perhaps you misspelled xxx. at /usr/local/cpanel/Cpanel/Api2/Exec.pm line 38
	Cpanel::Api2::Exec::api2_preexec('BlaBla', 'xxx') called at cpanel line 880
	main::docpanelaction(HASH(0x1085a680)) called at cpanel line 4921
	main::run_fast_xml_mode() called at cpanel line 409
I think it's not "dry" but how can I use PublicAPI in any other custom module?
 

cPanelDavidN

Well-Known Member
Staff member
Dec 17, 2009
571
3
68
Houston, TX
cPanel Access Level
Root Administrator
Hi ruyrocha,

There are a couple of things going on:
  1. API2 module/functions will execute as the 'proxied' user; in your case 'hgdummy' (cpanel_xmlapi_user=hgdummy). All cPanel API calls are run as the cPanel user, even when performed via the Remote API (XML/JSON API). When used in a Remote API call, the 'proxied' user is defined by the value passed in the parameter 'cpanel_xmlapi_user'; When in a cPanel interface, it will be the authenticated user (or the child account selected in the drop down box if you're logged in as root or a reseller). This is different than your example `ohai.pl` which will utilize a remote access hash (most likely root's) if you don't specify credentials.
  2. The 'domainuserdata' function is native to the Remote API and is an administrative call. It is not a cPanel API call, and thus requires root or a privileged reseller (e.g. the reseller would need the "all root" privilege), hence the need to invoke the whm_api() method of PublicAPI (it queries WHM, and not cPanel).
  3. Once authenticated (or masquerading) as the cPanel user, you should have ready access to most userdata (and likely don't need to make further calls within a custom API call; see example below) since a lot of it will be within variables that are in scope.
  4. If all you need is the homedir (or other like userdata that can be found in an ExpVar), you can use the xmlapi_query() method the xmlapi.php class to make a special call that utilizes a special cPanel API1 module named 'print'. And thus, retrieve this info (from what is normally an internal process variable, similar to the previous point above; see example below).
  5. In order for to arbitrarily use PublicAPI (and make native Remote API calls, not cPanel API calls) in a cPanel API module, you will need access to root or reseller credentials. This introduces some security concerns. Namely, you will need a way to securely escalate to root and perform the actual work as root. To avoid an overly lengthy post (on a completely different topic), I'm not doing that here...instead, FOR EXAMPLE PURPOSES ONLY, I've copied my root access hash into my test cPanel user account and chown'd it for him. This allows me to read the .accesshash and build my PublicAPI object with root credentials. My custom API2 function 'zzz' does this.
  6. Why you wouldn't have Base64.pm in your Perl @INC, I cannot say. That module should be compiled available the Perl that is executing the custom API module.

Example: Custom API2 Module
Code:
package Cpanel::BlaBla;

use strict;
use warnings;

use Cpanel::PublicAPI ();

sub api2_xxx {
    my %OPTS = @_;

    my $homedir = $Cpanel::homedir;

    return { 'homedir' => $homedir };
}

sub api2_zzz {
    my $user            = 'root';
    my $accesshash_file = $Cpanel::homedir . '/.accesshash';
    my $accesshash      = do { local ($/) = $accesshash_file; <> };
    my $domain          = $Cpanel::CPDATA{'DNS'};

    # NOTE: when constructing a PublicAPI object, you should probably provide explicit
    #  user credentials; in this case I've copied root's accesshash to the user's homedir
    #  and chown'd it for the user.  THIS IS A BAD IDEA, but works for example purposes.
    #
    # SO PLEASE DO NOT DO THIS IN THE REAL WORLD!
    #  A compromised root accesshash could lead to a world of hurt.
    #
    # The real solution would be to execute a privilege escalation script in order
    #  to escalate to root and do the logic as root (on behalf of a specific user)
    #  and return the data to this caller. 
    #  http://docs.cpanel.net/twiki/bin/view/SoftwareDevelopmentKit/PrivilegeEscalation
    #  This would be the only safe way to get the root credentials necessary to make the
    #  Remote API call (that the PublicAPI class is performing for you).

    my $cp  = Cpanel::PublicAPI->new( 'user' => $user, 'accesshash' => $accesshash );
    my $res = $cp->whm_api( 'domainuserdata', { 'domain' => $domain } );

    return { 'userdata' => $res->{'userdata'} };
}

sub api2 {
    my $func = shift;
    my %API;

    $API{'xxx'}{'func'}   = 'api2_xxx';
    $API{'xxx'}{'engine'} = 'hasharray';
    $API{'zzz'}{'func'}   = 'api2_zzz';
    $API{'zzz'}{'engine'} = 'hasharray';

    return \%{ $API{$func} };
}

1;
Example: Use the special 'print' module via xmlapi.php
PHP:
<?php
include "xmlapi.php";

$ip = "10.1.1.90";
$port = 2083;  # I HIGHLY recommend you use port 2083 over 2082 for security

$auth_user = 'user_you_auth_with'; // likely the cPanel user since you on 2082/2083
$pass ='somesecret';  // the password for $auth_user

// $user may be the same as $auth_user; if not, make sure $auser has
//  authority over this person
$user = 'cpanel_user_in_question';

$xmlapi = new xmlapi($ip);
$xmlapi->password_auth($auth_user,$pass);
$xmlapi->set_port($port);

$xmlapi->set_debug(1);

$function = 'cpanel';
$args = array(
    'cpanel_xmlapi_user'       => $user,
    'cpanel_xmlapi_module'     => 'print',
    'cpanel_xmlapi_apiversion' => '1',
    'arg-0'                    => '$homedir', // yes, a literal string containing a dollar sign
);

$xmlapi->xmlapi_query($function, $args);
?>
Regards,
-DavidN
 

ruyrocha

Member
Oct 12, 2010
24
0
51
Brasil
Hello David,

Thank you for your time. Answering question #6 - when I call PublicAPI on any custom module, it uses cPanel's perl (5.6.2, not system perl 5.8.8), and cannot find MIME::Base64. So if I touch anything on /usr/local/cpanel and correct MIME::Base64, I think it will be broken on others servers.

Given domain name notavailable.com which could be main/addon/parked domain, what is "the better cPanel way" to retrieve only user home directory? This task can be done in many different ways and I found calling Domainuserdata function in PublicAPI the easiest one.

I'm using privilege escalation already in other module (functions available for only one account). I'm not a real Perl programmer, so let me try to explain... I'll create function yyy on module "BlaBla", which takes domain name as argument and create function GETHOME on blablaadmin script then execute homedir.pl (which uses PublicAPI and is a pure Perl script) to retrieve only home directory for this domain - it's just an example, will be ugly but can achieve this task.
 

KostonConsulting

Well-Known Member
Verifed Vendor
Jun 17, 2010
255
1
68
San Francisco, CA
cPanel Access Level
Root Administrator
Hello David,

Thank you for your time. Answering question #6 - when I call PublicAPI on any custom module, it uses cPanel's perl (5.6.2, not system perl 5.8.8), and cannot find MIME::Base64. So if I touch anything on /usr/local/cpanel and correct MIME::Base64, I think it will be broken on others servers.

Given domain name notavailable.com which could be main/addon/parked domain, what is "the better cPanel way" to retrieve only user home directory? This task can be done in many different ways and I found calling Domainuserdata function in PublicAPI the easiest one.

I'm using privilege escalation already in other module (functions available for only one account). I'm not a real Perl programmer, so let me try to explain... I'll create function yyy on module "BlaBla", which takes domain name as argument and create function GETHOME on blablaadmin script then execute homedir.pl (which uses PublicAPI and is a pure Perl script) to retrieve only home directory for this domain - it's just an example, will be ugly but can achieve this task.

MIME::Base64 should be available under cPanel's internal Perl at /usr/local/cpanel/perl/MIME/Base64.


It would be helpful to see the specific error about MIME::Base64 not existing to see your @INC. Add 'use warnings;' to your script to see the full error and please paste it here.

What path are you executing the script from?
 

ruyrocha

Member
Oct 12, 2010
24
0
51
Brasil
In order to fix this "issue" with PublicAPI, I needed to copy these files:

root@opsbox [/usr/local/cpanel/perl]# cp /usr/local/lib/perl5/5.8.8/x86_64-linux/MIME/Base64.pm MIME/
root@opsbox [/usr/local/cpanel/perl]# cp /usr/local/lib/perl5/site_perl/5.8.8/IO/Socket/SSL.pm IO/Socket/