/*
 * The following is an *EXAMPLE* ProFTPD module.  While it can be compiled
 * in to ProFTPD, it is not by default, and doesn't really do anything all
 * that terribly functional.
 */

/*
 * ProFTPD - FTP server daemon
 * Copyright (c) 1997, 1998 Public Flood Software
 * Copyright (c) 1999, 2000 MacGyver aka Habeeb J. Dihu <macgyver@tos.net>
 * Copyright (c) 2001-2024 The ProFTPD Project team
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
 *
 * As a special exemption, Public Flood Software/MacGyver aka Habeeb J. Dihu
 * and other respective copyright holders give permission to link this program
 * with OpenSSL, and distribute the resulting executable, without including
 * the source code for OpenSSL in the source distribution.
 */

/* Sample module for ProFTPD */

#include "conf.h"

#define MOD_SAMPLE_VERSION		"mod_sample/0.0"

/* Command handlers
 */

/* Example of a PRE_CMD handler here, which simply logs all received
 * commands via pr_log_debug().  We are careful to return PR_DECLINED,
 * otherwise other PRE_CMD 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 don't return PR_DECLINED for all
 * their precmds.  In practice you should always return PR_DECLINED unless
 * you plan on having your module actually handle the command (or deny it).
 */
MODRET sample_pre_any(cmd_rec *cmd) {
  pr_log_debug(DEBUG0, "RECEIVED: command '%s', arguments '%s'.",
    cmd->argv[0], cmd->arg);

  return PR_DECLINED(cmd);
}

/* Next, an example of a LOG_CMD handler, which receives all commands
 * _after_ they have been processed, and additional only IF they were
 * successful.
 */
MODRET sample_log_any(cmd_rec *cmd) {
  pr_log_debug(DEBUG0, "SUCCESSFUL: command '%s', arguments '%s'.",
    cmd->argv[0], cmd->arg);

  return PR_DECLINED(cmd);
}

/* Now, a _slightly_ more useful handler.  We define POST_CMD handlers
 * for RETR, STOR and LIST/NLST, so we can calculate total data transfer
 * for a session.
 */
static unsigned long total_rx = 0, total_tx = 0;

MODRET sample_post_retr(cmd_rec *cmd) {

  /* The global variable 'session' contains lots of important data after
   * a file/directory transfer of any kind.  It doesn't get cleared until
   * mod_xfer gets a LOG_CMD, so we can still get to it here.
   */
  total_tx += session.xfer.total_bytes;

  return PR_DECLINED(cmd);
}

MODRET sample_post_stor(cmd_rec *cmd) {
  total_rx += session.xfer.total_bytes;
  return PR_DECLINED(cmd);
}

MODRET sample_post_list(cmd_rec *cmd) {
  return sample_post_retr(cmd);
}

MODRET sample_post_nlst(cmd_rec *cmd) {
  return sample_post_retr(cmd);
}

/* This command handler is for a non-standard FTP command, "XFOO".  It
 * illustrates how one can write a module that handles such non-standard
 * commands.
 */
MODRET sample_xfoo(cmd_rec *cmd) {
  char *path = NULL;

  if (cmd->argc < 2) {
    pr_response_add_err(R_500, "XFOO command needs at least one argument");
    return PR_ERROR(cmd);
  }

  /* We call pr_fs_decode_path() on the argument here, assuming that the
   * argument to this fictional XFOO command is indeed a path.  RFC2640
   * states that clients can encode paths as UTF8 strings; the
   * pr_fs_decode_path() function converts from UTF8 strings to the local
   * character set.
   */
  path = dir_realpath(cmd->tmp_pool,
    pr_fs_decode_path(cmd->tmp_pool, cmd->arg));

  if (!path) {
    pr_response_add_err(R_500, "It appears that '%s' does not exist",
      cmd->arg);
    return PR_ERROR(cmd);
  }

  pr_response_add_err(R_200, "XFOO command successful (yeah right!)");
  return PR_HANDLED(cmd);
}

/* Configuration handlers
 */

/* This sample configuration directive handler will get called
 * whenever the "FooBarDirective" directive is encountered in the
 * configuration file.
 */

MODRET set_foobardirective(cmd_rec *cmd) {
  int foo_bar = 1;
  config_rec *c = NULL;

  /* The CHECK_ARGS macro checks the number of arguments passed to the
   * directive against what we want.  Note that this is *one* less than
   * cmd->argc, because cmd->argc includes cmd->argv[0] (the directive
   * itself).  If CHECK_ARGS fails, a generic error is sent to the user
   */
  CHECK_ARGS(cmd, 1);

  /* The CHECK_CONF macro makes sure that this directive is not being
   * "used" in the wrong context (i.e. if the directive is only available
   * or applicable inside certain contexts).  In this case, we are allowing
   * the directive inside of <Anonymous> and <Limit>, but nowhere else.
   * If this macro fails a generic error is logged and the handler aborts.
   */
  CHECK_CONF(cmd, CONF_ANON|CONF_LIMIT);

  /* Get the Boolean value of the first directive parameter. */
  foo_bar = get_boolean(cmd, 1);
  if (foo_bar == -1) {

    /* The get_boolean() function returns -1 if the parameter was not a
     * recognized Boolean parameter.
     */
    CONF_ERROR(cmd, "requires a Boolean parameter");
  }

  /* add_config_param() adds a configuration parameter record to our current
   * configuration context.  We're initially setting the value stored in
   * the config_rec to be NULL, so that we can allocate memory of the
   * proper size for storing the Boolean value.
   */
  c = add_config_param(cmd->argv[0], 1, NULL);

  /* Allocate space for the Boolean value.  The smallest data type in C
   * is an unsigned char (1 byte), and a Boolean will easily fit within
   * that space.
   */
  c->argv[0] = pcalloc(c->pool, sizeof(unsigned char));
  *((unsigned char *) c->argv[0]) = foo_bar;

  /* By adding the CF_MERGEDOWN flag to the parameter we just created
   * we are telling proftpd that this parameter should be copied and
   * "merged" into all "lower" contexts until it either hits a
   * parameter w/ the same name or bottoms out.
   *
   * Example _without_ CF_MERGEDOWN:
   *
   * <VirtualHost>
   *      |----------\
   *             <Anonymous>
   *                 | - FooBarDirective  <------- Config places it here
   *                 |-----------\
   *                         <Directory>  <------- Doesn't apply here
   *                             |-------------\
   *                                        <Limit> <--- Or here.....
   *
   * Now, if we specify CF_MERGDOWN, the tree ends up looking like:
   *
   * <VirtualHost>
   *      |----------\
   *             <Anonymous>
   *                 | - FooBarDirective  <------- Config places it here
   *                 |-----------\
   *                         <Directory>  <------- Now, it DOES apply here
   *                             | - FooBarDirective
   *                             |-------------\
   *                                        <Limit> <-------- And here ...
   *                                           | - FooBarDirective
   *
   */

  c->flags |= CF_MERGEDOWN;

  /* Tell proftpd that we handled the request w/ no problems.
   */
  return PR_HANDLED(cmd);
}

/* Initialization routines
 */

/* Each module can supply up to two initialization routines (via
 * the module structure at the bottom of this file).  The first
 * init function is called immediately after the module is loaded,
 * while the second is called after proftpd is connected to a client,
 * and the main proftpd server (if not in inetd mode) has forked off.
 * The second init function's purpose is to let the module perform
 * any necessary work for initializing a session, once a client is connected
 * and the daemon is ready to service the new client.  In inetd mode, the
 * session initialization function will be called immediately after proftpd is
 * loaded, because proftpd is _always_ in "child mode" when run from inetd.
 * Note that both of these initialization routines are optional.  If you don't
 * need them (or only need one), simply set the function pointer to NULL
 * in the module structure.
 */

static int sample_init(void) {
  /* do something useful here, right? */

  return 0;
}

static int sample_sess_init(void) {
  /* same here */

  return 0;
}

/* Module API tables
 *
 * There are three tables which act as the "glue" between proftpd and
 * a module.  None of the tables are _required_ (however having none would
 * make the module fairly useless).
 *
 * The first table is the configuration directive handler table.  It specifies
 * handler routines in the module which will be used during configuration
 * file parsing.
 */

static conftable sample_conftab[] = {
  { "FooBarDirective",		set_foobardirective,	NULL },
  { NULL }
};

/* The command handler table:
 * first  : command "type" (see the doc/API for more info)
 *
 * second : command "name", or the actual null-terminated ascii text
 *          sent by a client (in uppercase) for this command.  see
 *          include/ftp.h for macros which define all rfced FTP protocol
 *          commands.  Can also be the special macro C_ANY, which receives
 *          ALL commands.
 *
 * third  : command "group" (used for access control via Limit directives),
 *          this can be either G_DIRS (for commands related to directory
 *          listing), G_READ (for commands related to reading files),
 *          G_WRITE (for commands related to file writing), or the
 *          special G_NONE for those commands against which the
 *          special <Limit READ|WRITE|DIRS> will not be applied.
 *
 * fourth : function pointer to your handler
 *
 * fifth  : TRUE if the command cannot be used before authentication
 *          (via USER/PASS), otherwise FALSE.
 *
 * sixth  : TRUE if the command can be sent during a file transfer
 *          (note: as of 1.1.5, this is obsolete)
 *
 */

static cmdtable sample_cmdtab[] = {
  { PRE_CMD,	C_ANY,	G_NONE, sample_pre_any,		FALSE, FALSE },
  { LOG_CMD,	C_ANY,	G_NONE, sample_log_any, 	FALSE, FALSE },
  { POST_CMD,	C_RETR, G_NONE, sample_post_retr,	FALSE, FALSE },
  { POST_CMD,	C_STOR,	G_NONE, sample_post_stor,	FALSE, FALSE },
  { POST_CMD,	C_APPE, G_NONE, sample_post_stor,	FALSE, FALSE },
  { POST_CMD,	C_LIST,	G_NONE,	sample_post_list,	FALSE, FALSE },
  { POST_CMD,	C_NLST, G_NONE, sample_post_nlst,	FALSE, FALSE },
  { CMD,	"XFOO",	G_DIRS,	sample_xfoo,		TRUE,  FALSE },
  { 0,		NULL }
};

module sample_module = {

  /* Always NULL */
  NULL, NULL,

  /* Module API version (2.0) */
  0x20,

  /* Module name */
  "sample",

  /* Module configuration directive handlers */
  sample_conftab,

  /* Module command handlers */
  sample_cmdtab,

  /* Module authentication handlers (none in this case) */
  NULL,

  /* Module initialization */
  sample_init,

  /* Session initialization */
  sample_sess_init,

  /* Module version */
  MOD_SAMPLE_VERSION
};
