diff --git a/src/etc/inc/captiveportal.inc b/src/etc/inc/captiveportal.inc index 23ac130e498..2bfca745103 100644 --- a/src/etc/inc/captiveportal.inc +++ b/src/etc/inc/captiveportal.inc @@ -33,6 +33,7 @@ require_once("config.inc"); require_once("functions.inc"); require_once("filter.inc"); require_once("voucher.inc"); +require_once("xmlrpc_client.inc"); /* Captiveportal Radius Accounting */ PEAR::loadExtension('bcmath'); @@ -383,7 +384,9 @@ EOD; captiveportal_send_server_accounting('on'); echo "done\n"; - if (isset($cpcfg['preservedb'])) { + if (isset($cpcfg['preservedb']) || + captiveportal_xmlrpc_sync_get_details($syncip, $syncport, $syncuser, $syncpass)) { + $connected_users = captiveportal_read_db(); if (!empty($connected_users)) { echo "Reconnecting users to captive portal {$cpcfg['zone']}... "; @@ -592,7 +595,7 @@ function captiveportal_ipfw_ruleno($id) { /* reinit will disconnect all users, be careful! */ function captiveportal_init_rules($reinit = false) { - global $config, $g, $cpzone, $cpzoneid; + global $config, $g, $cpzone; if (!isset($config['captiveportal'][$cpzone]['enable'])) { return; @@ -605,6 +608,7 @@ function captiveportal_init_rules($reinit = false) { captiveportal_free_dnrules(2000, 64500, false, $reinit); unlink_if_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"); + $cpzoneid = $config['captiveportal'][$cpzone]['zoneid']; $skipto = captiveportal_ipfw_ruleno($cpzoneid); $cprules = ''; @@ -764,7 +768,9 @@ function captiveportal_init_rules($reinit = false) { /* Delete all rules related to specific cpzone */ function captiveportal_delete_rules($pipes_to_remove = array(), $clear_auth_rules = true) { - global $g, $cpzoneid, $cpzone; + global $g, $config, $cpzone; + + $cpzoneid = $config['captiveportal'][$cpzone]['zoneid']; $skipto1 = captiveportal_ipfw_ruleno($cpzoneid); $skipto2 = $skipto1 + $g['captiveportal_rules_interval']; @@ -841,10 +847,11 @@ function captiveportal_prune_old() { $trafficquota = $cpcfg['trafficquota'] * 1048576; } - /* Is there any job to do? */ - if (!$timeout && !$idletimeout && !$trafficquota && !isset($cpcfg['reauthenticate']) && + /* Is there any job to do? If we are in High Availability sync, are we in backup mode ? */ + if ((!$timeout && !$idletimeout && !$trafficquota && !isset($cpcfg['reauthenticate']) && !isset($cpcfg['radiussession_timeout']) && !isset($cpcfg['radiustraffic_quota']) && - !isset($vcpcfg['enable']) && !isset($cpcfg['radacct_enable'])) { + !isset($vcpcfg['enable']) && !isset($cpcfg['radacct_enable'])) || + captiveportal_ha_is_node_in_backup_mode($cpzone)) { return; } @@ -1055,7 +1062,7 @@ function captiveportal_prune_old() { captiveportal_prune_old_automac(); if ($voucher_needs_sync == true) { - /* Trigger a sync of the vouchers on config */ + /* perform in-use vouchers expiration using check_reload_status */ send_event("service sync vouchers"); } @@ -1125,7 +1132,7 @@ function captiveportal_prune_old_automac() { } /* remove a single client according to the DB entry */ -function captiveportal_disconnect($dbent, $term_cause = 1, $stop_time = null) { +function captiveportal_disconnect($dbent, $term_cause = 1, $stop_time = null, $carp_loop = false) { global $g, $config, $cpzone, $cpzoneid; $stop_time = (empty($stop_time)) ? time() : $stop_time; @@ -1200,14 +1207,27 @@ function captiveportal_disconnect($dbent, $term_cause = 1, $stop_time = null) { } } - // XMLRPC Call over to the master Voucher node - if (xmlrpc_sync_voucher_details($syncip, $syncport, - $vouchersyncusername, $syncpass)) { - $remote_status = xmlrpc_sync_voucher_disconnect($dbent, $syncip, - $syncport, $syncpass, $vouchersyncusername, $term_cause, - $stop_time); - } + // XMLRPC Call over to the backup node if necessary + if (captiveportal_xmlrpc_sync_get_details($syncip, $syncport, + $syncuser, $syncpass, $carp_loop)) { + $rpc_client = new pfsense_xmlrpc_client(); + $rpc_client->setConnectionData($syncip, $syncport, $syncuser, $syncpass); + $rpc_client->set_noticefile("CaptivePortalUserSync"); + $arguments = array( + 'sessionid' => $dbent[5], + 'term_cause' => $term_cause, + 'stop_time' => $stop_time + ); + $rpc_client->xmlrpc_method('captive_portal_sync', + array( + 'op' => 'disconnect_user', + 'zone' => $cpzone, + 'session' => base64_encode(serialize($arguments)) + ) + ); + } + return true; } /* remove a single client by sessionid */ @@ -1231,9 +1251,26 @@ function captiveportal_disconnect_client($sessionid, $term_cause = 1, $logoutRea } /* remove all clients */ -function captiveportal_disconnect_all($term_cause = 6, $logoutReason = "DISCONNECT") { +function captiveportal_disconnect_all($term_cause = 6, $logoutReason = "DISCONNECT", $carp_loop = false) { global $g, $config, $cpzone, $cpzoneid; + if (captiveportal_xmlrpc_sync_get_details($syncip, $syncport, $syncuser, $syncpass, $carp_loop)) { + $rpc_client = new pfsense_xmlrpc_client(); + $rpc_client->setConnectionData($syncip, $syncport, $syncuser, $syncpass); + $rpc_client->set_noticefile("CaptivePortalUserSync"); + $arguments = array( + 'term_cause' => $term_cause, + 'logout_reason' => $logoutReason + ); + + $rpc_client->xmlrpc_method('captive_portal_sync', + array( + 'op' => 'disconnect_all', + 'zone' => $cpzone, + 'arguments' => base64_encode(serialize($arguments)) + ) + ); + } /* check if we're pruning old entries and eventually wait */ $rcprunelock = try_lock("rcprunecaptiveportal{$cpzone}", 15); @@ -1253,7 +1290,8 @@ function captiveportal_disconnect_all($term_cause = 6, $logoutReason = "DISCONNE $cpdb = captiveportal_read_db(); $unsetindexes = array_column($cpdb,5); if (!empty($unsetindexes)) { - captiveportal_remove_entries($unsetindexes); + // High Availability : do not sync removed entries + captiveportal_remove_entries($unsetindexes, true); } /* reinit ipfw rules */ @@ -1261,6 +1299,7 @@ function captiveportal_disconnect_all($term_cause = 6, $logoutReason = "DISCONNE unlock($cpdblck); unlock($rcprunelock); + return true; } /* send RADIUS acct stop for all current clients connected with RADIUS servers */ @@ -1851,7 +1890,8 @@ function captiveportal_read_db($query = "") { return $cpdb; } -function captiveportal_remove_entries($remove) { +function captiveportal_remove_entries($remove, $carp_loop = false) { + global $cpzone; if (!is_array($remove) || empty($remove)) { return; @@ -1866,6 +1906,20 @@ function captiveportal_remove_entries($remove) { } $query .= ")"; captiveportal_write_db($query); + + if (captiveportal_xmlrpc_sync_get_details($syncip, $syncport, $syncuser, $syncpass, $carp_loop)) { + $rpc_client = new pfsense_xmlrpc_client(); + $rpc_client->setConnectionData($syncip, $syncport, $syncuser, $syncpass); + $rpc_client->set_noticefile("CaptivePortalUserSync"); + $rpc_client->xmlrpc_method('captive_portal_sync', + array( + 'op' => 'remove_entries', + 'zone' => $cpzone, + 'entries' => base64_encode(serialize($remove)) + ) + ); + } + return true; } /* write captive portal DB */ @@ -2289,8 +2343,9 @@ function captiveportal_update_entry($sessionid, $new_value, $field_to_update) { 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; +function portal_allow($clientip, $clientmac, $username, $password = null, $redirurl = null, + $attributes = null, $pipeno = null, $authmethod = null, $context = 'first', $existing_sessionid = null) { + global $g, $config, $cpzone; // Ensure we create an array if we are missing attributes if (!is_array($attributes)) { @@ -2342,6 +2397,12 @@ function portal_allow($clientip, $clientmac, $username, $password = null, $attri /* Snapshot the timestamp */ $allow_time = time(); + + if ($existing_sessionid !== null) { + // If we recieved this connection through XMLRPC sync : + // we fetch allow_time from the info given by the other node + $allow_time = $attributes['allow_time']; + } $unsetindexes = array(); foreach ($cpdb as $cpentry) { @@ -2389,9 +2450,13 @@ function portal_allow($clientip, $clientmac, $username, $password = null, $attri } if (!isset($sessionid)) { - /* generate unique session ID */ - $tod = gettimeofday(); - $sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16); + if ($existing_sessionid != null) { // existing_sessionid should only be set during XMLRPC sync + $sessionid = $existing_sessionid; + } else { + /* generate unique session ID */ + $tod = gettimeofday(); + $sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16); + } if (isset($config['captiveportal'][$cpzone]['peruserbw'])) { $dwfaultbw_up = !empty($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0; @@ -2457,10 +2522,9 @@ function portal_allow($clientip, $clientmac, $username, $password = null, $attri /* if the pool is empty, return appropriate message and exit */ if (is_null($pipeno)) { - portal_reply_page($redirurl, "error", "System reached maximum login capacity"); - log_error("Zone: {$cpzone} - WARNING! Captive portal has reached maximum login capacity"); + captiveportal_syslog("Zone: {$cpzone} - WARNING! Captive portal has reached maximum login capacity"); unlock($cpdblck); - return; + return false; } $bw_up_pipeno = $pipeno; @@ -2511,6 +2575,32 @@ function portal_allow($clientip, $clientmac, $username, $password = null, $attri $sessionid, // sessionid time()); // start time } + if (captiveportal_xmlrpc_sync_get_details($syncip, $syncport, $syncuser, $syncpass, isset($existing_sessionid))) { + // $existing_sessionid prevent carp loop : only forward + // the connection to the other node if we generated the sessionid by ourselves + $rpc_client = new pfsense_xmlrpc_client(); + $rpc_client->setConnectionData($syncip, $syncport, $syncuser, $syncpass); + $rpc_client->set_noticefile("CaptivePortalUserSync"); + $arguments = array( + 'clientip' => $clientip, + 'clientmac' => $clientmac, + 'username' => $username, + 'password' => $password, + 'attributes' => $attributes, + 'allow_time' => $allow_time, + 'authmethod' => $authmethod, + 'context' => $context, + 'sessionid' => $sessionid + ); + + $rpc_client->xmlrpc_method('captive_portal_sync', + array( + 'op' => 'connect_user', + 'zone' => $cpzone, + 'user' => base64_encode(serialize($arguments)) + ) + ); + } } } else { /* NOTE: #3062-11 If the pipeno has been allocated free it to not DoS the CP */ @@ -2525,6 +2615,13 @@ function portal_allow($clientip, $clientmac, $username, $password = null, $attri write_config(gettext("Captive Portal allowed users configuration changed")); } + if ($existing_sessionid !== null) { + if (!empty($sessionid)) { + return $sessionid; + } else { + return false; + } + } /* redirect user to desired destination */ if (!empty($attributes['url_redirection'])) { $my_redirurl = $attributes['url_redirection']; @@ -2713,7 +2810,8 @@ function captiveportal_send_server_accounting($type = 'on', $ruleno = null, $use $cpcfg = $config['captiveportal'][$cpzone]; $acctcfg = auth_get_authserver($cpcfg['radacct_server']); - if (!isset($cpcfg['radacct_enable']) || empty($acctcfg)) { + if (!isset($cpcfg['radacct_enable']) || empty($acctcfg) || + captiveportal_ha_is_node_in_backup_mode($cpzone)) { return null; } @@ -2841,4 +2939,5 @@ function captiveportal_isip_logged($clientip) { return $cpentry; } } + ?> diff --git a/src/etc/inc/globals.inc b/src/etc/inc/globals.inc index a1c6f62fa1a..9904c1d7bd1 100644 --- a/src/etc/inc/globals.inc +++ b/src/etc/inc/globals.inc @@ -70,7 +70,7 @@ $g = array( "disablehelpicon" => false, "disablecrashreporter" => false, "debug" => false, - "latest_config" => "20.3", + "latest_config" => "20.4", "minimum_ram_warning" => "101", "minimum_ram_warning_text" => "128 MB", "wan_interface_name" => "wan", diff --git a/src/etc/inc/gwlb.inc b/src/etc/inc/gwlb.inc index 643e2b45511..6b260a6144f 100644 --- a/src/etc/inc/gwlb.inc +++ b/src/etc/inc/gwlb.inc @@ -1090,6 +1090,7 @@ function fixup_default_gateway($ipprotocol, $gateways_status, $gateways_arr) { } } if (empty($set_dfltgwname)) { + openlog("", LOG_PID, LOG_LOCAL0); log_error(sprintf("Gateway, none 'available' for %s, use the first one configured. '%s'", $ipprotocol, $fallback)); $set_dfltgwname = $fallback; } diff --git a/src/etc/inc/notices.inc b/src/etc/inc/notices.inc index 455e50a92a7..41c364fcf28 100644 --- a/src/etc/inc/notices.inc +++ b/src/etc/inc/notices.inc @@ -79,6 +79,7 @@ function file_notice($id, $notice, $category = "General", $url = "", $priority = } fwrite($queueout, serialize($queue)); fclose($queueout); + openlog("", LOG_PID, LOG_LOCAL0); log_error(sprintf(gettext("New alert found: %s"), $notice)); /* soekris */ if (file_exists("/dev/led/error")) { diff --git a/src/etc/inc/openvpn.inc b/src/etc/inc/openvpn.inc index e9e1f3f2202..8ae34569810 100644 --- a/src/etc/inc/openvpn.inc +++ b/src/etc/inc/openvpn.inc @@ -1655,6 +1655,7 @@ function openvpn_resync_all($interface = "") { $config['openvpn'] = array(); } + openlog("", LOG_PID, LOG_LOCAL0); if ($interface <> "") { log_error(sprintf(gettext("Resyncing OpenVPN instances for interface %s."), convert_friendly_interface_to_friendly_descr($interface))); } else { diff --git a/src/etc/inc/pfsense-utils.inc b/src/etc/inc/pfsense-utils.inc index bf0499aa669..c1f788a5dff 100644 --- a/src/etc/inc/pfsense-utils.inc +++ b/src/etc/inc/pfsense-utils.inc @@ -772,6 +772,29 @@ function get_carp_interface_status($carpid) { return ""; } +/* + * Return true if the CARP status of at least one interface of a captive portal zone is in backup mode + * This function return false if CARP is not enabled on any interface of the captive portal zone + */ +function captiveportal_ha_is_node_in_backup_mode($cpzone) { + global $config; + + $cpinterfaces = explode(",", $config['captiveportal'][$cpzone]['interface']); + + if (is_array($config['virtualip']['vip'])) { + foreach ($cpinterfaces as $interface) { + foreach ($config['virtualip']['vip'] as $vip) { + if (($vip['interface'] == $interface) && ($vip['mode'] == "carp")) { + if (get_carp_interface_status("_vip{$vip['uniqid']}") != "MASTER") { + return true; + } + } + } + } + } + return false; +} + /* * get_pfsync_interface_status($pfsyncinterface): returns the status of a pfsync */ diff --git a/src/etc/inc/priv.defs.inc b/src/etc/inc/priv.defs.inc index 5f230385877..f822420320f 100644 --- a/src/etc/inc/priv.defs.inc +++ b/src/etc/inc/priv.defs.inc @@ -556,6 +556,12 @@ $priv_list['page-services-captiveportal-filemanager']['descr'] = gettext("Allow $priv_list['page-services-captiveportal-filemanager']['match'] = array(); $priv_list['page-services-captiveportal-filemanager']['match'][] = "services_captiveportal_filemanager.php*"; +$priv_list['page-services-captiveportal-hasync'] = array(); +$priv_list['page-services-captiveportal-hasync']['name'] = gettext("WebCfg - Services: Captive Portal HA"); +$priv_list['page-services-captiveportal-hasync']['descr'] = gettext("Allow access to the 'Services: Captive Portal High Availability' page."); +$priv_list['page-services-captiveportal-hasync']['match'] = array(); +$priv_list['page-services-captiveportal-hasync']['match'][] = "services_captiveportal_hasync.php*"; + $priv_list['page-services-captiveportal-allowedhostnames'] = array(); $priv_list['page-services-captiveportal-allowedhostnames']['name'] = gettext("WebCfg - Services: Captive Portal: Allowed Hostnames"); $priv_list['page-services-captiveportal-allowedhostnames']['descr'] = gettext("Allow access to the 'Services: Captive Portal: Allowed Hostnames' page."); diff --git a/src/etc/inc/upgrade_config.inc b/src/etc/inc/upgrade_config.inc index afa893f6bf9..c25f5a4a74b 100644 --- a/src/etc/inc/upgrade_config.inc +++ b/src/etc/inc/upgrade_config.inc @@ -6177,6 +6177,25 @@ function upgrade_202_to_203() { } } +function upgrade_203_to_204() { + global $config, $g; + + if (is_array($config['captiveportal'])) { + foreach ($config['captiveportal'] as $cpzone => $cp) { + unlink_if_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db"); + + if (is_array($config['voucher'][$cpzone])) { + if (!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) { + $config['captiveportal'][$cpzone]['enablebackwardsync'] = ''; + $config['captiveportal'][$cpzone]['backwardsyncip'] = $config['voucher'][$cpzone]['vouchersyncdbip']; + $config['captiveportal'][$cpzone]['backwardsyncuser'] = $config['voucher'][$cpzone]['vouchersyncusername']; + $config['captiveportal'][$cpzone]['backwardsyncpassword'] = $config['voucher'][$cpzone]['vouchersyncpass']; + } + } + } + } +} + /* * Special function that is called independent of current config version. It's * a workaround to have config_upgrade running on older versions after next diff --git a/src/etc/inc/voucher.inc b/src/etc/inc/voucher.inc index bb165a27894..acfd23573a0 100644 --- a/src/etc/inc/voucher.inc +++ b/src/etc/inc/voucher.inc @@ -31,20 +31,29 @@ if (!function_exists('captiveportal_syslog')) { require_once("captiveportal.inc"); } -function xmlrpc_sync_voucher_details(&$syncip, &$port, &$username, &$password) { - global $config; - - if (!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) { - $syncip = $config['voucher'][$cpzone]['vouchersyncdbip']; - $port = $config['voucher'][$cpzone]['vouchersyncport']; - $username = $config['voucher'][$cpzone]['vouchersyncusername']; - $password = $config['voucher'][$cpzone]['vouchersyncpass']; +function captiveportal_xmlrpc_sync_get_details(&$syncip, &$port, &$username, &$password, $carp_loop = false) { + global $config, $cpzone; + + if (isset($config['captiveportal'][$cpzone]['enablebackwardsync']) && !$carp_loop + && captiveportal_ha_is_node_in_backup_mode($cpzone)) { + $syncip = $config['captiveportal'][$cpzone]['backwardsyncip']; + $username = $config['captiveportal'][$cpzone]['backwardsyncuser']; + $password = $config['captiveportal'][$cpzone]['backwardsyncpassword']; + $port = $config['system']['webgui']['port']; + if (empty($port)) { // if port is empty lets rely on the protocol selection + if ($config['system']['webgui']['protocol'] == "http") { + $port = "80"; + } else { + $port = "443"; + } + } return true; } if (!is_array($config['hasync']) || empty($config['hasync']['synchronizetoip']) || - $config['hasync']['synchronizecaptiveportal'] == "") { + $config['hasync']['synchronizecaptiveportal'] == "" || + $carp_loop == true) { return false; } @@ -69,100 +78,9 @@ function xmlrpc_sync_voucher_details(&$syncip, &$port, &$username, &$password) { return true; } -function xmlrpc_sync_voucher_expire($vouchers, $syncip, $port, $password, $username) { - global $cpzone; - require_once("xmlrpc_client.inc"); - - /* Construct code that is run on remote machine */ - $execcmd = <<setConnectionData($syncip, $port, $username, $password); - $resp = $rpc_client->xmlrpc_exec_php($execcmd); - if (empty($resp)) { - return false; - } - return $resp; -} - -function xmlrpc_sync_voucher_disconnect($dbent, $syncip, $port, $password, $username, $term_cause = 1, $stop_time = null) { - global $cpzone; - require_once("xmlrpc_client.inc"); - /* Construct code that is run on remote machine */ - $dbent_str = addslashes(serialize($dbent)); - $tmp_stop_time = (isset($stop_time)) ? $stop_time : "null"; - $execcmd = <<setConnectionData($syncip, $port, $username, $password); - $resp = $rpc_client->xmlrpc_exec_php($execcmd); - if (empty($resp)) { - return false; - } - return $resp; -} - -function xmlrpc_sync_used_voucher($voucher_received, $syncip, $port, $password, $username) { - global $config, $cpzone; - require_once("xmlrpc_client.inc"); - - /* Construct code that is run on remote machine */ - $execcmd = <<setConnectionData($syncip, $port, $username, $password); - $resp = $rpc_client->xmlrpc_exec_php($execcmd); - - if (!is_array($config['voucher'])) { - $config['voucher'] = array(); - } - - if (is_array($resp['voucher']['roll'])) { - $config['voucher'][$cpzone]['roll'] = $resp['voucher']['roll']; - write_config(sprintf(gettext('Captive Portal Voucher database synchronized with %1$s:%2$s'), $syncip, $port)); - voucher_configure_zone(true); - unset($resp['voucher']); - } else if (!isset($resp['timeleft'])) { - return 0; - } - - return $resp['timeleft']; -} - -function voucher_expire($voucher_received) { +function voucher_expire($voucher_received, $carp_loop = false) { global $g, $config, $cpzone, $cpzoneid; - // XMLRPC Call over to the master Voucher node - if (xmlrpc_sync_voucher_details($syncip, $syncport, - $vouchersyncusername, $syncpass)) { - xmlrpc_sync_voucher_expire($voucher_received, $syncip, - $syncport, $syncpass, $vouchersyncusername); - } - $voucherlck = lock("voucher{$cpzone}", LOCK_EX); // read rolls into assoc array with rollid as key and minutes as value @@ -241,10 +159,10 @@ function voucher_expire($voucher_received) { foreach ($active_vouchers as $roll => $active) { voucher_write_active_db($roll, $active); } - unset($active_vouchers); - - /* Trigger a sync of the vouchers on config */ + /* perform in-use vouchers expiration using check_reload_status */ send_event("service sync vouchers"); + } else { + $active_vouchers = array(); } // Write back the used DB's @@ -258,7 +176,6 @@ function voucher_expire($voucher_received) { voucher_write_used_db($roll, base64_encode($used)); } } - unset($bitstring); } unlock($voucherlck); @@ -268,6 +185,25 @@ function voucher_expire($voucher_received) { captiveportal_remove_entries($unsetindexes); } + // XMLRPC Call over to the other node + if (captiveportal_xmlrpc_sync_get_details($syncip, $syncport, + $syncuser, $syncpass, $carp_loop)) { + $rpc_client = new pfsense_xmlrpc_client(); + $rpc_client->setConnectionData($syncip, $syncport, $syncuser, $syncpass); + $rpc_client->set_noticefile("CaptivePortalVouchersSync"); + $arguments = array( + 'active_and_used_vouchers_bitmasks' => $bitstring, + 'active_vouchers' => $active_vouchers + ); + + $rpc_client->xmlrpc_method('captive_portal_sync', + array( + 'op' => 'write_vouchers', + 'zone' => $cpzone, + 'arguments' => base64_encode(serialize($arguments)) + ) + ); + } return true; } @@ -278,20 +214,13 @@ function voucher_expire($voucher_received) { * If $test is set, simply test the voucher. Don't change anything * but return a more verbose error and result message back */ -function voucher_auth($voucher_received, $test = 0) { +function voucher_auth($voucher_received, $test = 0, $carp_loop = false) { global $g, $config, $cpzone, $dbc; if (!isset($config['voucher'][$cpzone]['enable'])) { return 0; } - // XMLRPC Call over to the master Voucher node - if (xmlrpc_sync_voucher_details($syncip, $syncport, - $vouchersyncusername, $syncpass)) { - $remote_time_used = xmlrpc_sync_used_voucher($voucher_received, - $syncip, $syncport, $syncpass, $vouchersyncusername); - } - $voucherlck = lock("voucher{$cpzone}", LOCK_EX); // read rolls into assoc array with rollid as key and minutes as value @@ -404,15 +333,6 @@ function voucher_auth($voucher_received, $test = 0) { return $total_minutes; // well, at least one voucher had errors. Say NO ACCESS } - // If we did a XMLRPC sync earlier check the timeleft - if (!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) { - if (!is_null($remote_time_used)) { - $total_minutes = $remote_time_used; - } else if ($remote_time_used < $total_minutes) { - $total_minutes -= $remote_time_used; - } - } - // All given vouchers were valid and this isn't simply a test. // Write back the used DB's if (is_array($bitstring)) { @@ -441,7 +361,29 @@ function voucher_auth($voucher_received, $test = 0) { $active_vouchers[$first_voucher_roll][$first_voucher] = "$timestamp,$minutes"; voucher_write_active_db($first_voucher_roll, $active_vouchers[$first_voucher_roll]); - /* Trigger a sync of the vouchers on config */ + // XMLRPC Call over to the other node + if (captiveportal_xmlrpc_sync_get_details($syncip, $syncport, + $syncuser, $syncpass, $carp_loop)) { + $rpc_client = new pfsense_xmlrpc_client(); + $rpc_client->setConnectionData($syncip, $syncport, $syncuser, $syncpass); + $rpc_client->set_noticefile("CaptivePortalVouchersSync"); + $arguments = array( + 'active_and_used_vouchers_bitmasks' => $bitstring, + 'active_vouchers' => array( + $first_voucher_roll => $active_vouchers[$first_voucher_roll] + ) + ); + + $rpc_client->xmlrpc_method('captive_portal_sync', + array( + 'op' => 'write_vouchers', + 'zone' => $cpzone, + 'arguments' => base64_encode(serialize($arguments)) + ) + ); + } + + /* perform in-use vouchers expiration using check_reload_status */ send_event("service sync vouchers"); unlock($voucherlck); @@ -449,7 +391,7 @@ function voucher_auth($voucher_received, $test = 0) { return $total_minutes; } -function voucher_configure($sync = false) { +function voucher_configure() { global $config, $g, $cpzone; if (is_array($config['voucher'])) { @@ -458,7 +400,7 @@ function voucher_configure($sync = false) { echo gettext("Enabling voucher support... "); } $cpzone = $voucherzone; - $error = voucher_configure_zone($sync); + $error = voucher_configure_zone(); if (platform_booting()) { if ($error) { echo "error\n"; @@ -470,17 +412,13 @@ function voucher_configure($sync = false) { } } -function voucher_configure_zone($sync = false) { +function voucher_configure_zone() { global $config, $g, $cpzone; if (!isset($config['voucher'][$cpzone]['enable'])) { return 0; } - if ($sync == true) { - captiveportal_syslog("Writing voucher db from sync data..."); - } - $voucherlck = lock("voucher{$cpzone}", LOCK_EX); /* write public key used to verify vouchers */ @@ -507,38 +445,6 @@ function voucher_configure_zone($sync = false) { @chmod("{$g['varetc_path']}/voucher_{$cpzone}.cfg", 0600); unlock($voucherlck); - if ((platform_booting() || $sync == true) && is_array($config['voucher'][$cpzone]['roll'])) { - - $voucherlck = lock("voucher{$cpzone}", LOCK_EX); - - // create active and used DB per roll on ramdisk from config - foreach ($config['voucher'][$cpzone]['roll'] as $rollent) { - - $roll = $rollent['number']; - $len = ($rollent['count'] >> 3) + 1; - if (strlen(base64_decode($rollent['used'])) != $len) { - $rollent['used'] = base64_encode(str_repeat("\000", $len)); - } - voucher_write_used_db($roll, $rollent['used']); - $minutes = $rollent['minutes']; - $active_vouchers = array(); - $a_active = &$rollent['active']; - if (is_array($a_active)) { - foreach ($a_active as $activent) { - $voucher = $activent['voucher']; - $timestamp = $activent['timestamp']; - $minutes = $activent['minutes']; - // its tempting to check for expired timestamps, but during - // bootup, we most likely don't have the correct time. - $active_vouchers[$voucher] = "$timestamp,$minutes"; - } - } - voucher_write_active_db($roll, $active_vouchers); - } - - unlock($voucherlck); - } - return 0; } @@ -581,11 +487,8 @@ function voucher_read_active_db($roll) { } } fclose($fd); - if ($dirty) { // if we found expired entries, lets save our snapshot + if ($dirty) { // if we found expired entries, lets remove them voucher_write_active_db($roll, $active); - - /* Trigger a sync of the vouchers on config */ - send_event("service sync vouchers"); } } } @@ -660,59 +563,22 @@ function voucher_log($priority, $message) { closelog(); } -/* Save active and used voucher DB into XML config and write it to flash +/* Perform natural expiration of vouchers * Called during reboot -> system_reboot_cleanup() and every active voucher change */ function voucher_save_db_to_config() { - global $config, $g, $cpzone; + global $config, $g; if (is_array($config['voucher'])) { - foreach ($config['voucher'] as $voucherzone => $vcfg) { - $cpzone = $voucherzone; - voucher_save_db_to_config_zone(); - } - } -} - -function voucher_save_db_to_config_zone() { - global $config, $g, $cpzone; - - if (!isset($config['voucher'][$cpzone]['enable'])) { - return; // no vouchers or don't want to save DB's - } - - if (!is_array($config['voucher'][$cpzone]['roll'])) { - return; - } - - $voucherlck = lock("voucher{$cpzone}", LOCK_EX); - - // walk all active rolls and save runtime DB's to flash - $a_roll = &$config['voucher'][$cpzone]['roll']; - while (list($key, $value) = each($a_roll)) { - $rollent = &$a_roll[$key]; - $roll = $rollent['number']; - $bitmask = voucher_read_used_db($roll); - $rollent['used'] = base64_encode($bitmask); - $active_vouchers = voucher_read_active_db($roll); - $db = array(); - $dbi = 1; - foreach ($active_vouchers as $voucher => $line) { - list($timestamp, $minutes) = explode(",", $line); - $activent['voucher'] = $voucher; - $activent['timestamp'] = $timestamp; - $activent['minutes'] = $minutes; - $db["v{$dbi}"] = $activent; - $dbi++; + foreach ($config['voucher'] as $zone => $vcfg) { + if (isset($config['voucher'][$zone]['enable']) && is_array($config['voucher'][$zone]['roll'])) { + foreach ($config['voucher'][$zone]['roll'] as $key => $rollent) { + // read_active_db will remove expired vouchers that are still active + voucher_read_active_db($rollent['number']); + } + } } - $rollent['active'] = $db; - unset($active_vouchers); } - - unlock($voucherlck); - - write_config(gettext("Syncing vouchers")); - return; } ?> diff --git a/src/etc/inc/xmlrpc_client.inc b/src/etc/inc/xmlrpc_client.inc index 55c297c19e0..c1eb1394dd4 100644 --- a/src/etc/inc/xmlrpc_client.inc +++ b/src/etc/inc/xmlrpc_client.inc @@ -89,7 +89,6 @@ class pfsense_xmlrpc_client { while ($numberofruns < 2) { $numberofruns++; - log_error(sprintf(gettext("Beginning XMLRPC sync data to %s."), $this->logurl)); $cli = XML_RPC2_Client::create($this->url, $options); if (!is_object($cli)) { $this->error = sprintf(gettext("A communications error occurred while attempting XMLRPC sync with %s (pfsense.%s)."), $this->log, $method); @@ -139,7 +138,6 @@ class pfsense_xmlrpc_client { file_notice($this->filenotice, $this->error, "Settings Sync", ""); continue; } - log_error(sprintf(gettext("XMLRPC reload data success with %s (pfsense.{$method})."), $this->logurl)); return $resp; } return null; diff --git a/src/etc/rc.carpmaster b/src/etc/rc.carpmaster index 0f79d9f7c96..787edb3aef9 100755 --- a/src/etc/rc.carpmaster +++ b/src/etc/rc.carpmaster @@ -27,6 +27,7 @@ require_once("config.inc"); require_once("notices.inc"); require_once("openvpn.inc"); require_once("interfaces.inc"); +require_once("captiveportal.inc"); if (isset($_GET['interface'])) { $argument = $_GET['interface']; @@ -119,6 +120,68 @@ if (isset($config['dhcpdv6']) && is_array($config['dhcpdv6'])) { } } +/* Reconfigure captive portal when necessary : + If we are the primary node, and we are switching back from backup to master : Get user list from the backup node */ +if (!empty($config['captiveportal']) && is_array($config['hasync']) && !empty($config['hasync']['synchronizetoip']) && + $config['hasync']['synchronizecaptiveportal'] != "") { + if (empty($config['hasync']['username'])) { + $xmlrpc_username = "admin"; + } else { + $xmlrpc_username = $config['hasync']['username']; + } + $xmlrpc_port = $config['system']['webgui']['port']; + if (empty($port)) { + if ($config['system']['webgui']['protocol'] == "http") { + $xmlrpc_port = "80"; + } else { + $xmlrpc_port = "443"; + } + } + + foreach ($config['captiveportal'] as $cpzone=>$cp) { + $rpc_client = new pfsense_xmlrpc_client(); + $rpc_client->setConnectionData($config['hasync']['synchronizetoip'], $xmlrpc_port, $xmlrpc_username, $config['hasync']['password']); + $resp = $rpc_client->xmlrpc_method('captive_portal_sync', array('op' => 'get_databases', 'zone' => $cpzone)); + + if (is_array($resp)) { // $rep will be an array only if the communication was successful + // Contains array of connected users (will be stored in SQLite DB) + $connected_users = unserialize(base64_decode($resp['connected_users'])); + // Contains array of active vouchers (will be stored in active vouchers db) + $active_vouchers = unserialize(base64_decode($resp['active_vouchers'])); + // Contain bitmask of both in use and expired vouchers (will be stored in "used vouchers" db) + $expired_vouchers = unserialize(base64_decode($resp['expired_vouchers'])); + + $cpdb = captiveportal_read_db(); + $unsetindexes = array_column($cpdb, 5); + if (!empty($unsetindexes)) { + captiveportal_remove_entries($unsetindexes, true); // true: prevent carp loop + } + captiveportal_free_dnrules(); + + foreach ($connected_users as $id => $user) { + $pipeno = captiveportal_get_next_dn_ruleno('auth'); + $attributes = array(); + $attributes['allow_time'] = $user['allow_time']; + $attributes['session_timeout'] = $user['session_timeout']; + $attributes['idle_timeout'] = $user['idle_timeout']; + $attributes['session_terminate_time'] = $user['session_terminate_time']; + $attributes['interim_interval'] = $user['interim_interval']; + $attributes['maxbytes'] = $user['traffic_quota']; + + portal_allow($user['ip'], $user['mac'], $user['username'], base64_decode($user['bpassword']), null, + $attributes, $pipeno, $user['authmethod'], $user['context'], $user['sessionid'], true); + } + foreach ($expired_vouchers as $roll => $vdb) { + voucher_write_used_db($roll, $vdb); + } + foreach ($active_vouchers as $roll => $vouchers) { + voucher_write_active_db($roll, $vouchers); + } + } + captiveportal_syslog(sprintf(gettext('Connected users and used vouchers have been synchronized from %1$s'), $config['hasync']['synchronizetoip'])); + } +} +openlog("", LOG_PID, LOG_LOCAL0); $pluginparams = array(); $pluginparams['type'] = 'carp'; $pluginparams['event'] = 'rc.carpmaster'; diff --git a/src/etc/rc.filter_synchronize b/src/etc/rc.filter_synchronize index 77ce1f14b14..bb10f2134e6 100755 --- a/src/etc/rc.filter_synchronize +++ b/src/etc/rc.filter_synchronize @@ -99,7 +99,6 @@ function carp_check_version() { $rpc_client = new pfsense_xmlrpc_client(); $resp = $rpc_client->xmlrpc_method('host_firmware_version'); - log_error(sprintf(gettext("XMLRPC versioncheck: ").$resp['config_version'] ." -- ". $config['version'])); if (!isset($resp['config_version'])) { update_filter_reload_status("The {$g['product_name']} software configuration version of the other member could not be determined. Skipping synchronization to avoid causing a problem!"); log_error("The {$g['product_name']} software configuration version of the other member could not be determined. Skipping synchronization to avoid causing a problem!"); @@ -355,7 +354,10 @@ if (is_array($config['hasync'])) { } $sections[] = 'schedules'; } - if ($hasync['synchronizecaptiveportal'] != "" and is_array($config['captiveportal'])) { + if ($hasync['synchronizecaptiveportal'] != "") { + if (!is_array($config['captiveportal'])) { + $config['captiveportal'] = array(); + } $sections[] = 'captiveportal'; } if ($hasync['synchronizecaptiveportal'] != "" and is_array($config['voucher'])) { diff --git a/src/etc/rc.newwanip b/src/etc/rc.newwanip index 3bc6163c42a..8b096fb2a61 100755 --- a/src/etc/rc.newwanip +++ b/src/etc/rc.newwanip @@ -53,6 +53,7 @@ if (isset($_GET['interface'])) { $argument = str_replace("\n", "", $argv[1]); } +openlog("", LOG_PID, LOG_LOCAL0); log_error("rc.newwanip: Info: starting on {$argument}."); if (empty($argument)) { diff --git a/src/etc/rc.newwanipv6 b/src/etc/rc.newwanipv6 index 97d8e8ec027..0da81f0c3a5 100755 --- a/src/etc/rc.newwanipv6 +++ b/src/etc/rc.newwanipv6 @@ -54,6 +54,7 @@ if (isset($_GET['interface'])) { $argument = trim($argv[1], " \n\t"); } +openlog("", LOG_PID, LOG_LOCAL0); log_error("rc.newwanipv6: Info: starting on {$argument}."); if (empty($argument)) { diff --git a/src/usr/local/captiveportal/index.php b/src/usr/local/captiveportal/index.php index 2bff1175797..fd87b62e533 100644 --- a/src/usr/local/captiveportal/index.php +++ b/src/usr/local/captiveportal/index.php @@ -155,7 +155,7 @@ } elseif (portal_consume_passthrough_credit($clientmac)) { /* allow the client through if it had a pass-through credit for its MAC */ captiveportal_logportalauth("unauthenticated", $clientmac, $clientip, "ACCEPT"); - portal_allow($clientip, $clientmac, "unauthenticated"); + portal_allow($clientip, $clientmac, "unauthenticated", null, $redirurl); } elseif (isset($config['voucher'][$cpzone]['enable']) && $_POST['accept'] && $_POST['auth_voucher']) { $voucher = trim($_POST['auth_voucher']); @@ -170,7 +170,7 @@ 'voucher' => 1, 'session_timeout' => $timecredit*60, 'session_terminate_time' => 0); - if (portal_allow($clientip, $clientmac, $voucher, null, $attr, null, 'voucher', 'voucher')) { + if (portal_allow($clientip, $clientmac, $voucher, null, $redirurl, $attr, null, 'voucher', 'voucher')) { // YES: user is good for $timecredit minutes. captiveportal_logportalauth($voucher, $clientmac, $clientip, "Voucher login good for $timecredit min."); } else { @@ -219,7 +219,7 @@ if ($auth_result['result']) { captiveportal_logportalauth($user, $clientmac, $clientip, $auth_result['login_status']); - portal_allow($clientip, $clientmac, $user, $passwd, $auth_result['attributes'], $pipeno, $auth_result['auth_method'], $context); + portal_allow($clientip, $clientmac, $user, $passwd, $redirurl, $auth_result['attributes'], $pipeno, $auth_result['auth_method'], $context); } else { captiveportal_free_dn_ruleno($pipeno); diff --git a/src/usr/local/www/services_captiveportal.php b/src/usr/local/www/services_captiveportal.php index 5396791693d..ca27ea83d5f 100644 --- a/src/usr/local/www/services_captiveportal.php +++ b/src/usr/local/www/services_captiveportal.php @@ -495,6 +495,7 @@ function build_authserver_list() { $tab_array[] = array(gettext("Allowed IP Addresses"), false, "services_captiveportal_ip.php?zone={$cpzone}"); $tab_array[] = array(gettext("Allowed Hostnames"), false, "services_captiveportal_hostname.php?zone={$cpzone}"); $tab_array[] = array(gettext("Vouchers"), false, "services_captiveportal_vouchers.php?zone={$cpzone}"); +$tab_array[] = array(gettext("High Availability"), false, "services_captiveportal_hasync.php?zone={$cpzone}"); $tab_array[] = array(gettext("File Manager"), false, "services_captiveportal_filemanager.php?zone={$cpzone}"); display_top_tabs($tab_array, true); @@ -610,12 +611,21 @@ function build_authserver_list() { $pconfig['blockedmacsurl'] ))->setHelp('Blocked MAC addresses will be redirected to this URL when attempting access.'); -$section->addInput(new Form_Checkbox( +if (captiveportal_xmlrpc_sync_get_details($tmpsyncip, $tmpport, $tmpusername, $tmppassword)) { + $section->addInput(new Form_Checkbox( + 'preservedb_disabled', + 'Preserve users database', + 'Preserve connected users across reboot', + 'yes' + ))->setDisabled()->setHelp("If enabled, connected users won't be disconnected during a pfSense reboot. This setting is not editable because High Availability is enabled."); +} else { + $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."); + ))->setHelp("If enabled, connected users won't be disconnected during a pfSense reboot."); +} $section->addInput(new Form_Checkbox( 'noconcurrentlogins', @@ -1163,6 +1173,7 @@ function hideGeneral(hide) { hideInput('redirurl', hide); hideInput('blockedmacsurl', hide); hideCheckbox('preservedb', hide); + hideCheckbox('preservedb_disabled', hide); hideCheckbox('noconcurrentlogins', hide); hideCheckbox('nomacfilter', hide); hideCheckbox('passthrumacadd', hide); diff --git a/src/usr/local/www/services_captiveportal_filemanager.php b/src/usr/local/www/services_captiveportal_filemanager.php index 94f023cf82b..2042f5970d3 100644 --- a/src/usr/local/www/services_captiveportal_filemanager.php +++ b/src/usr/local/www/services_captiveportal_filemanager.php @@ -147,6 +147,7 @@ function cpelements_sort() { $tab_array[] = array(gettext("Allowed IP Addresses"), false, "services_captiveportal_ip.php?zone={$cpzone}"); $tab_array[] = array(gettext("Allowed Hostnames"), false, "services_captiveportal_hostname.php?zone={$cpzone}"); $tab_array[] = array(gettext("Vouchers"), false, "services_captiveportal_vouchers.php?zone={$cpzone}"); +$tab_array[] = array(gettext("High Availability"), false, "services_captiveportal_hasync.php?zone={$cpzone}"); $tab_array[] = array(gettext("File Manager"), true, "services_captiveportal_filemanager.php?zone={$cpzone}"); display_top_tabs($tab_array, true); diff --git a/src/usr/local/www/services_captiveportal_hasync.php b/src/usr/local/www/services_captiveportal_hasync.php new file mode 100644 index 00000000000..3dbacc11e5d --- /dev/null +++ b/src/usr/local/www/services_captiveportal_hasync.php @@ -0,0 +1,248 @@ +setConnectionData($newcp['backwardsyncip'], $port, $newcp['backwardsyncuser'], $newcp['backwardsyncpassword']); + + $resp = $rpc_client->xmlrpc_method('captive_portal_sync', array('op' => 'get_databases', 'zone' => $cpzone)); + + if (!is_array($resp)) { + if ($rpc_client->get_error() != '') { + $input_errors[] = $rpc_client->get_error(); + } else { + $input_errors[] = gettext('Error during communication with pfSense primary node.'); + } + } else { + // Contains array of connected users (will be stored in SQLite DB) + $connected_users = unserialize(base64_decode($resp['connected_users'])); + // Contains array of active vouchers (will be stored in active vouchers db) + $active_vouchers = unserialize(base64_decode($resp['active_vouchers'])); + // Contain bitmask of both in use and expired vouchers (will be stored in "used vouchers" db) + $expired_vouchers = unserialize(base64_decode($resp['expired_vouchers'])); + + foreach ($connected_users as $id => $user) { + $pipeno = captiveportal_get_next_dn_ruleno('auth'); + $attributes = array(); + $attributes['allow_time'] = $user['allow_time']; + $attributes['session_timeout'] = $user['session_timeout']; + $attributes['idle_timeout'] = $user['idle_timeout']; + $attributes['session_terminate_time'] = $user['session_terminate_time']; + $attributes['interim_interval'] = $user['interim_interval']; + $attributes['maxbytes'] = $user['traffic_quota']; + + portal_allow($user['ip'], $user['mac'], $user['username'], base64_decode($user['bpassword']), null, + $attributes, $pipeno, $user['authmethod'], $user['context'], $user['sessionid'], true); + } + foreach ($expired_vouchers as $roll => $vdb) { + voucher_write_used_db($roll, $vdb); + } + foreach ($active_vouchers as $roll => $vouchers) { + voucher_write_active_db($roll, $vouchers); + } + } + if (!$input_errors) { + $savemsg = sprintf(gettext('Connected users and used vouchers are now synchronized with %1$s'), $newcp['backwardsyncip']); + } + } + if (!$input_errors) { + $config['captiveportal'][$cpzone] = $newcp; + write_config('Updated captiveportal backward HA settings'); + } + } +} + +include("head.inc"); + +if ($input_errors) { + print_input_errors($input_errors); +} +if ($savemsg) { + print_info_box($savemsg, 'success'); +} + +$tab_array = array(); +$tab_array[] = array(gettext("Configuration"), false, "services_captiveportal.php?zone={$cpzone}"); +$tab_array[] = array(gettext("MACs"), false, "services_captiveportal_mac.php?zone={$cpzone}"); +$tab_array[] = array(gettext("Allowed IP Addresses"), false, "services_captiveportal_ip.php?zone={$cpzone}"); +$tab_array[] = array(gettext("Allowed Hostnames"), false, "services_captiveportal_hostname.php?zone={$cpzone}"); +$tab_array[] = array(gettext("Vouchers"), false, "services_captiveportal_vouchers.php?zone={$cpzone}"); +$tab_array[] = array(gettext("High Availability"), true, "services_captiveportal_hasync.php?zone={$cpzone}"); +$tab_array[] = array(gettext("File Manager"), false, "services_captiveportal_filemanager.php?zone={$cpzone}"); +display_top_tabs($tab_array, true); + +$form = new Form(); + +$section = new Form_Section('Secondary to Primary node Synchronization'); + +$section->addClass('rolledit'); + +$section->addInput(new Form_Checkbox( + 'enablebackwardsync', + 'Enable', + 'Enable backward High Availability Sync for connected users and used vouchers', + $pconfig['enablebackwardsync'] + ))->setHelp('The XMLRPC sync provided by pfSense in High Availability settings only synchronize a secondary node to its primary node.'. + 'This checkbox enable a backward sync from the secondary to the primary, in order to have a bi-directional synchronization.%1$s'. + 'The purpose of this feature is to keep connected users synchronized between servers even if a node does down, thus providing redundancy for the captive portal zone.%1$s%1$s'. + 'Important: these settings should be set on the secondary node only ! Do not update these settings if this pfSense is the primary node !', '
'); + +$section->addInput(new Form_IpAddress( + 'backwardsyncip', + 'Primary node IP', + $pconfig['backwardsyncip'] +))->setHelp('Please fill here the IP address of the primary node.%1$s', '
'); + +$section->addInput(new Form_Input( + 'backwardsyncuser', + 'Primary node username', + 'text', + $pconfig['backwardsyncuser'] +))->setHelp('Please enter the username of the primary node that the secondary node will use for backward sync. This could be any pfSense user on the primary node with "System - HA node sync" privileges.'); + +$section->addPassword(new Form_Input( + 'backwardsyncpassword', + 'Primary node password', + 'password', + $pconfig['backwardsyncpassword'] +))->setHelp('Please enter the password associated to this user.'); + +$form->addGlobal(new Form_Input( + 'zone', + null, + 'hidden', + $cpzone +)); + +$form->add($section); +print($form); + +print_info_box(sprintf(gettext('It is recommended to configure XMLRPC sync on the primary node before configuring backward synchronization for captive portal.'), $cpzone), 'info'); +?> + + + + diff --git a/src/usr/local/www/services_captiveportal_ip.php b/src/usr/local/www/services_captiveportal_ip.php index b3e52a66787..cecf75ff491 100644 --- a/src/usr/local/www/services_captiveportal_ip.php +++ b/src/usr/local/www/services_captiveportal_ip.php @@ -96,6 +96,7 @@ $tab_array[] = array(gettext("Allowed IP Addresses"), true, "services_captiveportal_ip.php?zone={$cpzone}"); $tab_array[] = array(gettext("Allowed Hostnames"), false, "services_captiveportal_hostname.php?zone={$cpzone}"); $tab_array[] = array(gettext("Vouchers"), false, "services_captiveportal_vouchers.php?zone={$cpzone}"); +$tab_array[] = array(gettext("High Availability"), false, "services_captiveportal_hasync.php?zone={$cpzone}"); $tab_array[] = array(gettext("File Manager"), false, "services_captiveportal_filemanager.php?zone={$cpzone}"); display_top_tabs($tab_array, true); diff --git a/src/usr/local/www/services_captiveportal_mac.php b/src/usr/local/www/services_captiveportal_mac.php index c799a26f955..37a504fd85c 100644 --- a/src/usr/local/www/services_captiveportal_mac.php +++ b/src/usr/local/www/services_captiveportal_mac.php @@ -152,6 +152,7 @@ $tab_array[] = array(gettext("Allowed IP Addresses"), false, "services_captiveportal_ip.php?zone={$cpzone}"); $tab_array[] = array(gettext("Allowed Hostnames"), false, "services_captiveportal_hostname.php?zone={$cpzone}"); $tab_array[] = array(gettext("Vouchers"), false, "services_captiveportal_vouchers.php?zone={$cpzone}"); +$tab_array[] = array(gettext("High Availability"), false, "services_captiveportal_hasync.php?zone={$cpzone}"); $tab_array[] = array(gettext("File Manager"), false, "services_captiveportal_filemanager.php?zone={$cpzone}"); display_top_tabs($tab_array, true); ?> diff --git a/src/usr/local/www/services_captiveportal_vouchers.php b/src/usr/local/www/services_captiveportal_vouchers.php index 6e1cf886c04..feefd7e20ce 100644 --- a/src/usr/local/www/services_captiveportal_vouchers.php +++ b/src/usr/local/www/services_captiveportal_vouchers.php @@ -139,7 +139,7 @@ unset($a_roll[$id]); voucher_unlink_db($roll); unlock($voucherlck); - write_config(); + write_config("Deleted voucher roll"); } header("Location: services_captiveportal_vouchers.php?zone={$cpzone}"); exit; @@ -191,10 +191,6 @@ $pconfig['privatekey'] = base64_decode($config['voucher'][$cpzone]['privatekey']); $pconfig['msgnoaccess'] = $config['voucher'][$cpzone]['descrmsgnoaccess']; $pconfig['msgexpired'] = $config['voucher'][$cpzone]['descrmsgexpired']; -$pconfig['vouchersyncdbip'] = $config['voucher'][$cpzone]['vouchersyncdbip']; -$pconfig['vouchersyncport'] = $config['voucher'][$cpzone]['vouchersyncport']; -$pconfig['vouchersyncpass'] = $config['voucher'][$cpzone]['vouchersyncpass']; -$pconfig['vouchersyncusername'] = $config['voucher'][$cpzone]['vouchersyncusername']; if ($_POST['save']) { unset($input_errors); @@ -208,49 +204,36 @@ /* input validation */ if ($_POST['enable'] == "yes") { - if (!$_POST['vouchersyncusername']) { - $reqdfields = explode(" ", "charset rollbits ticketbits checksumbits publickey magic"); - $reqdfieldsn = array(gettext("charset"), gettext("rollbits"), gettext("ticketbits"), gettext("checksumbits"), gettext("publickey"), gettext("magic")); - } else { - $reqdfields = explode(" ", "vouchersyncdbip vouchersyncport vouchersyncpass vouchersyncusername"); - $reqdfieldsn = array(gettext("Synchronize Voucher Database IP"), gettext("Sync port"), gettext("Sync password"), gettext("Sync username")); - } + $reqdfields = explode(" ", "charset rollbits ticketbits checksumbits publickey magic"); + $reqdfieldsn = array(gettext("charset"), gettext("rollbits"), gettext("ticketbits"), gettext("checksumbits"), gettext("publickey"), gettext("magic")); do_input_validation($_POST, $reqdfields, $reqdfieldsn, $input_errors); } - if (!$_POST['vouchersyncusername']) { - // Check for form errors - if ($_POST['charset'] && (strlen($_POST['charset']) < 2)) { - $input_errors[] = gettext("Need at least 2 characters to create vouchers."); - } - if ($_POST['charset'] && (strpos($_POST['charset'], "\"") > 0)) { - $input_errors[] = gettext("Double quotes aren't allowed."); - } - if ($_POST['charset'] && (strpos($_POST['charset'], ",") > 0)) { - $input_errors[] = gettext("',' aren't allowed."); - } - if ($_POST['rollbits'] && (!is_numeric($_POST['rollbits']) || ($_POST['rollbits'] < 1) || ($_POST['rollbits'] > 31))) { - $input_errors[] = gettext("# of Bits to store Roll Id needs to be between 1..31."); - } - if ($_POST['ticketbits'] && (!is_numeric($_POST['ticketbits']) || ($_POST['ticketbits'] < 1) || ($_POST['ticketbits'] > 16))) { - $input_errors[] = gettext("# of Bits to store Ticket Id needs to be between 1..16."); - } - if ($_POST['checksumbits'] && (!is_numeric($_POST['checksumbits']) || ($_POST['checksumbits'] < 1) || ($_POST['checksumbits'] > 31))) { - $input_errors[] = gettext("# of Bits to store checksum needs to be between 1..31."); - } - if ($_POST['publickey'] && (!strstr($_POST['publickey'], "BEGIN PUBLIC KEY"))) { - $input_errors[] = gettext("This doesn't look like an RSA Public key."); - } - if ($_POST['privatekey'] && (!strstr($_POST['privatekey'], "BEGIN RSA PRIVATE KEY"))) { - $input_errors[] = gettext("This doesn't look like an RSA Private key."); - } - if ($_POST['vouchersyncdbip'] && (is_ipaddr_configured($_POST['vouchersyncdbip']))) { - $input_errors[] = gettext("The voucher database cannot be sync'd to this host (itself)."); - } - if ($_POST['vouchersyncpass'] != $_POST['vouchersyncpass_confirm']) { - $input_errors[] = gettext("Password and confirmed password must match."); - } + // Check for form errors + if ($_POST['charset'] && (strlen($_POST['charset']) < 2)) { + $input_errors[] = gettext("Need at least 2 characters to create vouchers."); + } + if ($_POST['charset'] && (strpos($_POST['charset'], "\"") > 0)) { + $input_errors[] = gettext("Double quotes aren't allowed."); + } + if ($_POST['charset'] && (strpos($_POST['charset'], ",") > 0)) { + $input_errors[] = gettext("',' aren't allowed."); + } + if ($_POST['rollbits'] && (!is_numeric($_POST['rollbits']) || ($_POST['rollbits'] < 1) || ($_POST['rollbits'] > 31))) { + $input_errors[] = gettext("# of Bits to store Roll Id needs to be between 1..31."); + } + if ($_POST['ticketbits'] && (!is_numeric($_POST['ticketbits']) || ($_POST['ticketbits'] < 1) || ($_POST['ticketbits'] > 16))) { + $input_errors[] = gettext("# of Bits to store Ticket Id needs to be between 1..16."); + } + if ($_POST['checksumbits'] && (!is_numeric($_POST['checksumbits']) || ($_POST['checksumbits'] < 1) || ($_POST['checksumbits'] > 31))) { + $input_errors[] = gettext("# of Bits to store checksum needs to be between 1..31."); + } + if ($_POST['publickey'] && (!strstr($_POST['publickey'], "BEGIN PUBLIC KEY"))) { + $input_errors[] = gettext("This doesn't look like an RSA Public key."); + } + if ($_POST['privatekey'] && (!strstr($_POST['privatekey'], "BEGIN RSA PRIVATE KEY"))) { + $input_errors[] = gettext("This doesn't look like an RSA Private key."); } if (!$input_errors) { @@ -264,102 +247,21 @@ } else { unset($newvoucher['enable']); } - if (empty($_POST['vouchersyncusername'])) { - unset($newvoucher['vouchersyncdbip']); - unset($newvoucher['vouchersyncport']); - unset($newvoucher['vouchersyncusername']); - unset($newvoucher['vouchersyncpass']); - $newvoucher['charset'] = $_POST['charset']; - $newvoucher['rollbits'] = $_POST['rollbits']; - $newvoucher['ticketbits'] = $_POST['ticketbits']; - $newvoucher['checksumbits'] = $_POST['checksumbits']; - $newvoucher['magic'] = $_POST['magic']; - $newvoucher['exponent'] = $_POST['exponent']; - $newvoucher['publickey'] = base64_encode($_POST['publickey']); - $newvoucher['privatekey'] = base64_encode($_POST['privatekey']); - $newvoucher['descrmsgnoaccess'] = $_POST['msgnoaccess']; - $newvoucher['descrmsgexpired'] = $_POST['msgexpired']; - $config['voucher'][$cpzone] = $newvoucher; - write_config(); - voucher_configure_zone(); - // Refresh captiveportal login to show voucher changes - captiveportal_configure_zone($config['captiveportal'][$cpzone]); - } else { - $newvoucher['vouchersyncdbip'] = $_POST['vouchersyncdbip']; - $newvoucher['vouchersyncport'] = $_POST['vouchersyncport']; - $newvoucher['vouchersyncusername'] = $_POST['vouchersyncusername']; - if ($_POST['vouchersyncpass'] != DMYPWD ) { - $newvoucher['vouchersyncpass'] = $_POST['vouchersyncpass']; - } else { - $newvoucher['vouchersyncpass'] = $config['voucher'][$cpzone]['vouchersyncpass']; - } - if ($newvoucher['vouchersyncpass'] && $newvoucher['vouchersyncusername'] && - $newvoucher['vouchersyncport'] && $newvoucher['vouchersyncdbip']) { - - // Synchronize the voucher DB from the master node - $execcmd = <<setConnectionData( - $newvoucher['vouchersyncdbip'], $newvoucher['vouchersyncport'], - $newvoucher['vouchersyncusername'], $newvoucher['vouchersyncpass']); - $rpc_client->set_noticefile("CaptivePortalVoucherSync"); - $resp = $rpc_client->xmlrpc_exec_php($execcmd); - if ($resp == null) { - $input_errors[] = $rpc_client->get_error(); - } - - if (!$input_errors) { - if (is_array($resp)) { - log_error(sprintf(gettext("The Captive Portal voucher database has been synchronized with %s (pfsense.exec_php)."), $url)); - // If we received back the voucher roll and other information then store it. - if ($resp['voucher']['roll']) { - $newvoucher['roll'] = $resp['voucher']['roll']; - } - if ($resp['voucher']['rollbits']) { - $newvoucher['rollbits'] = $resp['voucher']['rollbits']; - } - if ($resp['voucher']['ticketbits']) { - $newvoucher['ticketbits'] = $resp['voucher']['ticketbits']; - } - if ($resp['voucher']['checksumbits']) { - $newvoucher['checksumbits'] = $resp['voucher']['checksumbits']; - } - if ($resp['voucher']['magic']) { - $newvoucher['magic'] = $resp['voucher']['magic']; - } - if ($resp['voucher']['exponent']) { - $newvoucher['exponent'] = $resp['voucher']['exponent']; - } - if ($resp['voucher']['publickey']) { - $newvoucher['publickey'] = $resp['voucher']['publickey']; - } - if ($resp['voucher']['privatekey']) { - $newvoucher['privatekey'] = $resp['voucher']['privatekey']; - } - if ($resp['voucher']['descrmsgnoaccess']) { - $newvoucher['descrmsgnoaccess'] = $resp['voucher']['descrmsgnoaccess']; - } - if ($resp['voucher']['descrmsgexpired']) { - $newvoucher['descrmsgexpired'] = $resp['voucher']['descrmsgexpired']; - } - $savemsg = sprintf(gettext('Voucher database has been synchronized from %1$s'), $url); - - $config['voucher'][$cpzone] = $newvoucher; - write_config(); - voucher_configure_zone(true); - // Refresh captiveportal login to show voucher changes - captiveportal_configure_zone($config['captiveportal'][$cpzone]); - } - } - } - } + $newvoucher['charset'] = $_POST['charset']; + $newvoucher['rollbits'] = $_POST['rollbits']; + $newvoucher['ticketbits'] = $_POST['ticketbits']; + $newvoucher['checksumbits'] = $_POST['checksumbits']; + $newvoucher['magic'] = $_POST['magic']; + $newvoucher['exponent'] = $_POST['exponent']; + $newvoucher['publickey'] = base64_encode($_POST['publickey']); + $newvoucher['privatekey'] = base64_encode($_POST['privatekey']); + $newvoucher['descrmsgnoaccess'] = $_POST['msgnoaccess']; + $newvoucher['descrmsgexpired'] = $_POST['msgexpired']; + $config['voucher'][$cpzone] = $newvoucher; + write_config('Updated vouchers settings'); + voucher_configure_zone(); + // Refresh captiveportal login to show voucher changes + captiveportal_configure_zone($config['captiveportal'][$cpzone]); if (!$input_errors) { header("Location: services_captiveportal_vouchers.php?zone={$cpzone}"); @@ -384,6 +286,7 @@ $tab_array[] = array(gettext("Allowed IP Addresses"), false, "services_captiveportal_ip.php?zone={$cpzone}"); $tab_array[] = array(gettext("Allowed Hostnames"), false, "services_captiveportal_hostname.php?zone={$cpzone}"); $tab_array[] = array(gettext("Vouchers"), true, "services_captiveportal_vouchers.php?zone={$cpzone}"); +$tab_array[] = array(gettext("High Availability"), false, "services_captiveportal_hasync.php?zone={$cpzone}"); $tab_array[] = array(gettext("File Manager"), false, "services_captiveportal_filemanager.php?zone={$cpzone}"); display_top_tabs($tab_array, true); @@ -522,40 +425,6 @@ $pconfig['msgexpired'] ))->setHelp('Error message displayed for expired vouchers on captive portal error page ($PORTAL_MESSAGE$).'); -$form->add($section); - -$section = new Form_Section('Voucher Database Synchronization'); -$section->addClass('rolledit'); - -$section->addInput(new Form_IpAddress( - 'vouchersyncdbip', - 'Synchronize Voucher Database IP', - $pconfig['vouchersyncdbip'] -))->setHelp('IP address of master nodes webConfigurator to synchronize voucher database and used vouchers from.%1$s' . - 'NOTE: this should be setup on the slave nodes and not the primary node!', '
'); - -$section->addInput(new Form_Input( - 'vouchersyncport', - 'Voucher sync port', - 'text', - $pconfig['vouchersyncport'] -))->setHelp('The port of the master voucher node\'s webConfigurator. Example: 443 '); - -$section->addInput(new Form_Input( - 'vouchersyncusername', - 'Voucher sync username', - 'text', - $pconfig['vouchersyncusername'], - ['autocomplete' => 'new-password'] -))->setHelp('This is the username of the master voucher nodes webConfigurator.'); - -$section->addPassword(new Form_Input( - 'vouchersyncpass', - 'Voucher sync password', - 'password', - $pconfig['vouchersyncpass'] -))->setHelp('This is the password of the master voucher nodes webConfigurator.'); - $form->addGlobal(new Form_Input( 'zone', null, @@ -573,12 +442,6 @@ $form->add($section); print($form); ?> -
- -