ProFTPD Developer's Guide: Writing Control Actions

ProFTPD Version 1.2


Table of Contents

Writing a custom action for the ftpdctl program adds to the capabilities of the daemon and enhances the ability of the administrator to fine-tune the daemon while it is running. The following example module implements a "hello" action, and shows how to support action options.

The key function that a module uses to indicate to the daemon that it supports a custom ftpdctl action is:

  int pr_ctrls_add(module *module, const char *action, int (*ctrls_cb)(pr_ctrls_t *, int, char **));
This function registers the action with the Controls layer, using the given action string as the retrieval key for the ctrls_cb callback handler function.

Example mod_ctrls_hello module code:

#include "mod_ctrls.h"

module ctrls_hello_module;

static int hello_handle_hello(pr_ctrls_t *ctrl, int reqargc,
    char **reqargv) {
  int c = 0, res = 0;

  /* Always be sure to add at least one response to the given ctrl.  Failure
   * to do so causes the Controls layer to complain.
   */
  pr_ctrls_add_response(ctrl, "Hello, %s!", ctrl->ctrls_cl->cl_user);

  /* Parse any shutdown options.  Note that this use of getopt() requires
   * that we be clever.  First, the setting of optind (above), which causes
   * getopt() to think that it is being called for the first time, when
   * in fact it will have already been called by main().  Second, the
   * working around of a rather restrictive assumption made by getopt():
   * that it will be used only on the argc, argv passed to main(), and
   * not any other argc, argv (as here).  Using that assumption, getopt
   * starts its parsing with the second element of argv (index 1), thinking
   * that argv[0] is the program name.  We lie to getopt with the addition
   * of one to reqargc and subtraction of one from reqargv, fooling getopt
   * into processing the provided argv correctly.  Just remember to
   * similarly adjust getopt's results (namely, the value of optind).
   */

  /* Reset getopt */
  optind = 0;

  while ((c = getopt(reqargc + 1, reqargv - 1, "t")) != -1) {
    switch (c) {
      case 't':
        /* Add the current time to the response. */
        char buf[80] = {'\0'};
        time_t now = time(NULL);
        struct tm t = localtime(&now);

        strftime(buf, sizeof(buf), "%b %d %H:%M:%S", t);
        buf[sizeof(buf)-1] = '\0';

        pr_ctrls_add_response(ctrl, "The time is now: %s", buf);
        break;

      case '?':
        pr_ctrls_add_response(ctrl, "unknown option: %c", c);
        return -1;

      default:
    }
  }

  /* Adjust optind to reflect the tweaks to reqargc, reqargv earlier. */
  optind--;

  return res;
}

static int hello_init(void) {

  if (pr_ctrls_add(&ctrls_hello_module, "hello", hello_handle_hello) < 0)
    log_pri(LOG_INFO, "error registering 'hello' control: %s",
      strerror(errno));

  return 0;
}

module ctrl_hello_module = {
  NULL, NULL,

  /* Module API version 2.0 */
  0x20,

  /* Module name */
  "ctrls_hello",

  /* Module configuration handler table */
  NULL,

  /* Module command handler table */
  NULL,

  /* Module authentication handler table */
  NULL,

  /* Module initialization function */
  hello_init,

  /* Module child initialization function */
  NULL
};
Copy this code into a source file and build it into your proftpd. Once you have started the daemon, you should be able to invoke your new control by using ftpdctl:
  ftpdctl hello -t
to see a response similar to this one:
  ftpdctl: Hello, tj!
  ftpdctl: The time is now: Jul 14 22:38:48
If, for example, you wanted to send the same control request as above (i.e. "hello -t") from within a module, rather than using ftpdctl, it would look something like this:
  int sockfd = 0, respargc = 0;
  unsigned int reqargc = 0;
  char **reqargv = NULL, **respargv = NULL; 
  register unsigned int i = 0;
  pool *tmp_pool = make_sub_pool(module_pool);
  ...

  /* Connect to the Controls Unix domain socket */
  if ((sockfd = pr_ctrls_connect()) < 0) {
    /* Handle the error as appropriate */
  }

  /* Parse the message to be sent into argc, argv form */
  if (pr_ctrls_parse_msg(tmp_pool, "hello -t", &reqargc, &reqargv) < 0) {
    /* Handle the error as appropriate */
  }

  /* Send the message */
  if (pr_ctrls_send_msg(sockfd, 0, reqargc, reqargv) < 0) {
    /* Handle the error as appropriate */
  }

  /* Wait for and read in the response(s) */
  if ((respargc = pr_ctrls_recv_response(ctl_pool, sockfd, &status,
      &respargv)) < 0) {
    /* Handle the error as appropriate */
  }

  /* Iterate through the responses.  It is possible for an impolite module to return no
   * responses, in which case respargv would be NULL.
   */
  if (respargv != NULL) {
    register unsigned int i = 0;

    for (i = 0; i < respargc; i++) {
      /* Do something with the response strings */
    }
  }  

Take great care in the abilities you provide to proftpd users via controls. The action callback function runs within the context of the daemon, which means it can affect everything. A badly coded handler that goes into an infinite loop will cause the daemon to hang, unable even to handle new connections. If the functionality your control provides is sensitive, and not meant for everyone, consider using control access lists. The mod_ctrls_common module uses such lists to restrict access to its "restart" and "shutdown" actions; see its code for the usage of access lists.

Table of Contents



Author: $Author: castaglia $
Last Updated: $Date: 2002/07/19 15:40:17 $


© Copyright 2000-2002 TJ Saunders
All Rights Reserved