Command Handlers
ProFTPD defines different phases, or types, to the handling of a
client-issued command. There are currently six such command types:
PRE_CMD, CMD, POST_CMD,
POST_CMD_ERR, LOG_CMD, and LOG_CMD_ERR.
When ProFTPD receives a command from a client, it enters a cascading command
delivery process:
PRE_CMD type
handlers that match the special * command
wildcard
PRE_CMD type
handlers that match the command.
CMD type handlers
that match the special * command
wildcard
CMD type handlers
that match the command.
POST_CMD type
handlers that match the special * command
wildcard if the
CMD handler(s) succeeded.
POST_CMD type
handlers that match the command if the CMD handler(s)
succeeded.
POST_CMD_ERR
type handlers that match the special * command
wildcard if the
PRE_CMD or CMD handler(s) failed.
POST_CMD_ERR
type handlers which match the command if the PRE_CMD or
CMD handler(s) failed.
LOG_CMD type
handlers which match the special * command
wildcard if the
CMD handler(s) succeeded.
LOG_CMD type
handlers which match the command if the CMD handler(s)
succeeded.
LOG_CMD_ERR
type handlers which match the command if the CMD
handler(s) failed.
The return value of a particular command can allow or disallow further dispatching. This allows a higher priority module to overload a command handler for a particular command, and allow or deny the command as needed.
Pre-Command Handlers
As a general rule of thumb,
PRE_CMD-type command
handlers should check command syntax and basic applicability of the command,
check access restrictions, ensure the necessary resources are available,
etc etc. When one of these handlers returns
DECLINEDPRE_CMD-type command handler will be called. However, if it
returns ERROR, the
dispatching to all command handlers will stop completely, with the exception
of LOG_CMD_ERR-type
command handlers, which will be called in the event of an
ERROR. These handlers should never return
HANDLED themselves;
this will break assumptions made by the command dispatch system.
Below is an example of a
PRE_CMD-type command
handler, which simply logs all received commands via
log_debug().
It is careful to return
DECLINED, otherwise
other
PRE_CMD-type command handlers would not get the request. Note
that in order for this to work properly, this module would need to be loaded
last, or after any other modules which do not return
DECLINED for all their PRE_CMD-type command
handlers. In practice, you should always return DECLINED unless
you plan on having your module actually handle the command, or deny it.
Example PRE_CMD Handler
MODRET pre_cmd(cmd_rec *cmd) {
log_debug(DEBUG0, "RECEIVED: command '%s', arguments '%s'",
cmd->argv[0], cmd->arg);
return DECLINED(cmd);
}
Command Handlers
CMD-type command handlers
should do the actual work. Much like
PRE_CMD-type command
handlers, if
DECLINED is returned
by a
CMD-type command handler, then the next registered
CMD-type command handler will be called with the same
cmd_rec pointer. And, like
PRE_CMD-type command handlers, if an ERROR is
returned by a handler, the dispatching to other command handlers will halt;
only the
LOG_CMD_ERR-type
command handlers will be called.
Post-Command Handlers
POST_CMD-type command handlers generally handle any kind of
cleanup. Should a command pass through the relevant
PRE_CMD and CMD command handlers without error,
then the registered POST_CMD handlers will be called. If these
handlers return ERROR, their message, if any, will be sent to
syslog; not much else can be done, for the real work of the
command has already been accomplished by the CMD handlers.
As with PRE_CMD handlers, these should never return
HANDLED.
Example POST_CMD Handlers
The global variable session contains a lot of important data after
a file/directory transfer of any kind, and is not cleared until
mod_xfer receives a LOG_CMD. This is used
in these POST_CMD handlers, declared for the LIST,
NLST, RETR, and STOR FTP commands in
order to calculate the total data transfer for a session.
static unsigned long total_received = 0, total_xferred = 0;
MODRET post_cmd_list(cmd_rec *cmd) {
total_xferred += session.xfer.total_bytes;
return DECLINED(cmd);
}
MODRET post_cmd_nlst(cmd_rec *cmd) {
return post_cmd_list(cmd);
}
MODRET post_cmd_retr(cmd_rec *cmd) {
return post_cmd_list(cmd);
}
MODRET post_cmd_stor(cmd_rec *cmd) {
total_received += session.xfer.total_bytes;
return DECLINED(cmd);
}
Post-Command Error Handlers
POST_CMD_ERR type handlers handle any reporting and logging
of error conditions that may occur during the processing of a
PRE_CMD or CMD handler. One thing that distinguishes
a POST_CMD_ERR handler from a LOG_CMD_ERR handler
is that any error responses added during a POST_CMD_ERR handle,
using add_response_err(), will be seen by the client when the
error response chain is flushed; similar responses added by a
LOG_CMD_ERR handler are not seen by the client.
Example POST_CMD_ERR Handler
MODRET post_stor_err(cmd_rec *cmd) {
add_response_err(R_DUP, "%s failed: try harder next time!", cmd->argv[0]);
return DECLINED(cmd);
}
Log Command Handlers
LOG_CMD type handlers handle logging of successful commands.
These "command" handlers receive their commands only after
they have been processed, and only if those commands were successful.
Example LOG_CMD Handler
MODRET log_cmd(cmd_rec *cmd) {
log_debug(DEBUG0, "SUCCESSFUL: command '%s', arguments '%s'",
cmd->argv[0], cmd->arg);
return DECLINED(cmd);
}
Log Command Error Handlers
And, as might be expected, LOG_CMD_ERR type handlers handle
logging of failed commands. Handlers of type LOG_CMD_ERR will
be called in the event of an ERROR from either a
PRE_CMD or a CMD handler. At present, only
modules/mod_log.c declares a LOG_CMD_ERR type
handler, and it uses the same handler for both LOG_CMD and
LOG_CMD_ERR handling. If for any reason you wanted to handle
logging or reporting of failed commands, either all or just specific commands,
this would be the type of handler to use.