This chapter takes module writers inside one of the standard Webmin modules, and explains which parts of its design they should copy.

Module design and CGI programs

Scheduled Cron Jobs module lets a user view, edit and create Cron jobs for all Unix users on a system. It gets the lists of jobs by reading several different files, such as those in the /var/spool/cron directory, those in /etc/cron.d and /etc/crontab. The exact paths depend upon the operating system that Webmin is running on, as every Unix variant seems to have its own implementation of Cron.

As well as editing jobs, the module can also be used to execute those that have already been defined and view their output. Users can also edit the files that control which users have access to Cron, usually named /etc/cron.allow and /etc/cron.deny.

The CGI programs that make up this module are:

index.cgi

Displays a list of jobs that the current Webmin user is allowed to access, each of which is a link to the editing page created by edit_cron.cgi with a parameter identifying the index of the job to edit. The actual list comes from the list_cron_jobs function in cron-lib.pl.

edit_cron.cgi

Produces HTML for a form for either editing an existing job or creating a new one, depending on the idx and new parameters. Again, the details of a job being edited are taken from the list_cron_jobs function. At the bottom of the generated page are buttons that submit to either save_cron.cgi or delete_cron.cgi.

save_cron.cgi

Calls ReadParse function to get the form inputs from edit_cron.cgi, and validates them to make sure all of the required fields have been filled. If so, functions from cron-lib.pl are called to either create a new job or update an existing, and then re-direct is called to make the user’s browser return to index.cgi. But if an error is detected, the standard error function is called instead. When changing the user that a job runs as, this program needs to delete and re-create it so that it ends up in the right file, instead of just changing it in place.

delete_cron.cgi

Run when the Delete button on the editing form is clicked. Just calls a function from cron-lib.pl to remove the job specified by the idx parameter, and then redirects the browser to index.cgi.

exec_cron.cgi

This CGI uses the safe_process_exec function from the Running Processes module to run the command for a specified Cron job as the user who owns it, and display the output. It also deletes any environment variables that are specific to Webmin, so that programs run by the Cron job do not get confused and think that they are being called as CGI programs when this is not really the case.

edit_allow.cgi

Just displays a form for entering either a list of users who are allowed to use Cron, or a list of those who cannot. The current settings are obtained by calling functions in cron-lib.pl.

save_allow.cgi

Saves the inputs from the form created by edit_allow.cgi back to the original files, again by calling functions from the module’s library.

This module follows a design common to many others Webmin – a single page listing objects to edit, each of which is a link to a form for editing it. Your modules should use the same layout where appropriate, instead of displaying a huge table for editing multiple objects at once. It is a good idea to imitate this module’s use of multiple CGI programs as well, instead of trying to out everything in a single script. In all of the standard modules, each page is generated by a separate program, and if it is a form it is submitted to yet another program. This makes each simpler and easier to understand, instead of putting both the form generating and processing code into a single script. The redirect function is used by all of the save_ CGIs for form processing to return the user’s browser to the module’s main page, rather than to the editing form again.

The cron-lib.pl library script

The real work in this module is done by the functions in cron-lib.pl, which actually read and write the various Cron job files in their different formats. This is the way a Webmin module should be written, as it cleanly separates the user interface from the configuration file management. This prevents unnecessary duplication of code, and makes it easy to add support for some new Cron file one arises.

The functions in this library that CGI programs call are:

list_cron_jobs():

Returns an array of hash references, each of which contains the details of some Cron job. This information is actually read from several different files, and each job hash contains the name of the file that it came from in the file key, the position in that file in the line key and the and the format in the type key. This is used when the job is saved with change_cron_job, so that it gets put back in the same place with the correct format. Many other Webmin modules store this kind of information in hashes that they create from configuration files, so that they know which part of the file to update.

create_cron_job( job ):

Takes a hash reference containing Cron job details, with the same keys as those returned by list_cron_jobs. This is then converted to a correctly formatted line, and appended to a temporary copy of the user’s Cron jobs file. The copy_crontab function is used to activate it, using the method explained below.

change_cron_job( job ):

Takes a hash reference returned by list_cron_jobs but with some of the details updated, and converts it to a correctly formatted line of text. If it is a user’s personal Cron job then the line must be updated in a copy of the his jobs file. Otherwise, the original file that it came from can be updated directly.

delete_cron_job( job ):

Deletes the job passed in as a parameter by removing its line from the original file. If it was a user’s personal Cron job this is done in a temporary copy of his file instead of directly updating the original source.

list_allowed() and list_denied()

Return arrays of users who are allowed or not allowed to access Cron, respectively. These functions are primarily used by edit_allow.cgi, and just read the contents of /etc/cron.allow and /etc/cron.deny. However, save_cron.cgi also uses them to check if the user that you are creating a Cron job for can actually use it, as the crontab command will often fail if this is not the case.

save_allowed( user, … ) and save_denied( user, … )

These functions write the lists of users given as parameters to the /etc/cron.allow or /etc/cron.deny files, respectively. They are only used by save_allow.cgi.

can_edit_user( access, user ):

This function is used to check if the current Webmin user can access the Cron jobs of a particular Unix user, based on the hash reference and username passed in as parameters. The reference is assumed to be the return value from get_module_acl, which contains settings made in the Webmin Users module. Most of the CGI programs use it to limit their displays and prevent attempts to access jobs belonging to unauthorized users. If your module has access control features that can limit that objects that a user can access, a function like this is useful to prevent the duplication of code that checks ACL settings. Note that it is called in both edit_cron.cgi and save_cron.cgi, to block sneaky users who try to invoke the save program directly instead of going through the form.

show_times_input( job ):

This code prints HTML for the part of a form for editing the times at which a Cron job is run. It used to be in edit_cron.cgi, but was moved into the library so that other modules which set up Cron jobs (such as Filesystem Backup) can make use of the same inputs in their user interface.

parse_times_input( job, in ):

This function parses the inputs from the form created by show_times_input. Again, it is used by other modules as well as in save_cron.cgi.

You might wonder, why do some of the functions above update a temporary file instead of directly editing the files in /var/spool/cron that contain user Cron jobs? The reason is that the crontab command must be used to install a modified file for the Cron daemon to notice the change and for it to take effect. This is done by the copy_crontab function, which invokes the appropriate crontab command for the operating system. Normally when crontab is run by a user, it starts an editor like vi for the user to edit a temporary copy of the file, which is when moved back into /var/spool/cron.

However, this module sets the EDITOR environment variable to the cron_editor.pl script which just copies the temporary file created by the module over the file passed to the script for editing by crontab. When it exists, the changes made by the module are properly installed and the temporary file can be deleted.

This process is not necessary for Cron jobs in /etc/crontab or /etc/cron.d though, as the Cron daemon automatically detects when those files have been updated. For this reason the change_cron_job and delete_cron_job functions can edit them directly.

Because Cron is a great tool for running scripts on a regular basic, several other modules make use of this one to set up jobs of their own. For example, Webmin Configuration uses it to schedule the automatic download of updated modules, Webalizer Logfile Analysis uses it to have logs processed regularly, and System and Server Status uses it to set up scheduled monitoring.

All of this is done by making foreign calls to the cron module. If your module needs to do the same, it is advisable to make use of the code in cron-lib.pl that already supports a wide variety of operating systems and creates jobs in the correct.

Module configuration settings

This module demonstrates how the various Cron file locations, formats and programs on different operating systems can be supported by the same code. If you look in its directory, you will see numerous files with names starting with config-, such as config-solaris and config-redhat-linux. Each specifies the files to read and commands to use for a particular operating systems. The code in cron-lib.pl makes numerous references to %config when listing and updating jobs, which of course is filled with the contents of /etc/webmin/cron/config. This file in turn comes from the appropriate config- file in the module’s directory, chosen at the time Webmin was installed.

If your module manages some service that differs slightly between operating systems, this method of using different default configurations makes sense. It can also be useful when writing a module for some server like Apache for the default configuration and program file paths will differ depending on the operating system or Linux distribution, due to the vast number of different Apache packages out there.

The file config.info in this module defines inputs for editing both the operating system dependant options in the configuration file, and those related only to the module’s user interface. Sometimes it makes very little sense to let users edit such settings as the location of users’ personal Cron job files, as they are pretty much determined by the operating system in use. For this reason, you might think that taking those fields out of config.info is a good idea, so that users cannot mess up the module’s configuration.

This will work fine, as it is really the entries in the appropriate config- file that gets (indirectly) loaded into the %config hash. The config.info file just controls which ones are editable and what values are allowed – any others will be left unchanged when the user clicks on Module Config. However, in the Scheduled Cron Jobs module all configuration settings can be edited, just in case the user upgrades the version of Cron that comes with his operating system to some totally different package.

The lang internationization directory

Thanks to the generous contributions of Webmin users, the lang subdirectory for this module contains files for several different languages. The setting in the Language form of the Webmin Configuration module determines which one is loaded into the %text hash when init_config is called, as explained earlier.

This module uses no hard-coded text strings in any of its CGI programs or other scripts. Instead, references to an appropriate message for the current language like $text{'index_create'} or &text('exec_cmd') are used. If your module might ever be translated into a different language, you should do the same in its CGI programs as well. Even though it is slightly more work to put messages into a separate file, it is worth it in the long run.

The acl_security.pl access control script

The Webmin Users module can be used to configure detailed access control settings for a particular user and module. The actual form for editing these settings is generate by the acl_security.pl script in the module’s directory. Because this module lets an admin define which Unix users a particular Webmin user can edit Cron jobs for, it has one of these scripts as well.

As you can see by opening the file in an editor, it contains the required acl_security_form and acl_security_save functions. The first prints HTML for form inputs within a 4-column table, with their current settings based on the contents of the hash reference passed in as a parameter. The second checks the values in %in and uses them to fill in the hash reference from its parameter, which upon exiting is saved by the Webmin Users module back to /etc/webmin/cron/username.acl.

The ACL settings for this module let the administrator choose allowed Unix users by several different means. He can either grant access to all of them, to just the one whose name matches the current Webmin user, to a specific list of users, to users with some primary group or to users with UIDs within some range. Many other modules have similar options to specifying allowed users of some kind. If your module deals with some kind of Unix user-related configuration, its acl_security.pl script should have similar inputs.

On many systems (such as those used for virtual hosting), a single sub-administrator may be responsible for many Unix accounts, possibly those with a certain primary group or with UIDs within some fixed range. This kind of access control makes it possible to safely give such as sub-admin a Webmin login to manage only those specific Unix users.

All of the CGI programs in this module use the get_module_acl standard function to get the access control settings for the current Webmin user. The return value is generally stored in the %access hash, which is consulted to determine if the Webmin user can access Cron jobs for a particular Unix user. This is mostly done by called can_edit_user (explained above), and then calling error if access was denied.

Code in your module should do the same, and every CGI program should check to make sure that it is not being accessed inappropriately. One change that you might want to make is to put the call to get_module_acl into your module’s library script so that the %access hash is available globally to every CGI program, instead of each of them having to call it explicitly.

When creating a module that can be set up to allow limited access like this, you must be very careful to stop the user from escaping its restrictions in any way. This means following all of the normal rules about programming CGI scripts, such as not passing user inputs directly to the system or open functions. Because Webmin modules are normally accessed by a user who has full root privileges, security holes like this would usually not matter. However, when the user has been given less privileges through the user of module access control, a bug could let him executed arbitrary commands or edit files as root.

The log_parser.pl log reporting script

Like all good Webmin modules, this one logs actions taken by users to that they can be viewer later in the Webmin Actions Log module. The save_cron.cgi, delete_cron.cgi, save_allow.cgi and exec_cron.cgi programs all call the standard webmin_log function with parameters indicating what action has just taken place, for this information is then written to a log file for later reporting.

Even though just about any arguments can be passed to the webmin_log function, it is usually a good idea to follow the standard that this and other modules use. The first action parameter should be the action performed, such as save or delete. The type parameter should be the kind of object the action applies to, such as cron or user. The object parameter should be the name of the object effected, such as fred or www.foo.com. Finally, the params parameter must be a hash reference containing additional information about the action, such as the structure of the object being modified or the contents of %in. All parameters except action are optional, so it is quite reasonable and common for a module to use code like &webmin_log("stop"). In addition, all of these programs make use of the lock_file and unlock_file functions to obtain locks on files that they change. This causes the actual changes to the Cron files to be captured for inclusion in the log as well, so that inexperienced administrators can see exactly what the module has been doing. Your module should make use of these functions as well, especially those for locking. They protect critical files from simultaneous, and give you detailed file change logs for free if you decide to add calls to action_log as well.

The other side of logging is the conversion of the logged parameters into human-readable form, which is done by the log_parser.pl script. If you view the code for this module, you will see that it simply uses the parameters to decide what to pass to text, and returns the resulting string. Note that the html_escape function is used to remove any special HTML characters from Cron commands, which may otherwise cause invalid HTML to be included in the log search results. If your module includes a log_parser.pl script which might return text containing characters like <, > or &, be sure to call html_escape on the appropriate parts.

Unlike the parse_webmin_log function is most other modules, the one in the Scheduled Cron Jobs module checks the long parameter to decide if a long or short action description should be output. The long form includes the actual command in the Cron job, which will only fit on the page displaying details of a single log entry in the Webmin Actions Log module. However, in most modules the message is always short enough to completely ignore this parameter.

If a parameter to webmin_log was omitted or set to undef by the CGI program that created it, the actual value passed to parse_webmin_log will be a single dash instead. This happens because a is used in the log file to represent a missing parameter.

The useradmin_update.pl user synchronization script

Other Webmin modules can choose to be notified when a user is created, modified or deleted in the Users and Groups module. This is normally used to keep some other user list in sync (such as the Samba password file), but can be handy for other purposes as well.

The Scheduled Cron Jobs module has a useradmin_update.pl script so that it can detect the renaming and deletion of users, and update their Cron job files respectively. Normally when a Unix user is removed his Cron jobs will continue to exist, even though they will no longer work. And if a user is renamed, his jobs will still be listed under the old name, which will prevent them from working properly.

To avoid the first problem, the useradmin_delete_user function removes the personal Cron jobs file for any Unix user who is being deleted. The useradmin_modify_user function checks to see if the user has been renamed, and if so renames both the user’s personal Cron file and any jobs in other files as well. Any other changes to the user are ignored, as they are not relevant to this module.

Those few modules that make use of a useradmin_update.pl script will probably have it perform different tasks to this module’s. See the script in the samba directory for an example of how to synchronize a separate password file instead. If your module’s script does do something similar, it should include options somewhere (perhaps on the Module Config page) to turn synchronization on or off. Any such options should be off by default, so that other configuration files are not unexpectedly updated when the user is managing Unix users.