Inside the Scheduled Cron Jobs Module
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
As chapter 10 explains, this 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? 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 creation 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,
as explained in the "Functions in Other Modules" section of chapter
56. 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 in the "Internationalization"
section of chapter 56.
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
As chapter 52 explains, 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,
covered in the "Module Access Control" section of chapter 56.
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 Unix users that ‘belong' to him.
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 explicity.
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. As the “Action Logging”
section of chapter 56 explains, 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
As the “User Update Notification” section of chapter 56 explains,
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.

Copyright © by the contributing authors. All material on Doxfer is the property of the contributing authors.
Ideas, requests, problems regarding Doxfer?
Send feedback