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

Captive Portal : Fix the bug affecting users getting disconnected when reconfiguring a zone #4031

Closed
wants to merge 5 commits into from
Closed
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
111 changes: 64 additions & 47 deletions src/etc/inc/captiveportal.inc
Expand Up @@ -225,8 +225,6 @@ function captiveportal_configure() {
function captiveportal_configure_zone($cpcfg) {
global $config, $g, $cpzone, $cpzoneid;

$captiveportallck = lock("captiveportal{$cpzone}", LOCK_EX);

if (isset($cpcfg['enable'])) {

if (platform_booting()) {
Expand All @@ -235,8 +233,8 @@ function captiveportal_configure_zone($cpcfg) {
captiveportal_syslog("Reconfiguring captive portal({$cpcfg['zone']}).");
}

/* (re)init ipfw rules. Cause all users to disconnect */
captiveportal_init_rules(true);
/* (re)init ipfw rules */
captiveportal_init_rules();

/* kill any running minicron */
killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
Expand Down Expand Up @@ -373,9 +371,6 @@ EOD;
mwexec("/usr/local/bin/minicron $croninterval {$g['varrun_path']}/cp_prunedb_{$cpzone}.pid " .
"/etc/rc.prunecaptiveportal {$cpzone}");

/* delete outdated radius server database if exist */
unlink_if_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db");

if (platform_booting()) {
/* send Accounting-On to server */
captiveportal_send_server_accounting('on');
Expand All @@ -396,8 +391,6 @@ EOD;

/* remove old information */
unlink_if_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db");
unlink_if_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db");
unlink_if_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules");
/* Release allocated pipes for this zone */
$pipes_to_remove = captiveportal_free_dnrules();

Expand All @@ -422,8 +415,6 @@ EOD;
}
}

unlock($captiveportallck);

return 0;
}

Expand Down Expand Up @@ -572,10 +563,6 @@ function captiveportal_init_rules($reinit = false) {
captiveportal_load_modules();
captiveportal_init_general_rules();

/* Cleanup so nothing is leaked */
captiveportal_free_dnrules();
unlink_if_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules");

$skipto = captiveportal_ipfw_ruleno($cpzoneid);

$cprules = '';
Expand Down Expand Up @@ -618,10 +605,6 @@ function captiveportal_init_rules($reinit = false) {
return false;
}

if ($reinit == false) {
$captiveportallck = lock("captiveportal{$cpzone}");
}

$rulenum = $skipto;
$cprules .= "table {$cpzone}_pipe_mac create type mac valtype pipe\n";
$cprules .= captiveportal_create_ipfw_rule("add", $rulenum,
Expand Down Expand Up @@ -715,17 +698,45 @@ function captiveportal_init_rules($reinit = false) {
/* allowed ipfw rules to make allowed hostnames work */
$cprules .= captiveportal_allowedhostname_configure();

/* if reinit : flush pipes, so nothing is leaked. if not reinit, just destroy tables without flushing pipes */
if ($reinit) {
$pipes_to_remove = captiveportal_free_dnrules();
captiveportal_delete_rules($pipes_to_remove);
} else {
captiveportal_delete_rules();
}

/* load rules */
captiveportal_delete_rules();
$captiveportallck = lock("captiveportal{$cpzone}");
file_put_contents("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", $cprules);
mwexec("/sbin/ipfw -q {$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", true);
@unlink("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules");
unset($cprules);

captiveportal_filterdns_configure();
unlock($captiveportallck);

if ($reinit) {
unlink_if_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db");
} else {
foreach (captiveportal_read_db() as $user) {
$bw_up = intval($user['bw_up']);
$bw_down = intval($user['bw_down']);
$clientip = $user['ip'];
$clientmac = $user['mac'];
$bw_up_pipeno = $user['pipeno'];
$bw_down_pipeno = $user['pipeno'] + 1;

if ($reinit == false) {
unlock($captiveportallck);
$rule_entry = "{$clientip}/" . (is_ipaddrv6($clientip) ? "128" : "32");
if (!isset($config['captiveportal'][$cpzone]['nomacfilter'])) {
$rule_entry .= ",{$clientmac}";
}
$_gb = @pfSense_ipfw_pipe("pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16");
$_gb = @pfSense_ipfw_pipe("pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16");

$_gb = @pfSense_ipfw_table("{$cpzone}_auth_up", IP_FW_TABLE_XADD, "{$rule_entry}", $bw_up_pipeno);
$_gb = @pfSense_ipfw_table("{$cpzone}_auth_down", IP_FW_TABLE_XADD, "{$rule_entry}", $bw_down_pipeno);
}
}
}

Expand Down Expand Up @@ -900,7 +911,7 @@ function captiveportal_prune_old() {
/* do periodic reauthentication? For Radius servers, send accounting updates? */
if (!$timedout) {
//Radius servers : send accounting
if (isset($cpcfg['radacct_enable']) && $cpentry[12] === 'radius') {
if (isset($cpcfg['radacct_enable']) && $cpentry['authmethod'] === 'radius') {
if (substr($cpcfg['reauthenticateacct'], 0, 9) === "stopstart") {
/* stop and restart accounting */
if ($cpcfg['reauthenticateacct'] === "stopstartfreeradius") {
Expand Down Expand Up @@ -956,27 +967,27 @@ function captiveportal_prune_old() {
}

/* check this user again */
if (isset($cpcfg['reauthenticate']) && $cpentry[13] !== 'voucher') {
if (isset($cpcfg['reauthenticate']) && $cpentry['context'] !== 'voucher') {
$auth_result = captiveportal_authenticate_user(
$cpentry[4], // username
base64_decode($cpentry[6]), // password
$cpentry[3], // clientmac
$cpentry[2], // clientip
$cpentry[1], // ruleno
$cpentry[13]); // context
$cpentry['context']); // context
if ($auth_result['result'] === false) {
captiveportal_disconnect($cpentry, 17);
captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "DISCONNECT - REAUTHENTICATION FAILED", $auth_list['reply_message']);
$unsetindexes[] = $cpentry[5];
} else if ($auth_result['result'] === true) {
if ($cpentry[12] !== $auth_result['auth_method']) {
if ($cpentry['authmethod'] !== $auth_result['auth_method']) {
// if the user got authenticated against another server type: we update the database
if (!empty($cpentry[5])) {
captiveportal_write_db("UPDATE captiveportal SET authmethod = '{$auth_result['auth_method']}' WHERE sessionid = '{$cpentry[5]}'");
captiveportal_update_entry($cpentry['sessionid'], $auth_result['auth_method'], 'authmethod');
captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CHANGED AUTHENTICATION SERVER", $auth_list['reply_message']);
}
// User was logged on a RADIUS server, but is now logged in by another server type : we send an accounting Stop
if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && $cpentry[12] =='radius') {
if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && $cpentry['authmethod'] == 'radius') {
if ($cpcfg['reauthenticateacct'] === "stopstartfreeradius") {
$rastart_time = 0;
$rastop_time = 60;
Expand Down Expand Up @@ -1090,7 +1101,7 @@ function captiveportal_disconnect($dbent, $term_cause = 1, $stop_time = null) {
$stop_time = (empty($stop_time)) ? time() : $stop_time;

/* this client needs to be deleted - remove ipfw rules */
if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && $dbent[12] =='radius') {
if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && $dbent['authmethod'] == 'radius') {
if ($config['captiveportal'][$cpzone]['reauthenticateacct'] == "stopstartfreeradius") {
/*
* Interim updates are on so the session time must be
Expand Down Expand Up @@ -1208,14 +1219,7 @@ function captiveportal_disconnect_all($term_cause = 6, $logoutReason = "DISCONNE

captiveportal_radius_stop_all($term_cause, $logoutReason);

/* remove users from the database */
$cpdb = captiveportal_read_db();
$unsetindexes = array_column($cpdb,5);
if (!empty($unsetindexes)) {
captiveportal_remove_entries($unsetindexes);
}

/* reinit ipfw rules */
/* reinit ipfw rules, flush user database */
captiveportal_init_rules(true);

unlock($cpdblck);
Expand All @@ -1230,7 +1234,7 @@ function captiveportal_radius_stop_all($term_cause = 6, $logoutReason = "DISCONN

$radacct = isset($config['captiveportal'][$cpzone]['radacct_enable']) ? true : false;
foreach ($cpdb as $cpentry) {
if ($cpentry[12] === 'radius' && $radacct) {
if ($cpentry['authmethod'] === 'radius' && $radacct) {
if ($config['captiveportal'][$cpzone]['reauthenticateacct'] == "stopstartfreeradius") {
$session_time = (time() - $cpentry[0]) % 60;
$start_time = time() - $session_time;
Expand Down Expand Up @@ -1679,8 +1683,8 @@ function captiveportal_authenticate_user(&$login = '', &$password = '', $clientm
$val = 0;
}

if ($val >= $auth_val) {
$auth_val = $val;
if ($val >= $authlevel) {
$authlevel = $val;
$auth_method = $authcfg['type'];
$login_status = $status;
$login_msg = $msg;
Expand All @@ -1702,7 +1706,7 @@ function captiveportal_opendb() {
"allow_time INTEGER, pipeno INTEGER, ip TEXT, mac TEXT, username TEXT, " .
"sessionid TEXT, bpassword TEXT, session_timeout INTEGER, idle_timeout INTEGER, " .
"session_terminate_time INTEGER, interim_interval INTEGER, traffic_quota INTEGER, " .
"authmethod TEXT, context TEXT); " .
"bw_up INTEGER, bw_down INTEGER, authmethod TEXT, context TEXT); " .
"CREATE UNIQUE INDEX IF NOT EXISTS idx_active ON captiveportal (sessionid, username); " .
"CREATE INDEX IF NOT EXISTS user ON captiveportal (username); " .
"CREATE INDEX IF NOT EXISTS ip ON captiveportal (ip); " .
Expand Down Expand Up @@ -2204,13 +2208,26 @@ function captiveportal_reapply_attributes($cpentry, $attributes) {
$bw_up_pipeno = $cpentry[1];
$bw_down_pipeno = $cpentry[1]+1;

$_gb = @pfSense_ipfw_pipe("pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16");
$_gb = @pfSense_ipfw_pipe("pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16");
//captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "RADIUS_BANDWIDTH_REAPPLY", "{$bw_up}/{$bw_down}");

if ($cpentry['bw_up'] !== $bw_up) {
$_gb = @pfSense_ipfw_pipe("pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16");
captiveportal_update_entry($cpentry['sessionid'], $bw_up, 'bw_up');
}
if ($cpentry['bw_down'] !== $bw_down) {
$_gb = @pfSense_ipfw_pipe("pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16");
captiveportal_update_entry($cpentry['sessionid'], $bw_down, 'bw_down');
}
unset($bw_up_pipeno, $bw_down_pipeno, $bw_up, $bw_down);
}

function captiveportal_update_entry($sessionid, $new_value, $field_to_update) {
global $config, $cpzone, $g;

if (!intval($new_value)) {
$new_value = "'{$new_value}'";
}
captiveportal_write_db("UPDATE captiveportal SET {$field_to_update} = {$new_value} WHERE sessionid = '{$sessionid}'");
}

function portal_allow($clientip, $clientmac, $username, $password = null, $attributes = null, $pipeno = null, $authmethod = null, $context = 'first') {
global $redirurl, $g, $config, $type, $_POST, $cpzone, $cpzoneid;

Expand Down Expand Up @@ -2414,9 +2431,9 @@ function portal_allow($clientip, $clientmac, $username, $password = null, $attri

/* encode password in Base64 just in case it contains commas */
$bpassword = (isset($config['captiveportal'][$cpzone]['reauthenticate'])) ? base64_encode($password) : '';
$insertquery = "INSERT INTO captiveportal (allow_time, pipeno, ip, mac, username, sessionid, bpassword, session_timeout, idle_timeout, session_terminate_time, interim_interval, traffic_quota, authmethod, context) ";
$insertquery = "INSERT INTO captiveportal (allow_time, pipeno, ip, mac, username, sessionid, bpassword, session_timeout, idle_timeout, session_terminate_time, interim_interval, traffic_quota, bw_up, bw_down, authmethod, context) ";
$insertquery .= "VALUES ({$allow_time}, {$pipeno}, '{$clientip}', '{$clientmac}', '{$safe_username}', '{$sessionid}', '{$bpassword}', ";
$insertquery .= "{$session_timeout}, {$idle_timeout}, {$session_terminate_time}, {$interim_interval}, {$traffic_quota}, '{$authmethod}', '{$context}')";
$insertquery .= "{$session_timeout}, {$idle_timeout}, {$session_terminate_time}, {$interim_interval}, {$traffic_quota}, {$bw_up}, {$bw_down}, '{$authmethod}', '{$context}')";

/* store information to database */
captiveportal_write_db($insertquery);
Expand Down
2 changes: 1 addition & 1 deletion src/etc/inc/globals.inc
Expand Up @@ -69,7 +69,7 @@ $g = array(
"disablecrashreporter" => false,
"crashreporterurl" => "https://crashreporter.pfsense.org/crash_reporter.php",
"debug" => false,
"latest_config" => "19.0",
"latest_config" => "19.1",
"minimum_ram_warning" => "101",
"minimum_ram_warning_text" => "128 MB",
"wan_interface_name" => "wan",
Expand Down
10 changes: 6 additions & 4 deletions src/etc/inc/system.inc
Expand Up @@ -2113,15 +2113,17 @@ function system_reboot_sync($reroot=false) {
}

function system_reboot_cleanup() {
global $config, $cpzone, $cpzoneid;
global $g, $config, $cpzone;

mwexec("/usr/local/bin/beep.sh stop");
require_once("captiveportal.inc");
if (is_array($config['captiveportal'])) {
foreach ($config['captiveportal'] as $cpzone=>$cp) {
/* send Accounting-Stop packet for all clients, termination cause 'Admin-Reboot' */
$cpzoneid = $cp['zoneid'];
captiveportal_radius_stop_all(7); // Admin-Reboot
if (!isset($cp['preservedb'])) {
/* send Accounting-Stop packet for all clients, termination cause 'Admin-Reboot' */
captiveportal_radius_stop_all(7); // Admin-Reboot
unlink_if_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db");
}
/* Send Accounting-Off packet to the RADIUS server */
captiveportal_send_server_accounting('off');
}
Expand Down
10 changes: 10 additions & 0 deletions src/etc/inc/upgrade_config.inc
Expand Up @@ -5934,6 +5934,16 @@ function upgrade_189_to_190() {
}
}

function upgrade_190_to_191() {
global $config, $g;

if (is_array($config['captiveportal'])) {
foreach ($config['captiveportal'] as $cpzone => $cp) {
unlink_if_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db");
}
}
}

/*
* Special function that is called independent of current config version. It's
* a workaround to have config_upgrade running on older versions after next
Expand Down
10 changes: 10 additions & 0 deletions src/usr/local/www/services_captiveportal.php
Expand Up @@ -151,6 +151,7 @@
$pconfig['radmac_secret'] = $a_cp[$cpzone]['radmac_secret'];
$pconfig['radmac_fallback'] = isset($a_cp[$cpzone]['radmac_fallback']);
$pconfig['reauthenticate'] = isset($a_cp[$cpzone]['reauthenticate']);
$pconfig['preservedb'] = isset($a_cp[$cpzone]['preservedb']);
$pconfig['reauthenticateacct'] = $a_cp[$cpzone]['reauthenticateacct'];
$pconfig['httpslogin_enable'] = isset($a_cp[$cpzone]['httpslogin']);
$pconfig['httpsname'] = $a_cp[$cpzone]['httpsname'];
Expand Down Expand Up @@ -353,6 +354,7 @@
$newcp['localauth_priv'] = isset($_POST['localauth_priv']);
$newcp['radacct_enable'] = $_POST['radacct_enable'] ? true : false;
$newcp['reauthenticate'] = $_POST['reauthenticate'] ? true : false;
$newcp['preservedb'] = $_POST['preservedb'] ? true : false;
$newcp['radmac_secret'] = $_POST['radmac_secret'] ? $_POST['radmac_secret'] : false;
$newcp['radmac_fallback'] = $_POST['radmac_fallback'] ? true : false;
$newcp['reauthenticateacct'] = $_POST['reauthenticateacct'];
Expand Down Expand Up @@ -633,6 +635,13 @@ function build_authserver_list() {
$pconfig['blockedmacsurl']
))->setHelp('Blocked MAC addresses will be redirected to this URL when attempting access.');

$section->addInput(new Form_Checkbox(
'preservedb',
'Preserve users database',
'Preserve connected users across reboot',
$pconfig['preservedb']
))->setHelp('If enabled, connected users won\'t be disconnected during a pfSense reboot.');

$section->addInput(new Form_Checkbox(
'noconcurrentlogins',
'Concurrent user logins',
Expand Down Expand Up @@ -1178,6 +1187,7 @@ function hideGeneral(hide) {
hideInput('preauthurl', hide);
hideInput('redirurl', hide);
hideInput('blockedmacsurl', hide);
hideCheckbox('preservedb', hide);
hideCheckbox('noconcurrentlogins', hide);
hideCheckbox('nomacfilter', hide);
hideCheckbox('passthrumacadd', hide);
Expand Down
2 changes: 1 addition & 1 deletion src/usr/local/www/status_captiveportal.php
Expand Up @@ -268,7 +268,7 @@ function print_details($cpent) {
<?php
if (mutiple_auth_server_type()):
?>
<td><?=htmlspecialchars($cpent[12]);?></td>
<td><?=htmlspecialchars($cpent['authmethod']);?></td>
<?php
endif;
?>
Expand Down