December 22, 2004

SysAdmin to SysAdmin: Scripting admin tasks with PHP

Author: Preston St. Pierre

I've had a love-hate relationship with PHP since PHP 3. I was introduced to it
as a simple way to do very simple things on the web without the overhead of
implementing Java on my web server, and without dealing with recompiling CGI
scripts depending on whether I was running things on Solaris or Linux. PHP was
(and is) Mind-Numbingly Easy(tm) to learn, quick to deploy, and for a long
time, it suited my needs just fine.

Then one day the PHP Website announced what
they called a "CLI SAPI", which enabled the use of the PHP parser without
generating HTML, and without using Apache as a delivery mechanism for the
output of your PHP code. No, the CLI SAPI essentially let you open a file,
specify the path to the PHP parser in the shebang line, and code away, just
like you can with Perl, Python, and Bash. At first I wasn't quite sure about
all of this, but as time went on, I continued to do all of my web development
with PHP, and all of my administrative tasks using Perl. Then one day, I hit a
bump in the road.

I was writing a script on the system where it was to run, in Perl, only to find
that the necessary Perl modules were not installed. Furthermore, installing the
Perl module was a huge headache, as I had learned from having been in this
position before. I tried anyway, and sure enough, CPAN choked on something or
other, and at that time I realized that PHP was already installed on the
system, and already had what I needed built in. 10 minutes later, the script
was working... using PHP.

Do we really need another scripting language?

Nope, we sure don't. But if it's there, I'm not gonna complain either.
Developers scratch their own itch, and if that itch results in another
scripting language, there's nothing you or I are going to say to change things.
The fact that PHP is available in this context is also helpful to web
developers, who can now test the functionality of their logic without
implementing a full-fledged web environment on their local machine, for
example. In addition, web developers familiar with PHP can now move those
skills over to the command line to help them administer their own machines, and
do stuff that used to require either learning how to do it in Perl, or figuring
out how to do it in the confines of a browser, which is extremely inconvenient if
all you want to do is something silly like renaming your MP3 files.

So, while we don't need another scripting language, there are some
conveniences here for users who know PHP and don't really want to learn another
language to do their administrivia. For those users, where shell becomes
cumbersome, PHP can fill the void quite nicely. Here, I'll go over some really
simple concepts to get you going with PHP on the command line.

A simple example

This example and the next are extremely simple and incomplete examples
meant to just show you that PHP is very easy, and that it can easily produce
pretty usable output. This first one is what prompted me to write this article.
I was writing an LDAP-related script, and couldn't get the Net::LDAP module
installed on one of the machines. This is probably no fault of the fine folks
at CPAN or the Perl developers, but I happened to have PHP installed, so off I
went! This isn't exactly what I wrote, but it's close enough:


#!/usr/bin/php -q
                                                                                                                                                           
&lt?php
                                                                                                                                                           
$conn=ldap_connect("ldap.linuxlaboratory.org")
 or die("Connect failed\n");
                                                                                                                                                           
$bind = ldap_bind($conn)
 or die("Bind failed\n");
                                                                                                                                                           
$answer = ldap_search($conn, "dc=linuxlaboratory,dc=org", "(sn=Jones)");
$output = ldap_get_entries($conn, $answer);
                                                                                                                                                           
echo $output["count"]." entries returned\n";
                                                                                                                                                           
for ($i=0; $i&ltcount($output); $i++) {
        if(!isset($output[$i])) break;
        echo $output[$i]["dn"]."\n";
}
?&gt

The above script shouldn't be intimidating at all if you've worked with PHP
before. I haven't defined any user functions here, everything is built into my
PHP installation. In my case, I've installed PHP using RPMs, which is
convenient, because if I want to add LDAP functionality, under Fedora, I can
just run "yum install php-ldap", and all's well. SuSE and Mandrake work
similarly, and I'm sure you can do the same or similar with
apt-get under Debian.

The first thing you're likely to notice is the "-q" argument to PHP. This tells
the parser not to generate HTTP header information and HTML output. The second
thing you'll notice is the PHP tags before and after the code section
( &lt?php and ?&gt ). These are the same tags used
to separate inline PHP code within HTML files. In this case, though, you're
separating PHP code from plain text output. If you have a lot of plain, fixed
text to output to the screen at some point in the program, you don't have to
echo it out to the screen. You can just close the PHP section,
write out your text, and open up the PHP code again with the
&lt?php tag again.

If it wasn't for the shebang line, this script could be included
as-is by any file in your Apache server's DocumentRoot. The script does a
simple search of an LDAP directory which I've pointed it at, looking for users
with a surname (sn) of "Jones." I've then stored the resulting array in the
$output variable. The astute reader will notice that
$output["count"] appears to be predifined somehow, and that's
true. The ldap_get_entries function defines it as the first key in
the array result, because LDAP attributes can have more than one value. You can
still find out what's going on in arrays without this predefined key using the
count() function, and for debugging purposes, you can use the
print_r($arrayname) function to dump the contents of an array for
further inspection. Here's the output from the above script:


1 entries returned
cn=Brian K Jones (jonesy@linuxlaboratory.org),dc=linuxlaboratory,dc=org

It's surreal to code PHP for the web all this time and then see it output,
well, exactly what you'd expect from a Perl script on the command line!

Another quick example

This one is also simple. It's a piece of code that will tell me, by running one
command with no flags, what ports are open on my machine by doing an
snmpwalk in just the right place (which I initially found by
walking the whole tree on my local machine):


#!/usr/bin/php -q
                                                                                                                                                           
&lt?php
                                                                                                                                                           
snmp_set_valueretrieval(SNMP_VALUE_PLAIN);
$openports = snmpwalk("localhost", "public", ".1.3.6.1.2.1.6.13.1.3");
                                                                                                                                                           
foreach($openports as $port)
{
        if($port &lt 1024)
        {
                $service = getservbyport($port, "tcp");
                echo "Port $port:  $service\n";
        }
}
?&gt

There's a secret weapon here that even a good number of PHP coders don't know.
The first line of code calls the snmp_set_valueretrieval()
function with a predefined constant as an argument. Without that line of code,
you'd need about 3 more lines of code to parse out net-snmp's datatype output.
With that line in there, it insures that the only value you get back (and
subsequently pass to getservbyport()) is the value of the
attribute in question, not "INTEGER: value", which would cause
getservbyport() to choke, since it expects a port number to be fed
to it in the form of a plain old number.

Here's the output from the above script, which I call "holes.php":


Port 22: ssh
Port 80: http
Port 199: smux
Port 53: domain
Port 953: rndc
Port 53: domain

There's probably a cleaner way to do this, but this will do for all of 3
minutes of coding time. The only thing really wrong here is it doesn't show
which interface it's looking at. Proof of that is evident in the fact that it
shows port 53 (DNS) twice. Once for the loopback interface, and one for eth0.
Filtering it out (the right way) would involve going back through the SNMP data
and matching up an index somewhere with a value somewhere else. Filtering it
out the wrong way would involve a call to a PHP function that throws out
redundant values in an array (array_unique(), if memory serves),
but we don't do things the wrong way!

In Closing

Well I'm not sure who the audience is for this article. Hardcore sysadmins will
likely find it sacreligious that I've suggested something other than shell or
Perl to do administrative tasks. Web coders, especially those with miserable
working conditions, might look at this as an opportunity to tweak their resumes
to be more sysadmin-like. This will further enrage the hardcore sysadmins, who
will undoubtedly let me have it in the comments (as usual). Then there will be
the Python coders, who will tell me that all this can be done with 2 lines of
(well-formatted) Python. Let the flamewars begin!

Click Here!