Webmin::API: Using Webmin as a library

Webmin is perhaps the largest bundle of system administration related Perl code in existence (outside of CPAN, of course), much of which is unavailable anywhere else.  I often find myself wishing for a function or two from Webmin in my day-to-day Perl scripting.  Historically, one could use Webmin functions by first pulling in all of the bits and pieces manually, and running a few of the helper functions.  For example, at Virtualmin, Inc. we use this bit of code to start up the configuration stage of our install scripts:

# Setup Webmin environment
$ENV{'WEBMIN_CONFIG'} ||= "/etc/webmin";
$ENV{'WEBMIN_VAR'} ||= "/var/webmin";
$ENV{'MINISERV_CONFIG'} = $ENV{'WEBMIN_CONFIG'}."/miniserv.conf";
open(CONF, "$ENV{'WEBMIN_CONFIG'}/miniserv.conf") || die "Failed to open miniserv.conf";
while(<CONF>) {
  if (/^root=(.*)/) {
    $root = $1;
$root ||= "/usr/libexec/webmin";
require './web-lib.pl';

Wow.  That’s a lot of extraneous crap just to make use of Webmin functions.  Not all of that is necessary in every script that wants to use Webmin functions, but it’s always something I have to refer to the documentation for.

So, I’ve been bugging Jamie for some time to make a simpler way to get at the Webmin API, and he’s just released the Webmin::API Perl module.   To use it, you’ll first need Webmin installed.  There’s an RPM, deb, tarball, and Solaris pkg, so it’s easy no matter what UNIX-like OS you run (it’ll also run on Windows, but only in relatively limited fashion), and then you can install it like any other Perl module:

# tar xvzf Webmin-API-1.0.tar.gz
# cd Webmin-API
# perl Makefile.PL
# make install

Once that’s done, you can make use of the entirety of the web-lib.pl, plus the libraries for all of the Webmin modules.  For example, one could access all of the Webmin variables, like %gconfig, as well as all of the web-lib.pl functions, such as ftp_download (pure Perl FTP client), kill_byname (like killall), nice_size (return a number in GB, MB, etc.), running_in_zone (detects whether it's running in a Solaris Zone), etc.

So, making an application that downloads and does something with remote files is trivial, for example.  But, probably more interesting, is that once Webmin::API has been loaded, you can make use of the foreign_require function, which is used to access any available Webmin module function library.

For example, if I wanted to make sure Postfix was configured to use Maildir mail spools, I could do the following:

foreign_require("postfix", "postfix-lib.pl");
postfix::set_current_value("home_mailbox", "Maildir/", 1);

That’s it.  No need to worry about parsing the file and no regex needed.  You don’t need to figure out where the Postfix main.cf is located (assuming Webmin is configured correctly), or what the proper way to restart the service is.

One common, and surprisingly complicated, task is setting up initscripts to start on boot.  It seems like every Linux distribution uses a slightly different directory layout, slightly different scripts, and different tools for managing the rc directories and files.  Webmin knows about the vast majority of those quirks, and provides a uniform interface to all of them, and this functionality is exposed to scripts via the init module.  For example, I could enable Postfix on boot with the following:

foreign_require("init", "init-lib.pl");

There is one unfortunate caveat to this: You have to know the name of the initscript.  On all of the systems I work with, this is pretty consistent across most services, with the exception of Apache.  One Red Hat based systems the Apache services is called httpd, while on Debian/Ubuntu systems it is apache2.  Some systems also call it apache.

Working With the Linux Firewall

One of the most powerful Webmin modules is the Linux Firewall module, which manages an iptables firewall.  It is nearly comprehensive, covering many of the advanced stateful capabilities, as well as logging and creation of and management of arbitrary chains.  We can make use of the basic functionality of the module by importing the firewall library.

foreign_require("firewall", "firewall-lib.pl");

Once imported, we have access to the get_iptables_save function, which imports any existing rules from the system default iptables save file into an array.  You can then work with them using standard Perl data management tools like push and splice.

Say you want to open ports 10000 and 20000 (for Webmin and Usermin, of course).  Maybe you also want to make sure ssh (port 22) is available for those times when you need to hit the command line.  The simplest is probably to drop them into an array (so you can add new ports later without having to read code):

use Webmin::API;
foreign_require("firewall", "firewall-lib.pl");
use warnings;
my @tcpports = qw(ssh 10000 20000);
my @tables = &amp;firewall::get_iptables_save();
(my $filter) = grep { $_->{'name'} eq 'filter' } @tables;
if (!$filter) {
  my $filter = { 'name' => 'filter',
              'rules' => [ ] };
foreach ( @tcpports ) {
  print "  Allowing traffic on TCP port: $_\n";
  my $newrule = { 'chain' => 'INPUT',
               'p' => [ [ '', 'tcp' ] ],
               'dport' => [ [ '', $_ ] ],
               'j' => [ [ '', 'ACCEPT' ] ],
  splice(@{$filter->{'rules'}}, 0, 0, $newrule);

This reads the existing rules, and adds new ones, saves it out, and applies the new rules. The rules that this creates are identical to what you would get if you’d entered the following on the command line on a Red Hat based system:

iptables -I INPUT -p tcp --dport ssh -j ACCEPT
iptables -I INPUT -p tcp --dport 10000 -j ACCEPT
iptables -I INPUT -p tcp --dport 20000 -j ACCEPT
service iptables save

Now, of course you could do all of that with backticks and subsitution, but you’d have to add a bunch of additional logic to figure out whether to use iptables-save, service iptables save, or some variant of the former with an option or two (Debian and Ubuntu have a rather complex set of firewall configuration files, and thus the appropriate iptables save file may not be immediately obvious).  And, dealing with things programmatically is more difficult, if you want to do something interesting like “only add a rule if these two other rules already exist, otherwise add the following two rules”.   And, reading and parsing the rather complex save file and writing it back out yourself can be a challenge (feel free to steal the Webmin code for it, if you prefer not to need all of Webmin).

Known Issues

This Perl module is new, so it’s pretty safe to say there is room for improvement.  The biggest is that only the core Webmin web-lib.pl and ui-lib.pl functions are documented, and thus the vast majority of functionality found in Webmin you’ll have to parse out from the relevant modules yourself.  I plan to spend some time adding POD documentation to each of those libraries in the not too distant future, but in the meantime, the best documentation is the source itself.  Luckily, every library has an accompanying working example application in the form of the module that it is part of.

Another issue is that Webmin is full of old code.  It’s a ten year old codebase…and much of it isn’t “use strict” or even “use warnings” compliant.   You can, of course, trigger warnings after “use Webmin::API” and it works fine.  See my final iptables example for that kind of usage.  Strict is only usable, even after the import of Webmin, if you disable many types of check.  This is another issue I’ll spend some time on in the future.

In the meantime, there’s a lot of great functionality that’s just been made a little easier to make use of.  I’ll be writing several more articles with examples of using this API in the near future.  Specifically, the next installment of my series on Analysis and Reporting of System Data  will make use of the Webmin System and Server Status module to build a flexible ping monitoring and reporting tool in just a few lines of code.