What Events
Are
Events are an intraprocess notification system, used to indicate certain
conditions to other parts of the code that have registered an interest
in these conditions. A module can register a callback for any given
event; when that event is generated, the callback is invoked. All registered
callbacks, or listeners, are invoked in the usual module load order.
The Events API replaces the old registration functions that were specifically for process exits, daemon startup, and configuration parsing. That old system would not scale well, as several new functions would need to be added to the core API for every new event handled. The new Events API can handle any number of arbitrary events; modules can easily generate their own custom events without requiring code changes in the core code.
When Not To Use Events
The ProFTPD API contains many hooks and entry points into the lifecycle
of an FTP session: authentication handlers, command handlers, configuration
directive handlers, filesystem and network callbacks, etc. These APIs
cover most of the situations in which a module developer is interested.
Events are not needed in cases where the existing API can be used
to achieve the same goal.
Event
List
Some of the current Events
generated by
proftpd
are:
CreateHome
directive is used, and
a home directory is actually being created for the user who is
authenticating. The name of the user whose home is being created is
sent as the event_data.
pr_exit_register_handler()
,
which is now deprecated.
Note: if a handler for this event is registered in a module initialization function, then that handler will receive the event when the daemon (not a session) shuts down. If a handler for this event is registered in a session initialization function, that handler will receive the event when the session exits. The same handler can be used for both types of exit. If you wish to have separate listeners to handle daemon exits differently from session exits, register one listener during module initialization, then during session initialization, unregister that first listener and register the listener for session exits.
Exit handlers, as mentioned, are run as part of the exit process, which is
executed according to various conditions: an ACL denies access to the
client, a MaxClients
or related limit is reached, the session
times out, the client issued a QUIT
command, etc. For
whatever reasons, the session process will decide to end the session, and
will call end_login()
.
The end_login()
function calls end_login_noexit()
,
which deletes the session entry from the scoreboard, calls all registered
exit handlers, cleans up the wtmp
log, and closes any
lingering network connections. Then end_login()
actually
causes the process to end, via the _exit(2)
system call.
Do not re-register listeners for core.exit
;
register them only once, during module/session initialization.
Mulitple registration will cause the callback to be executed multiple
times, which is probably not what is intended. Also, take care when
writing exit handlers. The handler should not assume anything
about the state of pointers or variables: check that pointers are not
NULL
before dereferencing them, that values are within
acceptable ranges before operating on them, etc. Failure to do so
will cause your exit handler to segfault.
MaxConnectionRate
limit, if present, is
reached.
MaxInstances
limit, if present, is reached.
event_data
for
this event is a const char *
of the name of the module
that was loaded, e.g. mod_example.c
.
event_data
for
this event is a const char *
of the name of the module
being unloaded, e.g. mod_example.c
.
Note: a handler for this event must be registered in a module initialization function (not in a session initialization function), for the event is generated before starting any sessions.
Note: a handler for this event must be registered in a module initialization function (not in a session initialization function), for the event is generated before starting any sessions.
SIGHUP
signal. This event replaces the old rehash handlers registered via
pr_rehash_register_handler()
, which is now deprecated.
There are some tasks that should be done every time the daemon is
restarted via the SIGHUP
signal, tasks such as destroying
and reallocating a module-specific pool
, or
"bouncing" any open file descriptors (e.g. for log files)
by closing and reopening them, etc. This process of restarting the
daemon is called rehashing in the code.
Do not re-register core.restart
handlers;
register them only once, during module initialization. Otherwise, you
will bloat up the memory usage of the daemon process, every time it is
restarted.
Note: a handler for this event must be registered in a module initialization function (not in a session initialization function), for the event is generated before starting any sessions.
mod_auth
module when the
AnonRejectPasswords
limit, if present, is reached.
mod_auth
module when the
MaxClients
limit, if present, is reached.
mod_auth
module when the
MaxClientsPerClass
limit, if present, is reached.
mod_auth
module when the
MaxClientsPerHost
limit, if present, is reached.
mod_auth
module when the
MaxClientsPerUser
limit, if present, is reached.
mod_auth
module when the
MaxHostsPerUser
limit, if present, is reached.
mod_auth
when the
MaxLoginAttempts
limit, if present, is reached.
Note that if a non-NULL void *user_data
pointer is provided when
registering an event handler, and that pointer is allocated from a pool
that does not survive a restart, then the caller should unregister that
event handler during a restart, and re-register the handler again with
an updated pointer. The problem is that the Events API keeps a pointer
to the given user_data
, and when the pool from which that
data is destroyed, the pointer becomes invalid. The invalid pointer would
be passed to the event handler, after a restart, unless the pointer is
properly replaced via re-registration.
Code Example
/* Event handler for the 'mod_foo.foo' event. */ static void my_foo_ev(const void *event_data, void *user_data) { (void) pr_log_writefile(my_logfd, MOD_MY_VERSION, "the 'mod_foo.foo' event occurred"); return; } ... /* Register our callback for the 'mod_foo.foo' event. * The last NULL argument indicates that we are not interested in passing our * own "user_data" parameter to the callback. */ pr_event_register(&my_module, "mod_foo.foo", my_foo_ev, NULL); ... /* Meanwhile, back in mod_foo.c's handlers...*/ pr_event_generate("mod_foo.foo", NULL);That's it. Events can be generated for which there are no listeners registered, and listeners can be registered for events which are never generated.