[Git][debian-proftpd-team/proftpd-mod-autohost][upstream] New upstream version 0.6
Hilmar Preuße
gitlab at salsa.debian.org
Tue Aug 18 08:26:13 BST 2020
Hilmar Preuße pushed to branch upstream at Debian ProFTPD Team / proftpd-mod-autohost
Commits:
eb22f567 by Hilmar Preuße at 2020-08-18T09:19:18+02:00
New upstream version 0.6
- - - - -
11 changed files:
- + .gitattributes
- + .gitignore
- + .travis.yml
- + README.md
- mod_autohost.c
- mod_autohost.html
- t/lib/ProFTPD/Tests/Modules/mod_autohost.pm
- + t/lib/ProFTPD/Tests/Modules/mod_autohost/host.pm
- + t/lib/ProFTPD/Tests/Modules/mod_autohost/sni.pm
- + t/modules/mod_autohost/host.t
- + t/modules/mod_autohost/sni.t
Changes:
=====================================
.gitattributes
=====================================
@@ -0,0 +1,2 @@
+*.pl linguist-language=C
+*.pm linguist-language=C
=====================================
.gitignore
=====================================
@@ -0,0 +1,2 @@
+*.log
+*.sw?
=====================================
.travis.yml
=====================================
@@ -0,0 +1,33 @@
+env: TRAVIS_CI=true
+language: c
+
+compiler:
+ - gcc
+ - clang
+
+install:
+ # for unit tests
+ - sudo apt-get install -y check
+ - sudo apt-get install -y libsubunit-dev
+ # for static code analysis
+ # - sudo apt-get install -y cppcheck
+ # - sudo apt-get install -y rats
+ # for test code coverage
+ - sudo apt-get install -y lcov
+ - gem install coveralls-lcov
+
+before_script:
+ - cd ${TRAVIS_BUILD_DIR}
+ - lcov --directory . --zerocounters
+
+script:
+ # - find . -type f -name "*.c" -print | grep -v t\/ | xargs cppcheck 2>&1
+ # - find . -type f -name "*.c" -print | grep -v t\/ | xargs rats --language=c
+ - git clone --depth 100 https://github.com/proftpd/proftpd.git
+ - cp mod_autohost.c proftpd/contrib/
+ - cd proftpd/
+ - ./configure LIBS="-lm -lsubunit -lrt -pthread" --enable-devel=coverage --enable-dso --enable-tests --with-shared=mod_autohost
+ - make
+ - make clean
+ - ./configure LIBS="-lm -lsubunit -lrt -pthread" --enable-devel=coverage --enable-tests --with-modules=mod_autohost
+ - make
=====================================
README.md
=====================================
@@ -0,0 +1,13 @@
+proftpd-mod_autohost
+====================
+
+Status
+------
+[![Build Status](https://travis-ci.org/Castaglia/proftpd-mod_autohost.svg?branch=master)](https://travis-ci.org/Castaglia/proftpd-mod_autohost)
+
+Synopsis
+--------
+The `mod_autohost` module for ProFTPD is used to support large numbers
+of `<VirtualHost>` sections _without_ requiring large config files to do so.
+
+For further module documentation, see [mod_autohost.html](https://htmlpreview.github.io/?https://github.com/Castaglia/proftpd-mod_autohost/blob/master/mod_autohost.html).
=====================================
mod_autohost.c
=====================================
@@ -1,7 +1,6 @@
/*
* ProFTPD: mod_autohost -- a module for mass virtual hosting
- *
- * Copyright (c) 2004-2011 TJ Saunders
+ * Copyright (c) 2004-2020 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
@@ -24,89 +23,145 @@
*
* This is mod_autohost, contrib software for proftpd 1.3.x and above.
* For more information contact TJ Saunders <tj at castaglia.org>.
- *
- * $Id: mod_autohost.c,v 1.4 2011/03/01 21:29:09 tj Exp tj $
*/
#include "conf.h"
#include "privs.h"
-#define MOD_AUTOHOST_VERSION "mod_autohost/0.4"
+#define MOD_AUTOHOST_VERSION "mod_autohost/0.6"
-#if PROFTPD_VERSION_NUMBER < 0x0001030401
-# error "ProFTPD 1.3.4rc1 or later required"
+#if PROFTPD_VERSION_NUMBER < 0x0001030604
+# error "ProFTPD 1.3.6 or later required"
#endif
module autohost_module;
static const char *autohost_config = NULL;
-static unsigned int autohost_engine = FALSE;
+static int autohost_engine = FALSE;
static int autohost_logfd = -1;
static pool *autohost_pool = NULL;
static xaset_t *autohost_server_list = NULL;
-/* XXX Note: this function makes crass assumptions about IPv4 connections;
- * we are soon going to need to properly support IPv6 addresses/connections.
- */
-static char *autohost_get_config(conn_t *conn) {
+static const char *trace_channel = "autohost";
+
+static char *autohost_get_config(conn_t *conn, const char *server_name) {
char *ipstr, *portstr, *path = (char *) autohost_config;
- char *oct1str, *oct2str, *oct3str, *oct4str;
- char *start, *end;
+ int family;
+ family = pr_netaddr_get_family(conn->local_addr);
ipstr = (char *) pr_netaddr_get_ipstr(conn->local_addr);
- start = ipstr;
- end = strchr(start, '.');
- *end = '\0';
- oct1str = pstrdup(autohost_pool, start);
+ if (family == AF_INET) {
+ char *oct1str, *oct2str, *oct3str, *oct4str;
+ char *start, *end;
+
+ start = ipstr;
+ end = strchr(start, '.');
+ *end = '\0';
+ oct1str = pstrdup(autohost_pool, start);
+
+ start = end + 1;
+ *end = '.';
+ end = strchr(start, '.');
+ *end = '\0';
+ oct2str = pstrdup(autohost_pool, start);
+
+ start = end + 1;
+ *end = '.';
+ end = strchr(start, '.');
+ *end = '\0';
+ oct3str = pstrdup(autohost_pool, start);
+
+ start = end + 1;
+ *end = '.';
+ oct4str = pstrdup(autohost_pool, start);
+
+ if (strstr(path, "%1") != NULL) {
+ path = (char *) sreplace(autohost_pool, path, "%1", oct1str, NULL);
+ }
- start = end + 1;
- *end = '.';
- end = strchr(start, '.');
- *end = '\0';
- oct2str = pstrdup(autohost_pool, start);
+ if (strstr(path, "%2") != NULL) {
+ path = (char *) sreplace(autohost_pool, path, "%2", oct2str, NULL);
+ }
- start = end + 1;
- *end = '.';
- end = strchr(start, '.');
- *end = '\0';
- oct3str = pstrdup(autohost_pool, start);
+ if (strstr(path, "%3") != NULL) {
+ path = (char *) sreplace(autohost_pool, path, "%3", oct3str, NULL);
+ }
- start = end + 1;
- *end = '.';
- oct4str = pstrdup(autohost_pool, start);
+ if (strstr(path, "%4") != NULL) {
+ path = (char *) sreplace(autohost_pool, path, "%4", oct4str, NULL);
+ }
+ }
portstr = pcalloc(autohost_pool, 10);
snprintf(portstr, 10, "%u", conn->local_port);
if (strstr(path, "%0") != NULL) {
- path = sreplace(autohost_pool, path, "%0", ipstr, NULL);
+ path = (char *) sreplace(autohost_pool, path, "%0", ipstr, NULL);
}
- if (strstr(path, "%1") != NULL) {
- path = sreplace(autohost_pool, path, "%1", oct1str, NULL);
+ if (server_name != NULL) {
+ /* Note: How to handle the case-insensitive name of SNI/HOST, and the
+ * case-sensitive nature of paths. Should we always downcase the entire
+ * server name, for purposes of config file lookup?
+ */
+ if (strstr(path, "%n") != NULL) {
+ path = (char *) sreplace(autohost_pool, path, "%n", server_name, NULL);
+ }
}
- if (strstr(path, "%2") != NULL) {
- path = sreplace(autohost_pool, path, "%2", oct2str, NULL);
+ if (strstr(path, "%p") != NULL) {
+ path = (char *) sreplace(autohost_pool, path, "%p", portstr, NULL);
}
- if (strstr(path, "%3") != NULL) {
- path = sreplace(autohost_pool, path, "%3", oct3str, NULL);
- }
+ return path;
+}
- if (strstr(path, "%4") != NULL) {
- path = sreplace(autohost_pool, path, "%4", oct4str, NULL);
+/* Largely borrowed/copied from src/bindings.c. */
+static unsigned int process_serveralias(server_rec *s) {
+ unsigned namebind_count = 0;
+ config_rec *c;
+
+ /* If there is no ipbind already for this server, we cannot associate
+ * any ServerAlias-based namebinds to it.
+ */
+ if (pr_ipbind_get_server(s->addr, s->ServerPort) == NULL) {
+ return 0;
}
- if (strstr(path, "%p") != NULL) {
- path = sreplace(autohost_pool, path, "%p", portstr, NULL);
+ c = find_config(s->conf, CONF_PARAM, "ServerAlias", FALSE);
+ while (c != NULL) {
+ int res;
+
+ pr_signals_handle();
+
+ res = pr_namebind_create(s, c->argv[0], s->addr, s->ServerPort);
+ if (res == 0) {
+ namebind_count++;
+
+ res = pr_namebind_open(c->argv[0], s->addr, s->ServerPort);
+ if (res < 0) {
+ pr_trace_msg(trace_channel, 2,
+ "notice: unable to open namebind '%s': %s", (char *) c->argv[0],
+ strerror(errno));
+ }
+
+ } else {
+ if (errno != ENOENT) {
+ pr_trace_msg(trace_channel, 3,
+ "unable to create namebind for '%s' to %s#%u: %s",
+ (char *) c->argv[0], pr_netaddr_get_ipstr(s->addr), s->ServerPort,
+ strerror(errno));
+ }
+ }
+
+ c = find_config_next(c, c->next, CONF_PARAM, "ServerAlias", FALSE);
}
- return path;
+ return namebind_count;
}
-static int autohost_parse_config(conn_t *conn, char *path) {
+static int autohost_parse_config(conn_t *conn, const char *path) {
server_rec *s;
pr_ipbind_t *binding;
@@ -131,8 +186,12 @@ static int autohost_parse_config(conn_t *conn, char *path) {
pr_parser_cleanup();
if (fixup_servers(autohost_server_list) < 0) {
+ int xerrno = errno;
+
(void) pr_log_writefile(autohost_logfd, MOD_AUTOHOST_VERSION,
- "error fixing up autohost: %s", strerror(errno));
+ "error fixing up autohost: %s", strerror(xerrno));
+
+ errno = xerrno;
return -1;
}
@@ -142,30 +201,39 @@ static int autohost_parse_config(conn_t *conn, char *path) {
/* Now that we have a valid server_rec, we need to bind it to
* the address to which the client connected.
*/
+ process_serveralias(s);
binding = pr_ipbind_find(conn->local_addr, conn->local_port, TRUE);
- if (binding == NULL) {
- if (pr_ipbind_create(s, conn->local_addr, conn->local_port) < 0) {
- (void) pr_log_writefile(autohost_logfd, MOD_AUTOHOST_VERSION,
- "error creating binding: %s", strerror(errno));
- return -1;
- }
-
- if (pr_ipbind_open(conn->local_addr, conn->local_port, main_server->listen,
- TRUE, TRUE, FALSE) < 0) {
- (void) pr_log_writefile(autohost_logfd, MOD_AUTOHOST_VERSION,
- "error opening binding for %s#%d: %s",
- pr_netaddr_get_ipstr(conn->local_addr), conn->local_port,
- strerror(errno));
- return -1;
- }
-
- } else {
-
+ if (binding != NULL) {
/* If we already have a binding in place, we need to replace the
* server_rec to which that binding points with our new server_rec.
*/
binding->ib_server = s;
+
+ return 0;
+ }
+
+ if (pr_ipbind_create(s, conn->local_addr, conn->local_port) < 0) {
+ int xerrno = errno;
+
+ (void) pr_log_writefile(autohost_logfd, MOD_AUTOHOST_VERSION,
+ "error creating binding: %s", strerror(xerrno));
+
+ errno = xerrno;
+ return -1;
+ }
+
+ if (pr_ipbind_open(conn->local_addr, conn->local_port, main_server->listen,
+ TRUE, TRUE, FALSE) < 0) {
+ int xerrno = errno;
+
+ (void) pr_log_writefile(autohost_logfd, MOD_AUTOHOST_VERSION,
+ "error opening binding for %s#%d: %s",
+ pr_netaddr_get_ipstr(conn->local_addr), conn->local_port,
+ strerror(xerrno));
+
+ errno = xerrno;
+ return -1;
}
return 0;
@@ -190,19 +258,20 @@ MODRET set_autohostconfig(cmd_rec *cmd) {
/* usage: AutoHostEngine on|off */
MODRET set_autohostengine(cmd_rec *cmd) {
- int bool;
+ int engine;
config_rec *c;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT);
- bool = get_boolean(cmd, 1);
- if (bool == -1)
+ 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] = pcalloc(c->pool, sizeof(unsigned int));
- *((unsigned int *) c->argv[0]) = bool;
+ c->argv[0] = pcalloc(c->pool, sizeof(int));
+ *((int *) c->argv[0]) = engine;
return PR_HANDLED(cmd);
}
@@ -255,25 +324,52 @@ MODRET set_autohostports(cmd_rec *cmd) {
return PR_HANDLED(cmd);
}
-/* Event handlers
+/* Command handlers
+ */
+
+MODRET autohost_pre_host(cmd_rec *cmd) {
+ const char *path, *server_name;
+ struct stat st;
+
+ if (autohost_engine == FALSE) {
+ return PR_DECLINED(cmd);
+ }
+
+ server_name = (const char *) cmd->argv[1];
+ path = autohost_get_config(session.c, server_name);
+ pr_trace_msg(trace_channel, 4, "using AutoHostConfig path '%s' for '%s %s'",
+ path, (const char *) cmd->argv[0], server_name);
+
+ if (pr_fsio_stat(path, &st) < 0) {
+ (void) pr_log_writefile(autohost_logfd, MOD_AUTOHOST_VERSION,
+ "error checking for '%s': %s", path, strerror(errno));
+ return PR_DECLINED(cmd);
+ }
+
+ if (autohost_parse_config(session.c, path) < 0) {
+ (void) pr_log_writefile(autohost_logfd, MOD_AUTOHOST_VERSION,
+ "error parsing '%s': %s", path, strerror(errno));
+ return PR_DECLINED(cmd);
+ }
+
+ pr_trace_msg(trace_channel, 9, "'%s %s' found using autohost for %s#%u",
+ (const char *) cmd->argv[0], server_name,
+ pr_netaddr_get_ipstr(session.c->local_addr), session.c->local_port);
+
+ return PR_DECLINED(cmd);
+}
+
+/* Event listeners
*/
static void autohost_connect_ev(const void *event_data, void *user_data) {
- char *path;
+ const char *path;
struct stat st;
conn_t *conn = (conn_t *) event_data;
- if (!autohost_engine)
- return;
-
-#ifdef PR_USE_IPV6
- /* NOTE: we currently do not handle IPv6 address. */
- if (pr_netaddr_get_family(conn->local_addr) == AF_INET6) {
- pr_log_debug(DEBUG0, MOD_AUTOHOST_VERSION
- ": unable to handle IPv6 addresses");
+ if (autohost_engine == FALSE) {
return;
}
-#endif /* PR_USE_IPV6 */
/* Autohost config files, if found, will take precedence over a matching
* server config found in the main config file.
@@ -291,11 +387,26 @@ static void autohost_connect_ev(const void *event_data, void *user_data) {
* It is allocated after the fork().
*/
- path = autohost_get_config(conn);
+ path = autohost_get_config(conn, NULL);
+ pr_trace_msg(trace_channel, 4, "using AutoHostConfig path '%s'", path);
if (pr_fsio_stat(path, &st) < 0) {
- (void) pr_log_writefile(autohost_logfd, MOD_AUTOHOST_VERSION,
- "error checking for '%s': %s", path, strerror(errno));
+ int xerrno = errno;
+
+ /* If the given path contains '%n', and the path is not found, do not log
+ * the error. The file in question may be intended for name-based vhosts,
+ * which can only be resolved at SNI/HOST time, not at TCP connect time.
+ */
+ if (xerrno == ENOENT &&
+ strstr(path, "%n") != NULL) {
+ pr_trace_msg(trace_channel, 19,
+ "ignoring connect-time check of name-based config file '%s'", path);
+
+ } else {
+ (void) pr_log_writefile(autohost_logfd, MOD_AUTOHOST_VERSION,
+ "error checking for '%s': %s", path, strerror(xerrno));
+ }
+
return;
}
@@ -305,50 +416,91 @@ static void autohost_connect_ev(const void *event_data, void *user_data) {
return;
}
- (void) pr_log_writefile(autohost_logfd, MOD_AUTOHOST_VERSION,
- "found autohost for %s#%u", pr_netaddr_get_ipstr(conn->local_addr),
- conn->local_port);
+ pr_trace_msg(trace_channel, 9, "found using autohost for %s#%u",
+ pr_netaddr_get_ipstr(conn->local_addr), conn->local_port);
return;
}
#if defined(PR_SHARED_MODULE)
static void autohost_mod_unload_ev(const void *event_data, void *user_data) {
- if (strcmp("mod_autohost.c", (const char *) event_data) == 0) {
- pr_event_unregister(&autohost_module, NULL, NULL);
+ if (strcmp("mod_autohost.c", (const char *) event_data) != 0) {
+ return;
+ }
+
+ pr_event_unregister(&autohost_module, NULL, NULL);
+
+ if (autohost_pool != NULL) {
+ destroy_pool(autohost_pool);
+ autohost_pool = NULL;
}
}
-#endif
+#endif /* PR_SHARED_MODULE */
+
+static void autohost_sni_ev(const void *event_data, void *user_data) {
+ const char *path, *server_name;
+ struct stat st;
+
+ if (autohost_engine == FALSE) {
+ return;
+ }
+
+ server_name = (const char *) event_data;
+ path = autohost_get_config(session.c, server_name);
+ pr_trace_msg(trace_channel, 4,
+ "using AutoHostConfig path '%s' for TLS SNI '%s'", path, server_name);
+
+ if (pr_fsio_stat(path, &st) < 0) {
+ (void) pr_log_writefile(autohost_logfd, MOD_AUTOHOST_VERSION,
+ "error checking for '%s': %s", path, strerror(errno));
+ return;
+ }
+
+ if (autohost_parse_config(session.c, path) < 0) {
+ (void) pr_log_writefile(autohost_logfd, MOD_AUTOHOST_VERSION,
+ "error parsing '%s': %s", path, strerror(errno));
+ return;
+ }
+
+ pr_trace_msg(trace_channel, 9, "TLS SNI '%s' found using autohost for %s#%u",
+ server_name, pr_netaddr_get_ipstr(session.c->local_addr),
+ session.c->local_port);
+
+ return;
+}
static void autohost_postparse_ev(const void *event_data, void *user_data) {
config_rec *c;
c = find_config(main_server->conf, CONF_PARAM, "AutoHostEngine", FALSE);
- if (c) {
- autohost_engine = *((unsigned int *) c->argv[0]);
+ if (c != NULL) {
+ autohost_engine = *((int *) c->argv[0]);
}
- if (!autohost_engine)
+ if (autohost_engine == FALSE) {
return;
+ }
autohost_pool = make_sub_pool(permanent_pool);
pr_pool_tag(autohost_pool, MOD_AUTOHOST_VERSION);
pr_event_register(&autohost_module, "core.connect", autohost_connect_ev,
NULL);
+ pr_event_register(&autohost_module, "mod_tls.sni", autohost_sni_ev, NULL);
c = find_config(main_server->conf, CONF_PARAM, "AutoHostConfig", FALSE);
- if (c) {
+ if (c != NULL) {
autohost_config = c->argv[0];
} else {
pr_log_debug(DEBUG0, MOD_AUTOHOST_VERSION
": missing required AutoHostConfig");
- end_login(1);
+ pr_session_disconnect(&autohost_module, PR_SESS_DISCONNECT_BAD_CONFIG,
+ "missing required AutoHostConfig directive");
}
c = find_config(main_server->conf, CONF_PARAM, "AutoHostLog", FALSE);
- if (c) {
+ if (c != NULL) {
int res;
char *autohost_log;
@@ -385,7 +537,7 @@ static void autohost_postparse_ev(const void *event_data, void *user_data) {
autohost_server_list = xaset_create(autohost_pool, NULL);
c = find_config(main_server->conf, CONF_PARAM, "AutoHostPorts", FALSE);
- if (c) {
+ if (c != NULL) {
register unsigned int i;
array_header *port_list;
int *ports;
@@ -468,6 +620,13 @@ static conftable autohost_conftab[] = {
{ NULL }
};
+static cmdtable autohost_cmdtab[] = {
+#if defined(C_HOST)
+ { PRE_CMD, C_HOST, G_NONE, autohost_pre_host, FALSE, FALSE },
+#endif /* FTP HOST support */
+ { 0, NULL }
+};
+
module autohost_module = {
NULL, NULL,
@@ -481,7 +640,7 @@ module autohost_module = {
autohost_conftab,
/* Module command handler table */
- NULL,
+ autohost_cmdtab,
/* Module authentication handler table */
NULL,
=====================================
mod_autohost.html
=====================================
@@ -1,6 +1,3 @@
-<!-- $Id: mod_autohost.html,v 1.5 2009/03/03 19:00:07 tj Exp tj $ -->
-<!-- $Source: /home/tj/proftpd/modules/doc/RCS/mod_autohost.html,v $ -->
-
<html>
<head>
<title>ProFTPD module mod_autohost</title>
@@ -15,26 +12,26 @@
<hr><br>
<p>
-For sites that run a large number of <code><VirtualHost></code>s for
-<code>proftpd</code>, it can be cumbersome to configure them all in
-the <code>proftpd.conf</code> file. Adding or removing virtual server
-configurations require restarting the daemon, as do changes to one of the
-server configurations. The daemon also consumes memory for each server
+For sites that run a large number of <code><VirtualHost></code>
+sections for ProFTPD, it can be cumbersome to configure them all in the
+<code>proftpd.conf</code> file. Adding or removing virtual server
+configurations requires <i>restarting</i> the daemon, as do changes to one of
+the server configurations. The daemon also consumes memory for each server
configuration, and the memory footprint for the daemon process can grow
large for large numbers of servers.
<p>
The <code>mod_autohost</code> module allows for server configurations to
be configured in individual files, and for those configuration to be used
-in an "on demand" fashion. Rather than loading the configurations into
+in an <i>on demand</i> fashion. Rather than loading the configurations into
memory when the daemon starts up, the daemon will check the IP address and
port being contacted by a connecting client, check in the filesystem for
a <code>mod_autohost</code> configuration file for that address/port,
dynamically parse the configuration, and insert the configuration into
the session's process space. Thus changes to the configuration are
-seen whenever a client connects, without requiring a daemon restart.
-The memory footprint is reduced because <code>proftpd</code>, via
-<code>mod_autohost</code>, only reads and uses the needed configuration.
+seen whenever a client connects, <i>without requiring a daemon restart</i>.
+The memory footprint is reduced because ProFTPD, via <code>mod_autohost</code>,
+only reads and uses the needed configuration.
<p>
This module is contained in the <code>mod_autohost</code> file for
@@ -44,7 +41,7 @@ instructions are discussed <a href="#Installation">here</a>.
<p>
The most current version of <code>mod_autohost</code> can be found at:
<pre>
- <a href="http://www.castaglia.org/proftpd/">http://www.castaglia.org/proftpd/</a>
+ <a href="https://github.com/Castaglia/proftpd-mod_autohost">https://github.com/Castaglia/proftpd-mod_autohost</a>
</pre>
<h2>Author</h2>
@@ -74,23 +71,41 @@ The <code>AutoHostConfig</code> directive specifies the path that
The given <em>path</em> must be an absolute path, and may contain the
following variables, which will be interpolated:
<ul>
- <li><dt>%0</dt>
- <dd>the entire IP address</dd>
-
- <li><dt>%1</dt>
- <dd>the first octet of an IPv4 address</dd>
-
- <li><dt>%2</dt>
- <dd>the second octet of an IPv4 address</dd>
-
- <li><dt>%3</dt>
- <dd>the third octet of an IPv4 address</dd>
-
- <li><dt>%4</dt>
- <dd>the fourth octet of an IPv4 address</dd>
-
- <li><dt>%p</dt>
- <dd>the port number</dd>
+ <li><code>%0</code><br>
+ <p>
+ The entire IP address
+ </li>
+
+ <p>
+ <li><code>%1</code><br>
+ <p>
+ The first octet of an IPv4 address
+ </li>
+
+ <li><code>%2</code><br>
+ <p>
+ The second octet of an IPv4 address
+ </li>
+
+ <li><code>%3</code><br>
+ <p>
+ The third octet of an IPv4 address
+ </li>
+
+ <li><code>%4</code><br>
+ <p>
+ The fourth octet of an IPv4 address
+ </li>
+
+ <li><code>%n</code><br>
+ <p>
+ The <em>name</em> used by the client, either via TLS SNI or FTP <code>HOST</code> command
+ </li>
+
+ <li><code>%p</code><br>
+ <p>
+ The port number
+ </li>
</ul>
<b>Note</b>: This directive is <b>required</b> for <code>mod_autohost</code>
to function.
@@ -106,6 +121,7 @@ and a client connecting to 1.2.3.4, the above <em>path</em> would expand into:
/etc/ftpd/vhosts/1.2.3.4/autohost.conf
</pre>
+<p>
Given a <em>path</em> of:
<pre>
/etc/ftpd/vhosts/%1/%2/%3/%4/%p/vhost.conf
@@ -128,8 +144,11 @@ would check for the following file:
<p>
The <code>AutoHostEngine</code> directive enables or disables the module's
runtime checks for dynamic server configuration files. If it is set to
-<em>off</em> this module does no checking. Use this directive to disable the
-module instead of commenting out all <code>mod_autohost</code> directives.
+<em>off</em> this module does no checking.
+
+<p>
+Use this directive to disable the module instead of commenting out all
+<code>mod_autohost</code> directives.
<p>
<hr>
@@ -201,16 +220,16 @@ file into:
after unpacking the latest proftpd-1.3 source code. For including
<code>mod_autohost</code> as a staticly linked module:
<pre>
- ./configure --with-modules=mod_autohost
+ $ ./configure --with-modules=mod_autohost
</pre>
Alternatively, <code>mod_autohost</code> could be built as a DSO module:
<pre>
- ./configure --enable-dso --with-shared=mod_autohost
+ $ ./configure --enable-dso --with-shared=mod_autohost
</pre>
Then follow the usual steps:
<pre>
- make
- make install
+ $ make
+ $ make install
</pre>
<p>
@@ -257,23 +276,12 @@ The <code>mod_tls</code> module will not be able to properly prompt for
passphrases for keys in an <code>autohost.conf</code> file on server startup.
<p>
-IPv6 addresses are not currently handled by <code>mod_autohost</code>.
-
-<p>
-<hr><br>
-
-Author: <i>$Author: tj $</i><br>
-Last Updated: <i>$Date: 2009/03/03 19:00:07 $</i><br>
-
-<br><hr>
-
+<hr>
<font size=2><b><i>
-© Copyright 2004-2009 TJ Saunders<br>
+© Copyright 2004-2020 TJ Saunders<br>
All Rights Reserved<br>
</i></b></font>
-
-<hr><br>
+<hr>
</body>
</html>
-
=====================================
t/lib/ProFTPD/Tests/Modules/mod_autohost.pm
=====================================
@@ -9,6 +9,7 @@ use Digest::MD5;
use File::Path qw(mkpath);
use File::Spec;
use IO::Handle;
+use IO::Socket::INET6;
use POSIX qw(:fcntl_h);
use ProFTPD::TestSuite::FTP;
@@ -24,6 +25,11 @@ my $TESTS = {
test_class => [qw(forking)],
},
+ autohost_config_ipv6 => {
+ order => ++$order,
+ test_class => [qw(features_ipv6 forking)],
+ },
+
autohost_ports => {
order => ++$order,
test_class => [qw(forking)],
@@ -34,6 +40,14 @@ my $TESTS = {
test_class => [qw(bug forking)],
},
+ # XXX mod_autohost does not picking up <Global> sections from proftpd.conf
+ # yet; requires a fair amount of reworking, since fixup_globals() removes
+ # the <Global> sections from the config tree.
+ autohost_global_config => {
+ order => ++$order,
+ test_class => [qw(bug forking)],
+ },
+
};
sub new {
@@ -173,6 +187,155 @@ EOC
unlink($log_file);
}
+sub autohost_config_ipv6 {
+ my $self = shift;
+ my $tmpdir = $self->{tmpdir};
+ my $setup = test_setup($tmpdir, 'autohost');
+
+ mkpath("$tmpdir/conf.d");
+ my $auto_config = File::Spec->rel2abs("$tmpdir/conf.d/::1.conf");
+ if (open(my $fh, "> $auto_config")) {
+ print $fh <<EOC;
+ServerName "AutoHost IPv6 Server"
+AuthUserFile $setup->{auth_user_file}
+AuthGroupFile $setup->{auth_group_file}
+ServerLog $setup->{log_file}
+RequireValidShell off
+EOC
+ unless (close($fh)) {
+ die("Can't write $auto_config: $!");
+ }
+
+ } else {
+ die("Can't open $auto_config: $!");
+ }
+
+ my $test_root = File::Spec->rel2abs($tmpdir);
+
+ my $config = {
+ PidFile => $setup->{pid_file},
+ ScoreboardFile => $setup->{scoreboard_file},
+ SystemLog => $setup->{log_file},
+ TraceLog => $setup->{log_file},
+ Trace => 'binding:10 autohost:20',
+
+ UseIPv6 => 'on',
+ DefaultAddress => '::1',
+
+ IfModules => {
+ 'mod_autohost.c' => {
+ AutoHostEngine => 'on',
+ AutoHostLog => $setup->{log_file},
+ AutoHostConfig => "$test_root/conf.d/%0.conf",
+ },
+
+ 'mod_delay.c' => {
+ DelayEngine => 'off',
+ },
+ },
+ };
+
+ my ($port, $config_user, $config_group) = config_write($setup->{config_file},
+ $config);
+
+ # 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 {
+ sleep(1);
+
+ my $client = IO::Socket::INET6->new(
+ PeerAddr => '::1',
+ PeerPort => $port,
+ Proto => 'tcp',
+ Timeout => 5,
+ Blocking => 1,
+ );
+ unless ($client) {
+ die("Can't connect to ::1: $!");
+ }
+
+ # Read the banner
+ my $banner = <$client>;
+ chomp($banner);
+ if ($ENV{TEST_VERBOSE}) {
+ print STDERR "# <<< $banner\n";
+ }
+
+ # Send the USER command
+ my $cmd = "USER $setup->{user}";
+ if ($ENV{TEST_VERBOSE}) {
+ print STDERR "# >>> $cmd\n";
+ }
+ $client->print("$cmd\r\n");
+ $client->flush();
+
+ # Read USER response
+ my $resp = <$client>;
+ if ($ENV{TEST_VERBOSE}) {
+ print STDERR "# <<< $resp";
+ }
+
+ my $expected = "331 Password required for $setup->{user}\r\n";
+ $self->assert($expected eq $resp,
+ test_msg("Expected response '$expected', got '$resp'"));
+
+ # Send the PASS command
+ $cmd = "PASS $setup->{passwd}";
+ if ($ENV{TEST_VERBOSE}) {
+ print STDERR "# >>> PASS ******\r\n";
+ }
+ $client->print("$cmd\r\n");
+ $client->flush();
+
+ # Read PASS response
+ $resp = <$client>;
+ if ($ENV{TEST_VERBOSE}) {
+ print STDERR "<<< $resp";
+ }
+
+ $expected = "230 User $setup->{user} logged in\r\n";
+ $self->assert($expected eq $resp,
+ test_msg("Expected response '$expected', got '$resp'"));
+
+ $client->close();
+ };
+
+ 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);
+
+ test_cleanup($setup->{log_file}, $ex);
+}
+
sub autohost_ports {
my $self = shift;
my $tmpdir = $self->{tmpdir};
@@ -316,6 +479,140 @@ EOC
sub autohost_extlog_var_p {
my $self = shift;
my $tmpdir = $self->{tmpdir};
+ my $setup = test_setup($tmpdir, 'autohost');
+
+ my $test_root = File::Spec->rel2abs($tmpdir);
+ my $ext_log = File::Spec->rel2abs("$tmpdir/custom.log");
+
+ my $config = {
+ PidFile => $setup->{pid_file},
+ ScoreboardFile => $setup->{scoreboard_file},
+ SystemLog => $setup->{log_file},
+ TraceLog => $setup->{log_file},
+ Trace => 'DEFAULT:10',
+
+ LogFormat => 'custom "%p"',
+
+ IfModules => {
+ 'mod_delay.c' => {
+ DelayEngine => 'off',
+ },
+ },
+ };
+
+ my ($port, $config_user, $config_group) = config_write($setup->{config_file},
+ $config);
+
+ if (open(my $fh, ">> $setup->{config_file}")) {
+ print $fh <<EOC;
+<IfModule mod_autohost.c>
+ AutoHostEngine on
+ AutoHostLog $setup->{log_file}
+ AutoHostConfig $test_root/conf.d/%0:%p.conf
+ AutoHostPorts $port
+</IfModule>
+EOC
+ unless (close($fh)) {
+ die("Can't write $setup->{config_file}: $!");
+ }
+
+ } else {
+ die("Can't open $setup->{config_file}: $!");
+ }
+
+ mkpath("$tmpdir/conf.d");
+ my $auto_config = File::Spec->rel2abs("$tmpdir/conf.d/127.0.0.1:$port.conf");
+ if (open(my $fh, "> $auto_config")) {
+ print $fh <<EOC;
+ServerName "AutoHost Server"
+AuthUserFile $setup->{auth_user_file}
+AuthGroupFile $setup->{auth_group_file}
+ServerLog $setup->{log_file}
+RequireValidShell off
+ExtendedLog $ext_log ALL custom
+EOC
+ unless (close($fh)) {
+ die("Can't write $auto_config: $!");
+ }
+
+ } else {
+ die("Can't open $auto_config: $!");
+ }
+
+ # 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);
+
+ if ($ex) {
+ test_cleanup($setup->{log_file}, $ex);
+ return;
+ }
+
+ # Now, read in the ExtendedLog, and see whether the %p variable was
+ # properly written out.
+ eval {
+ if (open(my $fh, "< $ext_log")) {
+ my $line = <$fh>;
+ chomp($line);
+ close($fh);
+
+ if ($ENV{TEST_VERBOSE}) {
+ print STDERR "# $line\n";
+ }
+
+ $self->assert($port eq $line,
+ test_msg("Expected '$port', got '$line'"));
+
+ } else {
+ die("Can't read $ext_log: $!");
+ }
+ };
+ if ($@) {
+ $ex = $@;
+ }
+
+ test_cleanup($setup->{log_file}, $ex);
+}
+
+sub autohost_global_config {
+ my $self = shift;
+ my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/autohost.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/autohost.pid");
@@ -367,6 +664,10 @@ sub autohost_extlog_var_p {
DelayEngine => 'off',
},
},
+
+ Global => {
+ ExtendedLog => "$ext_log ALL custom",
+ },
};
my ($port, $config_user, $config_group) = config_write($config_file, $config);
@@ -397,7 +698,6 @@ AuthUserFile $auth_user_file
AuthGroupFile $auth_group_file
ServerLog $log_file
RequireValidShell off
-ExtendedLog $ext_log ALL custom
EOC
unless (close($fh)) {
die("Can't write $auto_config: $!");
=====================================
t/lib/ProFTPD/Tests/Modules/mod_autohost/host.pm
=====================================
@@ -0,0 +1,560 @@
+package ProFTPD::Tests::Modules::mod_autohost::host;
+
+use lib qw(t/lib);
+use base qw(ProFTPD::TestSuite::Child);
+use strict;
+
+use Cwd;
+use Digest::MD5;
+use File::Path qw(mkpath);
+use File::Spec;
+use IO::Handle;
+use IO::Socket::INET6;
+use POSIX qw(:fcntl_h);
+
+use ProFTPD::TestSuite::FTP;
+use ProFTPD::TestSuite::Utils qw(:auth :config :running :test :testsuite);
+
+$| = 1;
+
+my $order = 0;
+
+my $TESTS = {
+ autohost_host_config_ok => {
+ order => ++$order,
+ test_class => [qw(forking)],
+ },
+
+ autohost_host_config_missing => {
+ order => ++$order,
+ test_class => [qw(forking)],
+ },
+
+ autohost_host_config_no_serveralias => {
+ order => ++$order,
+ test_class => [qw(forking)],
+ },
+
+ autohost_host_config_mismatched_serveralias => {
+ order => ++$order,
+ test_class => [qw(forking)],
+ },
+
+ autohost_host_config_existing_serveralias => {
+ order => ++$order,
+ test_class => [qw(forking)],
+ },
+
+};
+
+sub new {
+ return shift()->SUPER::new(@_);
+}
+
+sub list_tests {
+ return testsuite_get_runnable_tests($TESTS);
+}
+
+sub autohost_host_config_ok {
+ my $self = shift;
+ my $tmpdir = $self->{tmpdir};
+ my $setup = test_setup($tmpdir, 'autohost');
+
+ my $host = 'castaglia';
+
+ mkpath("$tmpdir/conf.d");
+ my $auto_config = File::Spec->rel2abs("$tmpdir/conf.d/127.0.0.1-$host.conf");
+ if (open(my $fh, "> $auto_config")) {
+ print $fh <<EOC;
+ServerAlias $host
+ServerName "AutoHost Server"
+AuthUserFile $setup->{auth_user_file}
+AuthGroupFile $setup->{auth_group_file}
+ServerLog $setup->{log_file}
+RequireValidShell off
+EOC
+ unless (close($fh)) {
+ die("Can't write $auto_config: $!");
+ }
+
+ } else {
+ die("Can't open $auto_config: $!");
+ }
+
+ my $test_root = File::Spec->rel2abs($tmpdir);
+
+ my $config = {
+ PidFile => $setup->{pid_file},
+ ScoreboardFile => $setup->{scoreboard_file},
+ SystemLog => $setup->{log_file},
+ TraceLog => $setup->{log_file},
+ Trace => 'autohost:20 binding:20 event:20',
+
+ IfModules => {
+ 'mod_autohost.c' => {
+ AutoHostEngine => 'on',
+ AutoHostLog => $setup->{log_file},
+ AutoHostConfig => "$test_root/conf.d/%0-%n.conf",
+ },
+
+ 'mod_delay.c' => {
+ DelayEngine => 'off',
+ },
+ },
+ };
+
+ my ($port, $config_user, $config_group) = config_write($setup->{config_file},
+ $config);
+
+ # 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->host($host);
+
+ my $resp_code = $client->response_code();
+ my $resp_msg = $client->response_msg();
+
+ my $expected = 'AutoHost Server';
+ $self->assert(qr/$expected/, $resp_msg,
+ test_msg("Expected response message '$expected', got '$resp_msg'"));
+
+ $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);
+
+ test_cleanup($setup->{log_file}, $ex);
+}
+
+sub autohost_host_config_missing {
+ my $self = shift;
+ my $tmpdir = $self->{tmpdir};
+ my $setup = test_setup($tmpdir, 'autohost');
+
+ my $host = 'castaglia';
+
+ mkpath("$tmpdir/conf.d");
+ my $auto_config = File::Spec->rel2abs("$tmpdir/conf.d/127.0.0.1-foo.conf");
+ if (open(my $fh, "> $auto_config")) {
+ print $fh <<EOC;
+ServerAlias $host
+ServerName "AutoHost Server"
+AuthUserFile $setup->{auth_user_file}
+AuthGroupFile $setup->{auth_group_file}
+ServerLog $setup->{log_file}
+RequireValidShell off
+EOC
+ unless (close($fh)) {
+ die("Can't write $auto_config: $!");
+ }
+
+ } else {
+ die("Can't open $auto_config: $!");
+ }
+
+ my $test_root = File::Spec->rel2abs($tmpdir);
+
+ my $config = {
+ PidFile => $setup->{pid_file},
+ ScoreboardFile => $setup->{scoreboard_file},
+ SystemLog => $setup->{log_file},
+ TraceLog => $setup->{log_file},
+ Trace => 'autohost:20 binding:20 event:20',
+
+ IfModules => {
+ 'mod_autohost.c' => {
+ AutoHostEngine => 'on',
+ AutoHostLog => $setup->{log_file},
+ AutoHostConfig => "$test_root/conf.d/%0-%n.conf",
+ },
+
+ 'mod_delay.c' => {
+ DelayEngine => 'off',
+ },
+ },
+ };
+
+ my ($port, $config_user, $config_group) = config_write($setup->{config_file},
+ $config);
+
+ # 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);
+ eval { $client->host($host) };
+ unless ($@) {
+ die("HOST $host succeeded unexpectedly");
+ }
+
+ $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);
+
+ test_cleanup($setup->{log_file}, $ex);
+}
+
+sub autohost_host_config_no_serveralias {
+ my $self = shift;
+ my $tmpdir = $self->{tmpdir};
+ my $setup = test_setup($tmpdir, 'autohost');
+
+ my $host = 'castaglia';
+
+ mkpath("$tmpdir/conf.d");
+ my $auto_config = File::Spec->rel2abs("$tmpdir/conf.d/127.0.0.1-$host.conf");
+ if (open(my $fh, "> $auto_config")) {
+ print $fh <<EOC;
+ServerName "AutoHost Server"
+AuthUserFile $setup->{auth_user_file}
+AuthGroupFile $setup->{auth_group_file}
+ServerLog $setup->{log_file}
+RequireValidShell off
+EOC
+ unless (close($fh)) {
+ die("Can't write $auto_config: $!");
+ }
+
+ } else {
+ die("Can't open $auto_config: $!");
+ }
+
+ my $test_root = File::Spec->rel2abs($tmpdir);
+
+ my $config = {
+ PidFile => $setup->{pid_file},
+ ScoreboardFile => $setup->{scoreboard_file},
+ SystemLog => $setup->{log_file},
+ TraceLog => $setup->{log_file},
+ Trace => 'autohost:20 binding:20 event:20',
+
+ IfModules => {
+ 'mod_autohost.c' => {
+ AutoHostEngine => 'on',
+ AutoHostLog => $setup->{log_file},
+ AutoHostConfig => "$test_root/conf.d/%0-%n.conf",
+ },
+
+ 'mod_delay.c' => {
+ DelayEngine => 'off',
+ },
+ },
+ };
+
+ my ($port, $config_user, $config_group) = config_write($setup->{config_file},
+ $config);
+
+ # 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);
+ eval { $client->host($host) };
+ unless ($@) {
+ die("HOST $host succeeded unexpectedly");
+ }
+
+ $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);
+
+ test_cleanup($setup->{log_file}, $ex);
+}
+
+sub autohost_host_config_mismatched_serveralias {
+ my $self = shift;
+ my $tmpdir = $self->{tmpdir};
+ my $setup = test_setup($tmpdir, 'autohost');
+
+ my $host = 'castaglia';
+
+ mkpath("$tmpdir/conf.d");
+ my $auto_config = File::Spec->rel2abs("$tmpdir/conf.d/127.0.0.1-$host.conf");
+ if (open(my $fh, "> $auto_config")) {
+ print $fh <<EOC;
+ServerAlias ftp.example.com
+ServerName "AutoHost Server"
+AuthUserFile $setup->{auth_user_file}
+AuthGroupFile $setup->{auth_group_file}
+ServerLog $setup->{log_file}
+RequireValidShell off
+EOC
+ unless (close($fh)) {
+ die("Can't write $auto_config: $!");
+ }
+
+ } else {
+ die("Can't open $auto_config: $!");
+ }
+
+ my $test_root = File::Spec->rel2abs($tmpdir);
+
+ my $config = {
+ PidFile => $setup->{pid_file},
+ ScoreboardFile => $setup->{scoreboard_file},
+ SystemLog => $setup->{log_file},
+ TraceLog => $setup->{log_file},
+ Trace => 'autohost:20 binding:20 event:20',
+
+ IfModules => {
+ 'mod_autohost.c' => {
+ AutoHostEngine => 'on',
+ AutoHostLog => $setup->{log_file},
+ AutoHostConfig => "$test_root/conf.d/%0-%n.conf",
+ },
+
+ 'mod_delay.c' => {
+ DelayEngine => 'off',
+ },
+ },
+ };
+
+ my ($port, $config_user, $config_group) = config_write($setup->{config_file},
+ $config);
+
+ # 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);
+ eval { $client->host($host) };
+ unless ($@) {
+ die("HOST $host succeeded unexpectedly");
+ }
+
+ $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);
+
+ test_cleanup($setup->{log_file}, $ex);
+}
+
+sub autohost_host_config_existing_serveralias {
+ my $self = shift;
+ my $tmpdir = $self->{tmpdir};
+ my $setup = test_setup($tmpdir, 'autohost');
+
+ my $host = 'castaglia';
+
+ mkpath("$tmpdir/conf.d");
+ my $auto_config = File::Spec->rel2abs("$tmpdir/conf.d/127.0.0.1-$host.conf");
+ if (open(my $fh, "> $auto_config")) {
+ print $fh <<EOC;
+ServerAlias $host
+ServerName "AutoHost Server"
+AuthUserFile $setup->{auth_user_file}
+AuthGroupFile $setup->{auth_group_file}
+ServerLog $setup->{log_file}
+RequireValidShell off
+EOC
+ unless (close($fh)) {
+ die("Can't write $auto_config: $!");
+ }
+
+ } else {
+ die("Can't open $auto_config: $!");
+ }
+
+ my $test_root = File::Spec->rel2abs($tmpdir);
+
+ my $config = {
+ PidFile => $setup->{pid_file},
+ ScoreboardFile => $setup->{scoreboard_file},
+ SystemLog => $setup->{log_file},
+ TraceLog => $setup->{log_file},
+ Trace => 'autohost:20 binding:20 event:20',
+
+ ServerAlias => $host,
+ ServerName => '"Default Server"',
+
+ IfModules => {
+ 'mod_autohost.c' => {
+ AutoHostEngine => 'on',
+ AutoHostLog => $setup->{log_file},
+ AutoHostConfig => "$test_root/conf.d/%0-%n.conf",
+ },
+
+ 'mod_delay.c' => {
+ DelayEngine => 'off',
+ },
+ },
+ };
+
+ my ($port, $config_user, $config_group) = config_write($setup->{config_file},
+ $config);
+
+ # 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->host($host);
+
+ my $resp_code = $client->response_code();
+ my $resp_msg = $client->response_msg();
+
+ my $expected = 'AutoHost Server';
+ $self->assert(qr/$expected/, $resp_msg,
+ test_msg("Expected response message '$expected', got '$resp_msg'"));
+
+ $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);
+
+ test_cleanup($setup->{log_file}, $ex);
+}
+
+1;
=====================================
t/lib/ProFTPD/Tests/Modules/mod_autohost/sni.pm
=====================================
@@ -0,0 +1,935 @@
+package ProFTPD::Tests::Modules::mod_autohost::sni;
+
+use lib qw(t/lib);
+use base qw(ProFTPD::TestSuite::Child);
+use strict;
+
+use Cwd;
+use Digest::MD5;
+use File::Path qw(mkpath);
+use File::Spec;
+use IO::Handle;
+use IO::Socket::INET6;
+use POSIX qw(:fcntl_h);
+
+use ProFTPD::TestSuite::FTP;
+use ProFTPD::TestSuite::Utils qw(:auth :config :running :test :testsuite);
+
+$| = 1;
+
+my $order = 0;
+
+my $TESTS = {
+ autohost_sni_config_ok => {
+ order => ++$order,
+ test_class => [qw(forking mod_tls)],
+ },
+
+ autohost_sni_config_missing => {
+ order => ++$order,
+ test_class => [qw(forking mod_tls)],
+ },
+
+ autohost_sni_config_no_serveralias => {
+ order => ++$order,
+ test_class => [qw(forking mod_tls)],
+ },
+
+ autohost_sni_config_mismatched_serveralias => {
+ order => ++$order,
+ test_class => [qw(forking mod_tls)],
+ },
+
+ autohost_sni_config_existing_serveralias => {
+ order => ++$order,
+ test_class => [qw(forking mod_tls)],
+ },
+
+ autohost_sni_config_ok_with_host => {
+ order => ++$order,
+ test_class => [qw(forking mod_tls)],
+ },
+
+};
+
+sub new {
+ return shift()->SUPER::new(@_);
+}
+
+sub list_tests {
+ # Check for the required Perl modules:
+ #
+ # Net-SSLeay
+ # IO-Socket-SSL
+ # Net-FTPSSL
+
+ my $required = [qw(
+ Net::SSLeay
+ IO::Socket::SSL
+ 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);
+ }
+ }
+
+ return testsuite_get_runnable_tests($TESTS);
+}
+
+sub autohost_sni_config_ok {
+ my $self = shift;
+ my $tmpdir = $self->{tmpdir};
+ my $setup = test_setup($tmpdir, 'autohost');
+
+ my $host = 'castaglia';
+
+ my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem");
+ my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem");
+
+ mkpath("$tmpdir/conf.d");
+ my $auto_config = File::Spec->rel2abs("$tmpdir/conf.d/127.0.0.1-$host.conf");
+ if (open(my $fh, "> $auto_config")) {
+ print $fh <<EOC;
+ServerAlias $host
+ServerName "AutoHost Server"
+AuthUserFile $setup->{auth_user_file}
+AuthGroupFile $setup->{auth_group_file}
+ServerLog $setup->{log_file}
+RequireValidShell off
+
+<IfModule mod_tls.c>
+ TLSEngine on
+ TLSLog $setup->{log_file}
+ TLSRequired on
+ TLSRSACertificateFile $cert_file
+ TLSCACertificateFile $ca_file
+ TLSOptions EnableDiags
+</IfModule>
+EOC
+ unless (close($fh)) {
+ die("Can't write $auto_config: $!");
+ }
+
+ } else {
+ die("Can't open $auto_config: $!");
+ }
+
+ my $test_root = File::Spec->rel2abs($tmpdir);
+
+ my $config = {
+ PidFile => $setup->{pid_file},
+ ScoreboardFile => $setup->{scoreboard_file},
+ SystemLog => $setup->{log_file},
+ TraceLog => $setup->{log_file},
+ Trace => 'autohost:20 binding:20 event:20 tls:20',
+
+ IfModules => {
+ 'mod_autohost.c' => {
+ AutoHostEngine => 'on',
+ AutoHostLog => $setup->{log_file},
+ AutoHostConfig => "$test_root/conf.d/%0-%n.conf",
+ },
+
+ 'mod_delay.c' => {
+ DelayEngine => 'off',
+ },
+
+ 'mod_tls.c' => {
+ TLSEngine => 'on',
+ TLSLog => $setup->{log_file},
+ TLSRequired => 'on',
+ TLSRSACertificateFile => $cert_file,
+ TLSCACertificateFile => $ca_file,
+ TLSOptions => 'EnableDiags',
+ },
+ },
+ };
+
+ my ($port, $config_user, $config_group) = config_write($setup->{config_file},
+ $config);
+
+ # 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(2);
+
+ my $ssl_opts = {
+ Encryption => 'E',
+ Port => $port,
+ SSL_ca_file => $ca_file,
+ SSL_hostname => $host,
+ SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_PEER(),
+ };
+
+ if ($ENV{TEST_VERBOSE}) {
+ $ssl_opts->{Debug} = 2;
+ }
+
+ my $client = Net::FTPSSL->new('127.0.0.1', $ssl_opts);
+ unless ($client) {
+ die("Can't connect to FTPS server: " . IO::Socket::SSL::errstr());
+ }
+
+ # Our default server will not have the AuthUserFile configured with our
+ # test user; only the name-based AutoHost config does. So if login
+ # succeeds, we will know that that name-based AutoHost config has been
+ # loaded/used successfully.
+ unless ($client->login($setup->{user}, $setup->{passwd})) {
+ die("Login failed unexpectedly: ". $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);
+
+ test_cleanup($setup->{log_file}, $ex);
+}
+
+sub autohost_sni_config_missing {
+ my $self = shift;
+ my $tmpdir = $self->{tmpdir};
+ my $setup = test_setup($tmpdir, 'autohost');
+
+ my $host = 'castaglia';
+
+ my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem");
+ my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem");
+
+ mkpath("$tmpdir/conf.d");
+ my $auto_config = File::Spec->rel2abs("$tmpdir/conf.d/127.0.0.1-foo.conf");
+ if (open(my $fh, "> $auto_config")) {
+ print $fh <<EOC;
+ServerAlias $host
+ServerName "AutoHost Server"
+AuthUserFile $setup->{auth_user_file}
+AuthGroupFile $setup->{auth_group_file}
+ServerLog $setup->{log_file}
+RequireValidShell off
+
+<IfModule mod_tls.c>
+ TLSEngine on
+ TLSLog $setup->{log_file}
+ TLSRequired on
+ TLSRSACertificateFile $cert_file
+ TLSCACertificateFile $ca_file
+ TLSOptions EnableDiags
+</IfModule>
+EOC
+ unless (close($fh)) {
+ die("Can't write $auto_config: $!");
+ }
+
+ } else {
+ die("Can't open $auto_config: $!");
+ }
+
+ my $test_root = File::Spec->rel2abs($tmpdir);
+
+ my $config = {
+ PidFile => $setup->{pid_file},
+ ScoreboardFile => $setup->{scoreboard_file},
+ SystemLog => $setup->{log_file},
+ TraceLog => $setup->{log_file},
+ Trace => 'autohost:20 binding:20 event:20 tls:20',
+
+ IfModules => {
+ 'mod_autohost.c' => {
+ AutoHostEngine => 'on',
+ AutoHostLog => $setup->{log_file},
+ AutoHostConfig => "$test_root/conf.d/%0-%n.conf",
+ },
+
+ 'mod_delay.c' => {
+ DelayEngine => 'off',
+ },
+
+ 'mod_tls.c' => {
+ TLSEngine => 'on',
+ TLSLog => $setup->{log_file},
+ TLSRequired => 'on',
+ TLSRSACertificateFile => $cert_file,
+ TLSCACertificateFile => $ca_file,
+ TLSOptions => 'EnableDiags',
+ },
+ },
+ };
+
+ my ($port, $config_user, $config_group) = config_write($setup->{config_file},
+ $config);
+
+ # 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(2);
+
+ my $ssl_opts = {
+ Encryption => 'E',
+ Port => $port,
+ SSL_ca_file => $ca_file,
+ SSL_hostname => $host,
+ SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_PEER(),
+ };
+
+ if ($ENV{TEST_VERBOSE}) {
+ $ssl_opts->{Debug} = 2;
+ }
+
+ my $client = Net::FTPSSL->new('127.0.0.1', $ssl_opts);
+ if ($client) {
+ die("Connected to FTPS server unexpectedly");
+ }
+
+ my $errstr = IO::Socket::SSL::errstr();
+ my $expected = 'handshake failure|unrecognized name';
+ $self->assert(qr/$expected/, $errstr,
+ test_msg("Expected '$expected', got '$errstr'"));
+ };
+ 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);
+
+ test_cleanup($setup->{log_file}, $ex);
+}
+
+sub autohost_sni_config_no_serveralias {
+ my $self = shift;
+ my $tmpdir = $self->{tmpdir};
+ my $setup = test_setup($tmpdir, 'autohost');
+
+ my $host = 'castaglia';
+
+ my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem");
+ my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem");
+
+ mkpath("$tmpdir/conf.d");
+ my $auto_config = File::Spec->rel2abs("$tmpdir/conf.d/127.0.0.1-$host.conf");
+ if (open(my $fh, "> $auto_config")) {
+ print $fh <<EOC;
+ServerName "AutoHost Server"
+AuthUserFile $setup->{auth_user_file}
+AuthGroupFile $setup->{auth_group_file}
+ServerLog $setup->{log_file}
+RequireValidShell off
+
+<IfModule mod_tls.c>
+ TLSEngine on
+ TLSLog $setup->{log_file}
+ TLSRequired on
+ TLSRSACertificateFile $cert_file
+ TLSCACertificateFile $ca_file
+ TLSOptions EnableDiags
+</IfModule>
+EOC
+ unless (close($fh)) {
+ die("Can't write $auto_config: $!");
+ }
+
+ } else {
+ die("Can't open $auto_config: $!");
+ }
+
+ my $test_root = File::Spec->rel2abs($tmpdir);
+
+ my $config = {
+ PidFile => $setup->{pid_file},
+ ScoreboardFile => $setup->{scoreboard_file},
+ SystemLog => $setup->{log_file},
+ TraceLog => $setup->{log_file},
+ Trace => 'autohost:20 binding:20 event:20 tls:20',
+
+ IfModules => {
+ 'mod_autohost.c' => {
+ AutoHostEngine => 'on',
+ AutoHostLog => $setup->{log_file},
+ AutoHostConfig => "$test_root/conf.d/%0-%n.conf",
+ },
+
+ 'mod_delay.c' => {
+ DelayEngine => 'off',
+ },
+
+ 'mod_tls.c' => {
+ TLSEngine => 'on',
+ TLSLog => $setup->{log_file},
+ TLSRequired => 'on',
+ TLSRSACertificateFile => $cert_file,
+ TLSCACertificateFile => $ca_file,
+ TLSOptions => 'EnableDiags',
+ },
+ },
+ };
+
+ my ($port, $config_user, $config_group) = config_write($setup->{config_file},
+ $config);
+
+ # 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(2);
+
+ my $ssl_opts = {
+ Encryption => 'E',
+ Port => $port,
+ SSL_ca_file => $ca_file,
+ SSL_hostname => $host,
+ SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_PEER(),
+ };
+
+ if ($ENV{TEST_VERBOSE}) {
+ $ssl_opts->{Debug} = 2;
+ }
+
+ my $client = Net::FTPSSL->new('127.0.0.1', $ssl_opts);
+ if ($client) {
+ die("Connected to FTPS server unexpectedly");
+ }
+
+ my $errstr = IO::Socket::SSL::errstr();
+ my $expected = 'handshake failure|unrecognized name';
+ $self->assert(qr/$expected/, $errstr,
+ test_msg("Expected '$expected', got '$errstr'"));
+ };
+ 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);
+
+ test_cleanup($setup->{log_file}, $ex);
+}
+
+sub autohost_sni_config_mismatched_serveralias {
+ my $self = shift;
+ my $tmpdir = $self->{tmpdir};
+ my $setup = test_setup($tmpdir, 'autohost');
+
+ my $host = 'castaglia';
+
+ my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem");
+ my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem");
+
+ mkpath("$tmpdir/conf.d");
+ my $auto_config = File::Spec->rel2abs("$tmpdir/conf.d/127.0.0.1-$host.conf");
+ if (open(my $fh, "> $auto_config")) {
+ print $fh <<EOC;
+ServerAlias ftp.example.com
+ServerName "AutoHost Server"
+AuthUserFile $setup->{auth_user_file}
+AuthGroupFile $setup->{auth_group_file}
+ServerLog $setup->{log_file}
+RequireValidShell off
+
+<IfModule mod_tls.c>
+ TLSEngine on
+ TLSLog $setup->{log_file}
+ TLSRequired on
+ TLSRSACertificateFile $cert_file
+ TLSCACertificateFile $ca_file
+ TLSOptions EnableDiags
+</IfModule>
+EOC
+ unless (close($fh)) {
+ die("Can't write $auto_config: $!");
+ }
+
+ } else {
+ die("Can't open $auto_config: $!");
+ }
+
+ my $test_root = File::Spec->rel2abs($tmpdir);
+
+ my $config = {
+ PidFile => $setup->{pid_file},
+ ScoreboardFile => $setup->{scoreboard_file},
+ SystemLog => $setup->{log_file},
+ TraceLog => $setup->{log_file},
+ Trace => 'autohost:20 binding:20 event:20 tls:20',
+
+ IfModules => {
+ 'mod_autohost.c' => {
+ AutoHostEngine => 'on',
+ AutoHostLog => $setup->{log_file},
+ AutoHostConfig => "$test_root/conf.d/%0-%n.conf",
+ },
+
+ 'mod_delay.c' => {
+ DelayEngine => 'off',
+ },
+
+ 'mod_tls.c' => {
+ TLSEngine => 'on',
+ TLSLog => $setup->{log_file},
+ TLSRequired => 'on',
+ TLSRSACertificateFile => $cert_file,
+ TLSCACertificateFile => $ca_file,
+ TLSOptions => 'EnableDiags',
+ },
+ },
+ };
+
+ my ($port, $config_user, $config_group) = config_write($setup->{config_file},
+ $config);
+
+ # 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(2);
+
+ my $ssl_opts = {
+ Encryption => 'E',
+ Port => $port,
+ SSL_ca_file => $ca_file,
+ SSL_hostname => $host,
+ SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_PEER(),
+ };
+
+ if ($ENV{TEST_VERBOSE}) {
+ $ssl_opts->{Debug} = 2;
+ }
+
+ my $client = Net::FTPSSL->new('127.0.0.1', $ssl_opts);
+ if ($client) {
+ die("Connected to FTPS server unexpectedly");
+ }
+
+ my $errstr = IO::Socket::SSL::errstr();
+ my $expected = 'handshake failure|unrecognized name';
+ $self->assert(qr/$expected/, $errstr,
+ test_msg("Expected '$expected', got '$errstr'"));
+ };
+ 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);
+
+ test_cleanup($setup->{log_file}, $ex);
+}
+
+sub autohost_sni_config_existing_serveralias {
+ my $self = shift;
+ my $tmpdir = $self->{tmpdir};
+ my $setup = test_setup($tmpdir, 'autohost');
+
+ my $host = 'castaglia';
+
+ my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem");
+ my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem");
+
+ mkpath("$tmpdir/conf.d");
+ my $auto_config = File::Spec->rel2abs("$tmpdir/conf.d/127.0.0.1-$host.conf");
+ if (open(my $fh, "> $auto_config")) {
+ print $fh <<EOC;
+ServerAlias $host
+ServerName "AutoHost Server"
+AuthUserFile $setup->{auth_user_file}
+AuthGroupFile $setup->{auth_group_file}
+ServerLog $setup->{log_file}
+RequireValidShell off
+
+<IfModule mod_tls.c>
+ TLSEngine on
+ TLSLog $setup->{log_file}
+ TLSRequired on
+ TLSRSACertificateFile $cert_file
+ TLSCACertificateFile $ca_file
+ TLSOptions EnableDiags
+</IfModule>
+EOC
+ unless (close($fh)) {
+ die("Can't write $auto_config: $!");
+ }
+
+ } else {
+ die("Can't open $auto_config: $!");
+ }
+
+ my $test_root = File::Spec->rel2abs($tmpdir);
+
+ my $config = {
+ PidFile => $setup->{pid_file},
+ ScoreboardFile => $setup->{scoreboard_file},
+ SystemLog => $setup->{log_file},
+ TraceLog => $setup->{log_file},
+ Trace => 'autohost:20 binding:20 event:20 tls:20',
+
+ ServerAlias => $host,
+
+ IfModules => {
+ 'mod_autohost.c' => {
+ AutoHostEngine => 'on',
+ AutoHostLog => $setup->{log_file},
+ AutoHostConfig => "$test_root/conf.d/%0-%n.conf",
+ },
+
+ 'mod_delay.c' => {
+ DelayEngine => 'off',
+ },
+
+ 'mod_tls.c' => {
+ TLSEngine => 'on',
+ TLSLog => $setup->{log_file},
+ TLSRequired => 'on',
+ TLSRSACertificateFile => $cert_file,
+ TLSCACertificateFile => $ca_file,
+ TLSOptions => 'EnableDiags',
+ },
+ },
+ };
+
+ my ($port, $config_user, $config_group) = config_write($setup->{config_file},
+ $config);
+
+ # 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(2);
+
+ my $ssl_opts = {
+ Encryption => 'E',
+ Port => $port,
+ SSL_ca_file => $ca_file,
+ SSL_hostname => $host,
+ SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_PEER(),
+ };
+
+ if ($ENV{TEST_VERBOSE}) {
+ $ssl_opts->{Debug} = 2;
+ }
+
+ my $client = Net::FTPSSL->new('127.0.0.1', $ssl_opts);
+ unless ($client) {
+ die("Can't connect to FTPS server: " . IO::Socket::SSL::errstr());
+ }
+
+ # Our default server will not have the AuthUserFile configured with our
+ # test user; only the name-based AutoHost config does. So if login
+ # succeeds, we will know that that name-based AutoHost config has been
+ # loaded/used successfully.
+ unless ($client->login($setup->{user}, $setup->{passwd})) {
+ die("Login failed unexpectedly: ". $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);
+
+ test_cleanup($setup->{log_file}, $ex);
+}
+
+sub autohost_sni_config_ok_with_host {
+ my $self = shift;
+ my $tmpdir = $self->{tmpdir};
+ my $setup = test_setup($tmpdir, 'autohost');
+
+ my $host = 'castaglia';
+
+ my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem");
+ my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem");
+
+ mkpath("$tmpdir/conf.d");
+ my $auto_config = File::Spec->rel2abs("$tmpdir/conf.d/127.0.0.1-$host.conf");
+ if (open(my $fh, "> $auto_config")) {
+ print $fh <<EOC;
+ServerAlias $host
+ServerName "AutoHost Server"
+AuthUserFile $setup->{auth_user_file}
+AuthGroupFile $setup->{auth_group_file}
+ServerLog $setup->{log_file}
+RequireValidShell off
+
+<IfModule mod_tls.c>
+ TLSEngine on
+ TLSLog $setup->{log_file}
+ TLSRequired on
+ TLSRSACertificateFile $cert_file
+ TLSCACertificateFile $ca_file
+ TLSOptions EnableDiags
+</IfModule>
+EOC
+ unless (close($fh)) {
+ die("Can't write $auto_config: $!");
+ }
+
+ } else {
+ die("Can't open $auto_config: $!");
+ }
+
+ my $test_root = File::Spec->rel2abs($tmpdir);
+
+ my $config = {
+ PidFile => $setup->{pid_file},
+ ScoreboardFile => $setup->{scoreboard_file},
+ SystemLog => $setup->{log_file},
+ TraceLog => $setup->{log_file},
+ Trace => 'autohost:20 binding:20 event:20 tls:20',
+
+ IfModules => {
+ 'mod_autohost.c' => {
+ AutoHostEngine => 'on',
+ AutoHostLog => $setup->{log_file},
+ AutoHostConfig => "$test_root/conf.d/%0-%n.conf",
+ },
+
+ 'mod_delay.c' => {
+ DelayEngine => 'off',
+ },
+
+ 'mod_tls.c' => {
+ TLSEngine => 'on',
+ TLSLog => $setup->{log_file},
+ TLSRequired => 'on',
+ TLSRSACertificateFile => $cert_file,
+ TLSCACertificateFile => $ca_file,
+ TLSOptions => 'EnableDiags',
+ },
+ },
+ };
+
+ my ($port, $config_user, $config_group) = config_write($setup->{config_file},
+ $config);
+
+ # 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(2);
+
+ my $ssl_opts = {
+ Encryption => 'E',
+ Port => $port,
+ SSL_ca_file => $ca_file,
+ SSL_hostname => $host,
+ SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_PEER(),
+ };
+
+ if ($ENV{TEST_VERBOSE}) {
+ $ssl_opts->{Debug} = 2;
+ }
+
+ my $client = Net::FTPSSL->new('127.0.0.1', $ssl_opts);
+ unless ($client) {
+ die("Can't connect to FTPS server: " . IO::Socket::SSL::errstr());
+ }
+
+ unless ($client->quot('HOST', $host)) {
+ die("HOST failed unexpectedly: ". $client->last_message());
+ }
+
+ my $resp_msg = $client->last_message();
+ my $expected = 'AutoHost Server';
+ $self->assert(qr/$expected/, $resp_msg,
+ test_msg("Expected response message '$expected', got '$resp_msg'"));
+
+ # Our default server will not have the AuthUserFile configured with our
+ # test user; only the name-based AutoHost config does. So if login
+ # succeeds, we will know that that name-based AutoHost config has been
+ # loaded/used successfully.
+ unless ($client->login($setup->{user}, $setup->{passwd})) {
+ die("Login failed unexpectedly: ". $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);
+
+ test_cleanup($setup->{log_file}, $ex);
+}
+
+1;
=====================================
t/modules/mod_autohost/host.t
=====================================
@@ -0,0 +1,11 @@
+#!/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_autohost::host");
=====================================
t/modules/mod_autohost/sni.t
=====================================
@@ -0,0 +1,11 @@
+#!/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_autohost::sni");
View it on GitLab: https://salsa.debian.org/debian-proftpd-team/proftpd-mod-autohost/-/commit/eb22f567865cac9874f0b49e3af262559d53bf6f
--
View it on GitLab: https://salsa.debian.org/debian-proftpd-team/proftpd-mod-autohost/-/commit/eb22f567865cac9874f0b49e3af262559d53bf6f
You're receiving this email because of your account on salsa.debian.org.
More information about the Pkg-proftpd-maintainers
mailing list