dnl
dnl This program is free software; you can redistribute it and/or modify
dnl it under the terms of the GNU General Public License as published by
dnl the Free Software Foundation; either version 2 of the License, or
dnl (at your option) any later version.
dnl
dnl This program is distributed in the hope that it will be useful,
dnl but WITHOUT ANY WARRANTY; without even the implied warranty of
dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
dnl GNU General Public License for more details.
dnl
dnl You should have received a copy of the GNU General Public License
dnl along with this program; if not, write to the Free Software
dnl Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
dnl
dnl Process this file with autoconf to produce a configure script.
AC_INIT(./mod_statsd.c)
AC_CANONICAL_SYSTEM
ostype=`echo $build_os | sed 's/\..*$//g' | sed 's/-.*//g' | tr abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ`
AC_PROG_CC
AC_PROG_CPP
AC_AIX
AC_ISC_POSIX
AC_MINIX
AC_PROG_MAKE_SET
dnl Need to support/handle the --enable-devel option, to see if coverage
dnl is being used
AC_ARG_ENABLE(devel,
[AC_HELP_STRING(
[--enable-devel],
[enable developer-only code (default=no)])
],
[
if test x"$enableval" != xno ; then
if test `echo $enableval | grep -c coverage` = "1" ; then
UTILS_LIBS="--coverage $UTILS_LIBS"
fi
fi
])
dnl Need to support/handle the --with-includes and --with-libraries options
AC_ARG_WITH(includes,
[AC_HELP_STRING(
[--with-includes=LIST],
[add additional include paths to proftpd. LIST is a colon-separated list of include paths to add e.g. --with-includes=/some/mysql/include:/my/include])
],
[ ac_addl_includes=`echo "$withval" | sed -e 's/:/ /g'` ;
for ainclude in $ac_addl_includes; do
if test x"$ac_build_addl_includes" = x ; then
ac_build_addl_includes="-I$ainclude"
else
ac_build_addl_includes="-I$ainclude $ac_build_addl_includes"
fi
done
CPPFLAGS="$CPPFLAGS $ac_build_addl_includes"
])
AC_ARG_WITH(libraries,
[AC_HELP_STRING(
[--with-libraries=LIST],
[add additional library paths to proftpd. LIST is a colon-separated list of include paths to add e.g. --with-libraries=/some/mysql/libdir:/my/libs])
],
[ ac_addl_libdirs=`echo "$withval" | sed -e 's/:/ /g'` ;
for alibdir in $ac_addl_libdirs; do
if test x"$ac_build_addl_libdirs" = x ; then
ac_build_addl_libdirs="-L$alibdir"
else
ac_build_addl_libdirs="-L$alibdir $ac_build_addl_libdirs"
fi
done
LDFLAGS="$LDFLAGS $ac_build_addl_libdirs"
])
AC_HEADER_STDC
AC_CHECK_HEADERS(stdlib.h unistd.h sys/sysctl.h sys/sysinfo.h)
AC_CHECK_FUNCS(random srandom sysctl sysinfo)
INCLUDES="$ac_build_addl_includes"
LIBDIRS="$ac_build_addl_libdirs"
AC_SUBST(INCLUDES)
AC_SUBST(LDFLAGS)
AC_SUBST(LIBDIRS)
AC_CONFIG_HEADER(mod_statsd.h)
AC_OUTPUT(
t/Makefile
Makefile
)
proftpd-mod_statsd-0.1/install-sh 0000775 0000000 0000000 00000012721 13066013616 0017200 0 ustar 00root root 0000000 0000000 #! /bin/sh
#
# install - install a program, script, or datafile
# This comes from X11R5 (mit/util/scripts/install.sh).
#
# Copyright 1991 by the Massachusetts Institute of Technology
#
# Permission to use, copy, modify, distribute, and sell this software and its
# documentation for any purpose is hereby granted without fee, provided that
# the above copyright notice appear in all copies and that both that
# copyright notice and this permission notice appear in supporting
# documentation, and that the name of M.I.T. not be used in advertising or
# publicity pertaining to distribution of the software without specific,
# written prior permission. M.I.T. makes no representations about the
# suitability of this software for any purpose. It is provided "as is"
# without express or implied warranty.
#
# Calling this script install-sh is preferred over install.sh, to prevent
# `make' implicit rules from creating a file called install from it
# when there is no Makefile.
#
# This script is compatible with the BSD install script, but was written
# from scratch. It can only install one file at a time, a restriction
# shared with many OS's install programs.
# set DOITPROG to echo to test this script
# Don't use :- since 4.3BSD and earlier shells don't like it.
doit="${DOITPROG-}"
# put in absolute paths if you don't have them in your path; or use env. vars.
mvprog="${MVPROG-mv}"
cpprog="${CPPROG-cp}"
chmodprog="${CHMODPROG-chmod}"
chownprog="${CHOWNPROG-chown}"
chgrpprog="${CHGRPPROG-chgrp}"
stripprog="${STRIPPROG-strip}"
rmprog="${RMPROG-rm}"
mkdirprog="${MKDIRPROG-mkdir}"
transformbasename=""
transform_arg=""
instcmd="$mvprog"
chmodcmd="$chmodprog 0755"
chowncmd=""
chgrpcmd=""
stripcmd=""
rmcmd="$rmprog -f"
mvcmd="$mvprog"
src=""
dst=""
dir_arg=""
while [ x"$1" != x ]; do
case $1 in
-c) instcmd="$cpprog"
shift
continue;;
-d) dir_arg=true
shift
continue;;
-m) chmodcmd="$chmodprog $2"
shift
shift
continue;;
-o) chowncmd="$chownprog $2"
shift
shift
continue;;
-g) chgrpcmd="$chgrpprog $2"
shift
shift
continue;;
-s) stripcmd="$stripprog"
shift
continue;;
-t=*) transformarg=`echo $1 | sed 's/-t=//'`
shift
continue;;
-b=*) transformbasename=`echo $1 | sed 's/-b=//'`
shift
continue;;
*) if [ x"$src" = x ]
then
src=$1
else
# this colon is to work around a 386BSD /bin/sh bug
:
dst=$1
fi
shift
continue;;
esac
done
if [ x"$src" = x ]
then
echo "install: no input file specified"
exit 1
else
true
fi
if [ x"$dir_arg" != x ]; then
dst=$src
src=""
if [ -d $dst ]; then
instcmd=:
else
instcmd=mkdir
fi
else
# Waiting for this to be detected by the "$instcmd $src $dsttmp" command
# might cause directories to be created, which would be especially bad
# if $src (and thus $dsttmp) contains '*'.
if [ -f $src -o -d $src ]
then
true
else
echo "install: $src does not exist"
exit 1
fi
if [ x"$dst" = x ]
then
echo "install: no destination specified"
exit 1
else
true
fi
# If destination is a directory, append the input filename; if your system
# does not like double slashes in filenames, you may need to add some logic
if [ -d $dst ]
then
dst="$dst"/`basename $src`
else
true
fi
fi
## this sed command emulates the dirname command
dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'`
# Make sure that the destination directory exists.
# this part is taken from Noah Friedman's mkinstalldirs script
# Skip lots of stat calls in the usual case.
if [ ! -d "$dstdir" ]; then
defaultIFS='
'
IFS="${IFS-${defaultIFS}}"
oIFS="${IFS}"
# Some sh's can't handle IFS=/ for some reason.
IFS='%'
set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'`
IFS="${oIFS}"
pathcomp=''
while [ $# -ne 0 ] ; do
pathcomp="${pathcomp}${1}"
shift
if [ ! -d "${pathcomp}" ] ;
then
$mkdirprog "${pathcomp}"
else
true
fi
pathcomp="${pathcomp}/"
done
fi
if [ x"$dir_arg" != x ]
then
$doit $instcmd $dst &&
if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi &&
if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi &&
if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi &&
if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi
else
# If we're going to rename the final executable, determine the name now.
if [ x"$transformarg" = x ]
then
dstfile=`basename $dst`
else
dstfile=`basename $dst $transformbasename |
sed $transformarg`$transformbasename
fi
# don't allow the sed command to completely eliminate the filename
if [ x"$dstfile" = x ]
then
dstfile=`basename $dst`
else
true
fi
# Make a temp file name in the proper directory.
dsttmp=$dstdir/#inst.$$#
# Move or copy the file name to the temp name
$doit $instcmd $src $dsttmp &&
trap "rm -f ${dsttmp}" 0 &&
# and set any options; do chmod last to preserve setuid bits
# If any of these fail, we abort the whole thing. If we want to
# ignore errors from any of these, just make sure not to ignore
# errors from the above "$doit $instcmd $src $dsttmp" command.
if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi &&
if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi &&
if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi &&
if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi &&
# Now rename the file to the real destination.
$doit $rmcmd -f $dstdir/$dstfile &&
$doit $mvcmd $dsttmp $dstdir/$dstfile
fi &&
exit 0
proftpd-mod_statsd-0.1/metric.c 0000664 0000000 0000000 00000010736 13066013616 0016627 0 ustar 00root root 0000000 0000000 /*
* ProFTPD: mod_statsd Metric API
* Copyright (c) 2017 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, the 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.
*/
#include "metric.h"
/* Don't allow timings longer than 1 year. */
#define STATSD_MAX_TIME_MS 31536000000UL
static const char *trace_channel = "statsd.metric";
/* Watch out for any characters which might interfere with the statsd format. */
static char *sanitize_name(pool *p, const char *name) {
char *cleaned_name, *ptr;
int adjusted_name = FALSE;
cleaned_name = pstrdup(p, name);
for (ptr = cleaned_name; *ptr; ptr++) {
if (*ptr == ':' ||
*ptr == '|' ||
*ptr == '@') {
*ptr = '_';
adjusted_name = TRUE;
}
}
if (adjusted_name == TRUE) {
pr_trace_msg(trace_channel, 12, "sanitized metric name '%s' into '%s'",
name, cleaned_name);
}
return cleaned_name;
}
static int write_metric(struct statsd *statsd, const char *metric_type,
const char *name, const char *val_prefix, int64_t val, float sampling) {
int res, xerrno;
pool *p, *tmp_pool;
const char *prefix = NULL, *suffix = NULL;
char *metric;
size_t metric_len;
statsd_statsd_get_namespacing(statsd, &prefix, &suffix);
p = statsd_statsd_get_pool(statsd);
tmp_pool = make_sub_pool(p);
metric_len = STATSD_MAX_METRIC_SIZE;
metric = pcalloc(tmp_pool, metric_len);
if (sampling >= 1.0) {
res = snprintf(metric, metric_len-1, "%s%s%s:%s%lld|%s",
prefix != NULL ? prefix : "", sanitize_name(tmp_pool, name),
suffix != NULL ? suffix : "", val_prefix, (long long) val, metric_type);
} else {
res = snprintf(metric, metric_len-1, "%s%s%s:%s%lld|%s|@%.2f",
prefix != NULL ? prefix : "", sanitize_name(tmp_pool, name),
suffix != NULL ? suffix : "", val_prefix, (long long) val, metric_type,
sampling);
}
res = statsd_statsd_write(statsd, metric, res, 0);
xerrno = errno;
destroy_pool(tmp_pool);
errno = xerrno;
return res;
}
int statsd_metric_counter(struct statsd *statsd, const char *name,
int64_t incr, int flags) {
float sampling;
if (statsd == NULL ||
name == NULL) {
errno = EINVAL;
return -1;
}
if (flags & STATSD_METRIC_FL_IGNORE_SAMPLING) {
sampling = 1.0;
} else {
sampling = statsd_statsd_get_sampling(statsd);
}
return write_metric(statsd, "c", name, "", incr, sampling);
}
int statsd_metric_timer(struct statsd *statsd, const char *name, uint64_t ms,
int flags) {
float sampling;
if (statsd == NULL ||
name == NULL) {
errno = EINVAL;
return -1;
}
if (ms > STATSD_MAX_TIME_MS) {
pr_trace_msg(trace_channel, 19, "truncating time %lu ms to max %lu ms",
(unsigned long) ms, (unsigned long) STATSD_MAX_TIME_MS);
ms = STATSD_MAX_TIME_MS;
}
if (flags & STATSD_METRIC_FL_IGNORE_SAMPLING) {
sampling = 1.0;
} else {
sampling = statsd_statsd_get_sampling(statsd);
}
return write_metric(statsd, "ms", name, "", ms, sampling);
}
int statsd_metric_gauge(struct statsd *statsd, const char *name, int64_t val,
int flags) {
char *val_prefix;
if (statsd == NULL ||
name == NULL) {
errno = EINVAL;
return -1;
}
val_prefix = "";
if (flags & STATSD_METRIC_FL_GAUGE_ADJUST) {
if (val > 0) {
val_prefix = "+";
}
} else {
/* If we are NOT adjusting an existing gauge value, then a negative
* gauge value makes no sense.
*/
if (val < 0) {
val = 0;
}
}
/* Unlikes counters and timers, gauges are NOT subject to sampling frequency;
* the statsd protocol does not allow for this, and rightly so.
*/
return write_metric(statsd, "g", name, val_prefix, val, 1.0);
}
proftpd-mod_statsd-0.1/metric.h 0000664 0000000 0000000 00000003341 13066013616 0016626 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_statsd Metric API
* Copyright (c) 2017 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., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, 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.
*/
#ifndef MOD_STATSD_METRIC_H
#define MOD_STATSD_METRIC_H
#include "mod_statsd.h"
#include "statsd.h"
int statsd_metric_counter(struct statsd *statsd, const char *name, int64_t incr,
int flags);
int statsd_metric_timer(struct statsd *statsd, const char *name, uint64_t ms,
int flags);
int statsd_metric_gauge(struct statsd *statsd, const char *name, int64_t val,
int flags);
/* Use this flag, for a gauge, for adjusting the existing gauge value, rather
* that setting it.
*/
#define STATSD_METRIC_FL_GAUGE_ADJUST 0x0001
/* Usage this flag to indicate that the metric is NOT subject to the sampling
* frequency.
*/
#define STATSD_METRIC_FL_IGNORE_SAMPLING 0x0002
#endif /* MOD_STATSD_METRIC_H */
proftpd-mod_statsd-0.1/mod_statsd.c 0000664 0000000 0000000 00000053220 13066013616 0017500 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_statsd
* Copyright (c) 2017 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., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, 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.
*
* -----DO NOT EDIT BELOW THIS LINE-----
* $Archive: mod_statsd.a $
*/
#include "mod_statsd.h"
#include "statsd.h"
#include "metric.h"
extern xaset_t *server_list;
module statsd_module;
#define STATSD_DEFAULT_ENGINE FALSE
#define STATSD_DEFAULT_SAMPLING 1.0F
static int statsd_engine = STATSD_DEFAULT_ENGINE;
static const char *statsd_exclude_filter = NULL;
#ifdef PR_USE_REGEX
static pr_regex_t *statsd_exclude_pre = NULL;
#endif /* PR_USE_REGEX */
static float statsd_sampling = STATSD_DEFAULT_SAMPLING;
static uint64_t statsd_sess_start_ms = 0;
static struct statsd *statsd = NULL;
static int statsd_sess_init(void);
static const char *trace_channel = "statsd";
static char *get_cmd_metric(pool *p, const char *cmd) {
const char *resp_code = NULL;
char *metric;
if (strcasecmp(cmd, C_QUIT) != 0) {
int res;
res = pr_response_get_last(p, &resp_code, NULL);
if (res < 0 ||
resp_code == NULL) {
resp_code = "-";
}
} else {
resp_code = R_221;
}
metric = pstrcat(p, "command.", cmd, ".", resp_code, NULL);
return metric;
}
static char *get_conn_metric(pool *p, const char *name) {
char *metric;
if (name == NULL) {
metric = pstrdup(p, "connection");
} else {
metric = pstrcat(p, name, ".connection", NULL);
}
return metric;
}
static char *get_timeout_metric(pool *p, const char *name) {
char *metric;
metric = pstrcat(p, "timeout.", name, NULL);
return metric;
}
static char *get_tls_metric(pool *p, const char *name) {
char *metric;
metric = pstrcat(p, "tls.", name, NULL);
return metric;
}
static int should_exclude(cmd_rec *cmd) {
int exclude = FALSE;
#ifdef PR_USE_REGEX
if (pr_regexp_exec(statsd_exclude_pre, (char *) cmd->argv[0], 0, NULL, 0, 0,
0) == 0) {
exclude = TRUE;
}
#endif /* PR_USE_REGEX */
return exclude;
}
static int should_sample(float sampling) {
float p;
if (sampling >= 1.0) {
return TRUE;
}
#ifdef HAVE_RANDOM
p = ((float) random() / RAND_MAX);
#else
p = ((float) rand() / RAND_MAX);
#endif /* HAVE_RANDOM */
pr_trace_msg(trace_channel, 19, "sampling: p = %f, sample percentage = %f", p,
sampling);
if (p > sampling) {
return FALSE;
}
return TRUE;
}
/* Configuration handlers
*/
/* usage: StatsdEngine on|off */
MODRET set_statsdengine(cmd_rec *cmd) {
int engine = -1;
config_rec *c;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
engine = get_boolean(cmd, 1);
if (engine == -1) {
CONF_ERROR(cmd, "expected Boolean parameter");
}
c = add_config_param(cmd->argv[0], 1, NULL);
c->argv[0] = palloc(c->pool, sizeof(int));
*((int *) c->argv[0]) = engine;
return PR_HANDLED(cmd);
}
/* usage: StatsdExcludeFilter regex|"none" */
MODRET set_statsdexcludefilter(cmd_rec *cmd) {
#ifdef PR_USE_REGEX
pr_regex_t *pre = NULL;
config_rec *c;
char *pattern;
int res;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
if (strcasecmp(cmd->argv[1], "none") == 0) {
(void) add_config_param(cmd->argv[0], 0);
return PR_HANDLED(cmd);
}
pre = pr_regexp_alloc(&statsd_module);
pattern = cmd->argv[1];
res = pr_regexp_compile(pre, pattern, REG_EXTENDED|REG_NOSUB);
if (res != 0) {
char errstr[256] = {'\0'};
pr_regexp_error(res, pre, errstr, sizeof(errstr));
pr_regexp_free(NULL, pre);
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "'", pattern,
"' failed regex compilation: ", errstr, NULL));
}
c = add_config_param(cmd->argv[0], 2, NULL, NULL);
c->argv[0] = pstrdup(c->pool, pattern);
c->argv[1] = (void *) pre;
return PR_HANDLED(cmd);
#else
CONF_ERROR(cmd, "The StatsdExcludeFilter directive cannot be used on this "
"system, as you do not have POSIX compliant regex support");
#endif
}
/* usage: StatsdSampling percentage */
MODRET set_statsdsampling(cmd_rec *cmd) {
config_rec *c;
char *ptr = NULL;
float percentage, sampling;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
percentage = strtof(cmd->argv[1], &ptr);
if (ptr && *ptr) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "badly formatted percentage value: ",
cmd->argv[1], NULL));
}
if (percentage <= 0.0 ||
percentage > 100.0) {
CONF_ERROR(cmd, "percentage must be between 0 and 100");
}
/* For easier comparison with e.g. random(3) values, and for formatting
* the statsd metric values, we convert from a 1-100 value to 0.00-1.00.
*/
sampling = percentage / 100.0;
c = add_config_param(cmd->argv[0], 1, NULL);
c->argv[0] = palloc(c->pool, sizeof(float));
*((float *) c->argv[0]) = sampling;
return PR_HANDLED(cmd);
}
/* usage: StatsdServer [scheme://]host[:port] [prefix] [suffix] */
MODRET set_statsdserver(cmd_rec *cmd) {
config_rec *c;
char *server, *ptr;
size_t server_len;
int port = STATSD_DEFAULT_PORT, use_tcp = FALSE;
if (cmd->argc < 2 ||
cmd->argc > 4) {
CONF_ERROR(cmd, "wrong number of parameters");
}
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
server = pstrdup(cmd->tmp_pool, cmd->argv[1]);
if (strncasecmp(server, "tcp://", 6) == 0) {
use_tcp = TRUE;
server += 6;
} else if (strncasecmp(server, "udp://", 6) == 0) {
use_tcp = FALSE;
server += 6;
}
server_len = strlen(server);
ptr = strrchr(server, ':');
if (ptr != NULL) {
/* We also need to check for IPv6 addresses, e.g. "[::1]" or "[::1]:8125",
* before assuming that the text following our discovered ':' is indeed
* a port number.
*/
if (*server == '[') {
if (*(ptr-1) == ']') {
/* We have an IPv6 address with an explicit port number. */
server = pstrndup(cmd->tmp_pool, server + 1, (ptr - 1) - (server + 1));
*ptr = '\0';
port = atoi(ptr + 1);
} else if (server[server_len-1] == ']') {
/* We have an IPv6 address without an explicit port number. */
server = pstrndup(cmd->tmp_pool, server + 1, server_len - 2);
port = STATSD_DEFAULT_PORT;
}
} else {
*ptr = '\0';
port = atoi(ptr + 1);
}
}
c = add_config_param(cmd->argv[0], 5, NULL, NULL, NULL, NULL, NULL);
c->argv[0] = pstrdup(c->pool, server);
c->argv[1] = palloc(c->pool, sizeof(int));
*((int *) c->argv[1]) = port;
c->argv[2] = pcalloc(c->pool, sizeof(int));
*((int *) c->argv[2]) = use_tcp;
if (cmd->argc > 2) {
char *prefix;
prefix = cmd->argv[2];
if (*prefix) {
/* Automatically append a '.' here, to make construction of the metric
* name easier.
*/
c->argv[3] = pstrcat(c->pool, prefix, ".", NULL);
}
}
if (cmd->argc == 4) {
char *suffix;
suffix = cmd->argv[3];
if (*suffix) {
/* Automatically prepend a '.' here, to make construction of the metric
* name easier.
*/
c->argv[4] = pstrcat(c->pool, ".", suffix, NULL);
}
}
return PR_HANDLED(cmd);
}
/* Command handlers
*/
static void log_tls_auth_metrics(cmd_rec *cmd, uint64_t now_ms) {
const uint64_t *start_ms;
char *handshake_metric, *proto_metric, *protocol_env, *cipher_env;
handshake_metric = get_tls_metric(cmd->tmp_pool, "handshake.ctrl");
statsd_metric_counter(statsd, handshake_metric, 1, 0);
proto_metric = get_conn_metric(cmd->tmp_pool, "ftps");
statsd_metric_counter(statsd, proto_metric, 1, 0);
statsd_metric_gauge(statsd, proto_metric, 1, STATSD_METRIC_FL_GAUGE_ADJUST);
start_ms = pr_table_get(cmd->notes, "start_ms", NULL);
if (start_ms != NULL) {
uint64_t handshake_ms;
handshake_ms = now_ms - *start_ms;
statsd_metric_timer(statsd, handshake_metric, handshake_ms, 0);
}
cipher_env = pr_env_get(cmd->tmp_pool, "TLS_CIPHER");
if (cipher_env != NULL) {
char *cipher_metric;
cipher_metric = get_tls_metric(cmd->tmp_pool,
pstrcat(cmd->tmp_pool, "cipher.", cipher_env, NULL));
statsd_metric_counter(statsd, cipher_metric, 1, 0);
}
protocol_env = pr_env_get(cmd->tmp_pool, "TLS_PROTOCOL");
if (protocol_env != NULL) {
char *protocol_metric;
protocol_metric = get_tls_metric(cmd->tmp_pool,
pstrcat(cmd->tmp_pool, "protocol.", protocol_env, NULL));
statsd_metric_counter(statsd, protocol_metric, 1, 0);
}
}
static void log_tls_metrics(cmd_rec *cmd, int had_error, uint64_t now_ms) {
if (pr_module_exists("mod_tls.c") != TRUE) {
return;
}
if (pr_cmd_cmp(cmd, PR_CMD_AUTH_ID) == 0 &&
cmd->argc == 2) {
char *tls_mode;
/* Find out if the args are one of the mod_tls (vs GSSAPI et al) ones. */
tls_mode = cmd->argv[1];
if (strcasecmp(tls_mode, "TLS") == 0 ||
strcasecmp(tls_mode, "TLS-C") == 0 ||
strcasecmp(tls_mode, "TLS-P") == 0 ||
strcasecmp(tls_mode, "SSL") == 0) {
/* We are only interested in tracking successful handshakes here; the
* failed handshakes are tracked elsewhere.
*/
if (had_error == FALSE) {
log_tls_auth_metrics(cmd, now_ms);
}
}
}
}
static void log_cmd_metrics(cmd_rec *cmd, int had_error) {
char *metric;
const uint64_t *start_ms = NULL;
uint64_t now_ms = 0;
if (statsd_engine == FALSE) {
return;
}
pr_gettimeofday_millis(&now_ms);
if (should_exclude(cmd) == TRUE) {
pr_trace_msg(trace_channel, 9,
"command '%s' excluded by StatsdExcludeFilter '%s'", (char *) cmd->argv[0],
statsd_exclude_filter);
return;
}
if (should_sample(statsd_sampling) != TRUE) {
pr_trace_msg(trace_channel, 28, "skipping sampling of metric for '%s'",
(char *) cmd->argv[0]);
return;
}
metric = get_cmd_metric(cmd->tmp_pool, cmd->argv[0]);
statsd_metric_counter(statsd, metric, 1, 0);
start_ms = pr_table_get(cmd->notes, "start_ms", NULL);
if (start_ms != NULL) {
uint64_t response_ms;
response_ms = now_ms - *start_ms;
statsd_metric_timer(statsd, metric, response_ms, 0);
}
log_tls_metrics(cmd, had_error, now_ms);
if (pr_cmd_cmp(cmd, PR_CMD_PASS_ID) == 0 &&
had_error == FALSE) {
const char *proto;
proto = pr_session_get_protocol(0);
if (strcmp(proto, "ftp") == 0) {
char *proto_metric;
/* At this point in time, we are certain that we have a plain FTP
* connection, not FTPS or SFTP or anything else.
*/
proto_metric = get_conn_metric(cmd->tmp_pool, "ftp");
statsd_metric_counter(statsd, proto_metric, 1, 0);
statsd_metric_gauge(statsd, proto_metric, 1, STATSD_METRIC_FL_GAUGE_ADJUST);
}
}
statsd_statsd_flush(statsd);
}
MODRET statsd_log_any(cmd_rec *cmd) {
log_cmd_metrics(cmd, FALSE);
return PR_DECLINED(cmd);
}
MODRET statsd_log_any_err(cmd_rec *cmd) {
log_cmd_metrics(cmd, TRUE);
return PR_DECLINED(cmd);
}
/* Event handlers
*/
static void statsd_exit_ev(const void *event_data, void *user_data) {
if (statsd != NULL) {
char *metric;
unsigned char *authenticated;
metric = get_conn_metric(session.pool, NULL);
statsd_metric_gauge(statsd, metric, -1, STATSD_METRIC_FL_GAUGE_ADJUST);
authenticated = get_param_ptr(main_server->conf, "authenticated", FALSE);
if (authenticated != NULL &&
*authenticated == TRUE) {
const char *proto;
uint64_t now_ms = 0, sess_ms;
proto = pr_session_get_protocol(0);
metric = get_conn_metric(session.pool, proto);
statsd_metric_gauge(statsd, metric, -1, STATSD_METRIC_FL_GAUGE_ADJUST);
pr_gettimeofday_millis(&now_ms);
sess_ms = now_ms - statsd_sess_start_ms;
statsd_metric_timer(statsd, metric, sess_ms, 0);
}
statsd_statsd_close(statsd);
statsd = NULL;
}
}
#if defined(PR_SHARED_MODULE)
static void statsd_mod_unload_ev(const void *event_data, void *user_data) {
if (strcmp("mod_statsd.c", (const char *) event_data) == 0) {
pr_event_unregister(&statsd_module, NULL, NULL);
}
}
#endif /* PR_SHARED_MODULE */
static void statsd_postparse_ev(const void *event_data, void *user_data) {
server_rec *s;
for (s = (server_rec *) server_list->xas_list; s; s = s->next) {
config_rec *c;
int engine;
c = find_config(s->conf, CONF_PARAM, "StatsdEngine", FALSE);
if (c == NULL) {
continue;
}
engine = *((int *) c->argv[0]);
if (engine == FALSE) {
continue;
}
c = find_config(s->conf, CONF_PARAM, "StatsdServer", FALSE);
if (c == NULL) {
pr_log_pri(PR_LOG_NOTICE, MOD_STATSD_VERSION
": Server %s: missing required StatsdServer directive", s->ServerName);
pr_session_disconnect(&statsd_module, PR_SESS_DISCONNECT_BAD_CONFIG,
NULL);
}
}
}
static void statsd_sess_reinit_ev(const void *event_data, void *user_data) {
int res;
/* A HOST command changed the main_server pointer; reinitialize ourselves. */
pr_event_unregister(&statsd_module, "core.exit", statsd_exit_ev);
pr_event_unregister(&statsd_module, "core.session-reinit",
statsd_sess_reinit_ev);
/* Reset internal state. */
statsd_engine = STATSD_DEFAULT_ENGINE;
statsd_exclude_filter = NULL;
#ifdef PR_USE_REGEX
statsd_exclude_pre = NULL;
#endif /* PR_USE_REGEX */
statsd_sampling = STATSD_DEFAULT_SAMPLING;
if (statsd != NULL) {
statsd_statsd_close(statsd);
statsd = NULL;
}
res = statsd_sess_init();
if (res < 0) {
pr_session_disconnect(&statsd_module, PR_SESS_DISCONNECT_SESSION_INIT_FAILED,
NULL);
}
}
static void statsd_shutdown_ev(const void *event_data, void *user_data) {
if (statsd != NULL) {
statsd_statsd_close(statsd);
statsd = NULL;
}
}
static void statsd_ssh2_sftp_sess_opened_ev(const void *event_data,
void *user_data) {
pool *tmp_pool;
char *proto_metric;
if (should_sample(statsd_sampling) == FALSE) {
return;
}
tmp_pool = make_sub_pool(session.pool);
proto_metric = get_conn_metric(tmp_pool, "sftp");
statsd_metric_counter(statsd, proto_metric, 1, 0);
statsd_metric_gauge(statsd, proto_metric, 1, STATSD_METRIC_FL_GAUGE_ADJUST);
statsd_statsd_flush(statsd);
destroy_pool(tmp_pool);
}
static void statsd_ssh2_scp_sess_opened_ev(const void *event_data,
void *user_data) {
pool *tmp_pool;
char *proto_metric;
if (should_sample(statsd_sampling) == FALSE) {
return;
}
tmp_pool = make_sub_pool(session.pool);
proto_metric = get_conn_metric(tmp_pool, "scp");
statsd_metric_counter(statsd, proto_metric, 1, 0);
statsd_metric_gauge(statsd, proto_metric, 1, STATSD_METRIC_FL_GAUGE_ADJUST);
statsd_statsd_flush(statsd);
destroy_pool(tmp_pool);
}
static void incr_timeout_metric(const char *name) {
pool *tmp_pool;
char *metric;
/* Unlike other common metrics, for now the timeout counters are NOT subject
* to the sampling frequency.
*/
tmp_pool = make_sub_pool(session.pool);
metric = get_timeout_metric(tmp_pool, name);
statsd_metric_counter(statsd, metric, 1, STATSD_METRIC_FL_IGNORE_SAMPLING);
statsd_statsd_flush(statsd);
destroy_pool(tmp_pool);
}
static void statsd_timeout_idle_ev(const void *event_data, void *user_data) {
incr_timeout_metric("TimeoutIdle");
}
static void statsd_timeout_login_ev(const void *event_data, void *user_data) {
incr_timeout_metric("TimeoutLogin");
}
static void statsd_timeout_noxfer_ev(const void *event_data, void *user_data) {
incr_timeout_metric("TimeoutNoTransfer");
}
static void statsd_timeout_session_ev(const void *event_data, void *user_data) {
incr_timeout_metric("TimeoutSession");
}
static void statsd_timeout_stalled_ev(const void *event_data, void *user_data) {
incr_timeout_metric("TimeoutStalled");
}
static void incr_tls_handshake_error_metric(const char *name) {
pool *tmp_pool;
char *metric;
tmp_pool = make_sub_pool(session.pool);
/* Unlike other common metrics, for now the TLS handshake counters are NOT
* subject to the sampling frequency.
*/
metric = get_tls_metric(tmp_pool, name);
statsd_metric_counter(statsd, metric, 1, STATSD_METRIC_FL_IGNORE_SAMPLING);
statsd_statsd_flush(statsd);
destroy_pool(tmp_pool);
}
static void statsd_tls_ctrl_handshake_error_ev(const void *event_data,
void *user_data) {
incr_tls_handshake_error_metric("handshake.ctrl.error");
}
static void statsd_tls_data_handshake_error_ev(const void *event_data,
void *user_data) {
incr_tls_handshake_error_metric("handshake.data.error");
}
/* Initialization functions
*/
static int statsd_sess_init(void) {
config_rec *c;
char *host, *metric, *prefix = NULL, *suffix = NULL;
int port, use_tcp = FALSE;
const pr_netaddr_t *addr;
pr_event_register(&statsd_module, "core.session-reinit", statsd_sess_reinit_ev,
NULL);
c = find_config(main_server->conf, CONF_PARAM, "StatsdEngine", FALSE);
if (c != NULL) {
statsd_engine = *((int *) c->argv[0]);
}
if (statsd_engine == FALSE) {
return 0;
}
c = find_config(main_server->conf, CONF_PARAM, "StatsdServer", FALSE);
if (c == NULL) {
pr_log_debug(DEBUG10, MOD_STATSD_VERSION
": missing required StatsdServer directive, disabling module");
statsd_engine = FALSE;
return 0;
}
host = c->argv[0];
addr = pr_netaddr_get_addr(session.pool, host, NULL);
if (addr == NULL) {
pr_log_pri(PR_LOG_NOTICE, MOD_STATSD_VERSION
": error resolving '%s' to IP address: %s", host, strerror(errno));
statsd_engine = FALSE;
return 0;
}
port = *((int *) c->argv[1]);
pr_netaddr_set_port2((pr_netaddr_t *) addr, port);
use_tcp = *((int *) c->argv[2]);
prefix = c->argv[3];
suffix = c->argv[4];
statsd = statsd_statsd_open(session.pool, addr, use_tcp, statsd_sampling,
prefix, suffix);
if (statsd == NULL) {
pr_log_pri(PR_LOG_NOTICE, MOD_STATSD_VERSION
": error opening statsd connection to %s%s:%d: %s",
use_tcp ? "tcp://" : "udp://", host, port, strerror(errno));
statsd_engine = FALSE;
return 0;
}
#ifdef HAVE_SRANDOM
srandom((unsigned int) (time(NULL) ^ getpid()));
#else
srand((unsigned int) (time(NULL) ^ getpid()));
#endif /* HAVE_SRANDOM */
c = find_config(main_server->conf, CONF_PARAM, "StatsdExcludeFilter", FALSE);
if (c != NULL &&
c->argc == 2) {
statsd_exclude_filter = c->argv[0];
statsd_exclude_pre = c->argv[1];
}
c = find_config(main_server->conf, CONF_PARAM, "StatsdSampling", FALSE);
if (c != NULL) {
statsd_sampling = *((float *) c->argv[0]);
}
metric = get_conn_metric(session.pool, NULL);
statsd_metric_gauge(statsd, metric, 1, STATSD_METRIC_FL_GAUGE_ADJUST);
statsd_statsd_flush(statsd);
pr_event_register(&statsd_module, "core.exit", statsd_exit_ev, NULL);
pr_event_register(&statsd_module, "core.timeout-idle",
statsd_timeout_idle_ev, NULL);
pr_event_register(&statsd_module, "core.timeout-login",
statsd_timeout_login_ev, NULL);
pr_event_register(&statsd_module, "core.timeout-no-transfer",
statsd_timeout_noxfer_ev, NULL);
pr_event_register(&statsd_module, "core.timeout-session",
statsd_timeout_session_ev, NULL);
pr_event_register(&statsd_module, "core.timeout-stalled",
statsd_timeout_stalled_ev, NULL);
if (pr_module_exists("mod_sftp.c") == TRUE) {
pr_event_register(&statsd_module, "mod_sftp.sftp.session-opened",
statsd_ssh2_sftp_sess_opened_ev, NULL);
pr_event_register(&statsd_module, "mod_sftp.scp.session-opened",
statsd_ssh2_scp_sess_opened_ev, NULL);
}
if (pr_module_exists("mod_tls.c") == TRUE) {
pr_event_register(&statsd_module, "mod_tls.ctrl-handshake-failed",
statsd_tls_ctrl_handshake_error_ev, NULL);
pr_event_register(&statsd_module, "mod_tls.data-handshake-failed",
statsd_tls_data_handshake_error_ev, NULL);
}
/* We only want to set the session start time once; this function could be
* called again due to e.g. a HOST command, and we do not want to reset
* the start time in that case.
*/
if (statsd_sess_start_ms == 0) {
pr_gettimeofday_millis(&statsd_sess_start_ms);
}
return 0;
}
static int statsd_init(void) {
#if defined(PR_SHARED_MODULE)
pr_event_register(&statsd_module, "core.module-unload", statsd_mod_unload_ev,
NULL);
#endif
pr_event_register(&statsd_module, "core.postparse", statsd_postparse_ev,
NULL);
pr_event_register(&statsd_module, "core.shutdown", statsd_shutdown_ev,
NULL);
return 0;
}
/* Module API tables
*/
static conftable statsd_conftab[] = {
{ "StatsdEngine", set_statsdengine, NULL },
{ "StatsdExcludeFilter", set_statsdexcludefilter, NULL },
{ "StatsdSampling", set_statsdsampling, NULL },
{ "StatsdServer", set_statsdserver, NULL },
{ NULL }
};
static cmdtable statsd_cmdtab[] = {
{ LOG_CMD, C_ANY, G_NONE, statsd_log_any, FALSE, FALSE },
{ LOG_CMD_ERR, C_ANY, G_NONE, statsd_log_any_err, FALSE, FALSE },
};
module statsd_module = {
NULL, NULL,
/* Module API version 2.0 */
0x20,
/* Module name */
"statsd",
/* Module config handler table */
statsd_conftab,
/* Module command handler table */
statsd_cmdtab,
/* Module auth handler table */
NULL,
/* Module init function */
statsd_init,
/* Session init function */
statsd_sess_init,
/* Module version */
MOD_STATSD_VERSION
};
proftpd-mod_statsd-0.1/mod_statsd.h.in 0000664 0000000 0000000 00000002740 13066013616 0020113 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_statsd
* Copyright (c) 2017 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., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, 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.
*/
#ifndef MOD_STATSD_H
#define MOD_STATSD_H
#include "conf.h"
#define MOD_STATSD_VERSION "mod_statsd/0.1"
/* Make sure the version of proftpd is as necessary. */
#if PROFTPD_VERSION_NUMBER < 0x0001030602
# error "ProFTPD 1.3.6rc2 or later required"
#endif
#define STATSD_DEFAULT_PORT 8125
/* Define if you have the random(3) function. */
#undef HAVE_RANDOM
/* Define if you have the srandom(3) function. */
#undef HAVE_SRANDOM
#endif /* MOD_STATSD_H */
proftpd-mod_statsd-0.1/mod_statsd.html 0000664 0000000 0000000 00000022577 13066013616 0020235 0 ustar 00root root 0000000 0000000
ProFTPD module mod_statsd
ProFTPD module mod_statsd
The mod_statsd
module instruments the ProFTPD code to emit
metrics directly to a
statsd
server,
avoiding the need for custom log file parsing.
This module is contained in the mod_statsd
files for
ProFTPD 1.3.x, and is not compiled by default. Installation
instructions are discussed here. More examples
of mod_statsd
usage can be found here.
The most current version of mod_statsd
can be found at:
https://github.com/Castaglia/proftpd-mod_statsd
Author
Please contact TJ Saunders <tj at castaglia.org> with any
questions, concerns, or suggestions regarding this module.
Directives
Syntax: StatsdEngine on|off
Default: off
Context: server config, <VirtualHost>
, <Global>
Module: mod_statsd
Compatibility: 1.3.6rc1 and later
The StatsdEngine
directive enables or disables the emitting of
metrics to the configured statsd
server.
Syntax: StatsdExcludeFilter regex|"none"
Default: None
Context: server config, <VirtualHost>
, <Global>
Module: mod_statsd
Compatibility: 1.3.6rc1 and later
The StatsdExcludeFilter
directive configures a regular expression
filter that is applied to every command. Any command which matches the configured
regular expression will not be sampled by mod_statsd
.
Example:
# Exclude SYST commands from our metrics
StatsdExcludeFilter ^SYST$
Syntax: StatsdSampling percentage
Default: 100
Context: server config, <VirtualHost>
, <Global>
Module: mod_statsd
Compatibility: 1.3.6rc1 and later
The StatsdSampling
directive configures the percentage
of events that mod_statsd
will sample sample, and thus
send to statsd
. With very busy systems, sampling 100% of the
metrics can place undue stress on the metrics collection, while sampling a
smaller percentage of the events can still provide a statistically relevant
view of the system.
The configured percentage value must be between 1 and 100.
Example:
# Sample only 10 percent of the metrics
StatsdSampling 10
Syntax: StatsdServer [scheme://]address[:port] [prefix] [suffix]
Default: None
Context: server config, <VirtualHost>
, <Global>
Module: mod_statsd
Compatibility: 1.3.6rc1 and later
The StatsdServer
directive is used to configure the IP address/port
of the statsd
server that the mod_statsd
module is
to use. For example:
StatsdServer 1.2.3.4:8125
or, for an IPv6 address, make sure the IPv6 address is enclosed in square
brackets:
StatsdServer [::ffff:1.2.3.4]:8125
By default, the mod_statsd
module will use UDP when sending metrics
to the configured statsd
server. For more reliable (but slower)
transmission of data using TCP, use the optional scheme prefix,
e.g.:
# Use TCP instead of UDP
StatsdServer tcp://1.2.3.4:8125
or, to explicitly declare that you want to use UDP:
# Use UDP, with an IPv6 address
StatsdServer udp://[::ffff:1.2.3.4]:8125
The StatsdServer
directive also supports optional prefix
and suffix values. These are strings which will be used as prefixes
and suffixes to the metric names. For example:
StatsdServer udp://1.2.3.4:8125 proftpd.prod ftp03
This will use the prefix "proftpd.prod", and the suffix "ftp03", to generate
counter/timer metric names such as proftpd.proftpd.command.PASS.230.ftp03
, instead of the default command.PASS.230
. Other examples:
# Use a prefix but no suffix for the metrics
StatsdServer udp://1.2.3.4:8125 proftpd.prod
# Use a suffix but no prefix for the metrics
StatsdServer udp://1.2.3.4:8125 "" ftp03
To install mod_statsd
, copy the mod_statsd
files into:
proftpd-dir/contrib/
after unpacking the latest proftpd-1.3.x source code. For including
mod_statsd
as a staticly linked module:
$ ./configure --with-modules=mod_statsd
To build mod_statsd
as a DSO module:
$ ./configure --enable-dso --with-shared=mod_statsd
Then follow the usual steps:
$ make
$ make install
An example configuration:
<IfModule mod_statsd.c>
StatsdEngine on
StatsdServer 127.0.0.1:8125
</IfModule>
Metrics
The mod_statsd
module emits one counter, and one timer, for each
command processed. For example, a USER
command which receives
a 331 response code would generate a counter and a timer whose name is:
command.USER.331
Optional metric prefixes and/or suffixes can be configured
via the StatsdServer
directive,
e.g.:
StatsdServer udp://1.2.3.4:8125 prod ftp01
would result in command metric names like:
prod.command.USER.331.ftp01
For each connection, mod_statsd
will emit one counter and one gauge
using a metric name of:
connection
Protocol-specific connection metrics are also emitted: a counter, a timer, and
a gauge, all using a metric name of "protocol.connection", thus:
ftp.connection
ftps.connection
sftp.connection
scp.connection
In addition, mod_statsd
increments counters when the following
timeouts are encountered:
using a metric name of "timeout.name", like:
timeout.TimeoutLogin
TLS-Specific Metrics
For FTPS connections, mod_statsd
emits some TLS-specific metrics.
The metric name tls.handshake.ctrl
is used for both a counter and
a timer, for tracking number of successful TLS handshakes and how
long they take. For failed TLS handshakes, the following two counters
are used:
tls.hansdshake.ctrl.error
tls.hansdshake.data.error
Counters on the TLS protocol versions and ciphers used by FTPS clients are also
available. Note that these TLS-related counter metrics are only available when
FTPS is used, and when the StdEnvVars
TLSOption
is enabled:
TLSOptions StdEnvVar
The counters in question are:
tls.cipher.cipher
tls.protocol.protocol
thus, for example, you would see:
tls.cipher.ECDHE-RSA-AES128-SHA
tls.protocol.TLSv1
Logging
The mod_statsd
module supports trace logging, via the module-specific log channels:
- statsd
- statsd.metric
- statsd.statsd
Thus for trace logging, to aid in debugging, you would use the following in
your proftpd.conf
:
TraceLog /path/to/ftpd/trace.log
Trace statsd:20
This trace logging can generate large files; it is intended for debugging use
only, and should be removed from any production configuration.
© Copyright 2017 TJ Saunders
All Rights Reserved
proftpd-mod_statsd-0.1/statsd.c 0000664 0000000 0000000 00000020416 13066013616 0016642 0 ustar 00root root 0000000 0000000 /*
* ProFTPD: mod_statsd Statsd API
* Copyright (c) 2017 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, the 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.
*/
#include "statsd.h"
struct statsd {
pool *pool;
const pr_netaddr_t *addr;
int fd;
/* For knowing how to handle newlines in the metrics. */
int use_tcp;
/* Sampling */
float sampling;
/* Namespacing */
const char *prefix;
const char *suffix;
/* Pending metrics */
pool *metrics_pool;
char *metrics_buf;
size_t metrics_buflen;
};
static int statsd_proto_tcp = IPPROTO_TCP;
static int statsd_proto_udp = IPPROTO_UDP;
static const char *trace_channel = "statsd.statsd";
struct statsd *statsd_statsd_open(pool *p, const pr_netaddr_t *addr,
int use_tcp, float sampling, const char *prefix, const char *suffix) {
int family, fd, xerrno;
pool *sub_pool;
struct statsd *statsd;
if (p == NULL ||
addr == NULL) {
errno = EINVAL;
return NULL;
}
if (sampling < 0.0 ||
sampling > 1.0) {
errno = EINVAL;
return NULL;
}
family = pr_netaddr_get_family(addr);
if (use_tcp == TRUE) {
fd = socket(family, SOCK_STREAM, statsd_proto_tcp);
} else {
fd = socket(family, SOCK_DGRAM, statsd_proto_udp);
}
xerrno = errno;
if (fd < 0) {
pr_trace_msg(trace_channel, 1, "error opening %s %s socket: %s",
family == AF_INET ? "IPv4" : "IPv6", use_tcp ? "TCP" : "UDP",
strerror(xerrno));
errno = xerrno;
return NULL;
}
if (use_tcp == TRUE) {
int res;
res = connect(fd, pr_netaddr_get_sockaddr(addr),
pr_netaddr_get_sockaddr_len(addr));
xerrno = errno;
if (res < 0) {
pr_trace_msg(trace_channel, 1,
"error connecting %s TCP socket to %s:%d: %s",
family == AF_INET ? "IPv4" : "IPv6", pr_netaddr_get_ipstr(addr),
ntohs(pr_netaddr_get_port(addr)), strerror(xerrno));
(void) close(fd);
errno = xerrno;
return NULL;
}
}
sub_pool = make_sub_pool(p);
pr_pool_tag(sub_pool, "Statsd Client Pool");
statsd = pcalloc(sub_pool, sizeof(struct statsd));
statsd->pool = sub_pool;
statsd->addr = addr;
statsd->fd = fd;
statsd->use_tcp = use_tcp;
statsd->sampling = sampling;
if (prefix != NULL) {
statsd->prefix = pstrdup(statsd->pool, prefix);
}
if (suffix != NULL) {
statsd->suffix = pstrdup(statsd->pool, suffix);
}
return statsd;
}
int statsd_statsd_close(struct statsd *statsd) {
if (statsd == NULL) {
errno = EINVAL;
return -1;
}
/* Flush any pending metrics. */
(void) statsd_statsd_flush(statsd);
(void) close(statsd->fd);
destroy_pool(statsd->pool);
return 0;
}
int statsd_statsd_get_namespacing(struct statsd *statsd, const char **prefix,
const char **suffix) {
if (statsd == NULL) {
errno = EINVAL;
return -1;
}
if (prefix == NULL &&
suffix == NULL) {
errno = EINVAL;
return -1;
}
if (prefix != NULL) {
*prefix = statsd->prefix;
}
if (suffix != NULL) {
*suffix = statsd->suffix;
}
return 0;
}
pool *statsd_statsd_get_pool(struct statsd *statsd) {
if (statsd == NULL) {
errno = EINVAL;
return NULL;
}
return statsd->pool;
}
float statsd_statsd_get_sampling(struct statsd *statsd) {
if (statsd == NULL) {
errno = EINVAL;
return -1.0;
}
return statsd->sampling;
}
int statsd_statsd_set_fd(struct statsd *statsd, int fd) {
if (statsd == NULL) {
errno = EINVAL;
return -1;
}
(void) close(statsd->fd);
statsd->fd = fd;
return 0;
}
static void send_metrics(struct statsd *statsd, const void *buf, size_t len) {
if (statsd->addr != NULL) {
int res, xerrno;
while (TRUE) {
res = sendto(statsd->fd, buf, len, 0, pr_netaddr_get_sockaddr(statsd->addr),
pr_netaddr_get_sockaddr_len(statsd->addr));
xerrno = errno;
if (res < 0) {
if (xerrno == EINTR) {
pr_signals_handle();
continue;
}
pr_trace_msg(trace_channel, 5,
"error sending %lu bytes of metrics data to %s:%d: %s",
(unsigned long) len, pr_netaddr_get_ipstr(statsd->addr),
ntohs(pr_netaddr_get_port(statsd->addr)), strerror(xerrno));
errno = xerrno;
} else {
/* XXX Should we watch for short writes? */
pr_trace_msg(trace_channel, 19,
"sent %d bytes of metrics data (of %lu bytes pending) to %s:%d", res,
(unsigned long) len, pr_netaddr_get_ipstr(statsd->addr),
ntohs(pr_netaddr_get_port(statsd->addr)));
}
break;
}
}
}
static void clear_metrics(struct statsd *statsd) {
if (statsd->metrics_pool != NULL) {
destroy_pool(statsd->metrics_pool);
}
statsd->metrics_pool = NULL;
statsd->metrics_buf = NULL;
statsd->metrics_buflen = 0;
}
int statsd_statsd_write(struct statsd *statsd, const char *metric,
size_t metric_len, int flags) {
if (statsd == NULL ||
metric == NULL ||
metric_len == 0) {
errno = EINVAL;
return -1;
}
pr_trace_msg(trace_channel, 19, "adding statsd metric: '%.*s'",
(int) metric_len, metric);
if (statsd->use_tcp == TRUE) {
/* When we have a TCP connection, there is no need/value in buffering
* the metrics into fewer packets. Is there?
*/
flags |= STATSD_STATSD_FL_SEND_NOW;
}
if (statsd->use_tcp == TRUE) {
/* No need to worry about existing buffered metrics; for TCP we will have
* sent them already.
*/
statsd->metrics_pool = make_sub_pool(statsd->pool);
pr_pool_tag(statsd->metrics_pool, "Statsd buffered metrics pool");
/* Note that we MUST add a newline for TCP-sent metrics; there are no
* packet boundaries (it's a stream, not a datagram) for delimiting.
*/
statsd->metrics_buf = pstrcat(statsd->pool,
pstrndup(statsd->metrics_pool, metric, metric_len), "\n", NULL);
statsd->metrics_buflen = metric_len + 1;
} else {
/* Would this metric put us over the max packet size? If so, flush the
* metrics now.
*/
if (statsd->metrics_buf != NULL) {
if ((statsd->metrics_buflen + metric_len + 1) > STATSD_MAX_UDP_PACKET_SIZE) {
send_metrics(statsd, statsd->metrics_buf, statsd->metrics_buflen);
clear_metrics(statsd);
}
}
if (statsd->metrics_buf != NULL) {
statsd->metrics_buf = pstrcat(statsd->metrics_pool, statsd->metrics_buf,
"\n", metric, NULL);
statsd->metrics_buflen += (metric_len + 1);
} else {
statsd->metrics_pool = make_sub_pool(statsd->pool);
pr_pool_tag(statsd->metrics_pool, "Statsd buffered metrics pool");
statsd->metrics_buf = pstrndup(statsd->metrics_pool, metric, metric_len);
statsd->metrics_buflen = metric_len;
}
}
if (flags & STATSD_STATSD_FL_SEND_NOW) {
send_metrics(statsd, statsd->metrics_buf, statsd->metrics_buflen);
clear_metrics(statsd);
}
return 0;
}
int statsd_statsd_flush(struct statsd *statsd) {
if (statsd == NULL) {
errno = EINVAL;
return -1;
}
send_metrics(statsd, statsd->metrics_buf, statsd->metrics_buflen);
clear_metrics(statsd);
return 0;
}
int statsd_statsd_init(void) {
struct protoent *pre = NULL;
#ifdef HAVE_SETPROTOENT
setprotoent(FALSE);
#endif /* SETPROTOENT */
pre = getprotobyname("tcp");
if (pre != NULL) {
statsd_proto_tcp = pre->p_proto;
}
pre = getprotobyname("udp");
if (pre != NULL) {
statsd_proto_udp = pre->p_proto;
}
#ifdef HAVE_ENDPROTOENT
endprotoent();
#endif /* HAVE_ENDPROTOENT */
return 0;
}
int statsd_statsd_free(void) {
return 0;
}
proftpd-mod_statsd-0.1/statsd.h 0000664 0000000 0000000 00000005056 13066013616 0016652 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_statsd Statsd API
* Copyright (c) 2017 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., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, 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.
*/
#ifndef MOD_STATSD_STATSD_H
#define MOD_STATSD_STATSD_H
#include "mod_statsd.h"
struct statsd;
/* Per the excellent documentation on multi-metric packets here:
*
* https://github.com/etsy/statsd/blob/master/docs/metric_types.md#multi-metric-packets
*
* We'll use a maximum UDP packet size of 512 bytes, for interoperability.
*/
#define STATSD_MAX_UDP_PACKET_SIZE 512
/* The max length of a single metric is the same as the max packet size. */
#define STATSD_MAX_METRIC_SIZE STATSD_MAX_UDP_PACKET_SIZE
struct statsd *statsd_statsd_open(pool *p, const pr_netaddr_t *addr,
int use_tcp, float sampling, const char *prefix, const char *suffix);
int statsd_statsd_close(struct statsd *statsd);
int statsd_statsd_write(struct statsd *statsd, const char *metric,
size_t metric_len, int flags);
#define STATSD_STATSD_FL_SEND_NOW 0x0001
/* Flush any buffered pending metrics */
int statsd_statsd_flush(struct statsd *statsd);
/* Returns a reference to the prefix/suffix labels, if any, for this statsd
* client.
*/
int statsd_statsd_get_namespacing(struct statsd *statsd, const char **prefix,
const char **suffix);
/* Returns a reference to pool used for the statsd client. */
pool *statsd_statsd_get_pool(struct statsd *statsd);
/* Returns the sampling percentage for the statsd client. */
float statsd_statsd_get_sampling(struct statsd *statsd);
/* This is for testing purposes. */
int statsd_statsd_set_fd(struct statsd *statsd, int fd);
int statsd_statsd_init(void);
int statsd_statsd_free(void);
#endif /* MOD_STATSD_STATSD_H */
proftpd-mod_statsd-0.1/t/ 0000775 0000000 0000000 00000000000 13066013616 0015434 5 ustar 00root root 0000000 0000000 proftpd-mod_statsd-0.1/t/Makefile.in 0000664 0000000 0000000 00000003021 13066013616 0017475 0 ustar 00root root 0000000 0000000 CC=@CC@
@SET_MAKE@
top_builddir=../../..
top_srcdir=../../..
module_srcdir=..
srcdir=@srcdir@
VPATH=@srcdir@
include $(top_srcdir)/Make.rules
# Necessary redefinitions
INCLUDES=-I. -I.. -I$(module_srcdir)/include -I../../.. -I../../../include @INCLUDES@
CPPFLAGS= $(ADDL_CPPFLAGS) -DHAVE_CONFIG_H $(DEFAULT_PATHS) $(PLATFORM) $(INCLUDES)
LDFLAGS=-L$(top_srcdir)/lib @LIBDIRS@
EXEEXT=@EXEEXT@
TEST_API_DEPS=\
$(top_srcdir)/src/pool.o \
$(top_srcdir)/src/privs.o \
$(top_srcdir)/src/str.o \
$(top_srcdir)/src/sets.o \
$(top_srcdir)/src/table.o \
$(top_srcdir)/src/event.o \
$(top_srcdir)/src/timers.o \
$(top_srcdir)/src/stash.o \
$(top_srcdir)/src/modules.o \
$(top_srcdir)/src/cmd.o \
$(top_srcdir)/src/configdb.o \
$(top_srcdir)/src/parser.o \
$(top_srcdir)/src/regexp.o \
$(top_srcdir)/src/fsio.o \
$(top_srcdir)/src/netio.o \
$(top_srcdir)/src/inet.o \
$(top_srcdir)/src/netaddr.o \
$(top_srcdir)/src/response.o \
$(top_srcdir)/src/auth.o \
$(top_srcdir)/src/env.o \
$(top_srcdir)/src/trace.o \
$(top_srcdir)/src/support.o \
$(module_srcdir)/statsd.o \
$(module_srcdir)/metric.o
TEST_API_LIBS=-lcheck
TEST_API_OBJS=\
api/statsd.o \
api/metric.o \
api/stubs.o \
api/tests.o
dummy:
api/.c.o:
$(CC) $(CPPFLAGS) $(CFLAGS) -c $<
api-tests$(EXEEXT): $(TEST_API_OBJS) $(TEST_API_DEPS)
$(LIBTOOL) --mode=link --tag=CC $(CC) $(LDFLAGS) -o $@ $(TEST_API_DEPS) $(TEST_API_OBJS) $(LIBS) $(TEST_API_LIBS)
./$@
clean:
$(LIBTOOL) --mode=clean $(RM) *.o api/*.o api-tests$(EXEEXT) api-tests.log
proftpd-mod_statsd-0.1/t/api/ 0000775 0000000 0000000 00000000000 13066013616 0016205 5 ustar 00root root 0000000 0000000 proftpd-mod_statsd-0.1/t/api/metric.c 0000664 0000000 0000000 00000014367 13066013616 0017647 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_statsd testsuite
* Copyright (c) 2017 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., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, 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.
*/
/* Alias tests. */
#include "tests.h"
#include "statsd.h"
#include "metric.h"
static pool *p = NULL;
static void set_up(void) {
if (p == NULL) {
p = make_sub_pool(NULL);
}
if (getenv("TEST_VERBOSE") != NULL) {
pr_trace_set_levels("statsd.metric", 1, 20);
}
}
static void tear_down(void) {
if (getenv("TEST_VERBOSE") != NULL) {
pr_trace_set_levels("statsd.metric", 0, 0);
}
if (p) {
destroy_pool(p);
p = NULL;
}
}
static const pr_netaddr_t *statsd_addr(unsigned int port) {
const pr_netaddr_t *addr;
addr = pr_netaddr_get_addr(p, "127.0.0.1", NULL);
fail_unless(addr != NULL, "Failed to resolve 127.0.0.1: %s", strerror(errno));
pr_netaddr_set_port2((pr_netaddr_t *) addr, port);
return addr;
}
START_TEST (metric_counter_test) {
int res;
const pr_netaddr_t *addr;
struct statsd *statsd;
mark_point();
res = statsd_metric_counter(NULL, NULL, 0, 0);
fail_unless(res < 0, "Failed to handle null statsd");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
addr = statsd_addr(STATSD_DEFAULT_PORT);
mark_point();
statsd = statsd_statsd_open(p, addr, FALSE, 1.0, NULL, NULL);
fail_unless(statsd != NULL, "Failed to open statsd connection: %s",
strerror(errno));
mark_point();
res = statsd_metric_counter(statsd, NULL, 0, 0);
fail_unless(res < 0, "Failed to handle null name");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
res = statsd_metric_counter(statsd, "foo", 0, 0);
fail_unless(res == 0, "Failed to set counter: %s", strerror(errno));
mark_point();
res = statsd_metric_counter(statsd, "foo", 0, STATSD_METRIC_FL_IGNORE_SAMPLING);
fail_unless(res == 0, "Failed to set counter: %s", strerror(errno));
mark_point();
res = statsd_statsd_flush(statsd);
fail_unless(res == 0, "Failed to flush metrics: %s", strerror(errno));
(void) statsd_statsd_close(statsd);
}
END_TEST
START_TEST (metric_timer_test) {
int res;
const pr_netaddr_t *addr;
struct statsd *statsd;
uint64_t ms;
mark_point();
res = statsd_metric_timer(NULL, NULL, 0, 0);
fail_unless(res < 0, "Failed to handle null statsd");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
addr = statsd_addr(STATSD_DEFAULT_PORT);
mark_point();
statsd = statsd_statsd_open(p, addr, FALSE, 1.0, NULL, NULL);
fail_unless(statsd != NULL, "Failed to open statsd connection: %s",
strerror(errno));
mark_point();
res = statsd_metric_timer(statsd, NULL, 0, 0);
fail_unless(res < 0, "Failed to handle null name");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
ms = 1;
mark_point();
res = statsd_metric_timer(statsd, "foo", ms, 0);
fail_unless(res == 0, "Failed to set timer: %s", strerror(errno));
/* Deliberately use a very large timer, to test the truncation. */
ms = 315360000000UL;
mark_point();
res = statsd_metric_timer(statsd, "bar", ms, 0);
fail_unless(res == 0, "Failed to set timer: %s", strerror(errno));
mark_point();
res = statsd_metric_timer(statsd, "bar", ms, STATSD_METRIC_FL_IGNORE_SAMPLING);
fail_unless(res == 0, "Failed to set timer: %s", strerror(errno));
mark_point();
res = statsd_statsd_flush(statsd);
fail_unless(res == 0, "Failed to flush metrics: %s", strerror(errno));
(void) statsd_statsd_close(statsd);
}
END_TEST
START_TEST (metric_gauge_test) {
int res;
const pr_netaddr_t *addr;
struct statsd *statsd;
mark_point();
res = statsd_metric_gauge(NULL, NULL, 0, 0);
fail_unless(res < 0, "Failed to handle null statsd");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
addr = statsd_addr(STATSD_DEFAULT_PORT);
mark_point();
statsd = statsd_statsd_open(p, addr, FALSE, 1.0, NULL, NULL);
fail_unless(statsd != NULL, "Failed to open statsd connection: %s",
strerror(errno));
mark_point();
res = statsd_metric_gauge(statsd, NULL, 0, 0);
fail_unless(res < 0, "Failed to handle null name");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
res = statsd_metric_gauge(statsd, "foo", 1, 0);
fail_unless(res == 0, "Failed to set gauge: %s", strerror(errno));
mark_point();
res = statsd_metric_gauge(statsd, "foo", 1, STATSD_METRIC_FL_GAUGE_ADJUST);
fail_unless(res == 0, "Failed to set gauge: %s", strerror(errno));
mark_point();
res = statsd_metric_gauge(statsd, "foo", -1, STATSD_METRIC_FL_GAUGE_ADJUST);
fail_unless(res == 0, "Failed to set gauge: %s", strerror(errno));
mark_point();
res = statsd_statsd_flush(statsd);
fail_unless(res == 0, "Failed to flush metrics: %s", strerror(errno));
(void) statsd_statsd_close(statsd);
}
END_TEST
Suite *tests_get_metric_suite(void) {
Suite *suite;
TCase *testcase;
suite = suite_create("metric");
testcase = tcase_create("base");
tcase_add_checked_fixture(testcase, set_up, tear_down);
tcase_add_test(testcase, metric_counter_test);
tcase_add_test(testcase, metric_timer_test);
tcase_add_test(testcase, metric_gauge_test);
suite_add_tcase(suite, testcase);
return suite;
}
proftpd-mod_statsd-0.1/t/api/statsd.c 0000664 0000000 0000000 00000026712 13066013616 0017663 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_statsd testsuite
* Copyright (c) 2017 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., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, 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.
*/
/* Alias tests. */
#include "tests.h"
#include "statsd.h"
static pool *p = NULL;
static void set_up(void) {
if (p == NULL) {
p = make_sub_pool(NULL);
}
statsd_statsd_init();
if (getenv("TEST_VERBOSE") != NULL) {
pr_trace_set_levels("statsd.statsd", 1, 20);
}
}
static void tear_down(void) {
if (getenv("TEST_VERBOSE") != NULL) {
pr_trace_set_levels("statsd.statsd", 0, 0);
}
statsd_statsd_free();
if (p) {
destroy_pool(p);
p = NULL;
}
}
static const pr_netaddr_t *statsd_addr(unsigned int port) {
const pr_netaddr_t *addr;
addr = pr_netaddr_get_addr(p, "127.0.0.1", NULL);
fail_unless(addr != NULL, "Failed to resolve 127.0.0.1: %s", strerror(errno));
pr_netaddr_set_port2((pr_netaddr_t *) addr, port);
return addr;
}
START_TEST (statsd_close_test) {
int res;
mark_point();
res = statsd_statsd_close(NULL);
fail_unless(res < 0, "Failed to handle null statsd");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
}
END_TEST
START_TEST (statsd_open_test) {
const pr_netaddr_t *addr;
struct statsd *statsd;
mark_point();
statsd = statsd_statsd_open(NULL, NULL, FALSE, 0.0, NULL, NULL);
fail_unless(statsd == NULL, "Failed to handle null pool");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
statsd = statsd_statsd_open(p, NULL, FALSE, -1.0, NULL, NULL);
fail_unless(statsd == NULL, "Failed to handle null addr");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
addr = statsd_addr(STATSD_DEFAULT_PORT);
mark_point();
statsd = statsd_statsd_open(p, addr, FALSE, -1.0, NULL, NULL);
fail_unless(statsd == NULL, "Failed to handle invalid sampling");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
statsd = statsd_statsd_open(p, addr, FALSE, 1.0, NULL, NULL);
fail_unless(statsd != NULL, "Failed to open statsd connection: %s",
strerror(errno));
(void) statsd_statsd_close(statsd);
mark_point();
statsd = statsd_statsd_open(p, addr, TRUE, 1.0, NULL, NULL);
/* If statsd IS running, but is not configued for TCP, the "Connection
* refused" error is expected.
*/
if (statsd != NULL &&
errno != ECONNREFUSED) {
fail("Failed to open TCP statsd connection: %s", strerror(errno));
} else {
(void) statsd_statsd_close(statsd);
}
}
END_TEST
START_TEST (statsd_get_namespacing_test) {
int res;
const char *prefix, *suffix;
const pr_netaddr_t *addr;
struct statsd *statsd;
mark_point();
res = statsd_statsd_get_namespacing(NULL, NULL, NULL);
fail_unless(res < 0, "Failed to handle null statsd");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
addr = statsd_addr(STATSD_DEFAULT_PORT);
mark_point();
statsd = statsd_statsd_open(p, addr, FALSE, 1.0, NULL, NULL);
fail_unless(statsd != NULL, "Failed to open statsd connection: %s",
strerror(errno));
mark_point();
res = statsd_statsd_get_namespacing(statsd, NULL, NULL);
fail_unless(res < 0, "Failed to handle null prefix AND suffix");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
prefix = suffix = NULL;
mark_point();
res = statsd_statsd_get_namespacing(statsd, &prefix, &suffix);
fail_unless(res == 0, "Failed to get namespacing: %s", strerror(errno));
fail_unless(prefix == NULL, "Got prefix %s unexpectedly", prefix);
fail_unless(suffix == NULL, "Got suffix %s unexpectedly", suffix);
(void) statsd_statsd_close(statsd);
mark_point();
statsd = statsd_statsd_open(p, addr, FALSE, 1.0, "foo", "bar");
fail_unless(statsd != NULL, "Failed to open statsd connection: %s",
strerror(errno));
prefix = suffix = NULL;
mark_point();
res = statsd_statsd_get_namespacing(statsd, &prefix, &suffix);
fail_unless(res == 0, "Failed to get namespacing: %s", strerror(errno));
fail_unless(prefix != NULL, "Expected prefix, got null");
fail_unless(strcmp(prefix, "foo") == 0, "Expected 'foo', got '%s'", prefix);
fail_unless(suffix != NULL, "Expected suffix, got null");
fail_unless(strcmp(suffix, "bar") == 0, "Expected 'bar', got '%s'", suffix);
prefix = suffix = NULL;
mark_point();
res = statsd_statsd_get_namespacing(statsd, &prefix, NULL);
fail_unless(res == 0, "Failed to get namespacing: %s", strerror(errno));
fail_unless(prefix != NULL, "Expected prefix, got null");
fail_unless(strcmp(prefix, "foo") == 0, "Expected 'foo', got '%s'", prefix);
prefix = suffix = NULL;
mark_point();
res = statsd_statsd_get_namespacing(statsd, NULL, &suffix);
fail_unless(res == 0, "Failed to get namespacing: %s", strerror(errno));
fail_unless(suffix != NULL, "Expected suffix, got null");
fail_unless(strcmp(suffix, "bar") == 0, "Expected 'bar', got '%s'", suffix);
(void) statsd_statsd_close(statsd);
}
END_TEST
START_TEST (statsd_get_pool_test) {
pool *res;
const pr_netaddr_t *addr;
struct statsd *statsd;
mark_point();
res = statsd_statsd_get_pool(NULL);
fail_unless(res == NULL, "Failed to handle null statsd");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
addr = statsd_addr(STATSD_DEFAULT_PORT);
mark_point();
statsd = statsd_statsd_open(p, addr, FALSE, 1.0, NULL, NULL);
fail_unless(statsd != NULL, "Failed to open statsd connection: %s",
strerror(errno));
mark_point();
res = statsd_statsd_get_pool(statsd);
fail_unless(res != NULL, "Failed to get pool: %s", strerror(errno));
(void) statsd_statsd_close(statsd);
}
END_TEST
START_TEST (statsd_get_sampling_test) {
float res;
const pr_netaddr_t *addr;
struct statsd *statsd;
mark_point();
res = statsd_statsd_get_sampling(NULL);
fail_unless(res < 0.0, "Failed to handle null statsd");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
addr = statsd_addr(STATSD_DEFAULT_PORT);
mark_point();
statsd = statsd_statsd_open(p, addr, FALSE, 1.0, NULL, NULL);
fail_unless(statsd != NULL, "Failed to open statsd connection: %s",
strerror(errno));
mark_point();
res = statsd_statsd_get_sampling(statsd);
fail_unless(res >= 1.0, "Failed to get sampling: %s", strerror(errno));
(void) statsd_statsd_close(statsd);
}
END_TEST
START_TEST (statsd_set_fd_test) {
int res;
const pr_netaddr_t *addr;
struct statsd *statsd;
mark_point();
res = statsd_statsd_set_fd(NULL, -1);
fail_unless(res < 0, "Failed to handle null statsd");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
addr = statsd_addr(STATSD_DEFAULT_PORT);
mark_point();
statsd = statsd_statsd_open(p, addr, FALSE, 1.0, NULL, NULL);
fail_unless(statsd != NULL, "Failed to open statsd connection: %s",
strerror(errno));
mark_point();
res = statsd_statsd_set_fd(statsd, -1);
fail_unless(res == 0, "Failed to set fd: %s", strerror(errno));
(void) statsd_statsd_close(statsd);
}
END_TEST
START_TEST (statsd_write_test) {
int res;
const pr_netaddr_t *addr;
struct statsd *statsd;
mark_point();
res = statsd_statsd_write(NULL, NULL, 0, 0);
fail_unless(res < 0, "Failed to handle null statsd");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
addr = statsd_addr(STATSD_DEFAULT_PORT);
mark_point();
statsd = statsd_statsd_open(p, addr, FALSE, 1.0, NULL, NULL);
fail_unless(statsd != NULL, "Failed to open statsd connection: %s",
strerror(errno));
mark_point();
res = statsd_statsd_write(statsd, NULL, 0, 0);
fail_unless(res < 0, "Failed to handle null metric");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
res = statsd_statsd_write(statsd, "foo", 0, 0);
fail_unless(res < 0, "Failed to handle zero length metric");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
mark_point();
res = statsd_statsd_write(statsd, "foo", 3, 0);
fail_unless(res == 0, "Failed to send metric: %s", strerror(errno));
mark_point();
res = statsd_statsd_write(statsd, "bar", 3, STATSD_STATSD_FL_SEND_NOW);
fail_unless(res == 0, "Failed to send metric now: %s", strerror(errno));
(void) statsd_statsd_close(statsd);
/* Now test sending metrics to a bad port. */
addr = statsd_addr(45778);
mark_point();
statsd = statsd_statsd_open(p, addr, FALSE, 1.0, NULL, NULL);
fail_unless(statsd != NULL, "Failed to open statsd connection: %s",
strerror(errno));
mark_point();
res = statsd_statsd_write(statsd, "bar", 3, STATSD_STATSD_FL_SEND_NOW);
fail_unless(res == 0, "Failed to send metric now: %s", strerror(errno));
(void) statsd_statsd_close(statsd);
}
END_TEST
START_TEST (statsd_flush_test) {
int res;
const pr_netaddr_t *addr;
struct statsd *statsd;
mark_point();
res = statsd_statsd_flush(NULL);
fail_unless(res < 0, "Failed to handle null statsd");
fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
addr = statsd_addr(STATSD_DEFAULT_PORT);
mark_point();
statsd = statsd_statsd_open(p, addr, FALSE, 1.0, NULL, NULL);
fail_unless(statsd != NULL, "Failed to open statsd connection: %s",
strerror(errno));
mark_point();
res = statsd_statsd_flush(statsd);
fail_unless(res == 0, "Failed to flush metrics: %s", strerror(errno));
mark_point();
res = statsd_statsd_write(statsd, "foo", 3, 0);
fail_unless(res == 0, "Failed to send metric: %s", strerror(errno));
mark_point();
res = statsd_statsd_flush(statsd);
fail_unless(res == 0, "Failed to flush metrics: %s", strerror(errno));
(void) statsd_statsd_close(statsd);
}
END_TEST
Suite *tests_get_statsd_suite(void) {
Suite *suite;
TCase *testcase;
suite = suite_create("statsd");
testcase = tcase_create("base");
tcase_add_checked_fixture(testcase, set_up, tear_down);
tcase_add_test(testcase, statsd_close_test);
tcase_add_test(testcase, statsd_open_test);
tcase_add_test(testcase, statsd_get_namespacing_test);
tcase_add_test(testcase, statsd_get_pool_test);
tcase_add_test(testcase, statsd_get_sampling_test);
tcase_add_test(testcase, statsd_set_fd_test);
tcase_add_test(testcase, statsd_write_test);
tcase_add_test(testcase, statsd_flush_test);
suite_add_tcase(suite, testcase);
return suite;
}
proftpd-mod_statsd-0.1/t/api/stubs.c 0000664 0000000 0000000 00000007070 13066013616 0017515 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_statsd API testsuite
* Copyright (c) 2017 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., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, 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.
*/
#include "tests.h"
/* Stubs */
session_t session;
int ServerUseReverseDNS = FALSE;
server_rec *main_server = NULL;
pid_t mpid = 1;
unsigned char is_master = TRUE;
unsigned int recvd_signal_flags = 0;
module *static_modules[] = { NULL };
module *loaded_modules = NULL;
xaset_t *server_list = NULL;
int login_check_limits(xaset_t *set, int recurse, int and, int *found) {
return TRUE;
}
int xferlog_open(const char *path) {
return 0;
}
int pr_cmd_read(cmd_rec **cmd) {
errno = ENOENT;
*cmd = NULL;
return -1;
}
int pr_config_get_server_xfer_bufsz(int direction) {
int bufsz = -1;
switch (direction) {
case PR_NETIO_IO_RD:
bufsz = PR_TUNABLE_DEFAULT_RCVBUFSZ;
break;
case PR_NETIO_IO_WR:
bufsz = PR_TUNABLE_DEFAULT_SNDBUFSZ;
break;
default:
errno = EINVAL;
return -1;
}
return bufsz;
}
void pr_log_auth(int priority, const char *fmt, ...) {
if (getenv("TEST_VERBOSE") != NULL) {
va_list msg;
fprintf(stderr, "AUTH: ");
va_start(msg, fmt);
vfprintf(stderr, fmt, msg);
va_end(msg);
fprintf(stderr, "\n");
}
}
void pr_log_debug(int level, const char *fmt, ...) {
if (getenv("TEST_VERBOSE") != NULL) {
va_list msg;
fprintf(stderr, "DEBUG%d: ", level);
va_start(msg, fmt);
vfprintf(stderr, fmt, msg);
va_end(msg);
fprintf(stderr, "\n");
}
}
int pr_log_event_generate(unsigned int log_type, int log_fd, int log_level,
const char *log_msg, size_t log_msglen) {
errno = ENOSYS;
return -1;
}
int pr_log_event_listening(unsigned int log_type) {
return FALSE;
}
int pr_log_openfile(const char *log_file, int *log_fd, mode_t log_mode) {
*log_fd = STDERR_FILENO;
return 0;
}
void pr_log_pri(int prio, const char *fmt, ...) {
if (getenv("TEST_VERBOSE") != NULL) {
va_list msg;
fprintf(stderr, "PRI%d: ", prio);
va_start(msg, fmt);
vfprintf(stderr, fmt, msg);
va_end(msg);
fprintf(stderr, "\n");
}
}
int pr_log_writefile(int fd, const char *name, const char *fmt, ...) {
if (getenv("TEST_VERBOSE") != NULL) {
va_list msg;
fprintf(stderr, "%s: ", name);
va_start(msg, fmt);
vfprintf(stderr, fmt, msg);
va_end(msg);
fprintf(stderr, "\n");
}
return 0;
}
int pr_scoreboard_entry_update(pid_t pid, ...) {
return 0;
}
void pr_session_disconnect(module *m, int reason_code, const char *details) {
}
void pr_session_end(int flags) {
}
int pr_session_set_protocol(const char *proto) {
return 0;
}
void pr_signals_handle(void) {
}
/* Module-specific stubs */
proftpd-mod_statsd-0.1/t/api/tests.c 0000664 0000000 0000000 00000006644 13066013616 0017525 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_statsd API testsuite
* Copyright (c) 2017 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., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, 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.
*/
#include "tests.h"
struct testsuite_info {
const char *name;
Suite *(*get_suite)(void);
};
static struct testsuite_info suites[] = {
{ "statsd", tests_get_statsd_suite },
{ "metric", tests_get_metric_suite },
{ NULL, NULL }
};
static Suite *tests_get_suite(const char *suite) {
register unsigned int i;
for (i = 0; suites[i].name != NULL; i++) {
if (strcmp(suite, suites[i].name) == 0) {
return (*suites[i].get_suite)();
}
}
errno = ENOENT;
return NULL;
}
int main(int argc, char *argv[]) {
const char *log_file = "api-tests.log";
int nfailed = 0;
SRunner *runner = NULL;
char *requested = NULL;
runner = srunner_create(NULL);
/* XXX This log name should be set outside this code, e.g. via environment
* variable or command-line option.
*/
srunner_set_log(runner, log_file);
requested = getenv("STATSD_TEST_SUITE");
if (requested) {
Suite *suite;
suite = tests_get_suite(requested);
if (suite) {
srunner_add_suite(runner, suite);
} else {
fprintf(stderr,
"No such test suite ('%s') requested via STATSD_TEST_SUITE\n",
requested);
return EXIT_FAILURE;
}
} else {
register unsigned int i;
for (i = 0; suites[i].name; i++) {
Suite *suite;
suite = (suites[i].get_suite)();
if (suite) {
srunner_add_suite(runner, suite);
}
}
}
/* Configure the Trace API to write to stderr. */
pr_trace_use_stderr(TRUE);
requested = getenv("STATSD_TEST_NOFORK");
if (requested) {
srunner_set_fork_status(runner, CK_NOFORK);
} else {
requested = getenv("CK_DEFAULT_TIMEOUT");
if (requested == NULL) {
setenv("CK_DEFAULT_TIMEOUT", "60", 1);
}
}
srunner_run_all(runner, CK_NORMAL);
nfailed = srunner_ntests_failed(runner);
if (runner) {
srunner_free(runner);
}
if (nfailed != 0) {
fprintf(stderr, "-------------------------------------------------\n");
fprintf(stderr, " FAILED %d %s\n\n", nfailed,
nfailed != 1 ? "tests" : "test");
fprintf(stderr, " Please send email to:\n\n");
fprintf(stderr, " tj@castaglia.org\n\n");
fprintf(stderr, " containing the `%s' file (in the t/ directory)\n", log_file);
fprintf(stderr, " and the output from running `proftpd -V'\n");
fprintf(stderr, "-------------------------------------------------\n");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
proftpd-mod_statsd-0.1/t/api/tests.h 0000664 0000000 0000000 00000002732 13066013616 0017524 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_statsd API testsuite
* Copyright (c) 2017 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., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, 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.
*/
/* Testsuite management */
#ifndef MOD_STATSD_TESTS_H
#define MOD_STATSD_TESTS_H
#include "mod_statsd.h"
#ifdef HAVE_CHECK_H
# include
#else
# error "Missing Check installation; necessary for ProFTPD testsuite"
#endif
Suite *tests_get_statsd_suite(void);
Suite *tests_get_metric_suite(void);
unsigned int recvd_signal_flags;
extern pid_t mpid;
extern server_rec *main_server;
#endif /* MOD_STATSD_TESTS_H */
proftpd-mod_statsd-0.1/t/lib/ 0000775 0000000 0000000 00000000000 13066013616 0016202 5 ustar 00root root 0000000 0000000 proftpd-mod_statsd-0.1/t/lib/ProFTPD/ 0000775 0000000 0000000 00000000000 13066013616 0017420 5 ustar 00root root 0000000 0000000 proftpd-mod_statsd-0.1/t/lib/ProFTPD/Tests/ 0000775 0000000 0000000 00000000000 13066013616 0020522 5 ustar 00root root 0000000 0000000 proftpd-mod_statsd-0.1/t/lib/ProFTPD/Tests/Modules/ 0000775 0000000 0000000 00000000000 13066013616 0022132 5 ustar 00root root 0000000 0000000 proftpd-mod_statsd-0.1/t/lib/ProFTPD/Tests/Modules/mod_statsd.pm 0000664 0000000 0000000 00000054754 13066013616 0024650 0 ustar 00root root 0000000 0000000 package ProFTPD::Tests::Modules::mod_statsd;
use lib qw(t/lib);
use base qw(ProFTPD::TestSuite::Child);
use strict;
use Cwd;
use File::Path qw(mkpath rmtree);
use File::Spec;
use IO::Handle;
use Socket;
use ProFTPD::TestSuite::FTP;
use ProFTPD::TestSuite::Utils qw(:auth :config :running :test :testsuite);
use ProFTPD::Tests::Modules::mod_statsd::mgmt qw(:admin);
$| = 1;
# NOTE: Use the TEST_DEBUG environment variable for debugging the on-the-wire
# communication with the statsd management port.
my $order = 0;
my $TESTS = {
statsd_engine => {
order => ++$order,
test_class => [qw(forking)],
},
statsd_server_udp => {
order => ++$order,
test_class => [qw(forking)],
},
statsd_server_tcp => {
order => ++$order,
test_class => [qw(forking)],
},
statsd_sampling => {
order => ++$order,
test_class => [qw(forking)],
},
statsd_namespacing => {
order => ++$order,
test_class => [qw(forking)],
},
statsd_timeout_login => {
order => ++$order,
test_class => [qw(forking)],
},
statsd_exclude_filter => {
order => ++$order,
test_class => [qw(forking)],
},
};
sub new {
return shift()->SUPER::new(@_);
}
sub list_tests {
# Check for the required Perl modules:
#
# JSON
my $required = [qw(
JSON
)];
foreach my $req (@$required) {
eval "use $req";
if ($@) {
print STDERR "\nWARNING:\n + Module '$req' not found, skipping all tests\n";
if ($ENV{TEST_VERBOSE}) {
print STDERR "Unable to load $req: $@\n";
}
return qw(testsuite_empty_test);
}
}
# Check for required environment variables, pointing us at the local
# statsd instance
$required = [qw(
STATSD_MGMT_PORT
)];
$ENV{STATSD_PORT} = 8125 unless defined($ENV{STATSD_PORT});
$ENV{STATSD_MGMT_PORT} = 8126 unless defined($ENV{STATSD_MGMT_PORT});
foreach my $req (@$required) {
unless (defined($ENV{$req})) {
print STDERR "\nWARNING:\n + Environment variable '$req' not found, skipping all tests\n";
return qw(testsuite_empty_test);
}
}
return testsuite_get_runnable_tests($TESTS);
}
sub statsd_engine {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'statsd');
my $statsd_port = $ENV{STATSD_PORT};
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'statsd:20 statsd.statsd:20 statsd.metric:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_statsd.c' => {
StatsdEngine => 'on',
StatsdServer => "127.0.0.1:$statsd_port",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
delete_statsd_info();
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->login($setup->{user}, $setup->{passwd});
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
eval {
my $counters = get_statsd_info('counters');
my $counter_names = [qw(
command.USER.331
command.PASS.230
command.QUIT.221
)];
foreach my $counter_name (@$counter_names) {
my $counts = $counters->{$counter_name};
$self->assert($counts > 0,
"Expected count values for $counter_name, found none");
}
my $timers = get_statsd_info('timers');
# For timers, we simply expect to HAVE timings
my $timer_names = [qw(
command.USER.331
command.PASS.230
command.QUIT.221
)];
foreach my $timer_name (@$timer_names) {
my $timings = $timers->{$timer_name};
$self->assert(scalar(@$timings) > 0,
"Expected timing values for $timer_name, found none");
}
my $gauges = get_statsd_info('gauges');
# Our connection gauge is a GAUGE; we expect it to have the same value after
# as before.
$self->assert($gauges->{connection} == 0,
"Expected connection gauge 0, got $gauges->{Connection}");
};
if ($@) {
$ex = $@;
}
test_cleanup($setup->{log_file}, $ex);
}
sub statsd_server_udp {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'statsd');
my $statsd_port = $ENV{STATSD_PORT};
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'statsd:20 statsd.statsd:20 statsd.metric:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_statsd.c' => {
StatsdEngine => 'on',
StatsdServer => "udp://127.0.0.1:$statsd_port",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
delete_statsd_info();
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->login($setup->{user}, $setup->{passwd});
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
eval {
my $counters = get_statsd_info('counters');
my $counter_names = [qw(
command.USER.331
command.PASS.230
command.QUIT.221
)];
foreach my $counter_name (@$counter_names) {
my $counts = $counters->{$counter_name};
$self->assert($counts > 0,
"Expected count values for $counter_name, found none");
}
my $timers = get_statsd_info('timers');
# For timers, we simply expect to HAVE timings
my $timer_names = [qw(
command.USER.331
command.PASS.230
command.QUIT.221
)];
foreach my $timer_name (@$timer_names) {
my $timings = $timers->{$timer_name};
$self->assert(scalar(@$timings) > 0,
"Expected timing values for $timer_name, found none");
}
my $gauges = get_statsd_info('gauges');
# Our connection gauge is a GAUGE; we expect it to have the same value after
# as before.
$self->assert($gauges->{connection} == 0,
"Expected connection gauge 0, got $gauges->{connection}");
};
if ($@) {
$ex = $@;
}
test_cleanup($setup->{log_file}, $ex);
}
sub statsd_server_tcp {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $statsd_port = $ENV{STATSD_PORT};
# Note: this test requires that statsd be listening for TCP, not UDP.
# Make a probe TCP connection to statsd; if that fails, skip this test.
my $opts = {
PeerHost => '127.0.0.1',
PeerPort => $statsd_port,
Proto => 'tcp',
Type => SOCK_STREAM,
Timeout => 3
};
my $client = IO::Socket::INET->new(%$opts);
unless ($client) {
print STDERR " + unable to run 'statsd_server_tcp' test because statsd not configured for TCP on port $statsd_port, skipping\n";
return;
}
$client->close();
my $setup = test_setup($tmpdir, 'statsd');
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'statsd:20 statsd.statsd:20 statsd.metric:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_statsd.c' => {
StatsdEngine => 'on',
StatsdServer => "tcp://127.0.0.1:$statsd_port",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
delete_statsd_info();
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->login($setup->{user}, $setup->{passwd});
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
eval {
my $counters = get_statsd_info('counters');
my $counter_names = [qw(
command.USER.331
command.PASS.230
command.QUIT.221
)];
foreach my $counter_name (@$counter_names) {
my $counts = $counters->{$counter_name};
$self->assert($counts > 0,
"Expected count values for $counter_name, found none");
}
my $timers = get_statsd_info('timers');
# For timers, we simply expect to HAVE timings
my $timer_names = [qw(
command.USER.331
command.PASS.230
command.QUIT.221
)];
foreach my $timer_name (@$timer_names) {
my $timings = $timers->{$timer_name};
$self->assert(scalar(@$timings) > 0,
"Expected timing values for $timer_name, found none");
}
my $gauges = get_statsd_info('gauges');
# Our connection gauge is a GAUGE; we expect it to have the same value after
# as before.
$self->assert($gauges->{connection} == 0,
"Expected connection gauge 0, got $gauges->{connection}");
};
if ($@) {
$ex = $@;
}
test_cleanup($setup->{log_file}, $ex);
}
sub statsd_sampling {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'statsd');
my $statsd_port = $ENV{STATSD_PORT};
my $sampling = 25.0;
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'statsd:20 statsd.statsd:20 statsd.metric:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_statsd.c' => {
StatsdEngine => 'on',
StatsdSampling => $sampling,
StatsdServer => "udp://127.0.0.1:$statsd_port",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
delete_statsd_info();
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->login($setup->{user}, $setup->{passwd});
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
eval {
my $counters = get_statsd_info('counters');
my $counter_names = [qw(
command.USER.331
command.PASS.230
command.QUIT.221
)];
foreach my $counter_name (@$counter_names) {
my $counts = $counters->{$counter_name};
if ($ENV{TEST_VERBOSE}) {
if ($counts > 0) {
print STDERR "# Sampling $sampling: got $counter_name counter = $counts\n";
}
}
}
my $timers = get_statsd_info('timers');
# For timers, we simply expect to HAVE timings
my $timer_names = [qw(
command.USER.331
command.PASS.230
command.QUIT.221
)];
foreach my $timer_name (@$timer_names) {
my $timings = $timers->{$timer_name};
if ($ENV{TEST_VERBOSE}) {
if ($timings &&
scalar(@$timings) > 0) {
print STDERR "# Sampling $sampling: got $timer_name timer\n";
}
}
}
my $gauges = get_statsd_info('gauges');
if ($ENV{TEST_VERBOSE}) {
if (defined($gauges->{connection})) {
print STDERR "# Sampling $sampling: got connection gauge $gauges->{connection}\n";
}
}
};
if ($@) {
$ex = $@;
}
test_cleanup($setup->{log_file}, $ex);
}
sub statsd_namespacing {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'statsd');
my $statsd_port = $ENV{STATSD_PORT};
my $prefix = "proftpd.prod";
my $suffix = "tests";
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'statsd:20 statsd.statsd:20 statsd.metric:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_statsd.c' => {
StatsdEngine => 'on',
StatsdServer => "udp://127.0.0.1:$statsd_port $prefix $suffix",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
delete_statsd_info();
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->login($setup->{user}, $setup->{passwd});
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
eval {
my $counters = get_statsd_info('counters');
my $counter_names = [
"$prefix.command.USER.331.$suffix",
"$prefix.command.PASS.230.$suffix",
"$prefix.command.QUIT.221.$suffix"
];
foreach my $counter_name (@$counter_names) {
my $counts = $counters->{$counter_name};
$self->assert($counts > 0,
"Expected count values for $counter_name, found none");
}
my $timers = get_statsd_info('timers');
# For timers, we simply expect to HAVE timings
my $timer_names = [
"$prefix.command.USER.331.$suffix",
"$prefix.command.PASS.230.$suffix",
"$prefix.command.QUIT.221.$suffix"
];
foreach my $timer_name (@$timer_names) {
my $timings = $timers->{$timer_name};
$self->assert(scalar(@$timings) > 0,
"Expected timing values for $timer_name, found none");
}
my $gauges = get_statsd_info('gauges');
# Our connection gauge is a GAUGE; we expect it to have the same value after
# as before.
my $gauge_name = "$prefix.connection.$suffix";
$self->assert($gauges->{$gauge_name} == 0,
"Expected $gauge_name gauge 0, got $gauges->{$gauge_name}");
};
if ($@) {
$ex = $@;
}
test_cleanup($setup->{log_file}, $ex);
}
sub statsd_timeout_login {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'statsd');
my $statsd_port = $ENV{STATSD_PORT};
my $timeout_login = 3;
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'events:10 timer:20 statsd:20 statsd.statsd:20 statsd.metric:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
TimeoutLogin => $timeout_login,
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_statsd.c' => {
StatsdEngine => 'on',
StatsdServer => "udp://127.0.0.1:$statsd_port",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
delete_statsd_info();
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Waiting for $timeout_login secs\n";
}
sleep($timeout_login + 1);
eval { $client->login($setup->{user}, $setup->{passwd}) };
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
eval {
my $counters = get_statsd_info('counters');
my $counter_names = [qw(
command.USER.331
command.PASS.230
command.QUIT.221
)];
foreach my $counter_name (@$counter_names) {
my $counts = $counters->{$counter_name};
$counts == 0 unless $counts;
$self->assert($counts == 0,
"Expected count values for $counter_name, found none");
}
$self->assert($counters->{'timeout.TimeoutLogin'} == 1,
"Expected count value for timeout.TimeoutLogin, found none");
my $timers = get_statsd_info('timers');
# For timers, we simply expect to HAVE timings
my $timer_names = [qw(
command.USER.331
command.PASS.230
command.QUIT.221
)];
foreach my $timer_name (@$timer_names) {
my $timings = $timers->{$timer_name};
$timings = [] unless $timings;
$self->assert(scalar(@$timings) == 0,
"Expected no timing values for $timer_name, found some");
}
my $gauges = get_statsd_info('gauges');
# Our connection gauge is a GAUGE; we expect it to have the same value after
# as before.
$self->assert($gauges->{connection} == 0,
"Expected connection gauge 0, got $gauges->{connection}");
};
if ($@) {
$ex = $@;
}
test_cleanup($setup->{log_file}, $ex);
}
sub statsd_exclude_filter {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'statsd');
my $statsd_port = $ENV{STATSD_PORT};
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'statsd:20 statsd.statsd:20 statsd.metric:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_statsd.c' => {
StatsdEngine => 'on',
StatsdExcludeFilter => '^SYST$',
StatsdServer => "udp://127.0.0.1:$statsd_port",
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
delete_statsd_info();
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->login($setup->{user}, $setup->{passwd});
$client->syst();
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
eval {
my $counters = get_statsd_info('counters');
my $counter_names = [qw(
command.USER.331
command.PASS.230
command.QUIT.221
)];
foreach my $counter_name (@$counter_names) {
my $counts = $counters->{$counter_name};
$self->assert($counts > 0,
"Expected count values for $counter_name, found none");
}
my $timers = get_statsd_info('timers');
# For timers, we simply expect to HAVE timings
my $timer_names = [qw(
command.USER.331
command.PASS.230
command.QUIT.221
)];
foreach my $timer_name (@$timer_names) {
my $timings = $timers->{$timer_name};
$self->assert(scalar(@$timings) > 0,
"Expected timing values for $timer_name, found none");
}
my $gauges = get_statsd_info('gauges');
# Our connection gauge is a GAUGE; we expect it to have the same value after
# as before.
$self->assert($gauges->{connection} == 0,
"Expected connection gauge 0, got $gauges->{connection}");
};
if ($@) {
$ex = $@;
}
test_cleanup($setup->{log_file}, $ex);
}
1;
proftpd-mod_statsd-0.1/t/lib/ProFTPD/Tests/Modules/mod_statsd/ 0000775 0000000 0000000 00000000000 13066013616 0024273 5 ustar 00root root 0000000 0000000 proftpd-mod_statsd-0.1/t/lib/ProFTPD/Tests/Modules/mod_statsd/mgmt.pm 0000664 0000000 0000000 00000003337 13066013616 0025603 0 ustar 00root root 0000000 0000000 package ProFTPD::Tests::Modules::mod_statsd::mgmt;
use strict;
use IO::Handle;
use Socket;
require Exporter;
our @ISA = qw(Exporter);
our @ADMIN = qw(
delete_statsd_info
get_statsd_info
statsd_mgmt
);
our @EXPORT_OK = (@ADMIN);
our %EXPORT_TAGS = (
admin => [@ADMIN],
);
sub statsd_mgmt {
my $port = $ENV{STATSD_MGMT_PORT};
my $opts = {
PeerHost => '127.0.0.1',
PeerPort => $port,
Proto => 'tcp',
Type => SOCK_STREAM,
Timeout => 3
};
my $client = IO::Socket::INET->new(%$opts);
unless ($client) {
croak("Can't connect to 127.0.0.1:$port: $!");
}
return $client;
}
sub statsd_cmd {
my $statsd = shift;
my $cmd = shift;
if ($ENV{TEST_DEBUG}) {
print STDERR "# Sending command: $cmd\n";
}
$statsd->print("$cmd\n");
$statsd->flush();
my $resp = '';
while (my $line = <$statsd>) {
chomp($line);
if ($ENV{TEST_DEBUG}) {
print STDERR "# Received response: '$line'\n";
}
last if $line eq 'END';
$resp .= $line;
}
return $resp;
}
sub delete_statsd_info {
my $statsd = statsd_mgmt();
my $cmd = "delcounters command.*";
statsd_cmd($statsd, $cmd);
$cmd = "deltimers command.*";
statsd_cmd($statsd, $cmd);
$cmd = "delgauges connections";
statsd_cmd($statsd, $cmd);
$statsd->close();
return 1;
}
sub get_statsd_info {
my $cmd = shift;
my $statsd = statsd_mgmt();
my $json = statsd_cmd($statsd, $cmd);
$statsd->close();
# statsd gives us (badly formatted) JSON; decode it into Perl.
$json =~ s/ (\S+): / '\1': /g;
$json =~ s/'{1,2}/\"/g;
if ($ENV{TEST_JSON}) {
print STDERR "# Received JSON: '$json'\n";
}
require JSON;
JSON->import(qw(decode_json));
my $info = decode_json($json);
return $info;
}
1;
proftpd-mod_statsd-0.1/t/lib/ProFTPD/Tests/Modules/mod_statsd/tls.pm 0000664 0000000 0000000 00000017732 13066013616 0025445 0 ustar 00root root 0000000 0000000 package ProFTPD::Tests::Modules::mod_statsd::tls;
use lib qw(t/lib);
use base qw(ProFTPD::TestSuite::Child);
use strict;
use Cwd;
use File::Path qw(mkpath rmtree);
use File::Spec;
use IO::Handle;
use Socket;
use ProFTPD::TestSuite::FTP;
use ProFTPD::TestSuite::Utils qw(:auth :config :running :test :testsuite);
use ProFTPD::Tests::Modules::mod_statsd::mgmt qw(:admin);
$| = 1;
# NOTE: Use the TEST_DEBUG environment variable for debugging the on-the-wire
# communication with the statsd management port.
my $order = 0;
my $TESTS = {
statsd_tls_handshakes => {
order => ++$order,
test_class => [qw(forking)],
},
statsd_tls_protocol_and_cipher => {
order => ++$order,
test_class => [qw(forking)],
},
};
sub new {
return shift()->SUPER::new(@_);
}
sub list_tests {
# Check for the required Perl modules:
#
# IO-Socket-SSL
# JSON
# Net-FTPSSL
my $required = [qw(
IO::Socket::SSL
JSON
Net::FTPSSL
)];
foreach my $req (@$required) {
eval "use $req";
if ($@) {
print STDERR "\nWARNING:\n + Module '$req' not found, skipping all tests\n";
if ($ENV{TEST_VERBOSE}) {
print STDERR "Unable to load $req: $@\n";
}
return qw(testsuite_empty_test);
}
}
# Check for required environment variables, pointing us at the local
# statsd instance and our config files.
$required = [qw(
PROFTPD_TEST_LIB
STATSD_MGMT_PORT
)];
$ENV{STATSD_PORT} = 8125 unless defined($ENV{STATSD_PORT});
$ENV{STATSD_MGMT_PORT} = 8126 unless defined($ENV{STATSD_MGMT_PORT});
foreach my $req (@$required) {
unless (defined($ENV{$req})) {
print STDERR "\nWARNING:\n + Environment variable '$req' not found, skipping all tests\n";
return qw(testsuite_empty_test);
}
}
return testsuite_get_runnable_tests($TESTS);
}
sub statsd_tls_handshakes {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'statsd');
my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_LIB}/t/etc/modules/mod_tls/server-cert.pem");
my $statsd_port = $ENV{STATSD_PORT};
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'statsd:20 statsd.statsd:20 statsd.metric:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_statsd.c' => {
StatsdEngine => 'on',
StatsdServer => "udp://127.0.0.1:$statsd_port",
},
'mod_tls.c' => {
TLSEngine => 'on',
TLSLog => $setup->{log_file},
TLSRequired => 'on',
TLSRSACertificateFile => $cert_file,
}
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
delete_statsd_info();
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require Net::FTPSSL;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Give the server a chance to start up
sleep(1);
my $client = Net::FTPSSL->new('127.0.0.1',
Encryption => 'E',
Port => $port,
);
unless ($client) {
die("Can't connect to FTPS server: " . IO::Socket::SSL::errstr());
}
unless ($client->login($setup->{user}, $setup->{passwd})) {
die("Can't login: " . $client->last_message());
}
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
eval {
my $counters = get_statsd_info('counters');
my $counter_names = [qw(
tls.handshake.ctrl
)];
foreach my $counter_name (@$counter_names) {
my $counts = $counters->{$counter_name};
$self->assert($counts > 0,
"Expected count values for $counter_name, found none");
}
my $timers = get_statsd_info('timers');
# For timers, we simply expect to HAVE timings
my $timer_names = [qw(
tls.handshake.ctrl
)];
foreach my $timer_name (@$timer_names) {
my $timings = $timers->{$timer_name};
$self->assert(scalar(@$timings) > 0,
"Expected timing values for $timer_name, found none");
}
};
if ($@) {
$ex = $@;
}
test_cleanup($setup->{log_file}, $ex);
}
sub statsd_tls_protocol_and_cipher {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'statsd');
my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_LIB}/t/etc/modules/mod_tls/server-cert.pem");
my $statsd_port = $ENV{STATSD_PORT};
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'statsd:20 statsd.statsd:20 statsd.metric:20 tls:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_statsd.c' => {
StatsdEngine => 'on',
StatsdServer => "udp://127.0.0.1:$statsd_port",
},
'mod_tls.c' => {
TLSEngine => 'on',
TLSLog => $setup->{log_file},
TLSRequired => 'on',
TLSRSACertificateFile => $cert_file,
TLSOptions => 'EnableDiags StdEnvVars',
TLSProtocol => 'SSLv3 TLSv1 TLSv1.1',
}
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
delete_statsd_info();
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
require Net::FTPSSL;
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Give the server a chance to start up
sleep(1);
my $ssl_opts = {
SSL_hostname => '127.0.0.1',
SSL_version => 'SSLv3',
};
my $client = Net::FTPSSL->new('127.0.0.1',
Encryption => 'E',
Port => $port,
SSL_Client_Certificate => $ssl_opts,
);
unless ($client) {
die("Can't connect to FTPS server: " . IO::Socket::SSL::errstr());
}
unless ($client->login($setup->{user}, $setup->{passwd})) {
die("Can't login: " . $client->last_message());
}
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
eval {
my $counters = get_statsd_info('counters');
my $counter_names = [qw(
tls.handshake.ctrl
)];
foreach my $counter_name (@$counter_names) {
my $counts = $counters->{$counter_name};
$self->assert($counts > 0,
"Expected count values for $counter_name, found none");
}
my $timers = get_statsd_info('timers');
# For timers, we simply expect to HAVE timings
my $timer_names = [qw(
tls.handshake.ctrl
)];
foreach my $timer_name (@$timer_names) {
my $timings = $timers->{$timer_name};
$self->assert(scalar(@$timings) > 0,
"Expected timing values for $timer_name, found none");
}
};
if ($@) {
$ex = $@;
}
test_cleanup($setup->{log_file}, $ex);
}
1;
proftpd-mod_statsd-0.1/t/modules/ 0000775 0000000 0000000 00000000000 13066013616 0017104 5 ustar 00root root 0000000 0000000 proftpd-mod_statsd-0.1/t/modules/mod_statsd.t 0000664 0000000 0000000 00000000266 13066013616 0021436 0 ustar 00root root 0000000 0000000 #!/usr/bin/env perl
use lib qw(t/lib);
use strict;
use Test::Unit::HarnessUnit;
$| = 1;
my $r = Test::Unit::HarnessUnit->new();
$r->start("ProFTPD::Tests::Modules::mod_statsd");
proftpd-mod_statsd-0.1/t/modules/mod_statsd/ 0000775 0000000 0000000 00000000000 13066013616 0021245 5 ustar 00root root 0000000 0000000 proftpd-mod_statsd-0.1/t/modules/mod_statsd/tls.t 0000664 0000000 0000000 00000000273 13066013616 0022236 0 ustar 00root root 0000000 0000000 #!/usr/bin/env perl
use lib qw(t/lib);
use strict;
use Test::Unit::HarnessUnit;
$| = 1;
my $r = Test::Unit::HarnessUnit->new();
$r->start("ProFTPD::Tests::Modules::mod_statsd::tls");