--- a/src/etc/inc/openvpn.inc +++ b/src/etc/inc/openvpn.inc @@ -757,11 +757,11 @@ if ($settings['netbios_enable']) { - if (!empty($settings['dhcp_nbttype']) && ($settings['dhcp_nbttype'] != 0)) { - $conf .= "push \"dhcp-option NBT {$settings['dhcp_nbttype']}\"\n"; + if (!empty($settings['netbios_ntype']) && ($settings['netbios_ntype'] != 0)) { + $conf .= "push \"dhcp-option NBT {$settings['netbios_ntype']}\"\n"; } - if (!empty($settings['dhcp_nbtscope'])) { - $conf .= "push \"dhcp-option NBS {$settings['dhcp_nbtscope']}\"\n"; + if (!empty($settings['netbios_scope'])) { + $conf .= "push \"dhcp-option NBS {$settings['netbios_scope']}\"\n"; } if (!empty($settings['wins_server1'])) { @@ -774,7 +774,10 @@ if (!empty($settings['nbdd_server1'])) { $conf .= "push \"dhcp-option NBDD {$settings['nbdd_server1']}\"\n"; } + if (!empty($settings['nbdd_server2'])) { + $conf .= "push \"dhcp-option NBDD {$settings['nbdd_server2']}\"\n"; } + } if ($settings['gwredir']) { $conf .= "push \"redirect-gateway def1\"\n"; @@ -1695,6 +1698,12 @@ } openvpn_create_dirs(); + /* Some options remove the gateway or topology which are required; + * in such cases, these can be automatically determined. */ + $auto_config_gateway4 = false; + $auto_config_gateway6 = false; + $auto_config_topology = false; + if (empty($settings['server_list'])) { $csc_server_list = array(); } else { @@ -1706,14 +1715,46 @@ $conf .= "disable\n"; } - if ($settings['push_reset']) { + // Reset options + if (isset($settings['override_options']) && ($settings['override_options'] == 'push_reset')) { + if (isset($settings['keep_minimal'])) { + $auto_config_gateway4 = true; + $auto_config_gateway6 = true; + $auto_config_topology = true; + } $conf .= "push-reset\n"; } - - if ($settings['remove_route']) { - $conf .= "push-remove route\n"; + if (!empty($settings['remove_options'])) { + foreach (explode(',', $settings['remove_options']) as $option) { + if (isset($settings['keep_minimal']) && ($option == 'remove_route')) { + $auto_config_gateway4 = true; + $auto_config_gateway6 = true; } + $options = match ($option) { + 'remove_route' => 'route', + 'remove_iroute' => 'iroute', + 'remove_redirect_gateway' => 'redirect-gateway', + 'remove_inactive' => 'inactive', + 'remove_ping' => 'ping', + 'remove_ping_action' => ['ping-restart', 'ping-exit'], + 'remove_dnsdomain' => '"dhcp-option DOMAIN"', + 'remove_dnsservers' => '"dhcp-option DNS"', + 'remove_ntpservers' => '"dhcp-option NTP"', + 'remove_netbios_ntype' => '"dhcp-option NBT"', + 'remove_netbios_scope' => '"dhcp-option NBS"', + 'remove_wins' => '"dhcp-option WINS"' + }; + if (is_array($options)) { + foreach ($options as $option_name) { + $conf .= "push-remove {$option_name}\n"; + } + } else { + $conf .= "push-remove {$options}\n"; + } + } + } + // Local network route options if ($settings['local_network']) { $conf .= openvpn_gen_routes($settings['local_network'], "ipv4", true); } @@ -1721,18 +1762,44 @@ $conf .= openvpn_gen_routes($settings['local_networkv6'], "ipv6", true); } - // Add a remote network iroute if set + // Remote network iroute options if (openvpn_validate_cidr($settings['remote_network'], "", true, "ipv4", true) === FALSE) { $conf .= openvpn_gen_routes($settings['remote_network'], "ipv4", false, true); } - // Add a remote network iroute if set if (openvpn_validate_cidr($settings['remote_networkv6'], "", true, "ipv6", true) === FALSE) { $conf .= openvpn_gen_routes($settings['remote_networkv6'], "ipv6", false, true); } + // Gatewy override options + if (!empty($settings['gateway'])) { + $auto_config_gateway4 = false; + $conf .= "push \"route-gateway {$settings['gateway']}\"\n"; + } + if (!empty($settings['gateway6'])) { + $auto_config_gateway6 = false; + $conf .= "push \"route-ipv6-gateway {$settings['gateway6']}\"\n"; + } + + // Inactivity override options + if (isset($settings['inactive_seconds'])) { + $conf .= "push \"inactive {$settings['inactive_seconds']}\"\n"; + } + + // Ping override options + if (isset($settings['ping_seconds'])) { + $conf .= "push \"ping {$settings['ping_seconds']}\"\n"; + } + if (isset($settings['ping_action'])) { + $action = str_replace("_", "-", $settings['ping_action']); + $conf .= "push \"{$action} {$settings['ping_action_seconds']}\"\n"; + } + + // DHCP and gateway redirect options openvpn_add_dhcpopts($settings, $conf); + // Custom options openvpn_add_custom($settings, $conf); + /* Loop through servers, find which ones can use this CSC */ foreach (config_get_path('openvpn/openvpn-server', []) as $serversettings) { if (isset($serversettings['disable'])) { @@ -1742,6 +1809,46 @@ if ($serversettings['vpnid'] && (empty($csc_server_list) || in_array($serversettings['vpnid'], $csc_server_list))) { $csc_path = "{$g['openvpn_base']}/server{$serversettings['vpnid']}/csc/" . basename($settings['common_name']); $csc_conf = $conf; + + /* Topology is depends on the server configuration. + * TAP mode always uses a subnet topology */ + $topology = ($serversettings['dev_mode'] == 'tap') ? 'subnet' : $serversettings['topology']; + if ($auto_config_topology) { + $csc_conf .= "push \"topology {$topology}\"\n"; + } + + /* The gateway is the first usable address in the tunnel network. + * If the tunnel network is not set, the gateway must be manually + * defined. This can happen when using a net30 topology and + * resetting options. */ + if ($auto_config_gateway6) { + // IPv6 always uses a subnet topology + $tunnel_network = null; + if (!empty($settings['tunnel_networkv6'])) { + $tunnel_network = $settings['tunnel_networkv6']; + } elseif (!empty($serversettings['tunnel_networkv6'])) { + $tunnel_network = $serversettings['tunnel_networkv6']; + } + if (!empty($tunnel_network)) { + $tunnel_network_parts = openvpn_gen_tunnel_network($tunnel_network); + $gateway = ip6_after(gen_subnetv6($tunnel_network_parts[0], $tunnel_network_parts[1])); + $csc_conf .= "push \"route-ipv6-gateway {$gateway}\"\n"; + } + } + if ($auto_config_gateway4) { + $tunnel_network = null; + if ($topology == "subnet") { + // The client tunnel network is assumed to be the same as the server's + $tunnel_network = $serversettings['tunnel_network']; + } elseif (!empty($settings['tunnel_network'])) { + $tunnel_network = $settings['tunnel_network']; + } + if (!empty($tunnel_network)) { + $tunnel_network_parts = openvpn_gen_tunnel_network($tunnel_network); + $gateway = ip_after(gen_subnetv4($tunnel_network_parts[0], $tunnel_network_parts[1]), 1); + $csc_conf .= "push \"route-gateway {$gateway}\"\n"; + } + } if (!empty($serversettings['tunnel_network']) && !empty($settings['tunnel_network'])) { list($ip, $mask) = openvpn_gen_tunnel_network($settings['tunnel_network']); --- a/src/usr/local/www/vpn_openvpn_csc.php +++ b/src/usr/local/www/vpn_openvpn_csc.php @@ -34,7 +34,7 @@ require_once("pfsense-utils.inc"); require_once("pkg-utils.inc"); -global $openvpn_tls_server_modes; +global $openvpn_tls_server_modes, $openvpn_ping_action; init_config_arr(array('openvpn', 'openvpn-csc')); $a_csc = &$config['openvpn']['openvpn-csc']; @@ -78,6 +78,15 @@ if (($act == "edit") || ($act == "dup")) { if (isset($id) && $a_csc[$id]) { + $pconfig['keep_minimal'] = $a_csc[$id]['keep_minimal']; + // Handle the "Reset Options" list + if (!empty($a_csc[$id]['remove_options'])) { + $pconfig['override_options'] = 'remove_specified'; + $pconfig['remove_options'] = explode(',', $a_csc[$id]['remove_options']); + } elseif (isset($a_csc[$id]['push_reset'])) { + $pconfig['override_options'] = 'push_reset'; + } + $pconfig['server_list'] = explode(",", $a_csc[$id]['server_list']); $pconfig['custom_options'] = $a_csc[$id]['custom_options']; $pconfig['disable'] = isset($a_csc[$id]['disable']); @@ -89,12 +98,17 @@ $pconfig['tunnel_networkv6'] = $a_csc[$id]['tunnel_networkv6']; $pconfig['local_network'] = $a_csc[$id]['local_network']; $pconfig['local_networkv6'] = $a_csc[$id]['local_networkv6']; + $pconfig['gateway'] = $a_csc[$id]['gateway']; + $pconfig['gateway6'] = $a_csc[$id]['gateway6']; $pconfig['remote_network'] = $a_csc[$id]['remote_network']; $pconfig['remote_networkv6'] = $a_csc[$id]['remote_networkv6']; $pconfig['gwredir'] = $a_csc[$id]['gwredir']; + $pconfig['gwredir6'] = $a_csc[$id]['gwredir6']; - $pconfig['push_reset'] = $a_csc[$id]['push_reset']; - $pconfig['remove_route'] = $a_csc[$id]['remove_route']; + $pconfig['inactive_seconds'] = $a_csc[$id]['inactive_seconds']; + $pconfig['ping_seconds'] = $a_csc[$id]['ping_seconds']; + $pconfig['ping_action'] = $a_csc[$id]['ping_action']; + $pconfig['ping_action_seconds'] = $a_csc[$id]['ping_action_seconds']; $pconfig['dns_domain'] = $a_csc[$id]['dns_domain']; if ($pconfig['dns_domain']) { @@ -113,6 +127,9 @@ $pconfig['dns_server_enable'] = true; } + $pconfig['push_blockoutsidedns'] = $a_csc[$id]['push_blockoutsidedns']; + $pconfig['push_register_dns'] = $a_csc[$id]['push_register_dns']; + $pconfig['ntp_server1'] = $a_csc[$id]['ntp_server1']; $pconfig['ntp_server2'] = $a_csc[$id]['ntp_server2']; @@ -134,7 +151,9 @@ } $pconfig['nbdd_server1'] = $a_csc[$id]['nbdd_server1']; - if ($pconfig['nbdd_server1']) { + $pconfig['nbdd_server2'] = $a_csc[$id]['nbdd_server2']; + + if ($pconfig['nbdd_server1'] || $pconfig['nbdd_server2']) { $pconfig['nbdd_server_enable'] = true; } } @@ -157,6 +176,7 @@ $input_errors[] = gettext("This user does not have sufficient privileges to edit Advanced options on this instance."); } if (!$user_can_edit_advanced && !empty($a_csc[$id]['custom_options'])) { + // Restore the "custom options" field $pconfig['custom_options'] = $a_csc[$id]['custom_options']; } @@ -180,14 +200,22 @@ $input_errors[] = gettext("The field 'IPv6 Tunnel Network' must contain a valid IPv6 prefix or an alias with a single IPv6 prefix."); } - if ($result = openvpn_validate_cidr($pconfig['local_network'], 'IPv4 Local Network', true, "ipv4", true)) { + if (empty($pconfig['gwredir']) && ($result = openvpn_validate_cidr($pconfig['local_network'], 'IPv4 Local Network', true, "ipv4", true))) { $input_errors[] = $result; } - if ($result = openvpn_validate_cidr($pconfig['local_networkv6'], 'IPv6 Local Network', true, "ipv6", true)) { + if (empty($pconfig['gwredir6']) && ($result = openvpn_validate_cidr($pconfig['local_networkv6'], 'IPv6 Local Network', true, "ipv6", true))) { $input_errors[] = $result; } + if (!empty($pconfig['gateway']) && !is_ipaddrv4($pconfig['gateway'])) { + $input_errors[] = gettext("The specified IPv4 gateway address is invalid."); + } + + if (!empty($pconfig['gateway6']) && !is_ipaddrv6($pconfig['gateway6'])) { + $input_errors[] = gettext("The specified IPv6 gateway address is invalid."); + } + if ($result = openvpn_validate_cidr($pconfig['remote_network'], 'IPv4 Remote Network', true, "ipv4", true)) { $input_errors[] = $result; } @@ -196,6 +224,22 @@ $input_errors[] = $result; } + if (!empty($pconfig['inactive_seconds']) && !is_numericint($pconfig['inactive_seconds'])) { + $input_errors[] = gettext('The supplied "Inactivity Timeout" value is invalid.'); + } + + if (!empty($pconfig['ping_seconds']) && !is_numericint($pconfig['ping_seconds'])) { + $input_errors[] = gettext('The supplied "Ping Interval" value is invalid.'); + } + if (!empty($pconfig['ping_action']) && ($pconfig['ping_action'] != 'default')) { + if (!isset($openvpn_ping_action[$pconfig['ping_action']])) { + $input_errors[] = gettext('The field "Ping Action" contains an invalid selection.'); + } + if (!is_numericint($pconfig['ping_action_seconds'])) { + $input_errors[] = gettext('The supplied "Ping Action" timeout value is invalid.'); + } + } + if ($pconfig['dns_server_enable']) { if (!empty($pconfig['dns_server1']) && !is_ipaddr(trim($pconfig['dns_server1']))) { $input_errors[] = gettext("The field 'DNS Server #1' must contain a valid IP address"); @@ -239,7 +283,10 @@ if (!empty($pconfig['nbdd_server1']) && !is_ipaddr(trim($pconfig['nbdd_server1']))) { $input_errors[] = gettext("The field 'NetBIOS Data Distribution Server #1' must contain a valid IP address"); } + if (!empty($pconfig['nbdd_server2']) && !is_ipaddr(trim($pconfig['nbdd_server2']))) { + $input_errors[] = gettext("The field 'NetBIOS Data Distribution Server #2' must contain a valid IP address"); } + } if (!empty($pconfig['netbios_ntype']) && !array_key_exists($pconfig['netbios_ntype'], $netbios_nodetypes)) { @@ -255,6 +302,19 @@ if (!$input_errors) { $csc = array(); + if (isset($pconfig['keep_minimal'])) { + $csc['keep_minimal'] = true; + } + // Handle "Reset Server Options" and "Reset Options" + if (($pconfig['override_options'] == 'remove_specified')) { + // If no options are specified, keep the default behavior. + if (!empty($pconfig['remove_options'])) { + $csc['remove_options'] = implode(',', $pconfig['remove_options']); + } + } elseif ($pconfig['override_options'] == 'push_reset') { + $csc['push_reset'] = true; + } + if (is_array($pconfig['server_list'])) { $csc['server_list'] = implode(",", $pconfig['server_list']); } else { @@ -269,14 +329,35 @@ $csc['description'] = $pconfig['description']; $csc['tunnel_network'] = $pconfig['tunnel_network']; $csc['tunnel_networkv6'] = $pconfig['tunnel_networkv6']; + + $csc['gateway'] = $pconfig['gateway']; + $csc['gateway6'] = $pconfig['gateway6']; + // Don't push routes if redirecting all traffic. + if (!empty($pconfig['gwredir'])) { + $csc['gwredir'] = $pconfig['gwredir']; + } else { $csc['local_network'] = $pconfig['local_network']; + } + if (!empty($pconfig['gwredir6'])) { + $csc['gwredir6'] = $pconfig['gwredir6']; + } else { $csc['local_networkv6'] = $pconfig['local_networkv6']; + } + $csc['remote_network'] = $pconfig['remote_network']; $csc['remote_networkv6'] = $pconfig['remote_networkv6']; - $csc['gwredir'] = $pconfig['gwredir']; - $csc['push_reset'] = $pconfig['push_reset']; - $csc['remove_route'] = $pconfig['remove_route']; + if (is_numericint($pconfig['inactive_seconds'])) { + $csc['inactive_seconds'] = $pconfig['inactive_seconds']; + } + if (is_numericint($pconfig['ping_seconds'])) { + $csc['ping_seconds'] = $pconfig['ping_seconds']; + } + if (!empty($pconfig['ping_action']) && ($pconfig['ping_action'] != 'default')) { + $csc['ping_action'] = $pconfig['ping_action']; + $csc['ping_action_seconds'] = $pconfig['ping_action_seconds']; + } + if ($pconfig['dns_domain_enable']) { $csc['dns_domain'] = $pconfig['dns_domain']; } @@ -288,23 +369,28 @@ $csc['dns_server4'] = $pconfig['dns_server4']; } + $csc['push_blockoutsidedns'] = $pconfig['push_blockoutsidedns']; + $csc['push_register_dns'] = $pconfig['push_register_dns']; + if ($pconfig['ntp_server_enable']) { $csc['ntp_server1'] = $pconfig['ntp_server1']; $csc['ntp_server2'] = $pconfig['ntp_server2']; } $csc['netbios_enable'] = $pconfig['netbios_enable']; + + if ($pconfig['netbios_enable']) { $csc['netbios_ntype'] = $pconfig['netbios_ntype']; $csc['netbios_scope'] = $pconfig['netbios_scope']; - if ($pconfig['netbios_enable']) { if ($pconfig['wins_server_enable']) { $csc['wins_server1'] = $pconfig['wins_server1']; $csc['wins_server2'] = $pconfig['wins_server2']; } - if ($pconfig['dns_server_enable']) { + if ($pconfig['nbdd_server_enable']) { $csc['nbdd_server1'] = $pconfig['nbdd_server1']; + $csc['nbdd_server2'] = $pconfig['nbdd_server2']; } } @@ -407,6 +493,49 @@ true ))->setHelp('Select the servers that will utilize this override. When no servers are selected, the override will apply to all servers.'); + + $section->addInput(new Form_Select( + 'override_options', + 'Reset Server Options', + ($pconfig['override_options'] ?? 'default'), + [ + 'default' => 'Keep all server options (default)', + 'push_reset' => 'Reset all options', + 'remove_specified' => 'Remove specified options' + ] + ))->setHelp('Prevent this client from receiving server-defined client settings. Other client-specific options on this page will supersede these options.'); + + $group = new Form_Group('Remove Options'); + $group->addClass('remove_options'); + $group->add(new Form_Select( + 'remove_options', + null, + $pconfig['remove_options'], + [ + 'remove_route' => 'Local Routes and Gateways', + 'remove_iroute' => 'Remote Routes', + 'remove_redirect_gateway' => 'Redirect Gateways', + 'remove_inactive' => 'Inactivity Timeout', + 'remove_ping' => 'Client Ping', + 'remove_ping_action' => 'Ping Action', + 'remove_dnsdomain' => 'DNS Domains', + 'remove_dnsservers' => 'DNS Servers', + 'remove_ntpservers' => 'NTP Options', + 'remove_netbios_ntype' => 'NetBIOS Type', + 'remove_netbios_scope' => 'NetBIOS Scope', + 'remove_wins' => 'WINS Options' + ], + true + ))->setHelp('A "push-remove" option will be sent to the client for the selected options, removing the respective server-defined option.'); + $section->add($group); + + $section->addInput(new Form_Checkbox( + 'keep_minimal', + 'Keep minimal options', + 'Automatically determine the client topology and gateway', + $pconfig['keep_minimal'] + ))->setHelp('If checked, generate the required client configuration when server options are reset or removed.'); + $form->add($section); $section = new Form_Section('Tunnel Settings'); @@ -431,6 +560,34 @@ '
'); $section->addInput(new Form_Input( + 'gateway', + 'IPv4 Gateway', + 'text', + $pconfig['gateway'] + ))->setHelp('This is the IPv4 Gateway to push to the client. Normally it is left blank and determined automatically.'); + + $section->addInput(new Form_Input( + 'gateway6', + 'IPv6 Gateway', + 'text', + $pconfig['gateway6'] + ))->setHelp('This is the IPv6 Gateway to push to the client. Normally it is left blank and determined automatically.'); + + $section->addInput(new Form_Checkbox( + 'gwredir', + 'Redirect IPv4 Gateway', + 'Force all client generated IPv4 traffic through the tunnel.', + $pconfig['gwredir'] + )); + + $section->addInput(new Form_Checkbox( + 'gwredir6', + 'Redirect IPv6 Gateway', + 'Force all client-generated IPv6 traffic through the tunnel.', + $pconfig['gwredir6'] + )); + + $section->addInput(new Form_Input( 'local_network', 'IPv4 Local Network/s', 'text', @@ -468,33 +625,44 @@ 'NOTE: Remember to add these subnets to the IPv6 Remote Networks list on the corresponding OpenVPN server settings.', '
'); - $section->addInput(new Form_Checkbox( - 'gwredir', - 'Redirect Gateway', - 'Force all client generated traffic through the tunnel.', - $pconfig['gwredir'] - )); - $form->add($section); - $section = new Form_Section('Client Settings'); + $section = new Form_Section('Other Client Settings'); - $section->addInput(new Form_Checkbox( - 'push_reset', - 'Server Definitions', - 'Prevent this client from receiving any server-defined client settings. ', - $pconfig['push_reset'] - )); + $section->addInput(new Form_Input( + 'inactive_seconds', + 'Inactivity Timeout', + 'number', + $pconfig['inactive_seconds'], + ['min' => '0'] + ))->setHelp('Set connection inactivity timeout')->setWidth(3); - /* as "push-reset" can break subnet topology, - * "push-remove route" removes only IPv4/IPv6 routes, see #9702 */ - $section->addInput(new Form_Checkbox( - 'remove_route', - 'Remove Server Routes', - 'Prevent this client from receiving any server-defined routes without removing any other options. ', - $pconfig['remove_route'] - )); + $section->addInput(new Form_Input( + 'ping_seconds', + 'Ping Interval', + 'number', + $pconfig['ping_seconds'], + ['min' => '0'] + ))->setHelp('Set peer ping interval')->setWidth(3); + $group = new Form_Group('Ping Action'); + $group->add(new Form_Select( + 'ping_action', + null, + $pconfig['ping_action'] ?? 'default', + array_merge([ + 'default' => 'Don\'t override option (default)' + ], $openvpn_ping_action) + ))->setHelp('Exit or restart OpenVPN client after server timeout')->setWidth(4); + $group->add(new Form_Input( + 'ping_action_seconds', + 'timeout seconds', + 'number', + $pconfig['ping_action_seconds'], + ['min' => '0'] + ))->setWidth(2)->addClass('ping_action_seconds'); + $section->add($group); + $section->addInput(new Form_Checkbox( 'dns_domain_enable', 'DNS Default Domain', @@ -555,6 +723,20 @@ $section->add($group); + $section->addInput(new Form_Checkbox( + 'push_blockoutsidedns', + 'Block Outside DNS', + 'Make Windows 10 Clients Block access to DNS servers except across OpenVPN while connected, forcing clients to use only VPN DNS servers.', + $pconfig['push_blockoutsidedns'] + ))->setHelp('Requires Windows 10 and OpenVPN 2.3.9 or later. Only Windows 10 is prone to DNS leakage in this way, other clients will ignore the option as they are not affected.'); + + $section->addInput(new Form_Checkbox( + 'push_register_dns', + 'Force DNS cache update', + 'Run "net stop dnscache", "net start dnscache", "ipconfig /flushdns" and "ipconfig /registerdns" on connection initiation.', + $pconfig['push_register_dns'] + ))->setHelp('This is known to kick Windows into recognizing pushed DNS servers.'); + // NTP servers $section->addInput(new Form_Checkbox( 'ntp_server_enable', @@ -634,6 +816,33 @@ $section->add($group); + $section->addInput(new Form_Checkbox( + 'nbdd_server_enable', + 'NBDD servers', + 'Provide a NetBIOS over TCP/IP Datagram Distribution Servers list to clients', + $pconfig['nbdd_server_enable'] + )); + + $group = new Form_Group(null); + + $group->add(new Form_Input( + 'nbdd_server1', + null, + 'text', + $pconfig['nbdd_server1'] + ))->setHelp('Server 1'); + + $group->add(new Form_Input( + 'nbdd_server2', + null, + 'text', + $pconfig['nbdd_server2'] + ))->setHelp('Server 2'); + + $group->addClass('nbddservers'); + + $section->add($group); + $custops = new Form_Textarea( 'custom_options', 'Advanced', @@ -671,6 +880,18 @@