/* * ProFTPD: mod_delay -- a module for adding arbitrary delays to the FTP * session lifecycle * * Copyright (c) 2004-2007 TJ Saunders * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. * * As a special exemption, TJ Saunders 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. * * This is mod_delay, contrib software for proftpd 1.2.10 and above. * For more information contact TJ Saunders . * * $Id: mod_delay.c,v 1.26 2007/10/01 21:41:28 castaglia Exp $ */ #include "conf.h" #include "privs.h" #define MOD_DELAY_VERSION "mod_delay/0.6" /* Make sure the version of proftpd is as necessary. */ #if PROFTPD_VERSION_NUMBER < 0x0001021001 # error "ProFTPD 1.2.10rc1 or later required" #endif #include #ifdef HAVE_SYS_MMAN_H # include #endif /* On some platforms, this may not be defined. On AIX, for example, this * symbol is only defined when _NO_PROTO is defined, and _XOPEN_SOURCE is 500. * How annoying. */ #ifndef MAP_FAILED # define MAP_FAILED ((void *) -1) #endif #if defined(PR_USE_CTRLS) # include #endif /* PR_USE_CTRLS */ /* Number of values to keep in a row. */ #ifndef DELAY_NVALUES # define DELAY_NVALUES 256 #endif /* Fraction of total values that are accepted from a single session. */ #ifndef DELAY_SESS_NVALUES # define DELAY_SESS_NVALUES 16 #endif #if defined(PR_USE_CTRLS) static ctrls_acttab_t delay_acttab[]; #endif /* PR_USE_CTRLS */ extern xaset_t *server_list; module delay_module; struct delay_rec { unsigned int d_sid; char d_addr[80]; unsigned int d_port; unsigned int d_nvals; long d_vals[DELAY_NVALUES]; }; struct { const char *dt_path; int dt_fd; off_t dt_size; void *dt_data; } delay_tab; static unsigned int delay_engine = TRUE; static unsigned int delay_nuser = 0; static unsigned int delay_npass = 0; static pool *delay_pool = NULL; static struct timeval delay_tv; static void delay_table_reset(void); #define delay_swap(a, b) \ tmp = (a); \ (a) = (b); \ (b) = tmp; static long delay_select_k(unsigned long k, array_header *values) { unsigned long l, ir, tmp = 0; long *elts = (long *) values->elts; int nelts = values->nelts; /* This is from "Numeric Recipes in C", Ch. 8.5, as the select() * algorithm, an in-place sorting algorithm for finding the Kth * element in an array, where all elements to the left of K are * smaller than K, and all elements to the right are larger. */ l = 1; ir = values->nelts - 1; while (TRUE) { if (ir <= l+1) { if (ir == l+1 && elts[ir] < elts[l]) delay_swap(elts[l], elts[ir]); return elts[k]; } else { unsigned long i, j; long p; unsigned long mid = (l + ir) >> 1; delay_swap(elts[mid], elts[l+1]); if (elts[l] > elts[ir]) delay_swap(elts[l], elts[ir]); if (elts[l+1] > elts[ir]) delay_swap(elts[l+1], elts[ir]); if (elts[l] > elts[l+1]) delay_swap(elts[l], elts[l+1]); i = l + 1; j = ir; p = elts[l+1]; while (TRUE) { do i++; while (i < nelts && elts[i] < p); do j--; while (j >= 0 && elts[j] > p); if (j < i) break; delay_swap(elts[i], elts[j]); } elts[l+1] = elts[j]; elts[j] = p; if (p >= k) ir = j - 1; if (p <= k) l = i; if (l >= (nelts - 1) || ir >= nelts) break; } } return -1; } static long delay_get_median(pool *p, unsigned int rownum, long interval) { register unsigned int i; struct delay_rec *row; long *tab_vals; array_header *list = make_array(p, 1, sizeof(long)); /* Calculate the median value of the current command's recorded values. * * When calculating the median, we use the current interval as well * as the recorded intervals in the table, giving us an odd number of * values. * * If the number of values in this row is less than the watermark * value, we'll actually return the maximum value, rather than the * median. Below the watermark, the server is "cold" and has not * yet accumulated enough data to make the median a useful value. */ row = &((struct delay_rec *) delay_tab.dt_data)[rownum]; tab_vals = row->d_vals; /* Start at the end of the row and work backward, as values are * always added at the end of the row, shifting everything to the left. */ for (i = 1; i < row->d_nvals; i++) *((long *) push_array(list)) = tab_vals[DELAY_NVALUES - i]; *((long *) push_array(list)) = interval; pr_trace_msg("delay", 6, "selecting median interval from %d %s", list->nelts, list->nelts != 1 ? "values" : "value"); return delay_select_k(((list->nelts + 1) / 2), list); } static void delay_mask_signals(unsigned char block) { static sigset_t mask_sigset; if (block) { sigemptyset(&mask_sigset); sigaddset(&mask_sigset, SIGCHLD); sigaddset(&mask_sigset, SIGUSR1); sigaddset(&mask_sigset, SIGINT); sigaddset(&mask_sigset, SIGQUIT); sigaddset(&mask_sigset, SIGALRM); #ifdef SIGIO sigaddset(&mask_sigset, SIGIO); #endif #ifdef SIGBUS sigaddset(&mask_sigset, SIGBUS); #endif sigaddset(&mask_sigset, SIGHUP); sigprocmask(SIG_BLOCK, &mask_sigset, NULL); } else sigprocmask(SIG_UNBLOCK, &mask_sigset, NULL); } static void delay_signals_block(void) { delay_mask_signals(TRUE); } static void delay_signals_unblock(void) { delay_mask_signals(FALSE); } static void delay_delay(long interval) { struct timeval tv; long rand_usec; /* Add an additional delay of a random number of usecs, with a * maximum of half of the given interval. */ rand_usec = ((interval / 2.0) * rand()) / RAND_MAX; interval += rand_usec; pr_trace_msg("delay", 8, "additional random delay of %ld usecs added", (long int) rand_usec); tv.tv_sec = interval / 1000000; tv.tv_usec = interval % 1000000; pr_trace_msg("delay", 8, "delaying for %ld usecs", (long int) ((tv.tv_sec * 1000000) + tv.tv_usec)); delay_signals_block(); (void) select(0, NULL, NULL, NULL, &tv); delay_signals_unblock(); } static void delay_table_add_interval(unsigned int rownum, long interval) { struct delay_rec *row; row = &((struct delay_rec *) delay_tab.dt_data)[rownum]; /* Shift all existing values to the left one position. */ memmove(&(row->d_vals[0]), &(row->d_vals[1]), sizeof(long) * (DELAY_NVALUES - 1)); /* Add the given value to the end. */ row->d_vals[DELAY_NVALUES-1] = interval; if (row->d_nvals < DELAY_NVALUES) row->d_nvals++; } static int delay_table_init(void) { pr_fh_t *fh; struct stat st; server_rec *s; unsigned int nservers = 0; off_t tab_size; int flags = O_RDWR|O_CREAT; int reset_table = FALSE; /* We only want to create the table if it does not already exist. * * If the ServerType is inetd, we want to leave the current contents * alone (don't we want to check to see that it's appropriate, sid-wise, * for the current server_list?), otherwse, we reset the table. * * The size of the table should be: * * number of vhosts * 2 * row size */ for (s = (server_rec *) server_list->xas_list; s; s = s->next) nservers++; tab_size = nservers * 2 * sizeof(struct delay_rec); PRIVS_ROOT fh = pr_fsio_open(delay_tab.dt_path, flags); PRIVS_RELINQUISH if (!fh) { pr_log_debug(DEBUG0, MOD_DELAY_VERSION ": error opening DelayTable '%s': %s", delay_tab.dt_path, strerror(errno)); pr_trace_msg("delay", 1, "error opening DelayTable '%s': %s", delay_tab.dt_path, strerror(errno)); return -1; } if (pr_fsio_fstat(fh, &st) < 0) { pr_trace_msg("delay", 1, "error stat'ing DelayTable '%s': %s", delay_tab.dt_path, strerror(errno)); return -1; } if (st.st_size != tab_size) { /* This check is for cases when the ServerType is inetd, and the * current DelayTable has the wrong size, which can happen if the * configuration has changed by having vhosts added or removed. */ pr_trace_msg("delay", 3, "expected table size %" PR_LU ", found %" PR_LU ", resetting table", (pr_off_t) tab_size, (pr_off_t) st.st_size); reset_table = TRUE; } if (reset_table) { struct flock lock; lock.l_type = F_WRLCK; lock.l_whence = 0; lock.l_start = 0; lock.l_len = 0; pr_trace_msg("delay", 8, "write-locking DelayTable '%s'", fh->fh_path); while (fcntl(fh->fh_fd, F_SETLKW, &lock) < 0) { if (errno == EINTR) { pr_signals_handle(); continue; } else { pr_log_pri(PR_LOG_WARNING, MOD_DELAY_VERSION "warning: unable to obtain write lock on DelayTable '%s': %s", fh->fh_path, strerror(errno)); pr_trace_msg("delay", 1, "unable to obtain write lock on DelayTable '%s': %s", fh->fh_path, strerror(errno)); pr_fsio_close(fh); return -1; } } /* Seek to the desired table size (actually, one byte less than the * desired size) and write a single byte, so that there's enough * allocated backing store on the filesystem to support the ensuing * mmap() call. */ lseek(fh->fh_fd, tab_size-1, SEEK_SET); (void) write(fh->fh_fd, "", 1); /* Truncate the table, in case we're shrinking an existing table. */ pr_fsio_ftruncate(fh, tab_size); lock.l_type = F_UNLCK; pr_trace_msg("delay", 8, "unlocking DelayTable '%s'", fh->fh_path); fcntl(fh->fh_fd, F_SETLK, &lock); } delay_tab.dt_fd = fh->fh_fd; delay_tab.dt_size = tab_size; pr_trace_msg("delay", 8, "mapping DelayTable '%s' (%" PR_LU " bytes, fd %d) into memory", fh->fh_path, (pr_off_t) delay_tab.dt_size, delay_tab.dt_fd); delay_tab.dt_data = mmap(NULL, delay_tab.dt_size, PROT_READ|PROT_WRITE, MAP_SHARED, delay_tab.dt_fd, 0); if (delay_tab.dt_data == MAP_FAILED) { pr_log_pri(PR_LOG_ERR, MOD_DELAY_VERSION ": error mapping DelayTable '%s' into memory: %s", delay_tab.dt_path, strerror(errno)); pr_trace_msg("delay", 1, "error mapping DelayTable '%s' into memory: %s", delay_tab.dt_path, strerror(errno)); pr_fsio_close(fh); return -1; } if (!reset_table) { struct delay_rec *row; for (s = (server_rec *) server_list->xas_list; s; s = s->next) { unsigned int i = s->sid - 1; /* Row for USER values */ row = &((struct delay_rec *) delay_tab.dt_data)[i]; if (strcmp(pr_netaddr_get_ipstr(s->addr), row->d_addr) != 0) { reset_table = TRUE; break; } if (s->ServerPort != row->d_port) { reset_table = TRUE; break; } /* Row for PASS values */ row = &((struct delay_rec *) delay_tab.dt_data)[i + 1]; if (strcmp(pr_netaddr_get_ipstr(s->addr), row->d_addr) != 0) { reset_table = TRUE; break; } if (s->ServerPort != row->d_port) { reset_table = TRUE; break; } } } if (reset_table) { struct flock lock; lock.l_type = F_WRLCK; lock.l_whence = 0; lock.l_start = 0; lock.l_len = 0; pr_trace_msg("delay", 8, "write-locking DelayTable '%s'", fh->fh_path); while (fcntl(fh->fh_fd, F_SETLKW, &lock) < 0) { if (errno == EINTR) { pr_signals_handle(); continue; } else { pr_log_pri(PR_LOG_WARNING, MOD_DELAY_VERSION "warning: unable to obtain write lock on DelayTable '%s': %s", fh->fh_path, strerror(errno)); pr_trace_msg("delay", 1, "unable to obtain write lock on DelayTable '%s': %s", fh->fh_path, strerror(errno)); pr_fsio_close(fh); return -1; } } /* Seek to the desired table size (actually, one byte less than the * desired size) and write a single byte, so that there's enough * allocated backing store on the filesystem to support the ensuing * mmap() call. */ lseek(fh->fh_fd, tab_size-1, SEEK_SET); (void) write(fh->fh_fd, "", 1); /* Truncate the table, in case we're shrinking an existing table. */ pr_fsio_ftruncate(fh, tab_size); pr_trace_msg("delay", 6, "resetting DelayTable '%s'", delay_tab.dt_path); delay_table_reset(); lock.l_type = F_UNLCK; pr_trace_msg("delay", 8, "unlocking DelayTable '%s'", fh->fh_path); fcntl(fh->fh_fd, F_SETLK, &lock); } /* Done */ pr_trace_msg("delay", 8, "unmapping DelayTable '%s' from memory", delay_tab.dt_path); if (munmap(delay_tab.dt_data, delay_tab.dt_size) < 0) { int xerrno = errno; pr_fsio_close(fh); errno = xerrno; pr_log_pri(PR_LOG_WARNING, MOD_DELAY_VERSION ": error unmapping DelayTable '%s': %s", delay_tab.dt_path, strerror(errno)); pr_trace_msg("delay", 1, "error unmapping DelayTable '%s': %s", delay_tab.dt_path, strerror(errno)); return -1; } delay_tab.dt_data = NULL; if (pr_fsio_close(fh) < 0) { pr_log_pri(PR_LOG_WARNING, MOD_DELAY_VERSION ": error closing DelayTable '%s': %s", delay_tab.dt_path, strerror(errno)); pr_trace_msg("delay", 1, "error closing DelayTable '%s': %s", delay_tab.dt_path, strerror(errno)); return -1; } delay_tab.dt_fd = -1; return 0; } static int delay_table_load(int lock_table) { if (lock_table) { struct flock lock; lock.l_type = F_WRLCK; lock.l_whence = 0; lock.l_start = 0; lock.l_len = 0; pr_trace_msg("delay", 8, "write-locking DelayTable '%s'", delay_tab.dt_path); while (fcntl(delay_tab.dt_fd, F_SETLKW, &lock) < 0) { if (errno == EINTR) { pr_signals_handle(); continue; } else return -1; } } if (!delay_tab.dt_data) { pr_trace_msg("delay", 8, "mapping DelayTable '%s' (%" PR_LU " bytes, fd %d) into memory", delay_tab.dt_path, (pr_off_t) delay_tab.dt_size, delay_tab.dt_fd); delay_tab.dt_data = mmap(NULL, delay_tab.dt_size, PROT_READ|PROT_WRITE, MAP_SHARED, delay_tab.dt_fd, 0); if (delay_tab.dt_data == MAP_FAILED) return -1; } return 0; } static void delay_table_reset(void) { server_rec *s; for (s = (server_rec *) server_list->xas_list; s; s = s->next) { unsigned int i = s->sid - 1; /* Row for USER values */ struct delay_rec *row = &((struct delay_rec *) delay_tab.dt_data)[i]; row->d_sid = s->sid; sstrncpy(row->d_addr, pr_netaddr_get_ipstr(s->addr), sizeof(row->d_addr)); row->d_port = s->ServerPort; row->d_nvals = 0; memset(row->d_vals, 0, sizeof(row->d_vals)); /* Row for PASS values */ row = &((struct delay_rec *) delay_tab.dt_data)[i + 1]; row->d_sid = s->sid; sstrncpy(row->d_addr, pr_netaddr_get_ipstr(s->addr), sizeof(row->d_addr)); row->d_port = s->ServerPort; row->d_nvals = 0; memset(row->d_vals, 0, sizeof(row->d_vals)); } return; } static int delay_table_wlock(unsigned int rownum) { struct flock lock; lock.l_type = F_WRLCK; lock.l_whence = 0; lock.l_start = sizeof(struct delay_rec) * rownum; lock.l_len = sizeof(struct delay_rec); pr_trace_msg("delay", 8, "write-locking DelayTable '%s', row %u", delay_tab.dt_path, rownum + 1); while (fcntl(delay_tab.dt_fd, F_SETLKW, &lock) < 0) { if (errno == EINTR) { pr_signals_handle(); continue; } pr_trace_msg("delay", 1, "error locking row: %s", strerror(errno)); return -1; } return 0; } static int delay_table_unload(int unlock_table) { if (delay_tab.dt_data) { pr_trace_msg("delay", 8, "unmapping DelayTable '%s' from memory", delay_tab.dt_path); if (munmap(delay_tab.dt_data, delay_tab.dt_size) < 0) { pr_log_pri(PR_LOG_WARNING, MOD_DELAY_VERSION ": error unmapping DelayTable '%s': %s", delay_tab.dt_path, strerror(errno)); pr_trace_msg("delay", 1, "error unmapping DelayTable '%s': %s", delay_tab.dt_path, strerror(errno)); return -1; } delay_tab.dt_data = NULL; } if (unlock_table) { struct flock lock; lock.l_type = F_UNLCK; lock.l_whence = SEEK_SET; lock.l_start = 0; lock.l_len = 0; pr_trace_msg("delay", 8, "unlocking DelayTable '%s'", delay_tab.dt_path); while (fcntl(delay_tab.dt_fd, F_SETLK, &lock) < 0) { if (errno == EINTR) { pr_signals_handle(); continue; } else return -1; } } return 0; } static int delay_table_unlock(unsigned int rownum) { struct flock lock; lock.l_type = F_UNLCK; lock.l_whence = 0; lock.l_start = sizeof(struct delay_rec) * rownum; lock.l_len = sizeof(struct delay_rec); pr_trace_msg("delay", 8, "unlocking DelayTable '%s', row %u", delay_tab.dt_path, rownum + 1); while (fcntl(delay_tab.dt_fd, F_SETLKW, &lock) < 0) { if (errno == EINTR) { pr_signals_handle(); continue; } pr_trace_msg("delay", 1, "error unlocking row: %s", strerror(errno)); return -1; } return 0; } #if defined(PR_USE_CTRLS) /* Control handlers */ static int delay_handle_info(pr_ctrls_t *ctrl, int reqargc, char **reqargv) { register server_rec *s; pool *tmp_pool; pr_fh_t *fh; char *vals; PRIVS_ROOT fh = pr_fsio_open(delay_tab.dt_path, O_RDWR); PRIVS_RELINQUISH if (!fh) { pr_ctrls_add_response(ctrl, "warning: unable to open DelayTable '%s': %s", delay_tab.dt_path, strerror(errno)); return -1; } delay_tab.dt_fd = fh->fh_fd; delay_tab.dt_data = NULL; if (delay_table_load(TRUE) < 0) { pr_ctrls_add_response(ctrl, "unable to load DelayTable '%s' into memory: %s", delay_tab.dt_path, strerror(errno)); pr_trace_msg("delay", 1, "unable to load DelayTable '%s' into memory: %s", delay_tab.dt_path, strerror(errno)); pr_fsio_close(fh); delay_tab.dt_fd = -1; delay_tab.dt_data = NULL; return -1; } tmp_pool = make_sub_pool(delay_pool); for (s = (server_rec *) server_list->xas_list; s; s = s->next) { unsigned int r = s->sid - 1; register unsigned int i; /* Row for USER values */ struct delay_rec *row = &((struct delay_rec *) delay_tab.dt_data)[r]; pr_ctrls_add_response(ctrl, "Address %s#%u: %u USER values (usecs):", row->d_addr, row->d_port, row->d_nvals); /* Start at the end of the row and work backward, as values are * always added at the end of the row, shifting everything to the left. */ vals = ""; for (i = 1; i < row->d_nvals; i++) { char buf[80]; memset(buf, '\0', sizeof(buf)); sprintf(buf, "%10ld", row->d_vals[DELAY_NVALUES - i]); vals = pstrcat(tmp_pool, vals, " ", buf, NULL); if (i != 0 && i % 4 == 0) { pr_ctrls_add_response(ctrl, " %s", vals); vals = ""; } } if (strlen(vals) > 0) pr_ctrls_add_response(ctrl, " %s", vals); pr_ctrls_add_response(ctrl, "%s", ""); /* Row for PASS values */ row = &((struct delay_rec *) delay_tab.dt_data)[r + 1]; pr_ctrls_add_response(ctrl, "Address %s#%u: %u PASS values (usecs):", row->d_addr, row->d_port, row->d_nvals); vals = ""; for (i = 1; i < row->d_nvals; i++) { char buf[80]; memset(buf, '\0', sizeof(buf)); sprintf(buf, "%10ld", row->d_vals[DELAY_NVALUES - i]); vals = pstrcat(tmp_pool, vals, " ", buf, NULL); if (i != 0 && i % 4 == 0) { pr_ctrls_add_response(ctrl, " %s", vals); vals = ""; } } if (strlen(vals) > 0) pr_ctrls_add_response(ctrl, " %s", vals); } pr_ctrls_add_response(ctrl, "%s", ""); if (delay_table_unload(TRUE) < 0) { pr_ctrls_add_response(ctrl, "unable to unload DelayTable '%s' from memory: %s", delay_tab.dt_path, strerror(errno)); } pr_fsio_close(fh); delay_tab.dt_fd = -1; delay_tab.dt_data = NULL; destroy_pool(tmp_pool); return 0; } static int delay_handle_reset(pr_ctrls_t *ctrl, int reqargc, char **reqarg) { struct flock lock; pr_fh_t *fh; PRIVS_ROOT fh = pr_fsio_open(delay_tab.dt_path, O_RDWR); PRIVS_RELINQUISH if (!fh) { pr_ctrls_add_response(ctrl, "unable to open DelayTable '%s': %s", delay_tab.dt_path, strerror(errno)); return -1; } lock.l_type = F_WRLCK; lock.l_whence = 0; lock.l_start = 0; lock.l_len = 0; while (fcntl(fh->fh_fd, F_SETLKW, &lock) < 0) { if (errno == EINTR) { pr_signals_handle(); continue; } else { pr_ctrls_add_response(ctrl, "unable to obtain write lock on DelayTable '%s': %s", fh->fh_path, strerror(errno)); pr_fsio_close(fh); return -1; } } if (pr_fsio_ftruncate(fh, 0) < 0) { pr_ctrls_add_response(ctrl, "error truncating DelayTable '%s': %s", fh->fh_path, strerror(errno)); pr_fsio_close(fh); return -1; } lock.l_type = F_UNLCK; fcntl(fh->fh_fd, F_SETLK, &lock); if (pr_fsio_close(fh) < 0) { pr_ctrls_add_response(ctrl, "error closing DelayTable '%s': %s", delay_tab.dt_path, strerror(errno)); return -1; } pr_ctrls_add_response(ctrl, "DelayTable '%s' reset", delay_tab.dt_path); return 0; } static int delay_handle_delay(pr_ctrls_t *ctrl, int reqargc, char **reqargv) { if (reqargc == 0 || reqargv == NULL) { pr_ctrls_add_response(ctrl, "delay: missing required parameters"); return -1; } if (strcmp(reqargv[0], "info") == 0) { if (!ctrls_check_acl(ctrl, delay_acttab, "info")) { pr_ctrls_add_response(ctrl, "access denied"); return -1; } return delay_handle_info(ctrl, --reqargc, ++reqargv); } else if (strcmp(reqargv[0], "reset") == 0) { if (!ctrls_check_acl(ctrl, delay_acttab, "reset")) { pr_ctrls_add_response(ctrl, "access denied"); return -1; } return delay_handle_reset(ctrl, --reqargc, ++reqargv); } else { pr_ctrls_add_response(ctrl, "unknown delay action: '%s'", reqargv[0]); return -1; } return 0; } #endif /* PR_USE_CTRLS */ /* Configuration handlers */ /* usage: DelayControlsACLs actions|all allow|deny user|group list */ MODRET set_delayctrlsacls(cmd_rec *cmd) { #if defined(PR_USE_CTRLS) char *bad_action = NULL, **actions = NULL; CHECK_ARGS(cmd, 4); CHECK_CONF(cmd, CONF_ROOT); actions = ctrls_parse_acl(cmd->tmp_pool, cmd->argv[1]); /* Check the second parameter to make sure it is "allow" or "deny" */ if (strcmp(cmd->argv[2], "allow") != 0 && strcmp(cmd->argv[2], "deny") != 0) CONF_ERROR(cmd, "second parameter must be 'allow' or 'deny'"); /* Check the third parameter to make sure it is "user" or "group" */ if (strcmp(cmd->argv[3], "user") != 0 && strcmp(cmd->argv[3], "group") != 0) CONF_ERROR(cmd, "third parameter must be 'user' or 'group'"); bad_action = ctrls_set_module_acls(delay_acttab, delay_pool, actions, cmd->argv[2], cmd->argv[3], cmd->argv[4]); if (bad_action != NULL) CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": unknown delay action: '", bad_action, "'", NULL)); return PR_HANDLED(cmd); #else CONF_ERROR(cmd, "requires Controls support (--enable-ctrls)") #endif /* PR_USE_CTRLS */ } /* usage: DelayEngine on|off */ MODRET set_delayengine(cmd_rec *cmd) { config_rec *c; int bool; CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); bool = get_boolean(cmd, 1); if (bool == -1) CONF_ERROR(cmd, "expected Boolean parameter"); c = add_config_param(cmd->argv[0], 1, NULL); c->argv[0] = pcalloc(c->pool, sizeof(unsigned int)); *((unsigned int *) c->argv[0]) = bool; return PR_HANDLED(cmd); } /* usage: DelayTable path */ MODRET set_delaytable(cmd_rec *cmd) { CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT); if (pr_fs_valid_path(cmd->argv[1]) < 0) CONF_ERROR(cmd, "must be an absolute path"); add_config_param_str(cmd->argv[0], 1, cmd->argv[1]); return PR_HANDLED(cmd); } /* Command handlers */ MODRET delay_post_pass(cmd_rec *cmd) { struct timeval tv; unsigned int rownum; long interval, median; if (!delay_engine) return PR_DECLINED(cmd); /* We use sid-1, since the sid is a server number, and the locking * routines want a row index. However, PASS rows are always after * USER rows, so we need to add 1 to the row number, leaving us * with just the sid. */ rownum = main_server->sid; /* Prepare for manipulating the table. */ if (delay_table_load(FALSE) < 0) { pr_log_pri(PR_LOG_WARNING, MOD_DELAY_VERSION "warning: unable to load DelayTable '%s' into memory: %s", delay_tab.dt_path, strerror(errno)); pr_trace_msg("delay", 1, "unable to load DelayTable '%s' into memory: %s", delay_tab.dt_path, strerror(errno)); return PR_DECLINED(cmd); } delay_table_wlock(rownum); gettimeofday(&tv, NULL); interval = (tv.tv_sec - delay_tv.tv_sec) * 1000000 + (tv.tv_usec - delay_tv.tv_usec); /* Get the median interval value. */ median = delay_get_median(cmd->tmp_pool, rownum, interval); /* Add the interval to the table. Only allow a single session to * add a portion of the cache size, to prevent a single client from * poisoning the cache. */ if (delay_npass < (DELAY_NVALUES / DELAY_SESS_NVALUES)) { pr_trace_msg("delay", 8, "adding %ld usecs to PASS row", interval); delay_table_add_interval(rownum, interval); delay_npass++; } else /* Generate an event, in case a module (i.e. mod_ban) might want * to do something appropriate to such an ill-behaved client. */ pr_event_generate("mod_delay.max-pass", session.c); /* Done with the table. */ delay_table_unlock(rownum); if (delay_table_unload(FALSE) < 0) { pr_log_pri(PR_LOG_WARNING, MOD_DELAY_VERSION "warning: unable to unload DelayTable '%s' from memory: %s", delay_tab.dt_path, strerror(errno)); } /* If the current interval is less than the median interval, we * need to delay ourselves a little. */ if (interval < median) delay_delay(median - interval); return PR_DECLINED(cmd); } MODRET delay_pre_pass(cmd_rec *cmd) { if (!delay_engine) return PR_DECLINED(cmd); gettimeofday(&delay_tv, NULL); return PR_DECLINED(cmd); } MODRET delay_post_user(cmd_rec *cmd) { struct timeval tv; unsigned int rownum; long interval, median; if (!delay_engine) return PR_DECLINED(cmd); /* We use sid-1, since the sid is a server number, and the locking * routines want a row index. */ rownum = main_server->sid - 1; /* Prepare for manipulating the table. */ if (delay_table_load(FALSE) < 0) { pr_log_pri(PR_LOG_WARNING, MOD_DELAY_VERSION "warning: unable to load DelayTable '%s' into memory: %s", delay_tab.dt_path, strerror(errno)); pr_trace_msg("delay", 1, "unable to load DelayTable '%s' into memory: %s", delay_tab.dt_path, strerror(errno)); return PR_DECLINED(cmd); } delay_table_wlock(rownum); gettimeofday(&tv, NULL); interval = (tv.tv_sec - delay_tv.tv_sec) * 1000000 + (tv.tv_usec - delay_tv.tv_usec); /* Get the median interval value. */ median = delay_get_median(cmd->tmp_pool, rownum, interval); /* Add the interval to the table. Only allow a single session to * add a portion of the cache size, to prevent a single client from * poisoning the cache. */ if (delay_nuser < (DELAY_NVALUES / DELAY_SESS_NVALUES)) { pr_trace_msg("delay", 8, "adding %ld usecs to USER row", interval); delay_table_add_interval(rownum, interval); delay_nuser++; } else /* Generate an event, in case a module (i.e. mod_ban) might want * to do something appropriate to such an ill-behaved client. */ pr_event_generate("mod_delay.max-user", session.c); /* Done with the table. */ delay_table_unlock(rownum); if (delay_table_unload(FALSE) < 0) { pr_log_pri(PR_LOG_WARNING, MOD_DELAY_VERSION "warning: unable to unload DelayTable '%s' from memory: %s", delay_tab.dt_path, strerror(errno)); } /* If the current interval is less than the median interval, we * need to delay ourselves a little. */ if (interval < median) delay_delay(median - interval); return PR_DECLINED(cmd); } MODRET delay_pre_user(cmd_rec *cmd) { if (!delay_engine) return PR_DECLINED(cmd); gettimeofday(&delay_tv, NULL); return PR_DECLINED(cmd); } /* Event handlers */ static void delay_exit_ev(const void *event_data, void *user_data) { pr_fh_t *fh; char *data; size_t datalen; if (!delay_engine) return; /* Write out the DelayTable to the filesystem, thus updating the * file metadata. */ PRIVS_ROOT fh = pr_fsio_open(delay_tab.dt_path, O_RDWR); PRIVS_RELINQUISH if (!fh) { pr_log_pri(PR_LOG_WARNING, MOD_DELAY_VERSION ": warning: unable to open DelayTable '%s': %s", delay_tab.dt_path, strerror(errno)); return; } delay_tab.dt_fd = fh->fh_fd; delay_tab.dt_data = NULL; if (delay_table_load(TRUE) < 0) { int xerrno = errno; pr_fsio_close(fh); errno = xerrno; pr_log_pri(PR_LOG_WARNING, MOD_DELAY_VERSION "warning: unable to load DelayTable '%s' into memory: %s", delay_tab.dt_path, strerror(errno)); pr_trace_msg("delay", 1, "unable to load DelayTable '%s' into memory: %s", delay_tab.dt_path, strerror(errno)); return; } datalen = delay_tab.dt_size; data = palloc(delay_pool, datalen); memcpy(data, delay_tab.dt_data, datalen); if (delay_table_unload(TRUE) < 0) { pr_log_pri(PR_LOG_WARNING, MOD_DELAY_VERSION ": warning: error unloading DelayTable '%s' from memory: %s", delay_tab.dt_path, strerror(errno)); } if (pr_fsio_write(fh, data, datalen) < 0) { pr_log_pri(PR_LOG_WARNING, MOD_DELAY_VERSION ": warning: error updating DelayTable '%s': %s", delay_tab.dt_path, strerror(errno)); } if (pr_fsio_close(fh) < 0) { pr_log_pri(PR_LOG_WARNING, MOD_DELAY_VERSION ": warning: error writing DelayTable '%s': %s", delay_tab.dt_path, strerror(errno)); } return; } static void delay_postparse_ev(const void *event_data, void *user_data) { config_rec *c; c = find_config(main_server->conf, CONF_PARAM, "DelayEngine", FALSE); if (c && *((unsigned int *) c->argv[0]) == FALSE) delay_engine = FALSE; if (!delay_engine) return; c = find_config(main_server->conf, CONF_PARAM, "DelayTable", FALSE); if (c) delay_tab.dt_path = c->argv[0]; (void) delay_table_init(); return; } static void delay_restart_ev(const void *event_data, void *user_data) { if (delay_pool) destroy_pool(delay_pool); delay_pool = make_sub_pool(permanent_pool); pr_pool_tag(delay_pool, MOD_DELAY_VERSION); return; } /* Initialization functions */ static int delay_init(void) { delay_tab.dt_path = PR_RUN_DIR "/proftpd.delay"; delay_tab.dt_data = NULL; pr_event_register(&delay_module, "core.exit", delay_exit_ev, NULL); pr_event_register(&delay_module, "core.postparse", delay_postparse_ev, NULL); pr_event_register(&delay_module, "core.restart", delay_restart_ev, NULL); delay_pool = make_sub_pool(permanent_pool); pr_pool_tag(delay_pool, MOD_DELAY_VERSION); #if defined(PR_USE_CTRLS) if (pr_ctrls_register(&delay_module, "delay", "tune mod_delay settings", delay_handle_delay) < 0) { pr_log_pri(PR_LOG_INFO, MOD_DELAY_VERSION ": error registering 'delay' control: %s", strerror(errno)); } else { register unsigned int i; for (i = 0; delay_acttab[i].act_action; i++) { delay_acttab[i].act_acl = pcalloc(delay_pool, sizeof(ctrls_acl_t)); ctrls_init_acl(delay_acttab[i].act_acl); } } #endif /* PR_USE_CTRLS */ return 0; } static int delay_sess_init(void) { pr_fh_t *fh; config_rec *c; pr_event_unregister(&delay_module, "core.exit", delay_exit_ev); if (!delay_engine) return 0; /* Look up DelayEngine again, as it may have been disabled in an * section. */ c = find_config(main_server->conf, CONF_PARAM, "DelayEngine", FALSE); if (c && *((unsigned int *) c->argv[0]) == FALSE) delay_engine = FALSE; if (!delay_engine) return 0; delay_nuser = 0; delay_npass = 0; pr_trace_msg("delay", 6, "opening DelayTable '%s'", delay_tab.dt_path); PRIVS_ROOT fh = pr_fsio_open(delay_tab.dt_path, O_RDWR); PRIVS_RELINQUISH if (!fh) { pr_log_pri(PR_LOG_WARNING, MOD_DELAY_VERSION ": warning: unable to open DelayTable '%s': %s", delay_tab.dt_path, strerror(errno)); pr_trace_msg("delay", 1, "unable to open DelayTable '%s': %s", delay_tab.dt_path, strerror(errno)); delay_engine = FALSE; return 0; } delay_tab.dt_fd = fh->fh_fd; delay_tab.dt_data = NULL; return 0; } /* Module API tables */ #if defined(PR_USE_CTRLS) static ctrls_acttab_t delay_acttab[] = { { "info", NULL, NULL, NULL }, { "reset", NULL, NULL, NULL }, { NULL, NULL, NULL, NULL } }; #endif /* PR_USE_CTRLS */ static conftable delay_conftab[] = { { "DelayControlsACLs",set_delayctrlsacls, NULL }, { "DelayEngine", set_delayengine, NULL }, { "DelayTable", set_delaytable, NULL }, { NULL } }; static cmdtable delay_cmdtab[] = { { PRE_CMD, C_PASS, G_NONE, delay_pre_pass, FALSE, FALSE }, { POST_CMD, C_PASS, G_NONE, delay_post_pass, FALSE, FALSE }, { POST_CMD_ERR, C_PASS, G_NONE, delay_post_pass, FALSE, FALSE }, { PRE_CMD, C_USER, G_NONE, delay_pre_user, FALSE, FALSE }, { POST_CMD, C_USER, G_NONE, delay_post_user, FALSE, FALSE }, { POST_CMD_ERR, C_USER, G_NONE, delay_post_user, FALSE, FALSE }, { 0, NULL } }; module delay_module = { NULL, NULL, /* Module API version 2.0 */ 0x20, /* Module name */ "delay", /* Module configuration handler table */ delay_conftab, /* Module command handler table */ delay_cmdtab, /* Module authentication handler table */ NULL, /* Module initialization function */ delay_init, /* Session initialization function */ delay_sess_init, /* Module version */ MOD_DELAY_VERSION };