=encoding UTF-8

=head1 NAME

cPanel::PublicAPI - A perl interface for interacting with cPanel

=head1 SYNOPSIS

  use cPanel::PublicAPI;

  # Auto detect authentication information
  my $cp = cPanel::PublicAPI->new();
  # or specify a user/password
  my $cp = cPanel::PublicAPI->new( 'user' => 'someuser', 'pass' => 'somepass' );
  # or specify an accesshash
  my $cp = cPanel::PublicAPI->new( 'user' => 'someuser', 'accesshash' => $accesshash );
  # or specify an API token
  my $cp = cPanel::PublicAPI->new( 'user' => 'someuser', 'api_token' => $api_token );

  # Perform an xml-api query
  $cp->whm_api('listaccts');
  # Pass parameters to the xml-api
  $cp->whm_api('createacct', {'username' => 'someuser', 'password' => 's0m3P4$$w()Rd' } );
  # Return JSON from xml-api (rather than a hash reference)
  $cp->whm_api('version', undef, 'json');

  # Perform an API2 query
  $cp->cpanel_api2_request('whostmgr',
    {
      'module' => 'Email',
      'func' => 'listpopswithdisk',
      'user' => 'someuser',
    }
  );
  # Perform an API2 query when authenticated as a user
  $cp->cpanel_api2_request('cpanel',
    {
      'module' => 'Email',
      'func' => 'listpopswithdisk',
    }
  );
  # Pass parameters to an API2 call
  $cp->cpanel_api2_request('cpanel'
    {
        'module' => 'Email',
        'func' => 'addpop',
    },
    {
        'domain' => 'domain.com',
        'email' => 'username',
        'password' => 'SojmASDM(#(Jinasifodanosd',
        'quota' => 200
    },
  );

  # Perform an API1 query
  $cp->cpanel_api1_request('whostmgr',
    {
        'module' => 'LastLogin',
        'func' => 'lastlogin',
        'user' => 'someuser'
    }
  );
  # Pass parameters to an API1 query
  $cp->cpanel_api1_request('cpanel',
    {
        'module' => 'Mysql',
        'func' => 'adduserdb',
    },
    [ 'somedb', 'somedbuser', 'ALL' ]
  );

  # perform an HTTP GET request against a URL
  $cp->api_request('whostmgr', '/xml-api/loadavg', 'GET');

  # perform an HTTP GET request with parameters
  $cp->api_request('whostmgr', '/xml-api/createacct', 'GET', {'username' => 'someuser', domain => 'domain.com'} );

  # perform an HTTP POST request (with parameters)
  $cp->api_request('whostmgr', '/xml-api/createacct', 'POST', {'username' => 'someuser', domain => 'domain.com'} );

=head1 DESCRIPTION

cPanel::PublicAPI is a supported interface for interacting with cPanel's APIs over HTTP.
This allows you to query either WHM or cPanel accounts from a perl interface.  The purpose
of this module is to provide an easy-to-use interface into cPanel's various APIs without
requiring much knowledge of how they work.

=head2 Object Construction

a cPanel::PublicAPI object is constructed with the new() method.

  my $publicapi = cPanel::PublicAPI->new();

When passed no parameters, this will create the object using the accesshash in ~/.accesshash. If no .accesshash file exists, it will attempt to use the REMOTE_PASS environment variable. If the REMOTE_PASS variable is not defined, object creation will error out.

=head3 new() parameters

options for new() are specified as a hash reference, the following parameters are supported:

=over

=item * user - The username to authenticate as.

=item * pass - The password to use for authentication.

=item * accesshash - The accesshash to use for authentication (WHM only).

=item * api_token - The API token to use for authentication.

=item * timeout - The length of time (in seconds) before an http request should time out.  Default to 300.

=item * ip - The IP to be queried.  defaults to 127.0.0.1, if host is defined it will take precedence over the 'ip' parameter.

=item * host - The hostname to be queried.  This will take precedence over the 'ip' parameter.

=item * usessl - 1 or 0, Indicates whether communication should be performed over SSL or not (default to 1).

=item * ssl_verify_mode - 1 or 0, Indicates whether to verify SSL certificates or not.  (default to 1).

=item * error_log - Path to where you want debug and error logging information to be written to.  If this is not defined or the module is unable to open the path in question, it will default to STDERR.

=item * debug - Enables debug logging, which will place considerably more information into the error_log.

=item * http_tiny_creator - An optional code reference that receives the list
of key/value pairs that normally goes into L<HTTP::Tiny>’s constructor to
create an instance of that class for this module’s internal use. The code
reference, when called, should return an object that implements
an HTTP::Tiny-compatible interface.

This parameter is useful for customizing that internal
HTTP::Tiny instance that cPanel::PublicAPI uses. Handle with care, though:
this parameter may break cPanel::PublicAPI in various ways—some subtle,
others less so.

For example, if you want a custom User-Agent header, you might do:

    http_tiny_creator => sub {
        return HTTP::Tiny->new(
            @_,
            agent => 'My Custom UA String',
        );
    },

=back

=head3 Authentication methods

This module supports three authentication methods:

=over

=item 1. Username & Password

  use cPanel::PublicAPI;
  my $pubapi = cPanel::PublicAPI->new( 'user' => 'foo', 'pass' => 'bar' );

=item 2. API Token

(Note: This method does not work with Webmail.)

To create an API token, visit "Manage API Tokens" in cPanel or WHM, and follow the appropriate prompts.
Your token will appear in a special notice. Make certain that you save your API token in a safe location, as this is the only time the token will be shown
to you.

To use an API token with this module, do the following:

  use cPanel::PublicAPI;
  my $pubapi = cPanel::PublicAPI->new( 'user' => 'foo', 'api_token' => $string_containing_api_token );

=item 3. WHM Access Hash

B<NOTE:> Accesshash authentication is deprecated as of cPanel & WHM version 64.

To configure accesshashes, you can either:

=over

=item * In cPanel & WHM version 66 and earlier, visit “Setup remote access key”
in WHM which will generate an accesshash for your server if one does not already exist.

=item * Run F</usr/local/cpanel/bin/mkaccesshash>. This will overwrite
any existing access hash.

=back

The generated accesshash is stored in F<~/.accesshash>.

To use an accesshash with this module, do the following:

  use cPanel::PublicAPI;
  my $pubapi = cPanel::PublicAPI->new( 'user' => 'foo', 'accesshash' => $string_containing_access_hash );

It should be noted that the accesshash can contain newlines in it. Newlines will be stripped by the object when
it attempts to perform a query.

=back

=head3 Dependencies

This module will fall back on different modules if one fails to load. This allows for compatibility with cPanel & WHM's internal perl parser and maintain compatibility with a standard perl implementation.  The order that it will fall back on serialization modules is:

=over

=item * JSON::Syck

=item * JSON

=item * JSON::XS

=item * JSON::PP

=back

If you installed this module via CPAN, this should never be an issue.  If you are wishing to use this
module on a system where you do not have access to compiled modules, JSON::PP is the recommended serializer.

=head1 Two-Factor Authentication (2FA)

cPanel version 54 and above allows users to configure 2FA on their accounts - this security policy requires that the API queries
are performed after authenticating and establishing a session. The workflow to accomodate 2FA will be as so:

    use cPanel::PublicAPI;

    use lib '/usr/local/cpanel';
    use Cpanel::Security::Authn::TwoFactorAuth::Google (); # only available in 11.54+

    my $pubapi = cPanel::PublicAPI->new( 'user' => 'foo', 'pass' => 'bar' );
    my $google_auth = Cpanel::Security::Authn::TwoFactorAuth::Google->new(
        {
            'account_name' => 'foo',
            'secret'       => $user_2fa_secret,
            'issuer'       => ''
        }
    );
    $pubapi->establish_tfa_session('whostmgr', $google_auth->generate_code());
    $pubapi->whm_api('applist');

Anytime you change services (e.g. from 'whostmgr' to 'cpanel'), you must establish the 2FA session for the new service.

    eval {
        $pubapi->cpanel_api2_request('cpanel', { 'user' => 'foo', 'module' => 'MysqlFE', 'func' => 'listdbs' }, {} );
    };
    print "failed cause 2fa session wasn't established\n" if $@;

    $pubapi->establish_tfa_session('cpanel', $google_auth->generate_code());
    eval {
        $pubapi->cpanel_api2_request('cpanel', { 'user' => 'foo', 'module' => 'MysqlFE', 'func' => 'listdbs' }, {} );
    };
    print "success\n" if not $@;

B<NOTE>: Additionally, since accesshash authentication is not allowed to establish sessions, you must use the 'user'/'pass'
authentication in order to make API requests as a user with 2FA configured.

=head1 Important Methods

=head2 Querying the xml-api - whm_api()

The XML-API is WHM's API used for administrative functions is handled via the whm_api() method.

The syntax for whmapi is:

  $cp->whm_api($call [, \%formdata, $format ] );

The meaning of these parameters is:

=over

=item * $call - The XML-API call you wish to query

=item * $formdata - The parameters for the XML-API call in question, f.ex. for suspendacct, here you would pass in a hashref containing “user” and “reason”.  If there are no parameters, this can be undef or a blank hash.

=item * $format - The requested response format.  The valid values here are “xml”, “json” or “ref” (perl hash reference).  This will default to returning a perl hash reference when the value is undef.

=back

By default, WHM API v1 is used.  If, for legacy reasons, you need to use v0,
please set the C<api.version> key to 0 in the formdata parameter.

For more information on what calls are available and how they can be referenced, please see the xml-api documentation at L<http://docs.cpanel.net/twiki/bin/view/AllDocumentation/AutomationIntegration/XmlApi>.

=head2 Querying cPanel's APIs

cPanel supports two APIs, designated "API1" and "API2".

cPanel API calls are seperate into Modules, these module names relate to modules within the Cpanel namespace on a cPanel server.  Each module defines a set of functions that are from either API1 or API2 (or both).

There are two distinct differences between API1 and API2:

=over

=item * API1 Takes in ordered parameters and returns strings

=item * API2 uses named parameters and returns hashes or arrays of hashes

=back

Within the context of the public api, calling a function from API1 or API2 will always return a hash, but the specific data returned from the API call will be contained within the 'data' key of the response.

For more information on the differences between API1 and API2 please see the documentation: L<http://docs.cpanel.net/twiki/bin/view/DeveloperResources/ApiBasics/WebHome>

For information on calling API1 and API2 direct via HTTP, please see: L<http://docs.cpanel.net/twiki/bin/view/AllDocumentation/AutomationIntegration/CallingAPIFunctions>

=head3 cpanel_api1_request()

C<cpanel_api1_request()> is used to query cPanel's API1, the function used for querying API1 has the following syntax:

  $cp->cpanel_api1_request($service, \%cfg [, \@params, $format ] );

=over

=item * $service - The service that you wish to query.  This can be 'cpanel' 'whostmgr', or 'webmail'.  It is important to note that what services you are able to query depends on the user you are authenticated as.  Only a user with reseller or root access can use the whostmgr service.  If you are authenticated as root, you will not be able to query the 'cpanel' service.  When the service is set to 'whostmgr' a 'user' must be set in the $cfg hash.

=item * $cfg - A hash reference describing the call you wish to make.  Required parameters are 'module' and 'func', which correspond to the module and function of the API call you wish to query. If you are querying the "whostmgr" service, you will need to specify 'user' as well.

=item * $params - An array reference containing the parameters you wish to pass to the API call.

=item * $format - The format for the xml-api to respond in.  The valid values here are “xml”, “json” or “ref” (perl hash reference).  This will default to returning a perl hash reference.

=back

To see what modules and functions are available for making API1 calls, please see: L<http://docs.cpanel.net/twiki/bin/view/ApiDocs/Api1/WebHome>

=head3 cpanel_api2_request()

C<cpanel_api2_request()> is used to query cPanel's API2, the function use for querying API2 has the following syntax:

  $cp->cpanel_api2_request( $service, \%cfg [, \%params, $format ] );

=over

=item * $service - The service that you wish to query.  This can be 'cpanel' 'whostmgr', or 'webmail'.  It is important to note that what services you are able to query depends on the user you are authenticated as.  A user that does not have reseller or root access will not be able to use the whostmgr service.  If you are authenticated as root, you will not be able to query the 'cpanel' service.  When the service is set to 'whostmgr' a 'user' must be set in the $cfg hash.

=item * $cfg - A hash reference describing the call you wish to make.  required parameters here are 'module' and 'func', which correspond to the module and function of the API call you wish to query. If you are querying the "whostmgr" service, you will need to specify 'user' as well.

=item * $params - An hash reference containing the parameters you wish to pass to the API call.

=item * $format - The format for the xml-api to respond in.  The valid values here are “xml”, “json” or “ref” (perl hash reference).  This will default to returning a perl hash reference when the value is undef.

=back

To see what modules and functions are available for making API2 calls, please see: L<http://docs.cpanel.net/twiki/bin/view/ApiDocs/Api2/WebHome>

=head2 api_request() - Making direct URL requests to cPanel & WHM.

There are some situations where you will need to query cPanel and WHM URLs directly.  This should ONLY be done when there is not an API call available for the function you wish to query.

The function used for querying URLs directly is api_request().  It will always return a string rather than converting the response into a hash reference.  It uses the following syntax:

  $cp->api_request( $service, $uri, $method, \%formdata, $headers)

=over

=item * $service - The service that you wish to query.  This can be 'cpanel' 'whostmgr', or 'webmail', when passed an numerical value, PublicAPI will query that port directly.

=item * $uri - The URL you wish to query, e.g. '/xml-api/cpanel'

=item * $method - 'GET' or 'POST'

=item * $formdata - The data you wish to pass to the URL

=item * $headers - Any additional headers are to be passed with the request.  These can be either a flat string or as a hashref like {'headertitle' =E<gt> 'headerdata'}

=back

=head1 Other Features

=over

=item C<establish_tfa_session()>

=back

See L</Two-Factor Authentication (2FA)> above.

=over

=item C<set_debug()>

=back

This function allows you to enable/disable debug mode by passing a value that evaluates to 'true' or 'false'.

=over

=item C<user()>

=back

Allows you to change the user that your PublicAPI object is authenticating with.

=over

=item C<pass()>

=back

Allows you to change the password that your PublicAPI object is authenticating with, this will remove the stored accesshash from the object.

=over

=item C<accesshash()>

=back

Allows you to change the accesshash that your PublicAPI object is authenticating with, this will remove the stored password from the object.

=over

=item C<api_token()>

=back

Allows you to change the API token that your PublicAPI object is authenticating with, this will remove the stored password from the object.

=over

=item C<format_http_query()>

=back

Allows you to construct formdata for an http query from a hash.  For Example:

  $pubapi->format_http_query( { 'one' => '1', 'two' => 2 } );

would return:

  'one=1&two=2'

=over

=item C<format_http_headers()>

=back

Allows you to construct headers for an http query from a hash.  For Example:

  $pubapi->format_http_headers( { 'Authorization' => 'Basic cm9vdDpsMGx1cnNtNHJ0IQ=='} );

would return:

  'Authorization: Basic cm9vdDpsMGx1cnNtNHJ0IQ==\r\n'

=over

=item C<$pubapi-E<gt>{'error'}>

=back

Errors encountered within the class are stored here before being written out to the error_fh filehandle.  This can be used for checking the existance of query errors.

=head1 Bugs

see http://rt.cpan.org to report and view bugs

=head1 License

Copyright (c) 2015, cPanel, Inc.
All rights reserved.
http://cpanel.net

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
     * Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer.
     * Redistributions in binary form must reproduce the above copyright
       notice, this list of conditions and the following disclaimer in the
       documentation and/or other materials provided with the distribution.
     * Neither the name of cPanel, Inc. nor the
       names of its contributors may be used to endorse or promote products
       derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL  BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.