diff --git a/src/etc/inc/openvpn.inc b/src/etc/inc/openvpn.inc
index 1983ad5f67c16b62560e1ddd2328cc1e978c47eb..c117fc245379bc4e808bda1d091ec96288fc298c 100644
--- a/src/etc/inc/openvpn.inc
+++ b/src/etc/inc/openvpn.inc
@@ -821,6 +821,24 @@ function openvpn_add_keyfile(& $data, & $conf, $mode_id, $directive, $opt = "")
$conf .= "{$directive} {$fpath} {$opt}\n";
}
+function openvpn_delete_tmp($mode, $id) {
+ global $g;
+
+ /* delete temporary files created by connect script */
+ if (($mode == "server") && (isset($id))) {
+ unlink_if_exists("{$g['tmp_path']}/ovpn_ovpns{$id}_*.rules");
+ }
+ /* delete temporary files created by OpenVPN */
+ $tmpfiles = array_filter(glob("{$g['tmp_path']}/openvpn_cc*.tmp"),'is_file');
+ if (!empty($tmpfiles)) {
+ foreach ($tmpfiles as $tmpfile) {
+ if ((time() - filemtime($tmpfile)) > 60) {
+ @unlink_if_exists($tmpfile);
+ }
+ }
+ }
+}
+
function openvpn_reconfigure($mode, $settings) {
global $g, $config, $openvpn_tls_server_modes, $openvpn_dh_lengths, $openvpn_default_keepalive_interval, $openvpn_default_keepalive_timeout;
@@ -1175,9 +1193,13 @@ function openvpn_reconfigure($mode, $settings) {
}
break;
}
+ $connlimit = "0";
if ($settings['mode'] != 'p2p_shared_key' &&
isset($settings['duplicate_cn'])) {
$conf .= "duplicate-cn\n";
+ if ($settings['connlimit']) {
+ $connlimit = "{$settings['connlimit']}";
+ }
}
if (($settings['mode'] != 'p2p_shared_key') &&
isset($settings['remote_cert_tls'])) {
@@ -1518,6 +1540,8 @@ function openvpn_reconfigure($mode, $settings) {
unset($conf);
$fpath = "{$g['openvpn_base']}/{$mode_id}/interface";
file_put_contents($fpath, $interface);
+ $fpath = "{$g['openvpn_base']}/{$mode_id}/connuserlimit";
+ file_put_contents($fpath, $connlimit);
//chown($fpath, 'nobody');
//chgrp($fpath, 'nobody');
@chmod("{$g['openvpn_base']}/{$mode_id}/config.ovpn", 0600);
@@ -1525,6 +1549,7 @@ function openvpn_reconfigure($mode, $settings) {
@chmod("{$g['openvpn_base']}/{$mode_id}/key", 0600);
@chmod("{$g['openvpn_base']}/{$mode_id}/tls-auth", 0600);
@chmod("{$g['openvpn_base']}/{$mode_id}/conf", 0600);
+ @chmod("{$g['openvpn_base']}/{$mode_id}/connuserlimit", 0600);
if ($wait_tentative) {
interface_wait_tentative($interface);
@@ -1537,7 +1562,6 @@ function openvpn_restart($mode, $settings) {
$vpnid = $settings['vpnid'];
$mode_id = $mode.$vpnid;
$lockhandle = lock("openvpnservice{$mode_id}", LOCK_EX);
- openvpn_clean_rules($mode, $vpnid);
openvpn_reconfigure($mode, $settings);
/* kill the process if running */
$pfile = $g['varrun_path']."/openvpn_{$mode_id}.pid";
@@ -1570,6 +1594,8 @@ function openvpn_restart($mode, $settings) {
return;
}
+ openvpn_delete_tmp($mode, $vpnid);
+
/* Do not start an instance if we are not CARP master on this vip! */
if (strstr($settings['interface'], "_vip")) {
$carpstatus = get_carp_bind_status($settings['interface']);
@@ -1649,7 +1675,7 @@ function openvpn_delete($mode, $settings) {
/* remove the configuration files */
unlink_if_exists("{$g['openvpn_base']}/{$mode_id}/*/*");
unlink_if_exists("{$g['openvpn_base']}/{$mode_id}/*");
- openvpn_clean_rules($mode, $vpnid);
+ openvpn_delete_tmp($mode, $vpnid);
}
function openvpn_resync_csc(& $settings) {
@@ -2405,12 +2431,4 @@ function openvpn_inuse($id, $mode) {
return false;
}
-function openvpn_clean_rules($mode, $id) {
- global $g;
-
- if ($mode == "server") {
- unlink_if_exists("{$g['tmp_path']}/ovpn_ovpns{$id}_*.rules");
- }
-}
-
?>
diff --git a/src/etc/inc/service-utils.inc b/src/etc/inc/service-utils.inc
index 35a74f74af9710e8e927e4136956f368c3dcd1d3..3228ecc14444f5a5cec744ab962e6adbbbe143fd 100644
--- a/src/etc/inc/service-utils.inc
+++ b/src/etc/inc/service-utils.inc
@@ -763,7 +763,7 @@ function service_control_stop($name, $extras) {
$id = htmlspecialchars($extras['id']);
$pidfile = "{$g['varrun_path']}/openvpn_{$vpnmode}{$id}.pid";
killbypid($pidfile);
- openvpn_clean_rules($vpnmode, $id);
+ openvpn_delete_tmp($vpnmode, $id);
}
break;
case 'syslogd':
diff --git a/src/usr/local/sbin/openvpn.attributes.sh b/src/usr/local/sbin/openvpn.attributes.sh
index 51ce83b95ec15bbbd3f8edc98b7bc005869ebb99..b66614aaca31bda9c3272653a6558c8c3c69810e 100755
--- a/src/usr/local/sbin/openvpn.attributes.sh
+++ b/src/usr/local/sbin/openvpn.attributes.sh
@@ -20,76 +20,17 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-if [ -z "${untrusted_ip6}" ]; then
- ipaddress="${untrusted_ip}"
-else
- ipaddress="${untrusted_ip6}"
-fi
-
-# Remote Access (SSL/TLS) mode
-if [ -z "${username}" ]; then
- if [ "$script_type" = "client-connect" ]; then
- /usr/bin/logger -t openvpn "openvpn server '${dev}' user cert CN '${X509_0_CN}' address '${ipaddress}' - connected"
- elif [ "$script_type" = "client-disconnect" ]; then
- /usr/bin/logger -t openvpn "openvpn server '${dev}' user cert CN '${X509_0_CN}' address '${ipaddress}' - disconnected"
- /sbin/pfctl -k $ifconfig_pool_remote_ip
- /sbin/pfctl -K $ifconfig_pool_remote_ip
- /sbin/pfctl -k $ifconfig_pool_remote_ip6
- /sbin/pfctl -K $ifconfig_pool_remote_ip6
+# Signal deferred handler
+if [ "${script_type}" = "client-connect" ]; then
+ /bin/echo 2 > "${client_connect_deferred_file}"
+ if [ -f /tmp/"${common_name}" ]; then
+ /bin/cat /tmp/"${common_name}" > "${client_connect_config_file}"
+ /bin/rm /tmp/"${common_name}"
fi
- exit 0
fi
-lockfile="/tmp/ovpn_${dev}_${username}_${trusted_port}.lock"
-rulesfile="/tmp/ovpn_${dev}_${username}_${trusted_port}.rules"
-anchorname="openvpn/${dev}_${username}_${trusted_port}"
-
-if [ "$script_type" = "client-connect" ]; then
- /usr/bin/logger -t openvpn "openvpn server '${dev}' user '${username}' address '${ipaddress}' - connected"
- i=1
- while [ -f "${lockfile}" ]; do
- if [ $i -ge 30 ]; then
- /bin/echo "Timeout while waiting for lockfile"
- exit 1
- fi
-
- /bin/sleep 1
- i=$(( i + 1 ))
- done
- /usr/bin/touch "${lockfile}"
-
- /bin/cat "${rulesfile}" | /usr/bin/sed "s/{clientip}/${ifconfig_pool_remote_ip}/g" | /usr/bin/sed "s/{clientipv6}/${ifconfig_pool_remote_ip6}/g" > "${rulesfile}.tmp" && /bin/mv "${rulesfile}.tmp" "${rulesfile}"
- /sbin/pfctl -a "openvpn/${dev}_${username}_${trusted_port}" -f "${rulesfile}"
-
- if [ -f /tmp/$common_name ]; then
- /bin/cat /tmp/$common_name > $1
- /bin/rm /tmp/$common_name
- fi
-
- /bin/rm "${lockfile}"
-elif [ "$script_type" = "client-disconnect" ]; then
- /usr/bin/logger -t openvpn "openvpn server '${dev}' user '${username}' address '${ipaddress}' - disconnected"
- i=1
- while [ -f "${lockfile}" ]; do
- if [ $i -ge 30 ]; then
- /bin/echo "Timeout while waiting for lockfile"
- exit 1
- fi
-
- /bin/sleep 1
- i=$(( i + 1 ))
- done
- /usr/bin/touch "${lockfile}"
-
- command="/sbin/pfctl -a '${anchorname}' -F rules"
- eval $command
- /sbin/pfctl -k $ifconfig_pool_remote_ip
- /sbin/pfctl -K $ifconfig_pool_remote_ip
- /sbin/pfctl -k $ifconfig_pool_remote_ip6
- /sbin/pfctl -K $ifconfig_pool_remote_ip6
-
- /bin/rm "${rulesfile}"
- /bin/rm "${lockfile}"
-fi
+# Handle 'client-connect' and 'client-disconnect'
+/usr/bin/nohup /usr/local/sbin/openvpn.connect_async.sh > /dev/null &
+# Signal "deferred handler started OK" for client-connect
exit 0
diff --git a/src/usr/local/sbin/openvpn.connect_async.sh b/src/usr/local/sbin/openvpn.connect_async.sh
new file mode 100755
index 0000000000000000000000000000000000000000..64ba4cf97ea9140e0070308e57f86785fb7c18fb
--- /dev/null
+++ b/src/usr/local/sbin/openvpn.connect_async.sh
@@ -0,0 +1,174 @@
+#!/bin/sh
+#
+# openvpn.connect_async.sh
+#
+# part of pfSense (https://www.pfsense.org)
+# Copyright (c) 2021-2022 Rubicon Communications, LLC (Netgate)
+# All rights reserved.
+#
+# 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.
+
+log_session() {
+ if [ -z "${1}" ]; then
+ logmsg=""
+ else
+ logmsg=" - ${1}"
+ fi
+
+ if [ -z "${untrusted_ip6}" ]; then
+ hostaddress="${untrusted_ip}:${untrusted_port}"
+ else
+ hostaddress="${untrusted_ip6}:${untrusted_port}"
+ fi
+
+ if [ -z "${username}" ]; then
+ hostuser="user cert CN '${X509_0_CN}'"
+ else
+ hostuser="user '${username}'"
+ fi
+
+ /usr/bin/logger -t openvpn "openvpn server '${dev}' ${hostuser} address '${hostaddress}'${logmsg}"
+}
+
+if [ -n "${username}" ]; then
+ lockfile="/tmp/ovpn_${dev}_${username}_${trusted_port}.lock"
+ rulesfile="/tmp/ovpn_${dev}_${username}_${trusted_port}.rules"
+ anchorname="openvpn/${dev}_${username}_${trusted_port}"
+fi
+
+if [ "${script_type}" = "client-disconnect" ]; then
+ log_session "disconnected"
+
+ if [ -n "${username}" ]; then
+ # Avoid race condition. See https://redmine.pfsense.org/issues/9206
+ i=1
+ while
+ if [ -f "${lockfile}" ]; then
+ /bin/sleep 1
+ i="$((i+1))"
+ else
+ break
+ fi
+ [ "${i}" -lt 30 ]
+ do :; done
+
+ if [ ${i} -ge 30 ]; then
+ log_session "Timeout while waiting for lockfile"
+ else
+ /usr/bin/touch "${lockfile}"
+ eval "/sbin/pfctl -a '${anchorname}' -F rules"
+ /bin/rm "${lockfile}"
+
+ /bin/rm "${rulesfile}"
+ fi
+ fi
+
+ /sbin/pfctl -k $ifconfig_pool_remote_ip
+ /sbin/pfctl -K $ifconfig_pool_remote_ip
+ /sbin/pfctl -k $ifconfig_pool_remote_ip6
+ /sbin/pfctl -K $ifconfig_pool_remote_ip6
+elif [ "${script_type}" = "client-connect" ]; then
+ log_session "connecting"
+
+ # Verify defer status code before continuing
+ i=1
+ while
+ deferstatus=$(/usr/bin/head -1 "${client_connect_deferred_file}")
+ if [ "${deferstatus}" -ne 2 ]; then
+ /bin/sleep 1
+ i="$((i+1))"
+ else
+ break
+ fi
+ [ "${i}" -lt 3 ]
+ do :; done
+ if [ ${i} -ge 3 ]; then
+ log_session "server write to defer file failed"
+ /bin/echo 0 > ${client_connect_deferred_file}
+ exit 1
+ fi
+
+ # Get active sessions
+ # active_sessions :: ovpns1_'user_01'_30001|ovpns1_'user_01'_30002|ovpns1_'user_01'_30003|
+ # Use php-cgi - see https://redmine.pfsense.org/issues/12382
+ active_sessions=$("/usr/local/bin/php-cgi" -f "/usr/local/sbin/openvpn_connect_async.php")
+
+ # Process "Duplicate Connection Limit" setting
+ if [ -n "${active_sessions}" ]; then
+ vpnid=$(/bin/echo ${dev} | /usr/bin/sed -e 's/ovpns//g')
+ if [ -f "/var/etc/openvpn/server${vpnid}/connuserlimit" ]; then
+ sessionlimit=$(/usr/bin/head -1 "/var/etc/openvpn/server${vpnid}/connuserlimit" | /usr/bin/sed -e 's/[[:space:]]//g')
+ if [ "${sessionlimit}" -ge 1 ]; then
+ if [ -z "${username}" ]; then
+ usersession="${dev}_'${X509_0_CN}'"
+ else
+ usersession="${dev}_'${username}'"
+ fi
+ sessioncount=$(/bin/echo "${active_sessions}" | /usr/bin/grep -o "${usersession}" | /usr/bin/wc -l | /usr/bin/sed -e 's/[[:space:]]//g')
+
+ if [ ${sessioncount} -gt ${sessionlimit} ]; then
+ log_session "active connection limit of '${sessionlimit}' reached"
+ /bin/echo 0 > ${client_connect_deferred_file}
+ if [ -n "${username}" ]; then
+ /bin/rm "${rulesfile}"
+ fi
+ exit 1
+ fi
+ fi
+ fi
+ fi
+
+ if [ -n "${username}" ]; then
+
+ i=1
+ while
+ if [ -f "${lockfile}" ]; then
+ /bin/sleep 1
+ i="$((i+1))"
+ else
+ break
+ fi
+ [ "${i}" -lt 30 ]
+ do :; done
+ if [ ${i} -ge 30 ]; then
+ log_session "Timeout while waiting for lockfile"
+ /bin/echo 0 > ${client_connect_deferred_file}
+ exit 1
+ else
+ /usr/bin/touch "${lockfile}"
+
+ # for each of this user's anchors loaded in pf
+ # $session :: ovpns3_'user_01'_61468
+ # $anchor :: openvpn/ovpns3_user_01_61468
+ anchors=$(/sbin/pfctl -s Anchors)
+ for anchor in $(/bin/echo "${anchors}" | /usr/bin/grep "${dev}_${username}"); do
+ session=$(/bin/echo "${anchor}" | /usr/bin/sed -r -e 's/.+'"${dev}_${username}"'/'"${dev}_\'${username}\'"'/')
+ # if no active session exists for the anchor, remove it from pf
+ if ! (/bin/echo "${active_sessions}" | /usr/bin/grep -q "${session}"); then
+ eval "/sbin/pfctl -a '${anchor}' -F rules"
+ fi
+ done
+
+ /bin/echo "$(/usr/bin/sed -e "s/{clientip}/${ifconfig_pool_remote_ip}/g;s/{clientipv6}/${ifconfig_pool_remote_ip6}/g" "${rulesfile}")" > "${rulesfile}"
+ eval "/sbin/pfctl -a '${anchorname}' -f '${rulesfile}'"
+
+ /bin/rm "${lockfile}"
+ fi
+ fi
+
+ # success; allow client connection
+ /bin/echo 1 > ${client_connect_deferred_file}
+ log_session "connected"
+fi
+
+exit 0
diff --git a/src/usr/local/sbin/openvpn_connect_async.php b/src/usr/local/sbin/openvpn_connect_async.php
new file mode 100644
index 0000000000000000000000000000000000000000..6aee3313735ed3529f37819db8091fece3e4f488
--- /dev/null
+++ b/src/usr/local/sbin/openvpn_connect_async.php
@@ -0,0 +1,51 @@
+
diff --git a/src/usr/local/www/vpn_openvpn_server.php b/src/usr/local/www/vpn_openvpn_server.php
index 379cc9a0c865538368fdb55264ef67a7ebc0f54f..139c46e7e988d64a9f1768840315d8d12a225cfd 100644
--- a/src/usr/local/www/vpn_openvpn_server.php
+++ b/src/usr/local/www/vpn_openvpn_server.php
@@ -189,6 +189,7 @@ if (($act == "edit") || ($act == "dup")) {
$pconfig['local_network'] = $a_server[$id]['local_network'];
$pconfig['local_networkv6'] = $a_server[$id]['local_networkv6'];
$pconfig['maxclients'] = $a_server[$id]['maxclients'];
+ $pconfig['connlimit'] = $a_server[$id]['connlimit'];
$pconfig['allow_compression'] = $a_server[$id]['allow_compression'];
$pconfig['compression'] = $a_server[$id]['compression'];
$pconfig['compression_push'] = $a_server[$id]['compression_push'];
@@ -497,6 +498,10 @@ if ($_POST['save']) {
$input_errors[] = gettext("The field 'Concurrent connections' must be numeric.");
}
+ if ($pconfig['connlimit'] && !is_numericint($pconfig['connlimit'])) {
+ $input_errors[] = gettext("The field 'Duplicate Connection Limit' must be numeric.");
+ }
+
if (!array_key_exists($pconfig['topology'], $openvpn_topologies)) {
$input_errors[] = gettext("The field 'Topology' contains an invalid selection");
}
@@ -671,6 +676,7 @@ if ($_POST['save']) {
$server['local_network'] = $pconfig['local_network'];
$server['local_networkv6'] = $pconfig['local_networkv6'];
$server['maxclients'] = $pconfig['maxclients'];
+ $server['connlimit'] = $pconfig['connlimit'];
$server['allow_compression'] = $pconfig['allow_compression'];
$server['compression'] = $pconfig['compression'];
$server['compression_push'] = $pconfig['compression_push'];
@@ -1338,6 +1344,13 @@ if ($act=="new" || $act=="edit"):
'When unset, a new connection from a user will disconnect the previous session. %1$s%1$s' .
'Users are identified by their username or certificate properties, depending on the VPN configuration. ' .
'This practice is discouraged security reasons, but may be necessary in some environments.', '
');
+
+ $section->addInput(new Form_Input(
+ 'connlimit',
+ 'Duplicate Connection Limit',
+ 'number',
+ $pconfig['connlimit']
+ ))->setHelp('Limit the number of concurrent connections from the same user.');
$form->add($section);
diff --git a/src/usr/local/www/wizards/openvpn_wizard.inc b/src/usr/local/www/wizards/openvpn_wizard.inc
index 0d6eb6665bfd5849fae03678568dd49ffec158a3..a6a17810ba353124e5a979993a399992a5143703 100644
--- a/src/usr/local/www/wizards/openvpn_wizard.inc
+++ b/src/usr/local/www/wizards/openvpn_wizard.inc
@@ -452,6 +452,9 @@ function step10_submitphpaction() {
if ($_POST['concurrentcon'] && !is_numeric($_POST['concurrentcon']))
$input_errors[] = "The field 'Concurrent connections' must be numeric.";
+
+ if ($_POST['connuserlimit'] && !is_numeric($_POST['connuserlimit']))
+ $input_errors[] = "The field 'Duplicate Connection Limit' must be numeric.";
if (empty($_POST['tunnelnet']))
$input_errors[] = "A 'Tunnel network' must be specified.";
@@ -649,6 +652,8 @@ function step12_submitphpaction() {
$server['local_network'] = $pconfig['step10']['localnet'];
if (isset($pconfig['step10']['concurrentcon']))
$server['maxclients'] = $pconfig['step10']['concurrentcon'];
+ if (isset($pconfig['step10']['connuserlimit']))
+ $server['connlimit'] = $pconfig['step10']['connuserlimit'];
if (isset($pconfig['step10']['allowcompression']))
$server['allow_compression'] = $pconfig['step10']['allowcompression'];
if (isset($pconfig['step10']['compression']))
diff --git a/src/usr/local/www/wizards/openvpn_wizard.xml b/src/usr/local/www/wizards/openvpn_wizard.xml
index cb88ed9ad9f554552d4f643b5583db785b3beb70..8165f92696fbc079a68b0635b9bf273c9e299298 100644
--- a/src/usr/local/www/wizards/openvpn_wizard.xml
+++ b/src/usr/local/www/wizards/openvpn_wizard.xml
@@ -971,6 +971,14 @@
Allow multiple concurrent connections from clients using the same Common Name.<br/>NOTE: This is not generally recommended, but may be needed for some scenarios.
ovpnserver->step10->duplicate_cn
+
+ Duplicate Connection Limit
+ connuserlimit
+ Limit the number of concurrent connections from the same user.
+ input
+ 10
+ ovpnserver->step10->connuserlimit
+
listtopic
Client Settings