Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add FTP Reverse Proxy with proftpd_mod_proxy. #1275

Open
wants to merge 1 commit into
base: devel
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
48 changes: 48 additions & 0 deletions ftp/pfSense-pkg-proftpd-mod_proxy/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# $FreeBSD$

PORTNAME= pfSense-pkg-proftpd-mod_proxy
PORTVERSION= 0.1
PORTREVISION= 1
CATEGORIES= ftp
MASTER_SITES= # empty
DISTFILES= # empty
EXTRACT_ONLY= # empty

MAINTAINER= cmassiot@upipe.org
COMMENT= pfSense package ProFTPd mod_proxy

LICENSE= APACHE20

NO_BUILD= yes
NO_MTREE= yes

RUN_DEPENDS= proftpd-mod_proxy>=0:ftp/proftpd-mod_proxy
RUN_DEPENDS+= proftpd-mod_sql_sqlite>=0:ftp/proftpd-mod_sql_sqlite

SUB_FILES= pkg-install pkg-deinstall
SUB_LIST= PORTNAME=${PORTNAME}

do-extract:
${MKDIR} ${WRKSRC}

do-install:
${MKDIR} ${STAGEDIR}${PREFIX}/pkg
${MKDIR} ${STAGEDIR}${PREFIX}/www/shortcuts
${MKDIR} ${STAGEDIR}/etc/inc/priv
${MKDIR} ${STAGEDIR}${DATADIR}
${INSTALL_DATA} -m 0644 ${FILESDIR}${PREFIX}/pkg/proftpd-mod_proxy.xml \
${STAGEDIR}${PREFIX}/pkg
${INSTALL_DATA} -m 0644 ${FILESDIR}${PREFIX}/pkg/proftpd-mod_proxy_users.xml \
${STAGEDIR}${PREFIX}/pkg
${INSTALL_DATA} ${FILESDIR}${PREFIX}/pkg/proftpd-mod_proxy.inc \
${STAGEDIR}${PREFIX}/pkg
${INSTALL_DATA} ${FILESDIR}${PREFIX}/www/shortcuts/pkg_proftpd-mod_proxy.inc \
${STAGEDIR}${PREFIX}/www/shortcuts
${INSTALL_DATA} ${FILESDIR}/etc/inc/priv/proftpd-mod_proxy.priv.inc \
${STAGEDIR}/etc/inc/priv
${INSTALL_DATA} ${FILESDIR}${DATADIR}/info.xml \
${STAGEDIR}${DATADIR}
@${REINPLACE_CMD} -i '' -e "s|%%PKGVERSION%%|${PKGVERSION}|" \
${STAGEDIR}${DATADIR}/info.xml

.include <bsd.port.mk>
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php
/*
* proftpd-mod_proxy.priv.inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
global $priv_list;

$priv_list['page-services-proftpd-mod_proxy'] = array();
$priv_list['page-services-proftpd-mod_proxy']['name'] = "WebCfg - Services: FTP Reverse Proxy package";
$priv_list['page-services-proftpd-mod_proxy']['descr'] = "Allow access to FTP Reverse Proxy package GUI";
$priv_list['page-services-proftpd-mod_proxy']['match'] = array();
$priv_list['page-services-proftpd-mod_proxy']['match'][] = "pkg*.php?xml=proftpd-mod_proxy.xml*";
$priv_list['page-services-proftpd-mod_proxy']['match'][] = "pkg*.php?xml=proftpd-mod_proxy_users.xml*";

?>
3 changes: 3 additions & 0 deletions ftp/pfSense-pkg-proftpd-mod_proxy/files/pkg-deinstall.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/sh

/usr/local/bin/php -f /etc/rc.packages %%PORTNAME%% ${2}
7 changes: 7 additions & 0 deletions ftp/pfSense-pkg-proftpd-mod_proxy/files/pkg-install.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/sh

if [ "${2}" != "POST-INSTALL" ]; then
exit 0
fi

${PKG_ROOTDIR}/usr/local/bin/php -f ${PKG_ROOTDIR}/etc/rc.packages %%PORTNAME%% ${2}
Original file line number Diff line number Diff line change
@@ -0,0 +1,292 @@
<?php
/*
* proftpd-mod_proxy.inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

require_once("config.inc");
require_once("globals.inc");
require_once("interfaces.inc");
require_once("services.inc");
require_once("service-utils.inc");
require_once("certs.inc");
require_once("util.inc");

global $proftpd_mod_proxy_sqlite_timeout;
$proftpd_mod_proxy_sqlite_timeout = 100000;

function proftpd_mod_proxy_get_real_interface_name($interface) {
$interface = convert_friendly_interface_to_real_interface_name($interface);
return array($interface);
}

function install_proftpd_mod_proxy() {
global $config;
if (!is_array($config['installedpackages'])) {
$config['installedpackages'] = array();
}
if (!is_array($config['installedpackages']['proftpdmodproxyusers'])) {
$config['installedpackages']['proftpdmodproxyusers'] = array();
}
if (!is_array($config['installedpackages']['proftpdmodproxyusers']['config'])) {
$config['installedpackages']['proftpdmodproxyusers']['config'] = array();
}
}

function sync_package_proftpd_mod_proxy() {
global $config, $g;
$configpath = "{$g['varetc_path']}/proftpd-mod_proxy";

$cf = $config['installedpackages']['proftpdmodproxy']['config'][0];

if ($cf["enable"] != "on") {
rmdir_recursive($configpath);
stop_service('proftpd-mod_proxy');
unlink_if_exists("/usr/local/etc/rc.d/proftpd-mod_proxy.sh");
return;
}

@mkdir($configpath, 0755, true);
$proftpd_config_file = $configpath."/proftpd.conf";
$proftpd_users_file = $configpath."/users.db";
$proftpd_tables_dir = $configpath."/tables";
$proftpd_cert_file = $configpath."/cert.pem";
$proftpd_ca_file = $configpath."/ca.pem";
$proftpd_key_file = $configpath."/key.pem";

$proftpd_config = "# This file is automatically generated by pfSense\n";
$proftpd_config .= "# Do not edit manually\n";

$proftpd_config .= "ServerName ".$cf['servername']."\n";
$proftpd_config .= "ServerType standalone\n";
$proftpd_config .= "DefaultServer on\n";

$interfaces = $cf['bindints'];
foreach (explode(",", $interfaces) as $interface) {
$interface_name = proftpd_mod_proxy_get_real_interface_name($interface);
if ($interface_name[0]) {
$proftpd_config .= "DefaultAddress ".$interface_name[0]."\n";
}
}

$proftpd_config .= "Port ".$cf['bindport']."\n";
$proftpd_config .= "AllowOverwrite on\n";
$proftpd_config .= "PassivePorts ".$cf['passiveportsfrom']." ".$cf['passiveportsto']."\n";
$proftpd_config .= "MaxInstances ".$cf['maxinstances']."\n";
$proftpd_config .= "User nobody\n";
$proftpd_config .= "Group nobody\n";

$proftpd_config .= "LoadModule mod_sql.c\n";
$proftpd_config .= "LoadModule mod_sql_sqlite.c\n";
$proftpd_config .= "SQLAuthenticate off\n";
$proftpd_config .= "SQLBackend sqlite3\n";
$proftpd_config .= "SQLConnectInfo ".$proftpd_users_file."\n";
$proftpd_config .= "SQLNamedQuery get-user-servers SELECT \"url FROM proxy_user_servers WHERE user_name = '%{0}'\"\n";

$proftpd_config .= "LoadModule mod_proxy.c\n";
$proftpd_config .= "ProxyEngine on\n";
$proftpd_config .= "ProxyTables ".$proftpd_tables_dir."\n";
$proftpd_config .= "ProxyRole reverse\n";
$proftpd_config .= "ProxyReverseConnectPolicy PerUser\n";
$proftpd_config .= "ProxyReverseServers sql:/get-user-servers\n";
$proftpd_config .= "ProxySourceAddress ".$cf['sourceaddr']."\n";

$cert = lookup_cert($cf['server_cert']);
if ($cert) {
$proftpd_config .= "LoadModule mod_tls.c\n";
$proftpd_config .= "TLSEngine on\n";
$proftpd_config .= "TLSProtocol TLSv1 TLSv1.1 TLSv1.2\n";
$proftpd_config .= "TLSRequired ".($cf['tlsrequired'] == "on" ? "on" : "off")."\n";
$proftpd_config .= "TLSVerifyClient off\n";
$proftpd_config .= "TLSRenegotiate none\n";
$proftpd_config .= "TLSCACertificateFile ".$proftpd_ca_file."\n";

$prv = base64_decode($cert['prv']);
$res_key = openssl_pkey_get_private($prv);
$key_details = openssl_pkey_get_details($res_key);
if (is_array($key_details) && array_key_exists('type', $key_details) && $key_details['type'] == OPENSSL_KEYTYPE_EC) {
$proftpd_config .= "TLSECCertificateFile ".$proftpd_cert_file."\n";
$proftpd_config .= "TLSECCertificateKeyFile ".$proftpd_key_file."\n";
} else {
/* Assume RSA. */
$proftpd_config .= "TLSRSACertificateFile ".$proftpd_cert_file."\n";
$proftpd_config .= "TLSRSACertificateKeyFile ".$proftpd_key_file."\n";
}
file_put_contents($proftpd_key_file, $prv);

file_put_contents($proftpd_cert_file, base64_decode($cert['crt']));

$ca_content = "";
$ca = $cert;
while (!empty($ca['caref'])) {
$ca = lookup_ca($ca['caref']);
if ($ca) {
if (cert_get_subject($ca['crt']) == cert_get_issuer($ca['crt'])) {
break;
}
$ca_content .= base64_decode($ca['crt'])."\r\n";
} else {
break;
}
}
file_put_contents($proftpd_ca_file, $ca_content);
}

file_put_contents($proftpd_config_file, $proftpd_config);

$rc_file_stop = <<<EOF
echo "Stopping ProFTPd..."
if [ -e /var/run/proftpd.pid ]; then
/bin/kill -9 `/bin/cat /var/run/proftpd.pid`
/bin/rm -f /var/run/proftpd.pid
fi
EOF;

/* When starting the daemon, reap any leftover processes and then start */
$rc_file_start = <<<EOF
echo "Starting ProFTPd..."
# Ensure no other copies of the daemons are running or it breaks.
/usr/bin/killall -9 proftpd 2>/dev/null
sleep 1

/usr/local/sbin/proftpd -c {$proftpd_config_file}
EOF;
write_rcfile(array(
"file" => "proftpd-mod_proxy.sh",
"start" => $rc_file_start,
"stop" => $rc_file_stop
)
);

sync_package_proftpd_mod_proxy_users();
restart_service("proftpd-mod_proxy");
}

function sync_package_proftpd_mod_proxy_users() {
global $config, $g;
$configpath = "{$g['varetc_path']}/proftpd-mod_proxy";

$cf = $config['installedpackages']['proftpdmodproxy']['config'][0];

if ($cf["enable"] != "on") {
return;
}

$proftpd_users_file = $configpath."/users.db";
if (!file_exists($proftpd_users_file)) {
$sqlite = proftpd_mod_proxy_open_sqlite($proftpd_users_file);
$sql = <<<EOF
CREATE TABLE proxy_user_servers (
user_name TEXT,
url TEXT
);

CREATE INDEX proxy_user_servers_name_idx ON proxy_user_servers (user_name);
EOF;
proftpd_mod_proxy_exec_sqlite($sqlite, $sql);
} else {
$sqlite = proftpd_mod_proxy_open_sqlite($proftpd_users_file);
}

$sql = "DELETE FROM proxy_user_servers;\n";
if (is_array($config['installedpackages']) &&
is_array($config['installedpackages']['proftpdmodproxyusers']) &&
is_array($config['installedpackages']['proftpdmodproxyusers']['config'])) {
$users = $config['installedpackages']['proftpdmodproxyusers']['config'];
foreach ($users as $user) {
$sql .= "INSERT INTO proxy_user_servers VALUES ('".$user["username"]."','ftp://".$user["server"]."');";
}
}
proftpd_mod_proxy_exec_sqlite($sqlite, $sql);

proftpd_mod_proxy_close_sqlite($sqlite);
}

function validate_form_proftpd_mod_proxy($post, &$input_errors) {
if (!is_port($post["bindport"])) {
$input_errors[] = 'You must specify a valid port number in the \'Bind Port\' field';
}
if (!is_port($post["passiveportsfrom"])) {
$input_errors[] = 'You must specify a valid port number in the \'From Port\' field';
}
if (!is_port($post["passiveportsto"])) {
$input_errors[] = 'You must specify a valid port number in the \'To Port\' field';
}
if (is_port($post["passiveportsfrom"]) && is_port($post["passiveportsto"]) &&
$post["passiveportsfrom"] > $post["passiveportsto"]) {
$input_errors[] = 'You must specify a valid port range in the \'Passive Ports\' field';
}
if (!is_numericint($post["maxinstances"])) {
$input_errors[] = 'You must specify a valid integer in the \'Max Instances\' field';
}
}

function validate_form_proftpd_mod_proxy_users($post, &$input_errors) {
if (preg_match('/\s/',$post["username"])) {
$input_errors[] = 'You must specify a valid user name in the \'Username\' field';
}
if (preg_match('/\s/',$post["server"])) {
$input_errors[] = 'You must specify a valid server name in the \'Server\' field';
}
}

/* Get a list of certificates */
function proftpd_mod_proxy_get_certs() {
global $config;
$cert_arr = array();
$cert_arr[] = array('refid' => 'none', 'descr' => 'None');

if (is_array($config['cert'])) {
foreach ($config['cert'] as $cert) {
$cert_arr[] = array('refid' => $cert['refid'], 'descr' => $cert['descr']);
}
}
return $cert_arr;
}

function proftpd_mod_proxy_open_sqlite($path) {
try {
$sqlite = new SQLite3($path);
$sqlite->busyTimeout($proftpd_mod_proxy_sqlite_timeout);
}
catch (Exception $e) {
log_error("[proftpd-mod_proxy] Failed to open '$path'");

try {
$sqlite = new SQLite3($path);
$sqlite->busyTimeout($proftpd_mod_proxy_sqlite_timeout);
}
catch (Exception $e) {
log_error("[proftpd-mod_proxy] Failed to open '$path' (last attempt)");
}
}
return $sqlite;
}

function proftpd_mod_proxy_exec_sqlite($sqlite, $sql) {
try {
$sqlite->exec("BEGIN TRANSACTION; PRAGMA journal_mode = delete;\n"
. "{$sql}"
. "END TRANSACTION;");
}
catch (Exception $e) {
log_error("[proftpd-mod_proxy] Failed to execute '$sql'");
}
}

function proftpd_mod_proxy_close_sqlite($sqlite) {
$sqlite->close();
}

?>