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:
<event>FUNCTION NAME</event>
<module>MODULE NAME</module>
.. Data from /var/cpanel/users/USERNAME

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;
$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.