ipsec, allow configuration of multiple ike phase1 encryption ciphers (algo/bits/hash/dh)

this is useful for mobile users that need to connect with different operating systems. This way there is no need to find a single commonly supported weaker cipher.
This commit is contained in:
PiBa-NL 2017-04-27 18:49:45 +02:00
parent 2737a09dd8
commit 22dbacd00b
10 changed files with 437 additions and 107 deletions

View File

@ -74,7 +74,7 @@ $g = array(
"disablecrashreporter" => false,
"crashreporterurl" => "https://crashreporter.pfsense.org/crash_reporter.php",
"debug" => false,
"latest_config" => "17.3",
"latest_config" => "17.4",
"minimum_ram_warning" => "101",
"minimum_ram_warning_text" => "128 MB",
"wan_interface_name" => "wan",

View File

@ -5505,4 +5505,23 @@ function additional_config_upgrade() {
global $config;
}
/* IPsec Phase1 now supports multiple authentication ciphers to be specified from the webgui.
* This is usefull for mobile users using different OS's supporting different ciphers.
*/
function upgrade_173_to_174() {
global $config;
if (is_array($config['ipsec']['phase1'])) {
$a_phase1 = &$config['ipsec']['phase1'];
foreach($a_phase1 as &$phase1) {
$item = array();
$item['encryption-algorithm'] = $phase1['encryption-algorithm'];
$item['hash-algorithm'] = $phase1['hash-algorithm'];
$item['dhgroup'] = $phase1['dhgroup'];
$phase1['encryption']['item'][] = $item;
unset($phase1['encryption-algorithm']);
unset($phase1['hash-algorithm']);
unset($phase1['dhgroup']);
}
}
}
?>

View File

@ -951,22 +951,30 @@ EOD;
}
}
if (is_array($ph1ent['encryption-algorithm']) && !empty($ph1ent['encryption-algorithm']['name']) && !empty($ph1ent['hash-algorithm'])) {
$ealgosp1 = '';
$ealg_id = $ph1ent['encryption-algorithm']['name'];
$ealg_kl = $ph1ent['encryption-algorithm']['keylen'];
if ($ealg_kl) {
$ealgosp1 = "ike = {$ealg_id}{$ealg_kl}-{$ph1ent['hash-algorithm']}";
} else {
$ealgosp1 = "ike = {$ealg_id}-{$ph1ent['hash-algorithm']}";
}
$ealgosp1 = '';
if (is_array($ph1ent['encryption']['item'])) {
$ciphers = "";
foreach($ph1ent['encryption']['item'] as $p1enc) {
if (!is_array($p1enc['encryption-algorithm']) ||
empty($p1enc['encryption-algorithm']['name']) ||
empty($p1enc['hash-algorithm'])) {
continue;
}
$ciphers .= ",";
$ciphers .= $p1enc['encryption-algorithm']['name'];
$ealg_kl = $p1enc['encryption-algorithm']['keylen'];
if ($ealg_kl) {
$ciphers .= "{$ealg_kl}";
}
$ciphers .= "-{$p1enc['hash-algorithm']}";
$modp = vpn_ipsec_convert_to_modp($ph1ent['dhgroup']);
if (!empty($modp)) {
$ealgosp1 .= "-{$modp}";
$modp = vpn_ipsec_convert_to_modp($p1enc['dhgroup']);
if (!empty($modp)) {
$ciphers .= "-{$modp}";
}
}
$ealgosp1 .= "!";
$ciphers = substr($ciphers, 1);
$ealgosp1 = "ike = {$ciphers}!";
}
if ($ph1ent['dpd_delay'] && $ph1ent['dpd_maxfail']) {

View File

@ -0,0 +1,186 @@
<?php
/*
* ListItem.class.php
*
* part of pfSense (https://www.pfsense.org)
* Copyright (c) 2004-2016 Rubicon Communications, LLC (Netgate)
* Copyright (c) 2015 Sjon Hortensius
* 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.
*/
class Form_ListItem extends Form_Group
{
protected $_tagName = 'div';
protected $_attributes = array(
'class' => array('form-listitem' => true),
);
protected $_title;
protected $_inputs = array();
protected $_labelTarget;
protected $_help;
protected $_helpParams = array();
public function __construct($title)
{
$this->_title = $title;
}
public function add(Form_Element $input)
{
if ($input instanceof Form_Input) {
$group = new Form_Group($input->getTitle());
$group->add($input);
$input = $group;
}
array_push($this->_inputs, $input);
return $input;
}
public function setLabelTarget(Form_Input $input)
{
$this->_labelTarget = $input;
}
public function setHelp()
{
$args = func_get_args();
$arg0_len = strlen($args[0]);
if (($arg0_len > 0) && ($arg0_len < 4096)) {
$args[0] = gettext($args[0]);
}
if (func_num_args() == 1) {
$this->_help = $args[0];
} else {
$this->_help = call_user_func_array('sprintf', $args);
}
$this->_helpParams = "";
return $this;
}
public function enableDuplication($max = null, $horiz = false)
{
if ($horiz)
$this->addClass('user-duplication-horiz'); // added buttons are 2 cols wide with no offset
else
$this->addClass('user-duplication'); // added buttons 10 cols wide with 2 col offset
if (isset($max))
$this->_attributes('data-duplicate-max', $max);
foreach ($this->_inputs as $input) {
if ($input instanceof Form_Input)
$input->setIsRepeated();
}
return $this;
}
protected function _getHelp()
{
if (empty($this->_help))
return null;
$group = new Form_Element;
$group->addClass('col-sm-'. Form::MAX_INPUT_WIDTH, 'col-sm-offset-'. Form::LABEL_WIDTH);
$help = $this->_help;
return <<<EOT
{$group}
<span class="help-block">
{$help}
</span>
</div>
EOT;
}
public function __toString()
{
global $config, $user_settings;
$element = Form_Element::__toString();
// Automatically determine width for inputs without explicit set
$spaceLeft = Form::MAX_INPUT_WIDTH;
$missingWidth = array();
foreach ($this->_inputs as $input)
{
if ($input instanceof Form_Input) {
$width = $input->getWidth();
} else {
unset($width);
}
if (isset($width))
$spaceLeft -= $width;
else
array_push($missingWidth, $input);
}
if ($this->_labelTarget instanceof Form_Input) {
if (strtolower($this->_labelTarget->getType()) == 'hidden') {
$hidden = true;
}
$form_controls = array('input', 'select', 'button', 'textarea', 'option', 'optgroup', 'fieldset', 'label');
if (in_array(strtolower($this->_labelTarget->getTagName()), $form_controls) && !$hidden) {
$target = $this->_labelTarget->getId();
}
}
$inputs = implode('', $this->_inputs);
$help = $this->_getHelp();
if (!$user_settings['webgui']['webguileftcolumnhyper']) {
$target = null;
}
if (!empty(trim($this->_title)) || is_numeric($this->_title)) {
$title = htmlspecialchars(gettext($this->_title));
// If the element tile (label) begins with a '*', remove the '*' and add a span with class
// 'element-required'. Text decoration can then be added in the CSS to indicate that this is a
// required field
if (substr($title, 0, 1 ) === "*" ) {
$title = '<span class="element-required">' . substr($title, 1) . '</span>';
} else {
$title = '<span>' . $title . '</span>';
}
}
/*return <<<EOT
<div class="hoihoi">
{$inputs}
</div>
EOT;*/
return <<<EOT
{$element}
{$label}
{$title}
</label>
{$inputs}
{$help}
</div>
EOT;
}
}

View File

@ -128,6 +128,11 @@ body {
.form-group {
padding: 2px 5px 2px 5px;
}
.user-duplication .controls {
margin-top: 0px;
}
.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th {
padding:2px;
}

View File

@ -303,6 +303,13 @@ tr.disabled a {
border-bottom: none;
}
.form-listitem {
border-top: 3px solid #E0E0E0;
}
.form-listitem:first-child {
border-top: none;
}
.input-group-addon {
padding: 0 12px;
}

View File

@ -113,6 +113,37 @@ $(function() {
});
})();
// Add +/- buttons to certain Groups; to allow adding multiple entries
(function()
{
var groups = $('div.form-listitem.user-duplication');
var fg = $('<div class="form-group"></div>');
var controlsContainer = $('<div class="col-sm-10 col-sm-offset-2 controls"></div>');
var plus = $('<a class="btn btn-xs btn-success"><i class="fa fa-plus icon-embed-btn"></i>Add</a>');
var minus = $('<a class="btn btn-xs btn-warning"><i class="fa fa-trash icon-embed-btn"></i>Delete</a>');
minus.on('click', function(){
var groups = $('div.form-listitem.user-duplication');
if (groups.length > 1) {
$(this).parents('div.form-listitem').remove();
}
});
plus.on('click', function(){
var group = $(this).parents('div.form-listitem');
var clone = group.clone(true);
bump_input_id(clone);
clone.appendTo(group.parent());
});
groups.each(function(idx, group){
var fgClone = fg.clone(true).appendTo(group);
var controlsClone = controlsContainer.clone(true).appendTo(fgClone);
minus.clone(true).appendTo(controlsClone);
plus.clone(true).appendTo(controlsClone);
});
})();
// Automatically change IpAddress mask selectors to 128/32 options for IPv6/IPv4 addresses
$('span.pfIpMask + select').each(function (idx, select){
var input = $(select).prevAll('input[type=text]');

View File

@ -219,6 +219,12 @@ function moveHelpText(id) {
});
}
// Increment the number at the end of the string
function getStringInt( str ) {
var data = str.match(/(\D*)(\d+)(\D*)/), newStr = "";
return Number( data[ 2 ] );
}
// Increment the number at the end of the string
function bumpStringInt( str ) {
var data = str.match(/(\D*)(\d+)(\D*)/), newStr = "";
@ -293,23 +299,7 @@ function checkLastRow() {
}
}
function add_row() {
// Find the last repeatable group
var lastRepeatableGroup = $('.repeatable:last');
// If the number of repeats exceeds the maximum, do not add another clone
if ($('.repeatable').length >= lastRepeatableGroup.attr('max_repeats')) {
// Alert user if alert message is specified
if (typeof lastRepeatableGroup.attr('max_repeats_alert') !== 'undefined') {
alert(lastRepeatableGroup.attr('max_repeats_alert'));
}
return;
}
// Clone it
var newGroup = lastRepeatableGroup.clone();
// Increment the suffix number for each input element in the new group
function bump_input_id(newGroup) {
$(newGroup).find('input').each(function() {
$(this).prop("id", bumpStringInt(this.id));
$(this).prop("name", bumpStringInt(this.name));
@ -339,6 +329,25 @@ function add_row() {
}
}
});
}
function add_row() {
// Find the last repeatable group
var lastRepeatableGroup = $('.repeatable:last');
// If the number of repeats exceeds the maximum, do not add another clone
if ($('.repeatable').length >= lastRepeatableGroup.attr('max_repeats')) {
// Alert user if alert message is specified
if (typeof lastRepeatableGroup.attr('max_repeats_alert') !== 'undefined') {
alert(lastRepeatableGroup.attr('max_repeats_alert'));
}
return;
}
// Clone it
var newGroup = lastRepeatableGroup.clone();
// Increment the suffix number for each input element in the new group
bump_input_id(newGroup);
// And for "for" tags
// $(newGroup).find('label').attr('for', bumpStringInt($(newGroup).find('label').attr('for')));

View File

@ -252,6 +252,7 @@ if (is_subsystem_dirty('ipsec')) {
<th><?=gettext("Mode")?></th>
<th><?=gettext("P1 Protocol")?></th>
<th><?=gettext("P1 Transforms")?></th>
<th><?=gettext("P1 DH-Group")?></th>
<th><?=gettext("P1 Description")?></th>
<th><?=gettext("Actions")?></th>
</tr>
@ -334,19 +335,51 @@ $i = 0; foreach ($a_phase1 as $ph1ent):
<?=$spane?>
</td>
<td id="frd<?=$i?>">
<?=$p1_ealgos[$ph1ent['encryption-algorithm']['name']]['name']?>
<?php
if ($ph1ent['encryption-algorithm']['keylen']) {
if ($ph1ent['encryption-algorithm']['keylen'] == "auto") {
echo " (" . gettext("auto") . ")";
} else {
echo " ({$ph1ent['encryption-algorithm']['keylen']} " . gettext("bits") . ")";
$first = true;
if (is_array($ph1ent['encryption']['item'])) {
foreach($ph1ent['encryption']['item'] as $p1algo) {
if (!$first) {
echo "<br/>";
}
echo $p1_ealgos[$p1algo['encryption-algorithm']['name']]['name'];
if ($p1algo['encryption-algorithm']['keylen']) {
if ($p1algo['encryption-algorithm']['keylen'] == "auto") {
echo " (" . gettext("auto") . ")";
} else {
echo " ({$p1algo['encryption-algorithm']['keylen']} " . gettext("bits") . ")";
}
}
$first = false;
}
}
}
?>
</td>
<td>
<?=$p1_halgos[$ph1ent['hash-algorithm']]?>
<?php $first = true;
if (is_array($ph1ent['encryption']['item'])) {
foreach($ph1ent['encryption']['item'] as $p1algo) {
if (!$first) {
echo "<br/>";
}
echo $p1_halgos[$p1algo['hash-algorithm']];
$first = false;
}
}
?>
</td>
<td>
<?php $first = true;
if (is_array($ph1ent['encryption']['item'])) {
foreach($ph1ent['encryption']['item'] as $p1algo) {
if (!$first) {
echo "<br/>";
}
echo str_replace(" ","&nbsp;",$p1_dhgroups[$p1algo['dhgroup']]);
$first = false;
}
}
?>
</td>
<td>
<?=htmlspecialchars($ph1ent['descr'])?>

View File

@ -91,9 +91,7 @@ if (isset($p1index) && $a_phase1[$p1index]) {
$pconfig['myid_data'] = $a_phase1[$p1index]['myid_data'];
$pconfig['peerid_type'] = $a_phase1[$p1index]['peerid_type'];
$pconfig['peerid_data'] = $a_phase1[$p1index]['peerid_data'];
$pconfig['ealgo'] = $a_phase1[$p1index]['encryption-algorithm'];
$pconfig['halgo'] = $a_phase1[$p1index]['hash-algorithm'];
$pconfig['dhgroup'] = $a_phase1[$p1index]['dhgroup'];
$pconfig['encryption'] = $a_phase1[$p1index]['encryption'];
$pconfig['lifetime'] = $a_phase1[$p1index]['lifetime'];
$pconfig['authentication_method'] = $a_phase1[$p1index]['authentication_method'];
@ -153,9 +151,6 @@ if (isset($p1index) && $a_phase1[$p1index]) {
$pconfig['myid_type'] = "myaddress";
$pconfig['peerid_type'] = "peeraddress";
$pconfig['authentication_method'] = "pre_shared_key";
$pconfig['ealgo'] = array(name => "aes");
$pconfig['halgo'] = "sha1";
$pconfig['dhgroup'] = "2";
$pconfig['lifetime'] = "28800";
$pconfig['rekey_enable'] = true;
$pconfig['nat_traversal'] = 'on';
@ -169,6 +164,14 @@ if (isset($p1index) && $a_phase1[$p1index]) {
$pconfig['mode'] = "aggressive";
}
}
// default value for new P1 and failsafe to always have at least 1 encryption item for the Form_ListItem
if (!is_array($pconfig['encryption']['item']) || count($pconfig['encryption']['item']) == 0) {
$item = array();
$item['encryption-algorithm'] = array(name => "aes");
$item['hash-algorithm'] = "sha1";
$item['dhgroup'] = "2";
$pconfig['encryption']['item'][] = $item;
}
if (isset($_REQUEST['dup']) && is_numericint($_REQUEST['dup'])) {
unset($p1index);
@ -178,6 +181,17 @@ if ($_POST['save']) {
unset($input_errors);
$pconfig = $_POST;
for($i = 0; $i < 100; $i++) {
if (isset($_POST['ealgo_algo'.$i])) {
$item = array();
$item['encryption-algorithm']['name'] = $_POST['ealgo_algo'.$i];
$item['encryption-algorithm']['keylen'] = $_POST['ealgo_keylen'.$i];
$item['hash-algorithm'] = $_POST['halgo'.$i];
$item['dhgroup'] = $_POST['dhgroup'.$i];
$pconfig['encryption']['item'][] = $item;
}
}
/* input validation */
$method = $pconfig['authentication_method'];
@ -407,11 +421,12 @@ if ($_POST['save']) {
if (!empty($pconfig['iketype']) && $pconfig['iketype'] != "ikev1" && $pconfig['iketype'] != "ikev2" && $pconfig['iketype'] != "auto") {
$input_errors[] = gettext("Valid arguments for IKE type are v1, v2 or auto");
}
if (preg_match("/aes\d+gcm/", $_POST['ealgo']) && $_POST['iketype'] != "ikev2") {
$input_errors[] = gettext("Encryption Algorithm AES-GCM can only be used with IKEv2");
foreach($pconfig['encryption']['item'] as $p1algo) {
if (preg_match("/aes\d+gcm/", $p1algo['encryption-algorithm']['name']) && $_POST['iketype'] != "ikev2") {
$input_errors[] = gettext("Encryption Algorithm AES-GCM can only be used with IKEv2");
}
}
/* auth backend for mobile eap-radius VPNs should be a RADIUS server */
if (($pconfig['authentication_method'] == 'eap-radius') && $pconfig['mobile']) {
if (!empty($config['ipsec']['client']['user_source'])) {
@ -425,13 +440,6 @@ if ($_POST['save']) {
}
}
/* build our encryption algorithms array */
$pconfig['ealgo'] = array();
$pconfig['ealgo']['name'] = $_POST['ealgo'];
if ($pconfig['ealgo_keylen']) {
$pconfig['ealgo']['keylen'] = $_POST['ealgo_keylen'];
}
if (!$input_errors) {
$ph1ent['ikeid'] = $pconfig['ikeid'];
$ph1ent['iketype'] = $pconfig['iketype'];
@ -463,9 +471,7 @@ if ($_POST['save']) {
$ph1ent['peerid_type'] = $pconfig['peerid_type'];
$ph1ent['peerid_data'] = $pconfig['peerid_data'];
$ph1ent['encryption-algorithm'] = $pconfig['ealgo'];
$ph1ent['hash-algorithm'] = $pconfig['halgo'];
$ph1ent['dhgroup'] = $pconfig['dhgroup'];
$ph1ent['encryption'] = $pconfig['encryption'];
$ph1ent['lifetime'] = $pconfig['lifetime'];
$ph1ent['pre-shared-key'] = $pconfig['pskey'];
$ph1ent['private-key'] = base64_encode($pconfig['privatekey']);
@ -799,40 +805,51 @@ $section->addInput(new Form_Select(
))->setHelp('Select a certificate authority previously configured in the Certificate Manager.');
$form->add($section);
$section = new Form_Section('NOTITLE');
foreach($pconfig['encryption']['item'] as $key => $p1enc) {
$li = new Form_ListItem("");
$group = new Form_Group('*Encryption Algorithm');
$group->add(new Form_Select(
'ealgo_algo'.$key,
null,
$p1enc['encryption-algorithm']['name'],
build_eal_list()
));
$group->add(new Form_Select(
'ealgo_keylen'.$key,
null,
$p1enc['encryption-algorithm']['keylen'],
array()
));
$li->add($group);
$section = new Form_Section('Phase 1 Proposal (Algorithms)');
$li->add(new Form_Select(
'halgo'.$key,
'*Hash Algorithm',
$p1enc['hash-algorithm'],
$p1_halgos
))->setHelp('Must match the setting chosen on the remote side.');
$group = new Form_Group('*Encryption Algorithm');
$li->add(new Form_Select(
'dhgroup'.$key,
'*DH Group',
$p1enc['dhgroup'],
$p1_dhgroups
))->setHelp('Must match the setting chosen on the remote side.');
$group->add(new Form_Select(
'ealgo',
$li->enableDuplication(null);
$section->add($li);
}
$form->add($section);
$section = new Form_Section('NOTITLE');
$btnaddopt = new Form_Button(
'algoaddrow',
'Add Encryption Settings',
null,
$pconfig['ealgo']['name'],
build_eal_list()
));
$group->add(new Form_Select(
'ealgo_keylen',
null,
$pconfig['ealgo_keylen'],
array()
));
$section->add($group);
$section->addInput(new Form_Select(
'halgo',
'*Hash Algorithm',
$pconfig['halgo'],
$p1_halgos
))->setHelp('Must match the setting chosen on the remote side.');
$section->addInput(new Form_Select(
'dhgroup',
'*DH Group',
$pconfig['dhgroup'],
$p1_dhgroups
))->setHelp('Must match the setting chosen on the remote side.');
'fa-plus'
);
$btnaddopt->removeClass('btn-primary')->addClass('btn-success btn-sm');
$section->addInput(new Form_Input(
'lifetime',
@ -964,13 +981,6 @@ $form->add($section);
print($form);
/* determine if we should init the key length */
$keyset = '';
if (isset($pconfig['ealgo']['keylen'])) {
if (is_numericint($pconfig['ealgo']['keylen'])) {
$keyset = $pconfig['ealgo']['keylen'];
}
}
?>
@ -979,6 +989,19 @@ if (isset($pconfig['ealgo']['keylen'])) {
<script type="text/javascript">
//<![CDATA[
events.push(function() {
$('[id^=algoaddrow]').prop('type','button');
$('[id^=algoaddrow]').click(function() {
add_row();
var lastRepeatableGroup = $('.repeatable:last');
$(lastRepeatableGroup).find('[id^=ealgo_algo]select').change(function () {
id = getStringInt(this.id);
ealgosel_change(id, '');
});
});
function myidsel_change() {
hideGroupInput('myid_data', ($('#myid_type').val() == 'myaddress'));
@ -1054,18 +1077,18 @@ events.push(function() {
}
/* PHP generates javascript case statements for variable length keys */
function ealgosel_change(bits) {
function ealgosel_change(id, bits) {
$("select[name='ealgo_keylen']").find('option').remove().end();
$("select[name='ealgo_keylen"+id+"']").find('option').remove().end();
switch ($('#ealgo').find(":selected").index().toString()) {
switch ($('#ealgo_algo'+id).find(":selected").index().toString()) {
<?php
$i = 0;
foreach ($p1_ealgos as $algo => $algodata) {
if (is_array($algodata['keysel'])) {
?>
case '<?=$i?>':
hideGroupInput('ealgo_keylen', false);
hideGroupInput('ealgo_keylen'+id, false);
<?php
$key_hi = $algodata['keysel']['hi'];
$key_lo = $algodata['keysel']['lo'];
@ -1073,7 +1096,7 @@ events.push(function() {
for ($keylen = $key_hi; $keylen >= $key_lo; $keylen -= $key_step) {
?>
$("select[name='ealgo_keylen']").append($('<option value="<?=$keylen?>"><?=$keylen?> bits</option>'));
$("select[name='ealgo_keylen"+id+"']").append($('<option value="<?=$keylen?>"><?=$keylen?> bits</option>'));
<?php
}
?>
@ -1082,7 +1105,7 @@ events.push(function() {
} else {
?>
case '<?=$i?>':
hideGroupInput('ealgo_keylen', true);
hideGroupInput('ealgo_keylen'+id, true);
break;
<?php
}
@ -1092,7 +1115,7 @@ events.push(function() {
}
if (bits) {
$('#ealgo_keylen').val(bits);
$('#ealgo_keylen'+id).val(bits);
}
}
@ -1161,8 +1184,9 @@ events.push(function() {
});
// algorithm
$('#ealgo').change(function () {
ealgosel_change(<?=$keyset?>);
$('[id^=ealgo_algo]select').change(function () {
id = getStringInt(this.id);
ealgosel_change(id, <?=$keyset?>);
});
// On ititial page load
@ -1170,9 +1194,17 @@ events.push(function() {
peeridsel_change();
iketype_change();
methodsel_change();
ealgosel_change(<?=$keyset?>);
rekeychkbox_change();
dpdchkbox_change();
<?php
foreach($pconfig['encryption']['item'] as $key => $p1enc) {
$keylen = $p1enc['encryption-algorithm']['keylen'];
if (!is_numericint($keylen)) {
$keylen = "''";
}
echo "ealgosel_change({$key}, {$keylen});";
}
?>
// ---------- On initial page load ------------------------------------------------------------