Universal Password Trap
As a cPanel account password can be changed in a number of places, including both cPanel and WHM, we've created a universal hook that fires anytime an account password is changed.
To use this hook, you must write a module in Perl that contains a subroutine called
process(). This is because
MyModule::process() is invoked by the parent process.
Creating the Module
In order to capture password output from any function that changes a password, you'll need to create a module located in
/usr/local/cpanel/Cpanel/ChangePasswd/. This module will be run as
root whenever a password is changed in WHM, cPanel, APIs, etc.
In this example, we're going to be sending this password back to our custom billing system so that we can have a record of the user's password in case they forget it. Therefore, we're going to create
/usr/local/cpanel/Cpanel/ChangePasswd/SendtoBilling.pm.
Module Internals
The internals of the module are pretty simple, in fact, there are only a few variables that can be accessed. In order to access these variables, we'll have our module dump them into a hash.
- user (string) — User name of the account. Example:
user.
- newpass (string) — New account password. Example:
p@ssw0rd!12345.
- message (string) — Status message about the password change. Example:
password changed successfully.
- rawout (string)* — Raw output from the password change. Example:
Changing system password for user x.
- service_ref (string) — Service changing the password. Example:
WHM.
- applist (array) — An array of hashes that contain the services to which the parent process modified the password.
After accessing the variables, you can send the username, password and any other variables to a local or remote location, store them in a database, etc. You should only transmit or store these variables using encryption (SSL/TLS). Sending out or storing password information in plain text is a
huge security risk.
Note: These variables are only available when the
root user is changing the account password.
Example
#!/usr/bin/perl
#
##
# This module will be run as ROOT. If you do not understand the security implications of running this as root
# do not use this module.
##
#
# Modules in /usr/local/cpanel/Cpanel/ChangePasswd/* are called when a users
# changes their password in cP/WHM. This allows you to update any database you may keep with their
# new password or your third party app.
#
# DO NOT STORE PASSWORD IN PLAIN TEXT
# DO NOT STORE PASSWORD IN PLAIN TEXT
#
# To use move this to /usr/local/cpanel/Cpanel/ChangePasswd/SampleModule.pm or
# /usr/local/cpanel/Cpanel/ChangePasswd/__YOURMODULENAMEHERE__.pm
package Cpanel::ChangePasswd::SampleModule;
use strict;
# You may use non XS modules here if they are in @INC (/usr/local/cpanel, /usr/local/cpanel/perl)
# use XXX
sub process {
my %OPTS = @_;
my $user = $OPTS{'user'};
my $newpass = $OPTS{'newpass'};
my $message = $OPTS{'message'}; #only when changing password as root
my $rawout = $OPTS{'rawout'}; #only when changing password as root
my $service_ref = $OPTS{'service_ref'}; #only when changing password as root
$rawout =~ s/[\n\r]/ /g;
$message =~ s/[\n\r]/ /g;
$newpass = '__PLAINTEXT__PASSWORD__NOT__WRITTEN__TO__DISK__FOR__SECURITY__REASONS__';
my @SRVLIST;
sysopen( my $pw_changelog_fh, '/var/cpanel/password_change.log', &Fcntl::O_WRONLY | &Fcntl::O_CREAT | &Fcntl::O_APPEND, 0600 );
print {$pw_changelog_fh} "$user:$newpass:$message:$rawout:";
foreach my $service (@{$service_ref}) {
push @SRVLIST,$service->{'app'};
}
print {$pw_changelog_fh} join(',',@SRVLIST) . "\n";
close($pw_changelog_fh);
}
1;
A note about the applist key
The
applist key contains a list of services that are checked whenever a password is changed. As of cPanel & WHM 11.28, you have the option to
not update your MySQL password. If you have selected to never change your MySQL password, the MySQL service is not included in the
applist key.
$VAR1 = [ { 'app' => 'system' }, { 'app' => 'ftp' }, { 'app' => 'mail' }, { 'app' => 'mySQL' }, { 'app' => 'FrontPage' } ]
Creating a script in a language other than Perl
If you wish to use a language other than Perl, you can create a wrapper (in Perl) that will call and execute another script, in the language of your choosing. For example:
#!/usr/bin/perl
package Cpanel::ChangePasswd::WrapperModule;
use strict;
sub process {
my %OPTS = @_;
my $user = $OPTS{'user'};
my $newpass = $OPTS{'newpass'};
my $message = $OPTS{'message'}; #only when changing password as root
my $rawout = $OPTS{'rawout'}; #only when changing password as root
my $applist = $OPTS{'applist'}; #only when changing password as root
$rawout =~ s/[\n\r]/ /g;
$message =~ s/[\n\r]/ /g;
foreach my $app ( @{$applist} ) {
if ( $app->{'app'} =~ m/mysql/i ) {
my $path = '/usr/local/cpanel/Cpanel/ChangePasswd';
my $script = 'change_password.php';
system( 'php', '-f', "$path/$script", $user, $newpass );
}
}
}
1;