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
DECLINED
PRE_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.