#!/usr/bin/perl
###############################################################################
# #
# IPFire.org - A linux based firewall #
# Copyright (C) 2007-2023 IPFire Team #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see . #
# #
###############################################################################
use strict;
use Archive::Zip qw(:ERROR_CODES :CONSTANTS);
use CGI;
use CGI qw/:standard/;
use Date::Parse;
use File::Copy;
use File::Temp qw/ tempfile tempdir /;
use Imager::QRCode;
use MIME::Base32;
use MIME::Base64;
use Net::DNS;
use Net::Ping;
use Net::Telnet;
use Sort::Naturally;
use URI::Encode qw(uri_encode uri_decode);;
require '/var/ipfire/general-functions.pl';
require "${General::swroot}/header.pl";
require "${General::swroot}/countries.pl";
require "${General::swroot}/location-functions.pl";
# enable only the following on debugging purpose
#use warnings;
#use CGI::Carp 'fatalsToBrowser';
my %mainsettings = ();
&General::readhash("${General::swroot}/main/settings", \%mainsettings);
# Supported ciphers for NCP
my @SUPPORTED_CIPHERS = (
"AES-256-GCM",
"AES-128-GCM",
"AES-256-CBC",
"AES-128-CBC",
"CHACHA20-POLY1305",
);
my @LEGACY_CIPHERS = (
"BF-CBC",
"CAST5-CBC",
"DES-CBC",
"DESX-CBC",
"SEED-CBC",
);
my @LEGACY_AUTHS = (
"whirlpool",
);
# Translations for the cipher selection
my %CIPHERS = (
# AES
"AES-256-GCM" => $Lang::tr{'AES-256-GCM'},
"AES-128-GCM" => $Lang::tr{'AES-128-GCM'},
"AES-256-CBC" => $Lang::tr{'AES-256-CBC'},
"AES-128-CBC" => $Lang::tr{'AES-128-CBC'},
# ChaCha20-Poly1305
"CHACHA20-POLY1305" => $Lang::tr{'CHACHA20-POLY1305'},
);
# Use the precomputed DH paramter from RFC7919
my $DHPARAM = "/etc/ssl/ffdhe4096.pem";
my $RW_PID = "/var/run/openvpn-rw.pid";
my $RW_STATUS = "/var/run/openvpn-rw.log";
###
### Initialize variables
###
my %ccdconfhash=();
my %ccdroutehash=();
my %ccdroute2hash=();
my %vpnsettings=();
my %checked=();
my %confighash=();
my %cahash=();
my %selected=();
my %cgiparams = ();
my $warnmessage = '';
my $errormessage = '';
my %settings=();
my $routes_push_file = "${General::swroot}/ovpn/routes_push";
my $confighost="${General::swroot}/fwhosts/customhosts";
my $configgrp="${General::swroot}/fwhosts/customgroups";
my $customnet="${General::swroot}/fwhosts/customnetworks";
my $name;
my $col="";
# Load the configuration (once)
&General::readhash("${General::swroot}/ovpn/settings", \%vpnsettings);
&read_routepushfile(\%vpnsettings);
# Configure any defaults
&General::set_defaults(\%vpnsettings, {
# The RW Server is disabled by default
"ENABLED" => "off",
"VPN_IP" => "$mainsettings{'HOSTNAME'}.$mainsettings{'DOMAINNAME'}",
"DOVPN_SUBNET" => sprintf("10.%d.%d.0/24", rand(256), rand(256)),
# Cryptographic Settings
"DATACIPHERS" => "AES-256-GCM|AES-128-GCM|CHACHA20-POLY1305",
"DAUTH" => "SHA512",
"DCIPHER" => "", # no fallback cipher
# Advanced Settings
"DPROTOCOL" => "udp",
"DDEST_PORT" => 1194,
"DMTU" => 1500,
"MAX_CLIENTS" => 100,
"MSSFIX" => "off",
"TLSAUTH" => "on",
}) unless (%vpnsettings);
# Load CGI parameters
&Header::getcgihash(\%cgiparams, {'wantfile' => 1, 'filevar' => 'FH'});
###
### Useful functions
###
sub iscertlegacy
{
my $file=$_[0];
my @certinfo = &General::system_output("/usr/bin/openssl", "pkcs12", "-info", "-nodes",
"-in", "$file.p12", "-noout", "-passin", "pass:''");
if (index ($certinfo[0], "MAC: sha1") != -1) {
return 1;
}
return 0;
}
sub is_cert_rfc3280_compliant($) {
my $path = shift;
my @output = &General::system_output("/usr/bin/openssl", "x509", "-text", "-in", $path);
return grep(/TLS Web Server Authentication/, @output);
}
sub is_legacy_cipher($) {
my $cipher = shift;
foreach my $c (@LEGACY_CIPHERS) {
return 1 if ($cipher eq $c);
}
return 0;
}
sub is_legacy_auth($) {
my $auth = shift;
foreach my $a (@LEGACY_AUTHS) {
return 1 if ($auth eq $a);
}
return 0;
}
sub cleanssldatabase() {
if (open(FILE, ">${General::swroot}/ovpn/certs/serial")) {
print FILE "01";
close FILE;
}
if (open(FILE, ">${General::swroot}/ovpn/certs/index.txt")) {
print FILE "";
close FILE;
}
if (open(FILE, ">${General::swroot}/ovpn/certs/index.txt.attr")) {
print FILE "";
close FILE;
}
unlink("${General::swroot}/ovpn/certs/index.txt.old");
unlink("${General::swroot}/ovpn/certs/index.txt.attr.old");
unlink("${General::swroot}/ovpn/certs/serial.old");
unlink("${General::swroot}/ovpn/certs/01.pem");
}
sub deletebackupcert
{
if (open(FILE, "${General::swroot}/ovpn/certs/serial.old")) {
my $hexvalue = ;
chomp $hexvalue;
close FILE;
unlink ("${General::swroot}/ovpn/certs/$hexvalue.pem");
}
}
sub writeserverconf {
# Do we require the OpenSSL Legacy Provider?
my $requires_legacy_provider = 0;
open(CONF, ">${General::swroot}/ovpn/server.conf") or die "Unable to open ${General::swroot}/ovpn/server.conf: $!";
flock CONF, 2;
print CONF "#OpenVPN Server conf\n";
print CONF "\n";
print CONF "daemon openvpnserver\n";
print CONF "writepid $RW_PID\n";
print CONF "#DAN prepare OpenVPN for listening on blue and orange\n";
print CONF ";local $vpnsettings{'VPN_IP'}\n";
print CONF "dev tun\n";
print CONF "proto $vpnsettings{'DPROTOCOL'}\n";
print CONF "port $vpnsettings{'DDEST_PORT'}\n";
print CONF "script-security 3\n";
print CONF "ifconfig-pool-persist /var/ipfire/ovpn/ovpn-leases.db 3600\n";
print CONF "client-config-dir /var/ipfire/ovpn/ccd\n";
print CONF "tls-server\n";
print CONF "ca ${General::swroot}/ovpn/ca/cacert.pem\n";
print CONF "cert ${General::swroot}/ovpn/certs/servercert.pem\n";
print CONF "key ${General::swroot}/ovpn/certs/serverkey.pem\n";
print CONF "dh $DHPARAM\n";
# Enable subnet topology
print CONF "# Topology\n";
print CONF "topology subnet\n\n";
my $netaddress = &Network::get_netaddress($vpnsettings{'DOVPN_SUBNET'});
my $subnetmask = &Network::get_netmask($vpnsettings{'DOVPN_SUBNET'});
print CONF "server $netaddress $subnetmask\n";
print CONF "tun-mtu $vpnsettings{'DMTU'}\n";
# Write custom routes
if ($vpnsettings{'ROUTES_PUSH'} ne '') {
my @routes = split(/\|/, $vpnsettings{'ROUTES_PUSH'});
foreach my $route (@routes) {
my $netaddr = &Network::get_netaddress($route);
my $netmask = &Network::get_netmask($route);
if (defined($netaddr) && defined($netmask)) {
print CONF "push \"route ${netaddr} ${netmask}\"\n";
}
}
}
my %ccdconfhash=();
&General::readhasharray("${General::swroot}/ovpn/ccd.conf", \%ccdconfhash);
foreach my $key (keys %ccdconfhash) {
my $a=$ccdconfhash{$key}[1];
my ($b,$c) = split (/\//, $a);
print CONF "route $b ".&General::cidrtosub($c)."\n";
}
my %ccdroutehash=();
&General::readhasharray("${General::swroot}/ovpn/ccdroute", \%ccdroutehash);
foreach my $key (keys %ccdroutehash) {
foreach my $i ( 1 .. $#{$ccdroutehash{$key}}){
my ($a,$b)=split (/\//,$ccdroutehash{$key}[$i]);
print CONF "route $a $b\n";
}
}
if ($vpnsettings{MSSFIX} eq 'on') {
print CONF "mssfix\n";
} else {
print CONF "mssfix 0\n";
}
if ($vpnsettings{FRAGMENT} ne '' && $vpnsettings{'DPROTOCOL'} ne 'tcp') {
print CONF "fragment $vpnsettings{'FRAGMENT'}\n";
}
# Regularly send keep-alive packets
print CONF "keepalive 10 60\n";
print CONF "status-version 1\n";
print CONF "status $RW_STATUS 30\n";
# Cryptography
if ($vpnsettings{'DATACIPHERS'} eq '') {
print CONF "ncp-disable\n";
} else {
print CONF "data-ciphers " . $vpnsettings{'DATACIPHERS'} =~ s/\|/:/gr . "\n";
}
# Enable fallback cipher?
if ($vpnsettings{'DCIPHER'} ne '') {
if (&is_legacy_cipher($vpnsettings{'DCIPHER'})) {
$requires_legacy_provider++;
}
print CONF "data-ciphers-fallback $vpnsettings{'DCIPHER'}\n";
}
print CONF "auth $vpnsettings{'DAUTH'}\n";
if (&is_legacy_auth($vpnsettings{'DAUTH'})) {
$requires_legacy_provider++;
}
# Set TLSv2 as minimum
print CONF "tls-version-min 1.2\n";
if ($vpnsettings{'TLSAUTH'} eq 'on') {
print CONF "tls-auth ${General::swroot}/ovpn/certs/ta.key\n";
}
# Compression
# Use migration to support clients that have compression enabled, but disable
# compression for everybody else.
print CONF "compress migrate\n";
if ($vpnsettings{REDIRECT_GW_DEF1} eq 'on') {
print CONF "push \"redirect-gateway def1\"\n";
}
if ($vpnsettings{DHCP_DOMAIN} ne '') {
print CONF "push \"dhcp-option DOMAIN $vpnsettings{DHCP_DOMAIN}\"\n";
}
if ($vpnsettings{DHCP_DNS} ne '') {
print CONF "push \"dhcp-option DNS $vpnsettings{DHCP_DNS}\"\n";
}
if ($vpnsettings{DHCP_WINS} ne '') {
print CONF "push \"dhcp-option WINS $vpnsettings{DHCP_WINS}\"\n";
}
if ($vpnsettings{MAX_CLIENTS} eq '') {
print CONF "max-clients 100\n";
}
if ($vpnsettings{MAX_CLIENTS} ne '') {
print CONF "max-clients $vpnsettings{MAX_CLIENTS}\n";
}
print CONF "tls-verify /usr/lib/openvpn/verify\n";
print CONF "crl-verify /var/ipfire/ovpn/crls/cacrl.pem\n";
print CONF "auth-user-pass-optional\n";
print CONF "reneg-sec 86400\n";
print CONF "user nobody\n";
print CONF "group nobody\n";
print CONF "persist-key\n";
print CONF "persist-tun\n";
print CONF "verb 3\n";
print CONF "# Log clients connecting/disconnecting\n";
print CONF "client-connect \"/usr/sbin/openvpn-metrics client-connect\"\n";
print CONF "client-disconnect \"/usr/sbin/openvpn-metrics client-disconnect\"\n";
print CONF "\n";
print CONF "# Enable Management Socket\n";
print CONF "management /var/run/openvpn.sock unix\n";
print CONF "management-client-auth\n";
# Enable the legacy provider
if ($requires_legacy_provider > 0) {
print CONF "providers legacy default\n";
}
# Send clients a message when the server is being shut down
print CONF "explicit-exit-notify\n";
close(CONF);
# Rewrite all CCD configurations
&write_ccd_configs();
}
##
## CCD Name
##
# Checks a ccdname for letters, numbers and spaces
sub validccdname($) {
my $name = shift;
# name should be at least one character in length
# but no more than 63 characters
if (length ($name) < 1 || length ($name) > 63) {
return 0;
}
# Only valid characters are a-z, A-Z, 0-9, space and -
if ($name !~ /^[a-zA-Z0-9 -]*$/) {
return 0;
}
return 1;
}
sub delccdnet($) {
my $name = shift;
my %conns = ();
my %subnets = ();
# Load all connections
&General::readhasharray("${General::swroot}/ovpn/ovpnconfig", \%conns);
# Check if the subnet is in use
foreach my $key (keys %conns) {
if ($conns{$key}[32] eq $name) {
return $Lang::tr{'ccd err hostinnet'};
}
}
# Load all subnets
&General::readhasharray("${General::swroot}/ovpn/ccd.conf", \%subnets);
# Remove the subnet
foreach my $key (keys %subnets) {
if ($subnets{$key}[0] eq $name){
delete $subnets{$key};
}
}
# Write the subnets back
&General::writehasharray("${General::swroot}/ovpn/ccd.conf", \%subnets);
# Update the server configuration to remove routes
&writeserverconf();
}
# Returns the network with the matching name
sub get_cdd_network($) {
my $name = shift;
my %subnets = ();
# Load all subnets
&General::readhasharray("${General::swroot}/ovpn/ccd.conf", \%subnets);
# Find the matching subnet
foreach my $key (keys %subnets) {
if ($subnets{$key}[0] eq $name) {
return $subnets{$key}[1];
}
}
return undef;
}
sub addccdnet($$) {
my $name = shift;
my $network = shift;
my %ccdconfhash = ();
# Check if the name is valid
unless (&validccdname($name)) {
return $Lang::tr{'ccd err invalidname'};
}
# Fetch the network address & prefix
my $address = &Network::get_netaddress($network);
my $prefix = &Network::get_prefix($network);
# If we could not decode the subnet, it must be invalid
if (!defined $address || !defined $prefix) {
return $Lang::tr{'ccd err invalidnet'};
# If the network is smaller than /30, there is no point in using it
} elsif ($prefix > 30) {
return $Lang::tr{'ccd err invalidnet'};
}
# Read the configuration
&General::readhasharray("${General::swroot}/ovpn/ccd.conf", \%ccdconfhash);
# Create a new entry
my $key = &General::findhasharraykey(\%ccdconfhash);
# Store name
$ccdconfhash{$key}[0] = $name;
$ccdconfhash{$key}[1] = "$address/$prefix";
# Write the hash back
&General::writehasharray("${General::swroot}/ovpn/ccd.conf", \%ccdconfhash);
# Update the server configuration to add routes
&writeserverconf();
}
sub modccdnet($$) {
my $subnet = shift;
my $newname = shift;
my $oldname;
my %ccdconfhash=();
my %conns=();
# Check if the new name is valid
unless (&validccdname($newname)) {
$errormessage = $Lang::tr{'ccd err invalidname'};
return;
}
# Load all subnets
&General::readhasharray("${General::swroot}/ovpn/ccd.conf", \%ccdconfhash);
# Check if the name already exists
foreach my $key (keys %ccdconfhash) {
if ($ccdconfhash{$key}[0] eq $newname) {
return $Lang::tr{'ccd err netadrexist'};
}
}
# Update!
foreach my $key (keys %ccdconfhash) {
if ($ccdconfhash{$key}[1] eq $subnet) {
$oldname = $ccdconfhash{$key}[0];
$ccdconfhash{$key}[0] = $newname;
last;
}
}
# Load all configurations
&General::readhasharray("${General::swroot}/ovpn/ovpnconfig", \%conns);
# Update all matching connections
foreach my $key (keys %conns) {
if ($conns{$key}[32] eq $oldname) {
$conns{$key}[32] = $newname;
}
}
# Write back the configuration
&General::writehasharray("${General::swroot}/ovpn/ccd.conf", \%ccdconfhash);
&General::writehasharray("${General::swroot}/ovpn/ovpnconfig", \%conns);
}
sub get_ccd_client_routes($) {
my $name = shift;
my %client_routes = ();
my @routes = ();
# Load all client routes
&General::readhasharray("${General::swroot}/ovpn/ccdroute", \%client_routes);
foreach my $key (keys %client_routes) {
if ($client_routes{$key}[0] eq $name) {
push(@routes, $client_routes{$key}[1]);
}
}
return @routes;
}
sub get_ccd_server_routes($) {
my $name = shift;
my %server_routes = ();
my @routes = ();
# Load all server routes
&General::readhasharray("${General::swroot}/ovpn/ccdroute2", \%server_routes);
foreach my $key (keys %server_routes) {
if ($server_routes{$key}[0] eq $name) {
my $i = 1;
while (my $route = $server_routes{$key}[$i++]) {
push(@routes, $route);
}
}
}
return @routes;
}
# This function rewrites all CCD configuration files upon change
sub write_ccd_configs() {
my %conns = ();
# Load all configurations
&General::readhasharray("${General::swroot}/ovpn/ovpnconfig", \%conns);
foreach my $key (keys %conns) {
my $name = $conns{$key}[1];
my $type = $conns{$key}[3];
# Skip anything that isn't a host connection
next unless ($type eq "host");
my $filename = "${General::swroot}/ovpn/ccd/$conns{$key}[2]";
# Open the configuration file
open(CONF, ">${filename}") or die "Unable to open ${filename} for writing: $!";
# Write a header
print CONF "# OpenVPN Client Configuration File\n\n";
# Fetch the allocated IP address (if any)
my $pool = $conns{$key}[32];
my $address = $conns{$key}[33];
# If the client has a dynamically allocated IP address, there is nothing to do
if ($pool eq "dynamic") {
print CONF "# This client uses the dynamic pool\n\n";
# Otherwise we need to push the selected IP address
} else {
$address = &convert_top30_ccd_allocation($address);
# Fetch the network of the pool
my $network = &get_cdd_network($pool);
my $netmask = &Network::get_netmask($network);
if (defined $address && defined $network && defined $netmask) {
print CONF "# Allocated IP address from $pool\n";
print CONF "ifconfig-push ${address} ${netmask}\n\n";
}
}
# Redirect Gateway?
my $redirect = $conns{$key}[34];
if ($redirect eq "on") {
print CONF "# Redirect all traffic to us\n";
print CONF "push redirect-gateway\n\n";
}
# DHCP Options
my %options = (
"DNS" => (
$conns{$key}[35],
$conns{$key}[36],
),
"WINS" => (
$conns{$key}[37],
),
);
print CONF "# DHCP Options";
foreach my $option (keys %options) {
foreach (@options{$option}) {
# Skip empty options
next if ($_ eq "");
print CONF "push \"dhcp-option $option $_\"\n";
}
}
# Newline
print CONF "\n";
# Networks routed to client
my @client_routes = &get_ccd_client_routes($name);
if (scalar @client_routes) {
print CONF "# Networks routed to the client\n";
foreach my $route (@client_routes) {
my $netaddress = &Network::get_netaddress($route);
my $netmask = &Network::get_netmask($route);
if (!defined $netaddress || !defined $netmask) {
next;
}
print CONF "iroute $netaddress $netmask\n";
}
# Newline
print CONF "\n";
}
# Networks routed to server
my @server_routes = &get_ccd_server_routes($name);
if (scalar @server_routes) {
print CONF "# Networks routed to the server\n";
foreach my $route (@server_routes) {
my $netaddress = &Network::get_netaddress($route);
my $netmask = &Network::get_netmask($route);
if (!defined $netaddress || !defined $netmask) {
next;
}
print CONF "push \"route $netaddress $netmask\"\n";
}
# Newline
print CONF "\n";
}
close CONF;
}
}
sub ccdmaxclients($) {
my $network = shift;
# Fetch the prefix
my $prefix = &Network::get_prefix($network);
# Return undef on invalid input
if (!defined $prefix) {
return undef;
}
# We take three addresses away: the network base address, the gateway, and broadcast
return (1 << (32 - $prefix)) - 3;
}
# Lists all selectable CCD addresses for the given network
sub getccdadresses($) {
my $network = shift;
# Collect all available addresses
my @addresses = ();
# Convert the network into binary
my ($start, $netmask) = &Network::network2bin($network);
# Fetch the broadcast address
my $broadcast = &Network::get_broadcast($network);
$broadcast = &Network::ip2bin($broadcast);
# Fail if we could not parse the network
if (!defined $start || !defined $netmask || !defined $broadcast) {
return undef;
}
# Skip the base address and gateway
$start += 2;
while ($start < $broadcast) {
push(@addresses, &Network::bin2ip($start++));
}
return @addresses;
}
sub convert_top30_ccd_allocation($) {
my $address = shift;
# Do nothing if the address does not end on /30
return $address unless ($address =~ m/\/30$/);
# Fetch the network base address
my $netaddress = &Network::get_netaddress($address);
# Break on invalid input
return undef if (!defined $netaddress);
# The client IP address was the second address of the subnet
return &Network::find_next_ip_address($netaddress, 2);
}
sub get_addresses_in_use($) {
my $network = shift;
my %conns = ();
# Load all connections
&General::readhasharray("${General::swroot}/ovpn/ovpnconfig", \%conns);
my @addresses = ();
# Check if the address is in use
foreach my $key (keys %conns) {
my $address = &convert_top30_ccd_allocation($conns{$key}[33]);
# Skip on invalid inputs
next if (!defined $address);
# If the first address is part of the network, we have a match
if (&Network::ip_address_in_network($address, $network)) {
push(@addresses, $address);
}
}
return @addresses;
}
sub fillselectbox($$) {
my $boxname = shift;
my $network = shift;
my @selected = shift;
# Fetch all available addresses for this network
my @addresses = &getccdadresses($network);
# Fetch all addresses in use
my @addresses_in_use = &get_addresses_in_use($network);
print "";
}
# XXX THIS WILL NO LONGER WORK
sub check_routes_push
{
my $val=$_[0];
my ($ip,$cidr) = split (/\//, $val);
##check for existing routes in routes_push
if (-e "${General::swroot}/ovpn/routes_push") {
open(FILE,"${General::swroot}/ovpn/routes_push");
while () {
$_=~s/\s*$//g;
my ($ip2,$cidr2) = split (/\//,"$_");
my $val2=$ip2."/".&General::iporsubtodec($cidr2);
if($val eq $val2){
return 0;
}
#subnetcheck
if (&General::IpInSubnet ($ip,$ip2,&General::iporsubtodec($cidr2))){
return 0;
}
};
close(FILE);
}
return 1;
}
sub check_ccdroute
{
my %ccdroutehash=();
my $val=$_[0];
my ($ip,$cidr) = split (/\//, $val);
#check for existing routes in ccdroute
&General::readhasharray("${General::swroot}/ovpn/ccdroute", \%ccdroutehash);
foreach my $key (keys %ccdroutehash) {
foreach my $i (1 .. $#{$ccdroutehash{$key}}) {
if (&General::iporsubtodec($val) eq $ccdroutehash{$key}[$i] && $ccdroutehash{$key}[0] ne $cgiparams{'NAME'}){
return 0;
}
my ($ip2,$cidr2) = split (/\//,$ccdroutehash{$key}[$i]);
#subnetcheck
if (&General::IpInSubnet ($ip,$ip2,$cidr2)&& $ccdroutehash{$key}[0] ne $cgiparams{'NAME'} ){
return 0;
}
}
}
return 1;
}
sub check_ccdconf
{
my %ccdconfhash=();
my $val=$_[0];
my ($ip,$cidr) = split (/\//, $val);
#check for existing routes in ccdroute
&General::readhasharray("${General::swroot}/ovpn/ccd.conf", \%ccdconfhash);
foreach my $key (keys %ccdconfhash) {
if (&General::iporsubtocidr($val) eq $ccdconfhash{$key}[1]){
return 0;
}
my ($ip2,$cidr2) = split (/\//,$ccdconfhash{$key}[1]);
#subnetcheck
if (&General::IpInSubnet ($ip,$ip2,&General::cidrtosub($cidr2))){
return 0;
}
}
return 1;
}
# -------------------------------------------------------------------
sub read_routepushfile($) {
my $hash = shift;
# Don't read the legacy file if we already have a value
if ($hash->{'ROUTES_PUSH'} ne "") {
unlink($routes_push_file);
# This is some legacy code that reads the routes file if it is still present
} elsif (-e "$routes_push_file") {
delete $hash->{'ROUTES_PUSH'};
my @routes = ();
open(FILE,"$routes_push_file");
while () {
push(@routes, $_);
}
close(FILE);
$hash->{'ROUTES_PUSH'} = join("|", @routes);
}
}
sub writecollectdconf {
my $vpncollectd;
my %ccdhash=();
open(COLLECTDVPN, ">${General::swroot}/ovpn/collectd.vpn") or die "Unable to open collectd.vpn: $!";
print COLLECTDVPN "Loadplugin openvpn\n";
print COLLECTDVPN "\n";
print COLLECTDVPN "\n";
print COLLECTDVPN "Statusfile \"${RW_STATUS}\"\n";
&General::readhasharray("${General::swroot}/ovpn/ovpnconfig", \%ccdhash);
foreach my $key (keys %ccdhash) {
if ($ccdhash{$key}[0] eq 'on' && $ccdhash{$key}[3] eq 'net') {
print COLLECTDVPN "Statusfile \"/var/run/openvpn/$ccdhash{$key}[1]-n2n\"\n";
}
}
print COLLECTDVPN "\n";
close(COLLECTDVPN);
# Reload collectd afterwards
&General::system("/usr/local/bin/collectdctrl", "restart");
}
sub openvpn_status($) {
my $port = shift;
# Create a new Telnet session
my $telnet = new Net::Telnet(
Port => $port,
Timeout => 1,
Errmode => "return",
);
# Connect
$telnet->open("127.0.0.1");
# Send a command
my @output = $telnet->cmd(
String => "state",
Prompt => "/(END.*\n|ERROR:.*\n)/"
);
my ($time, $status) = split(/\,/, $output[1]);
###
#CONNECTING -- OpenVPN's initial state.
#WAIT -- (Client only) Waiting for initial response from server.
#AUTH -- (Client only) Authenticating with server.
#GET_CONFIG -- (Client only) Downloading configuration options from server.
#ASSIGN_IP -- Assigning IP address to virtual network interface.
#ADD_ROUTES -- Adding routes to system.
#CONNECTED -- Initialization Sequence Completed.
#RECONNECTING -- A restart has occurred.
#EXITING -- A graceful exit is in progress.
####
if ($status eq "CONNECTING") {
return "DISCONNECTED";
} elsif ($status eq "WAIT") {
return "DISCONNECTED";
} elsif ($status eq "AUTH") {
return "DISCONNECTED";
} elsif ($status eq "GET_CONFIG") {
return "DISCONNECTED";
} elsif ($status eq "ASSIGN_IP") {
return "DISCONNECTED";
} elsif ($status eq "ADD_ROUTES") {
return "DISCONNECTED";
} elsif ($status eq "RECONNECTING") {
return "CONNECTED";
} elsif ($status eq "EXITING") {
return "DISCONNECTED";
}
return $status;
}
# Hook to regenerate the configuration files
if ($ENV{"REMOTE_ADDR"} eq "") {
&writeserverconf();
exit(0);
}
###
### Save Advanced options
###
if ($cgiparams{'ACTION'} eq $Lang::tr{'save-adv-options'}) {
$vpnsettings{'DPROTOCOL'} = $cgiparams{'DPROTOCOL'};
$vpnsettings{'DDEST_PORT'} = $cgiparams{'DDEST_PORT'};
$vpnsettings{'DMTU'} = $cgiparams{'DMTU'};
$vpnsettings{'MAX_CLIENTS'} = $cgiparams{'MAX_CLIENTS'};
$vpnsettings{'REDIRECT_GW_DEF1'} = $cgiparams{'REDIRECT_GW_DEF1'};
$vpnsettings{'DHCP_DOMAIN'} = $cgiparams{'DHCP_DOMAIN'};
$vpnsettings{'DHCP_DNS'} = $cgiparams{'DHCP_DNS'};
$vpnsettings{'DHCP_WINS'} = $cgiparams{'DHCP_WINS'};
$vpnsettings{'ROUTES_PUSH'} = $cgiparams{'ROUTES_PUSH'};
$vpnsettings{'DATACIPHERS'} = $cgiparams{'DATACIPHERS'};
$vpnsettings{'DCIPHER'} = $cgiparams{'DCIPHER'};
$vpnsettings{'DAUTH'} = $cgiparams{'DAUTH'};
$vpnsettings{'TLSAUTH'} = $cgiparams{'TLSAUTH'};
# We must have at least one cipher selected
if ($cgiparams{'DATACIPHERS'} eq '') {
$errormessage = $Lang::tr{'ovpn no cipher selected'};
goto ADV_ERROR;
}
# Split data ciphers
my @dataciphers = split(/\|/, $cgiparams{'DATACIPHERS'});
# Check if all ciphers are supported
foreach my $cipher (@dataciphers) {
if (!grep(/^$cipher$/, @SUPPORTED_CIPHERS)) {
$errormessage = $Lang::tr{'ovpn unsupported cipher selected'};
goto ADV_ERROR;
}
}
# Check port
unless (&General::validport($cgiparams{'DDEST_PORT'})) {
$errormessage = $Lang::tr{'invalid port'};
goto ADV_ERROR;
}
# Check MTU
if (($cgiparams{'DMTU'} eq "") || (($cgiparams{'DMTU'}) < 1280 )) {
$errormessage = $Lang::tr{'invalid mtu input'};
goto ADV_ERROR;
}
if ($cgiparams{'FRAGMENT'} eq '') {
delete $vpnsettings{'FRAGMENT'};
} else {
if ($cgiparams{'FRAGMENT'} !~ /^[0-9]+$/) {
$errormessage = "Incorrect value, please insert only numbers.";
goto ADV_ERROR;
} else {
$vpnsettings{'FRAGMENT'} = $cgiparams{'FRAGMENT'};
}
}
if ($cgiparams{'MSSFIX'} ne 'on') {
delete $vpnsettings{'MSSFIX'};
} else {
$vpnsettings{'MSSFIX'} = $cgiparams{'MSSFIX'};
}
if ($cgiparams{'DHCP_DOMAIN'} ne ''){
unless (&General::validdomainname($cgiparams{'DHCP_DOMAIN'}) || &General::validip($cgiparams{'DHCP_DOMAIN'})) {
$errormessage = $Lang::tr{'invalid input for dhcp domain'};
goto ADV_ERROR;
}
}
if ($cgiparams{'DHCP_DNS'} ne ''){
unless (&General::validfqdn($cgiparams{'DHCP_DNS'}) || &General::validip($cgiparams{'DHCP_DNS'})) {
$errormessage = $Lang::tr{'invalid input for dhcp dns'};
goto ADV_ERROR;
}
}
if ($cgiparams{'DHCP_WINS'} ne ''){
unless (&General::validfqdn($cgiparams{'DHCP_WINS'}) || &General::validip($cgiparams{'DHCP_WINS'})) {
$errormessage = $Lang::tr{'invalid input for dhcp wins'};
goto ADV_ERROR;
}
}
# Validate pushed routes
if ($cgiparams{'ROUTES_PUSH'} ne ''){
my @temp = split(/\n/, $cgiparams{'ROUTES_PUSH'});
my @routes = ();
foreach my $route (@temp) {
chomp($route);
# Remove any excess whitespace
$route =~ s/^\s+//g;
$route =~ s/\s+$//g;
# Skip empty lines
next if ($route eq "");
unless (&Network::check_subnet($route)) {
$errormessage = "$Lang::tr{'ovpn errmsg invalid route'}: $route";
goto ADV_ERROR;
}
push(@routes, $route);
}
$vpnsettings{'ROUTES_PUSH'} = join("|", @routes);
}
if ((length($cgiparams{'MAX_CLIENTS'}) == 0) || (($cgiparams{'MAX_CLIENTS'}) < 1 ) || (($cgiparams{'MAX_CLIENTS'}) > 1024 )) {
$errormessage = $Lang::tr{'invalid input for max clients'};
goto ADV_ERROR;
}
# Store our configuration
&General::writehash("${General::swroot}/ovpn/settings", \%vpnsettings);
# Write the server configuration
&writeserverconf();
# Restart the server if it is enabled
if ($vpnsettings{'ENABLED'} eq "on") {
&General::system("/usr/local/bin/openvpnctrl", "rw", "restart");
}
}
if ($cgiparams{'ACTION'} eq $Lang::tr{'save'} && $cgiparams{'TYPE'} eq 'net' && $cgiparams{'SIDE'} eq 'server')
{
my @remsubnet = split(/\//,$cgiparams{'REMOTE_SUBNET'});
my @ovsubnettemp = split(/\./,$cgiparams{'OVPN_SUBNET'});
my $ovsubnet = "$ovsubnettemp[0].$ovsubnettemp[1].$ovsubnettemp[2]";
my $tunmtu = '';
unless(-d "${General::swroot}/ovpn/n2nconf/"){mkdir "${General::swroot}/ovpn/n2nconf", 0755 or die "Unable to create dir $!";}
unless(-d "${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}"){mkdir "${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}", 0770 or die "Unable to create dir $!";}
open(SERVERCONF, ">${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}/$cgiparams{'NAME'}.conf") or die "Unable to open ${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}/$cgiparams{'NAME'}.conf: $!";
flock SERVERCONF, 2;
print SERVERCONF "# IPFire n2n Open VPN Server Config by ummeegge und m.a.d\n";
print SERVERCONF "\n";
print SERVERCONF "# User Security\n";
print SERVERCONF "user nobody\n";
print SERVERCONF "group nobody\n";
print SERVERCONF "persist-tun\n";
print SERVERCONF "persist-key\n";
print SERVERCONF "script-security 2\n";
print SERVERCONF "# IP/DNS for remote Server Gateway\n";
if ($cgiparams{'REMOTE'} ne '') {
print SERVERCONF "remote $cgiparams{'REMOTE'}\n";
}
print SERVERCONF "float\n";
print SERVERCONF "# IP adresses of the VPN Subnet\n";
print SERVERCONF "ifconfig $ovsubnet.1 $ovsubnet.2\n";
print SERVERCONF "# Client Gateway Network\n";
print SERVERCONF "route $remsubnet[0] $remsubnet[1]\n";
print SERVERCONF "up \"/etc/init.d/static-routes start\"\n";
print SERVERCONF "# tun Device\n";
print SERVERCONF "dev tun\n";
print SERVERCONF "#Logfile for statistics\n";
print SERVERCONF "status-version 1\n";
print SERVERCONF "status /var/run/openvpn/$cgiparams{'NAME'}-n2n 10\n";
print SERVERCONF "# Port and Protokol\n";
print SERVERCONF "port $cgiparams{'DEST_PORT'}\n";
if ($cgiparams{'PROTOCOL'} eq 'tcp') {
print SERVERCONF "proto tcp4-server\n";
print SERVERCONF "# Packet size\n";
if ($cgiparams{'MTU'} eq '') {$tunmtu = '1400'} else {$tunmtu = $cgiparams{'MTU'}};
print SERVERCONF "tun-mtu $tunmtu\n";
}
if ($cgiparams{'PROTOCOL'} eq 'udp') {
print SERVERCONF "proto udp4\n";
print SERVERCONF "# Paketsize\n";
if ($cgiparams{'MTU'} eq '') {$tunmtu = '1500'} else {$tunmtu = $cgiparams{'MTU'}};
print SERVERCONF "tun-mtu $tunmtu\n";
if ($cgiparams{'FRAGMENT'} ne '') {print SERVERCONF "fragment $cgiparams{'FRAGMENT'}\n";}
if ($cgiparams{'MSSFIX'} eq 'on') {print SERVERCONF "mssfix\n"; } else { print SERVERCONF "mssfix 0\n" };
}
print SERVERCONF "# Auth. Server\n";
print SERVERCONF "tls-server\n";
print SERVERCONF "ca ${General::swroot}/ovpn/ca/cacert.pem\n";
print SERVERCONF "cert ${General::swroot}/ovpn/certs/servercert.pem\n";
print SERVERCONF "key ${General::swroot}/ovpn/certs/serverkey.pem\n";
print SERVERCONF "dh $DHPARAM\n";
print SERVERCONF "# Cipher\n";
print SERVERCONF "cipher $cgiparams{'DCIPHER'}\n";
# If GCM cipher is used, do not use --auth
if (($cgiparams{'DCIPHER'} eq 'AES-256-GCM') ||
($cgiparams{'DCIPHER'} eq 'AES-192-GCM') ||
($cgiparams{'DCIPHER'} eq 'AES-128-GCM')) {
print SERVERCONF unless "# HMAC algorithm\n";
print SERVERCONF unless "auth $cgiparams{'DAUTH'}\n";
} else {
print SERVERCONF "# HMAC algorithm\n";
print SERVERCONF "auth $cgiparams{'DAUTH'}\n";
}
# Set TLSv1.2 as minimum
print SERVERCONF "tls-version-min 1.2\n";
if ($cgiparams{'COMPLZO'} eq 'on') {
print SERVERCONF "# Enable Compression\n";
print SERVERCONF "comp-lzo\n";
}
print SERVERCONF "# Debug Level\n";
print SERVERCONF "verb 3\n";
print SERVERCONF "# Tunnel check\n";
print SERVERCONF "keepalive 10 60\n";
print SERVERCONF "# Start as daemon\n";
print SERVERCONF "daemon $cgiparams{'NAME'}n2n\n";
print SERVERCONF "writepid /var/run/$cgiparams{'NAME'}n2n.pid\n";
print SERVERCONF "# Activate Management Interface and Port\n";
if ($cgiparams{'OVPN_MGMT'} eq '') {print SERVERCONF "management localhost $cgiparams{'DEST_PORT'}\n"}
else {print SERVERCONF "management localhost $cgiparams{'OVPN_MGMT'}\n"};
close(SERVERCONF);
}
if ($cgiparams{'ACTION'} eq $Lang::tr{'save'} && $cgiparams{'TYPE'} eq 'net' && $cgiparams{'SIDE'} eq 'client')
{
my @ovsubnettemp = split(/\./,$cgiparams{'OVPN_SUBNET'});
my $ovsubnet = "$ovsubnettemp[0].$ovsubnettemp[1].$ovsubnettemp[2]";
my @remsubnet = split(/\//,$cgiparams{'REMOTE_SUBNET'});
my $tunmtu = '';
unless(-d "${General::swroot}/ovpn/n2nconf/"){mkdir "${General::swroot}/ovpn/n2nconf", 0755 or die "Unable to create dir $!";}
unless(-d "${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}"){mkdir "${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}", 0770 or die "Unable to create dir $!";}
open(CLIENTCONF, ">${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}/$cgiparams{'NAME'}.conf") or die "Unable to open ${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}/$cgiparams{'NAME'}.conf: $!";
flock CLIENTCONF, 2;
print CLIENTCONF "# IPFire rewritten n2n Open VPN Client Config by ummeegge und m.a.d\n";
print CLIENTCONF "#\n";
print CLIENTCONF "# User Security\n";
print CLIENTCONF "user nobody\n";
print CLIENTCONF "group nobody\n";
print CLIENTCONF "persist-tun\n";
print CLIENTCONF "persist-key\n";
print CLIENTCONF "script-security 2\n";
print CLIENTCONF "# IP/DNS for remote Server Gateway\n";
print CLIENTCONF "remote $cgiparams{'REMOTE'}\n";
print CLIENTCONF "float\n";
print CLIENTCONF "# IP adresses of the VPN Subnet\n";
print CLIENTCONF "ifconfig $ovsubnet.2 $ovsubnet.1\n";
print CLIENTCONF "# Server Gateway Network\n";
print CLIENTCONF "route $remsubnet[0] $remsubnet[1]\n";
print CLIENTCONF "up \"/etc/init.d/static-routes start\"\n";
print CLIENTCONF "# tun Device\n";
print CLIENTCONF "dev tun\n";
print CLIENTCONF "#Logfile for statistics\n";
print CLIENTCONF "status-version 1\n";
print CLIENTCONF "status /var/run/openvpn/$cgiparams{'NAME'}-n2n 10\n";
print CLIENTCONF "# Port and Protokol\n";
print CLIENTCONF "port $cgiparams{'DEST_PORT'}\n";
if ($cgiparams{'PROTOCOL'} eq 'tcp') {
print CLIENTCONF "proto tcp4-client\n";
print CLIENTCONF "# Packet size\n";
if ($cgiparams{'MTU'} eq '') {$tunmtu = '1400'} else {$tunmtu = $cgiparams{'MTU'}};
print CLIENTCONF "tun-mtu $tunmtu\n";
}
if ($cgiparams{'PROTOCOL'} eq 'udp') {
print CLIENTCONF "proto udp4\n";
print CLIENTCONF "# Paketsize\n";
if ($cgiparams{'MTU'} eq '') {$tunmtu = '1500'} else {$tunmtu = $cgiparams{'MTU'}};
print CLIENTCONF "tun-mtu $tunmtu\n";
if ($cgiparams{'FRAGMENT'} ne '') {print CLIENTCONF "fragment $cgiparams{'FRAGMENT'}\n";}
if ($cgiparams{'MSSFIX'} eq 'on') {print CLIENTCONF "mssfix\n"; } else { print CLIENTCONF "mssfix 0\n" };
}
# Check host certificate if X509 is RFC3280 compliant.
# If not, old --ns-cert-type directive will be used.
# If appropriate key usage extension exists, new --remote-cert-tls directive will be used.
my @hostcert = &General::system_output("/usr/bin/openssl", "x509", "-text", "-in", "${General::swroot}/ovpn/certs/servercert.pem");
if ( ! grep(/TLS Web Server Authentication/, @hostcert)) {
print CLIENTCONF "ns-cert-type server\n";
} else {
print CLIENTCONF "remote-cert-tls server\n";
}
print CLIENTCONF "# Auth. Client\n";
print CLIENTCONF "tls-client\n";
print CLIENTCONF "# Cipher\n";
print CLIENTCONF "cipher $cgiparams{'DCIPHER'}\n";
print CLIENTCONF "pkcs12 ${General::swroot}/ovpn/certs/$cgiparams{'NAME'}.p12\r\n";
# If GCM cipher is used, do not use --auth
if (($cgiparams{'DCIPHER'} eq 'AES-256-GCM') ||
($cgiparams{'DCIPHER'} eq 'AES-192-GCM') ||
($cgiparams{'DCIPHER'} eq 'AES-128-GCM')) {
print CLIENTCONF unless "# HMAC algorithm\n";
print CLIENTCONF unless "auth $cgiparams{'DAUTH'}\n";
} else {
print CLIENTCONF "# HMAC algorithm\n";
print CLIENTCONF "auth $cgiparams{'DAUTH'}\n";
}
# Set TLSv1.2 as minimum
print CLIENTCONF "tls-version-min 1.2\n";
if ($cgiparams{'COMPLZO'} eq 'on') {
print CLIENTCONF "# Enable Compression\n";
print CLIENTCONF "comp-lzo\n";
}
print CLIENTCONF "# Debug Level\n";
print CLIENTCONF "verb 3\n";
print CLIENTCONF "# Tunnel check\n";
print CLIENTCONF "keepalive 10 60\n";
print CLIENTCONF "# Start as daemon\n";
print CLIENTCONF "daemon $cgiparams{'NAME'}n2n\n";
print CLIENTCONF "writepid /var/run/$cgiparams{'NAME'}n2n.pid\n";
print CLIENTCONF "# Activate Management Interface and Port\n";
if ($cgiparams{'OVPN_MGMT'} eq '') {print CLIENTCONF "management localhost $cgiparams{'DEST_PORT'}\n"}
else {print CLIENTCONF "management localhost $cgiparams{'OVPN_MGMT'}\n"};
if (&iscertlegacy("${General::swroot}/ovpn/certs/$cgiparams{'NAME'}")) {
print CLIENTCONF "providers legacy default\n";
}
close(CLIENTCONF);
}
###
### Save main settings
###
if ($cgiparams{'ACTION'} eq $Lang::tr{'save'} && $cgiparams{'TYPE'} eq '' && $cgiparams{'KEY'} eq '') {
#DAN do we really need (to to check) this value? Besides if we listen on blue and orange too,
#DAN this value has to leave.
if ($cgiparams{'ENABLED'} eq 'on'){
unless (&General::validfqdn($cgiparams{'VPN_IP'}) || &General::validip($cgiparams{'VPN_IP'})) {
$errormessage = $Lang::tr{'invalid input for hostname'};
goto SETTINGS_ERROR;
}
}
if (! &General::validipandmask($cgiparams{'DOVPN_SUBNET'})) {
$errormessage = $Lang::tr{'ovpn subnet is invalid'};
goto SETTINGS_ERROR;
}
my @tmpovpnsubnet = split("\/",$cgiparams{'DOVPN_SUBNET'});
if (&General::IpInSubnet ( $Network::ethernet{'RED_ADDRESS'},
$tmpovpnsubnet[0], $tmpovpnsubnet[1])) {
$errormessage = "$Lang::tr{'ovpn subnet overlap'} IPFire RED Network $Network::ethernet{'RED_ADDRESS'}";
goto SETTINGS_ERROR;
}
if (&General::IpInSubnet ( $Network::ethernet{'GREEN_ADDRESS'},
$tmpovpnsubnet[0], $tmpovpnsubnet[1])) {
$errormessage = "$Lang::tr{'ovpn subnet overlap'} IPFire Green Network $Network::ethernet{'GREEN_ADDRESS'}";
goto SETTINGS_ERROR;
}
if (&General::IpInSubnet ( $Network::ethernet{'BLUE_ADDRESS'},
$tmpovpnsubnet[0], $tmpovpnsubnet[1])) {
$errormessage = "$Lang::tr{'ovpn subnet overlap'} IPFire Blue Network $Network::ethernet{'BLUE_ADDRESS'}";
goto SETTINGS_ERROR;
}
if (&General::IpInSubnet ( $Network::ethernet{'ORANGE_ADDRESS'},
$tmpovpnsubnet[0], $tmpovpnsubnet[1])) {
$errormessage = "$Lang::tr{'ovpn subnet overlap'} IPFire Orange Network $Network::ethernet{'ORANGE_ADDRESS'}";
goto SETTINGS_ERROR;
}
open(ALIASES, "${General::swroot}/ethernet/aliases") or die 'Unable to open aliases file.';
while ()
{
chomp($_);
my @tempalias = split(/\,/,$_);
if ($tempalias[1] eq 'on') {
if (&General::IpInSubnet ($tempalias[0] ,
$tmpovpnsubnet[0], $tmpovpnsubnet[1])) {
$errormessage = "$Lang::tr{'ovpn subnet overlap'} IPFire alias entry $tempalias[0]";
}
}
}
close(ALIASES);
if ($errormessage ne ''){
goto SETTINGS_ERROR;
}
if ($cgiparams{'ENABLED'} !~ /^(on|off|)$/) {
$errormessage = $Lang::tr{'invalid input'};
goto SETTINGS_ERROR;
}
# Create ta.key for tls-auth if not presant
if ($cgiparams{'TLSAUTH'} eq 'on') {
if ( ! -e "${General::swroot}/ovpn/certs/ta.key") {
# This system call is safe, because all arguements are passed as an array.
system("/usr/sbin/openvpn", "--genkey", "secret", "${General::swroot}/ovpn/certs/ta.key");
if ($?) {
$errormessage = "$Lang::tr{'openssl produced an error'}: $?";
goto SETTINGS_ERROR;
}
}
}
$vpnsettings{'ENABLED'} = $cgiparams{'ENABLED'};
$vpnsettings{'VPN_IP'} = $cgiparams{'VPN_IP'};
$vpnsettings{'DOVPN_SUBNET'} = $cgiparams{'DOVPN_SUBNET'};
# Store our configuration
&General::writehash("${General::swroot}/ovpn/settings", \%vpnsettings);
# Write the OpenVPN server configuration
&writeserverconf();
# Start/Stop the server
if ($vpnsettings{'ENABLED'} eq "on") {
&General::system("/usr/local/bin/openvpnctrl", "rw", "restart");
} else {
&General::system("/usr/local/bin/openvpnctrl", "rw", "stop");
}
SETTINGS_ERROR:
###
### Reset all step 2
###
}elsif ($cgiparams{'ACTION'} eq $Lang::tr{'remove x509'} && $cgiparams{'AREUSURE'} eq 'yes') {
my $file = '';
&General::readhasharray("${General::swroot}/ovpn/ovpnconfig", \%confighash);
# Stop all N2N connections
&General::system("/usr/local/bin/openvpnctrl", "n2n", "stop");
foreach my $key (keys %confighash) {
my $name = $confighash{$cgiparams{'$key'}}[1];
if ($confighash{$key}[4] eq 'cert') {
delete $confighash{$cgiparams{'$key'}};
}
&General::system("/usr/local/bin/openvpnctrl", "n2n", "delete", "$name");
}
while ($file = glob("${General::swroot}/ovpn/ca/*")) {
unlink $file;
}
while ($file = glob("${General::swroot}/ovpn/certs/*")) {
unlink $file;
}
while ($file = glob("${General::swroot}/ovpn/crls/*")) {
unlink $file;
}
&cleanssldatabase();
if (open(FILE, ">${General::swroot}/ovpn/caconfig")) {
print FILE "";
close FILE;
}
if (open(FILE, ">${General::swroot}/ovpn/ccdroute")) {
print FILE "";
close FILE;
}
if (open(FILE, ">${General::swroot}/ovpn/ccdroute2")) {
print FILE "";
close FILE;
}
while ($file = glob("${General::swroot}/ovpn/ccd/*")) {
unlink $file
}
while ($file = glob("${General::swroot}/ovpn/ccd/*")) {
unlink $file
}
if (open(FILE, ">${General::swroot}/ovpn/ovpn-leases.db")) {
print FILE "";
close FILE;
}
if (open(FILE, ">${General::swroot}/ovpn/ovpnconfig")) {
print FILE "";
close FILE;
}
while ($file = glob("${General::swroot}/ovpn/n2nconf/*")) {
unlink($file);
}
# Remove everything from the collectd configuration
&writecollectdconf();
#&writeserverconf();
###
### Reset all step 1
###
}elsif ($cgiparams{'ACTION'} eq $Lang::tr{'remove x509'}) {
&Header::showhttpheaders();
&Header::openpage($Lang::tr{'ovpn'}, 1, '');
&Header::openbigbox('100%', 'left', '', '');
&Header::openbox('100%', 'left', $Lang::tr{'are you sure'});
print <
$Lang::tr{'capswarning'}:
$Lang::tr{'resetting the vpn configuration will remove the root ca, the host certificate and all certificate based connections'}
END
;
&Header::closebox();
&Header::closebigbox();
&Header::closepage();
exit (0);
###
### Upload CA Certificate
###
} elsif ($cgiparams{'ACTION'} eq $Lang::tr{'upload ca certificate'}) {
&General::readhasharray("${General::swroot}/ovpn/caconfig", \%cahash);
if ($cgiparams{'CA_NAME'} !~ /^[a-zA-Z0-9]+$/) {
$errormessage = $Lang::tr{'name must only contain characters'};
goto UPLOADCA_ERROR;
}
if (length($cgiparams{'CA_NAME'}) >60) {
$errormessage = $Lang::tr{'name too long'};
goto VPNCONF_ERROR;
}
if ($cgiparams{'CA_NAME'} eq 'ca') {
$errormessage = $Lang::tr{'name is invalid'};
goto UPLOADCA_ERROR;
}
# Check if there is no other entry with this name
foreach my $key (keys %cahash) {
if ($cahash{$key}[0] eq $cgiparams{'CA_NAME'}) {
$errormessage = $Lang::tr{'a ca certificate with this name already exists'};
goto UPLOADCA_ERROR;
}
}
unless (ref ($cgiparams{'FH'})) {
$errormessage = $Lang::tr{'there was no file upload'};
goto UPLOADCA_ERROR;
}
# Move uploaded ca to a temporary file
(my $fh, my $filename) = tempfile( );
if (copy ($cgiparams{'FH'}, $fh) != 1) {
$errormessage = $!;
goto UPLOADCA_ERROR;
}
my @temp = &General::system_output("/usr/bin/openssl", "x509", "-text", "-in", "$filename");
if ( ! grep(/CA:TRUE/i, @temp )) {
$errormessage = $Lang::tr{'not a valid ca certificate'};
unlink ($filename);
goto UPLOADCA_ERROR;
} else {
unless(move($filename, "${General::swroot}/ovpn/ca/$cgiparams{'CA_NAME'}cert.pem")) {
$errormessage = "$Lang::tr{'certificate file move failed'}: $!";
unlink ($filename);
goto UPLOADCA_ERROR;
}
}
my @casubject = &General::system_output("/usr/bin/openssl", "x509", "-text", "-in", "${General::swroot}/ovpn/ca/$cgiparams{'CA_NAME'}cert.pem");
my $casubject;
foreach my $line (@casubject) {
if ($line =~ /Subject: (.*)[\n]/) {
$casubject = $1;
$casubject =~ s+/Email+, E+;
$casubject =~ s/ ST=/ S=/;
last;
}
}
$casubject = &Header::cleanhtml($casubject);
my $key = &General::findhasharraykey (\%cahash);
$cahash{$key}[0] = $cgiparams{'CA_NAME'};
$cahash{$key}[1] = $casubject;
&General::writehasharray("${General::swroot}/ovpn/caconfig", \%cahash);
# system('/usr/local/bin/ipsecctrl', 'R');
UPLOADCA_ERROR:
###
### Display ca certificate
###
} elsif ($cgiparams{'ACTION'} eq $Lang::tr{'show ca certificate'}) {
&General::readhasharray("${General::swroot}/ovpn/caconfig", \%cahash);
if ( -f "${General::swroot}/ovpn/ca/$cahash{$cgiparams{'KEY'}}[0]cert.pem") {
&Header::showhttpheaders();
&Header::openpage($Lang::tr{'ovpn'}, 1, '');
&Header::openbigbox('100%', 'LEFT', '', $errormessage);
&Header::openbox('100%', 'LEFT', "$Lang::tr{'ca certificate'}:");
my @output = &General::system_output("/usr/bin/openssl", "x509", "-text", "-in", "${General::swroot}/ovpn/ca/$cahash{$cgiparams{'KEY'}}[0]cert.pem");
my $output = &Header::cleanhtml(join("", @output),"y");
print "
";
} else {
print "";
}
&Header::closebigbox();
&Header::closepage();
exit(0);
##
### Accept IPFire n2n Package Settings
###
} elsif (($cgiparams{'ACTION'} eq $Lang::tr{'add'}) && ($cgiparams{'TYPE'} eq 'net2netakn')){
###
### Discard and Rollback IPFire n2n Package Settings
###
} elsif (($cgiparams{'ACTION'} eq $Lang::tr{'cancel'}) && ($cgiparams{'TYPE'} eq 'net2netakn')){
&General::readhasharray("${General::swroot}/ovpn/ovpnconfig", \%confighash);
if ($confighash{$cgiparams{'KEY'}}) {
my $conffile = glob("${General::swroot}/ovpn/n2nconf/$confighash{$cgiparams{'KEY'}}[1]/$confighash{$cgiparams{'KEY'}}[1].conf");
my $certfile = glob("${General::swroot}/ovpn/certs/$confighash{$cgiparams{'KEY'}}[1].p12");
unlink ($certfile) or die "Removing $certfile fail: $!";
unlink ($conffile) or die "Removing $conffile fail: $!";
rmdir ("${General::swroot}/ovpn/n2nconf/$confighash{$cgiparams{'KEY'}}[1]") || die "Kann Verzeichnis nicht loeschen: $!";
delete $confighash{$cgiparams{'KEY'}};
&General::writehasharray("${General::swroot}/ovpn/ovpnconfig", \%confighash);
} else {
$errormessage = $Lang::tr{'invalid key'};
}
###
### Adding a new connection
###
} elsif (($cgiparams{'ACTION'} eq $Lang::tr{'add'}) ||
($cgiparams{'ACTION'} eq $Lang::tr{'edit'}) ||
($cgiparams{'ACTION'} eq $Lang::tr{'save'} && $cgiparams{'ADVANCED'} eq '')) {
&General::readhasharray("${General::swroot}/ovpn/caconfig", \%cahash);
&General::readhasharray("${General::swroot}/ovpn/ovpnconfig", \%confighash);
if ($cgiparams{'ACTION'} eq $Lang::tr{'edit'}) {
if (! $confighash{$cgiparams{'KEY'}}[0]) {
$errormessage = $Lang::tr{'invalid key'};
goto VPNCONF_END;
}
$cgiparams{'ENABLED'} = $confighash{$cgiparams{'KEY'}}[0];
$cgiparams{'NAME'} = $confighash{$cgiparams{'KEY'}}[1];
$cgiparams{'TYPE'} = $confighash{$cgiparams{'KEY'}}[3];
$cgiparams{'AUTH'} = $confighash{$cgiparams{'KEY'}}[4];
$cgiparams{'PSK'} = $confighash{$cgiparams{'KEY'}}[5];
$cgiparams{'SIDE'} = $confighash{$cgiparams{'KEY'}}[6];
$cgiparams{'LOCAL_SUBNET'} = $confighash{$cgiparams{'KEY'}}[8];
$cgiparams{'REMOTE'} = $confighash{$cgiparams{'KEY'}}[10];
$cgiparams{'REMOTE_SUBNET'} = $confighash{$cgiparams{'KEY'}}[11];
$cgiparams{'OVPN_MGMT'} = $confighash{$cgiparams{'KEY'}}[22];
$cgiparams{'MSSFIX'} = $confighash{$cgiparams{'KEY'}}[23];
$cgiparams{'FRAGMENT'} = $confighash{$cgiparams{'KEY'}}[24];
$cgiparams{'REMARK'} = $confighash{$cgiparams{'KEY'}}[25];
$cgiparams{'INTERFACE'} = $confighash{$cgiparams{'KEY'}}[26];
$cgiparams{'OVPN_SUBNET'} = $confighash{$cgiparams{'KEY'}}[27];
$cgiparams{'PROTOCOL'} = $confighash{$cgiparams{'KEY'}}[28];
$cgiparams{'DEST_PORT'} = $confighash{$cgiparams{'KEY'}}[29];
$cgiparams{'COMPLZO'} = $confighash{$cgiparams{'KEY'}}[30];
$cgiparams{'MTU'} = $confighash{$cgiparams{'KEY'}}[31];
$cgiparams{'CHECK1'} = $confighash{$cgiparams{'KEY'}}[32];
$name=$cgiparams{'CHECK1'} ;
$cgiparams{$name} = $confighash{$cgiparams{'KEY'}}[33];
$cgiparams{'RG'} = $confighash{$cgiparams{'KEY'}}[34];
$cgiparams{'CCD_DNS1'} = $confighash{$cgiparams{'KEY'}}[35];
$cgiparams{'CCD_DNS2'} = $confighash{$cgiparams{'KEY'}}[36];
$cgiparams{'CCD_WINS'} = $confighash{$cgiparams{'KEY'}}[37];
$cgiparams{'DAUTH'} = $confighash{$cgiparams{'KEY'}}[39];
$cgiparams{'DCIPHER'} = $confighash{$cgiparams{'KEY'}}[40];
$cgiparams{'TLSAUTH'} = $confighash{$cgiparams{'KEY'}}[41];
$cgiparams{'OTP_STATE'} = $confighash{$cgiparams{'KEY'}}[43];
} elsif ($cgiparams{'ACTION'} eq $Lang::tr{'save'}) {
$cgiparams{'REMARK'} = &Header::cleanhtml($cgiparams{'REMARK'});
# CCD check iroute field and convert it to decimal
if ($cgiparams{'TYPE'} eq 'host') {
my @temp=();
my %ccdroutehash=();
my $keypoint=0;
my $ip;
my $cidr;
if ($cgiparams{'IR'} ne ''){
@temp = split("\n",$cgiparams{'IR'});
&General::readhasharray("${General::swroot}/ovpn/ccdroute", \%ccdroutehash);
#find key to use
foreach my $key (keys %ccdroutehash) {
if ($ccdroutehash{$key}[0] eq $cgiparams{'NAME'}) {
$keypoint=$key;
delete $ccdroutehash{$key};
}else{
$keypoint = &General::findhasharraykey (\%ccdroutehash);
}
}
$ccdroutehash{$keypoint}[0]=$cgiparams{'NAME'};
my $i=1;
my $val=0;
foreach $val (@temp){
chomp($val);
$val=~s/\s*$//g;
#check if iroute exists in ccdroute or if new iroute is part of an existing one
foreach my $key (keys %ccdroutehash) {
foreach my $oldiroute ( 1 .. $#{$ccdroutehash{$key}}){
if ($ccdroutehash{$key}[$oldiroute] eq "$val") {
$errormessage=$errormessage.$Lang::tr{'ccd err irouteexist'};
goto VPNCONF_ERROR;
}
my ($ip1,$cidr1) = split (/\//, $val);
$ip1 = &General::getnetworkip($ip1,&General::iporsubtocidr($cidr1));
my ($ip2,$cidr2) = split (/\//, $ccdroutehash{$key}[$oldiroute]);
if (&General::IpInSubnet ($ip1,$ip2,$cidr2)){
$errormessage=$errormessage.$Lang::tr{'ccd err irouteexist'};
goto VPNCONF_ERROR;
}
}
}
if (!&General::validipandmask($val)){
$errormessage=$errormessage."Route ".$Lang::tr{'ccd invalid'}." ($val)";
goto VPNCONF_ERROR;
}else{
($ip,$cidr) = split(/\//,$val);
$ip=&General::getnetworkip($ip,&General::iporsubtocidr($cidr));
$cidr=&General::iporsubtodec($cidr);
$ccdroutehash{$keypoint}[$i] = $ip."/".$cidr;
}
#check for existing network IP's
if (&General::IpInSubnet ($ip,$Network::ethernet{GREEN_NETADDRESS},$Network::ethernet{GREEN_NETMASK}) && $Network::ethernet{GREEN_NETADDRESS} ne '0.0.0.0')
{
$errormessage=$Lang::tr{'ccd err green'};
goto VPNCONF_ERROR;
}elsif(&General::IpInSubnet ($ip,$Network::ethernet{RED_NETADDRESS},$Network::ethernet{RED_NETMASK}) && $Network::ethernet{RED_NETADDRESS} ne '0.0.0.0')
{
$errormessage=$Lang::tr{'ccd err red'};
goto VPNCONF_ERROR;
}elsif(&General::IpInSubnet ($ip,$Network::ethernet{BLUE_NETADDRESS},$Network::ethernet{BLUE_NETMASK}) && $Network::ethernet{BLUE_NETADDRESS} ne '0.0.0.0' && $Network::ethernet{BLUE_NETADDRESS} gt '')
{
$errormessage=$Lang::tr{'ccd err blue'};
goto VPNCONF_ERROR;
}elsif(&General::IpInSubnet ($ip,$Network::ethernet{ORANGE_NETADDRESS},$Network::ethernet{ORANGE_NETMASK}) && $Network::ethernet{ORANGE_NETADDRESS} ne '0.0.0.0' && $Network::ethernet{ORANGE_NETADDRESS} gt '' )
{
$errormessage=$Lang::tr{'ccd err orange'};
goto VPNCONF_ERROR;
}
if (&General::validipandmask($val)){
$ccdroutehash{$keypoint}[$i] = $ip."/".$cidr;
}else{
$errormessage=$errormessage."Route ".$Lang::tr{'ccd invalid'}." ($ip/$cidr)";
goto VPNCONF_ERROR;
}
$i++;
}
&General::writehasharray("${General::swroot}/ovpn/ccdroute", \%ccdroutehash);
&writeserverconf;
}else{
&General::readhasharray("${General::swroot}/ovpn/ccdroute", \%ccdroutehash);
foreach my $key (keys %ccdroutehash) {
if ($ccdroutehash{$key}[0] eq $cgiparams{'NAME'}) {
delete $ccdroutehash{$key};
&General::writehasharray("${General::swroot}/ovpn/ccdroute", \%ccdroutehash);
&writeserverconf;
}
}
}
undef @temp;
#check route field and convert it to decimal
my $val=0;
my $i=1;
&General::readhasharray("${General::swroot}/ovpn/ccdroute2", \%ccdroute2hash);
#find key to use
foreach my $key (keys %ccdroute2hash) {
if ($ccdroute2hash{$key}[0] eq $cgiparams{'NAME'}) {
$keypoint=$key;
delete $ccdroute2hash{$key};
}else{
$keypoint = &General::findhasharraykey (\%ccdroute2hash);
&General::writehasharray("${General::swroot}/ovpn/ccdroute", \%ccdroutehash);
&writeserverconf;
}
}
$ccdroute2hash{$keypoint}[0]=$cgiparams{'NAME'};
if ($cgiparams{'IFROUTE'} eq ''){$cgiparams{'IFROUTE'} = $Lang::tr{'ccd none'};}
@temp = split(/\|/,$cgiparams{'IFROUTE'});
foreach $val (@temp){
chomp($val);
$val=~s/\s*$//g;
if ($val eq $Lang::tr{'green'})
{
$val=$Network::ethernet{GREEN_NETADDRESS}."/".$Network::ethernet{GREEN_NETMASK};
}
if ($val eq $Lang::tr{'blue'})
{
$val=$Network::ethernet{BLUE_NETADDRESS}."/".$Network::ethernet{BLUE_NETMASK};
}
if ($val eq $Lang::tr{'orange'})
{
$val=$Network::ethernet{ORANGE_NETADDRESS}."/".$Network::ethernet{ORANGE_NETMASK};
}
my ($ip,$cidr) = split (/\//, $val);
if ($val ne $Lang::tr{'ccd none'})
{
if (! &check_routes_push($val)){$errormessage=$errormessage."Route $val ".$Lang::tr{'ccd err routeovpn2'}." ($val)";goto VPNCONF_ERROR;}
if (! &check_ccdroute($val)){$errormessage=$errormessage." Route $val ".$Lang::tr{'ccd err inuse'}." ($val)" ;goto VPNCONF_ERROR;}
if (! &check_ccdconf($val)){$errormessage=$errormessage." Route $val ".$Lang::tr{'ccd err routeovpn'}." ($val)";goto VPNCONF_ERROR;}
if (&General::validipandmask($val)){
$val=$ip."/".&General::iporsubtodec($cidr);
$ccdroute2hash{$keypoint}[$i] = $val;
}else{
$errormessage=$errormessage."Route ".$Lang::tr{'ccd invalid'}." ($val)";
goto VPNCONF_ERROR;
}
}else{
$ccdroute2hash{$keypoint}[$i]='';
}
$i++;
}
&General::writehasharray("${General::swroot}/ovpn/ccdroute2", \%ccdroute2hash);
#check dns1 ip
if ($cgiparams{'CCD_DNS1'} ne '' && ! &General::validip($cgiparams{'CCD_DNS1'})) {
$errormessage=$errormessage." ".$Lang::tr{'invalid input for dhcp dns'}." 1";
goto VPNCONF_ERROR;
}
#check dns2 ip
if ($cgiparams{'CCD_DNS2'} ne '' && ! &General::validip($cgiparams{'CCD_DNS2'})) {
$errormessage=$errormessage." ".$Lang::tr{'invalid input for dhcp dns'}." 2";
goto VPNCONF_ERROR;
}
#check wins ip
if ($cgiparams{'CCD_WINS'} ne '' && ! &General::validip($cgiparams{'CCD_WINS'})) {
$errormessage=$errormessage." ".$Lang::tr{'invalid input for dhcp wins'};
goto VPNCONF_ERROR;
}
}
if ($cgiparams{'TYPE'} !~ /^(host|net)$/) {
$errormessage = $Lang::tr{'connection type is invalid'};
if ($cgiparams{'TYPE'} eq 'net') {
unlink ("${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}/$cgiparams{'NAME'}.conf") or die "Removing Configfile fail: $!";
rmdir ("${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}") || die "Removing Directory fail: $!";
goto VPNCONF_ERROR;
}
goto VPNCONF_ERROR;
}
if ($cgiparams{'NAME'} !~ /^[a-zA-Z0-9]+$/) {
$errormessage = $Lang::tr{'name must only contain characters'};
if ($cgiparams{'TYPE'} eq 'net') {
goto VPNCONF_ERROR;
}
goto VPNCONF_ERROR;
}
if ($cgiparams{'NAME'} =~ /^(host|01|block|private|clear|packetdefault)$/) {
$errormessage = $Lang::tr{'name is invalid'};
if ($cgiparams{'TYPE'} eq 'net') {
unlink ("${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}/$cgiparams{'NAME'}.conf") or die "Removing Configfile fail: $!";
rmdir ("${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}") || die "Removing Directory fail: $!";
goto VPNCONF_ERROR;
}
goto VPNCONF_ERROR;
}
if (length($cgiparams{'NAME'}) >60) {
$errormessage = $Lang::tr{'name too long'};
if ($cgiparams{'TYPE'} eq 'net') {
unlink ("${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}/$cgiparams{'NAME'}.conf") or die "Removing Configfile fail: $!";
rmdir ("${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}") || die "Removing Directory fail: $!";
goto VPNCONF_ERROR;
}
goto VPNCONF_ERROR;
}
if ($cgiparams{'TYPE'} eq 'net') {
if ($cgiparams{'DEST_PORT'} eq $vpnsettings{'DDEST_PORT'}) {
$errormessage = $Lang::tr{'openvpn destination port used'};
unlink ("${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}/$cgiparams{'NAME'}.conf") or die "Removing Configfile fail: $!";
rmdir ("${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}") || die "Removing Directory fail: $!";
goto VPNCONF_ERROR;
}
#Bugfix 10357
foreach my $key (sort keys %confighash){
if ( ($confighash{$key}[22] eq $cgiparams{'DEST_PORT'} && $cgiparams{'NAME'} ne $confighash{$key}[1]) || ($confighash{$key}[29] eq $cgiparams{'DEST_PORT'} && $cgiparams{'NAME'} ne $confighash{$key}[1])){
$errormessage = $Lang::tr{'openvpn destination port used'};
unlink ("${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}/$cgiparams{'NAME'}.conf") or die "Removing Configfile fail: $!";
rmdir ("${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}") || die "Removing Directory fail: $!";
goto VPNCONF_ERROR;
}
}
if ($cgiparams{'DEST_PORT'} eq '') {
$errormessage = $Lang::tr{'invalid port'};
unlink ("${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}/$cgiparams{'NAME'}.conf") or die "Removing Configfile fail: $!";
rmdir ("${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}") || die "Removing Directory fail: $!";
goto VPNCONF_ERROR;
}
# Check if the input for the transfer net is valid.
if (!&General::validipandmask($cgiparams{'OVPN_SUBNET'})){
$errormessage = $Lang::tr{'ccd err invalidnet'};
unlink ("${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}/$cgiparams{'NAME'}.conf") or die "Removing Configfile fail: $!";
rmdir ("${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}") || die "Removing Directory fail: $!";
goto VPNCONF_ERROR;
}
if ($cgiparams{'OVPN_SUBNET'} eq $vpnsettings{'DOVPN_SUBNET'}) {
$errormessage = $Lang::tr{'openvpn subnet is used'};
unlink ("${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}/$cgiparams{'NAME'}.conf") or die "Removing Configfile fail: $!";
rmdir ("${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}") || die "Removing Directory fail: $!";
goto VPNCONF_ERROR;
}
if (($cgiparams{'PROTOCOL'} eq 'tcp') && ($cgiparams{'MSSFIX'} eq 'on')) {
$errormessage = $Lang::tr{'openvpn mssfix allowed with udp'};
unlink ("${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}/$cgiparams{'NAME'}.conf") or die "Removing Configfile fail: $!";
rmdir ("${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}") || die "Removing Directory fail: $!";
goto VPNCONF_ERROR;
}
if (($cgiparams{'PROTOCOL'} eq 'tcp') && ($cgiparams{'FRAGMENT'} ne '')) {
$errormessage = $Lang::tr{'openvpn fragment allowed with udp'};
unlink ("${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}/$cgiparams{'NAME'}.conf") or die "Removing Configfile fail: $!";
rmdir ("${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}") || die "Removing Directory fail: $!";
goto VPNCONF_ERROR;
}
if (!&Network::check_subnet($cgiparams{'LOCAL_SUBNET'})) {
$errormessage = $Lang::tr{'openvpn prefix local subnet'};
unlink ("${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}/$cgiparams{'NAME'}.conf") or die "Removing Configfile fail: $!";
rmdir ("${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}") || die "Removing Directory fail: $!";
goto VPNCONF_ERROR;
}
if (!&Network::check_subnet($cgiparams{'OVPN_SUBNET'})) {
$errormessage = $Lang::tr{'openvpn prefix openvpn subnet'};
unlink ("${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}/$cgiparams{'NAME'}.conf") or die "Removing Configfile fail: $!";
rmdir ("${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}") || die "Removing Directory fail: $!";
goto VPNCONF_ERROR;
}
if (!&Network::check_subnet($cgiparams{'REMOTE_SUBNET'})) {
$errormessage = $Lang::tr{'openvpn prefix remote subnet'};
unlink ("${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}/$cgiparams{'NAME'}.conf") or die "Removing Configfile fail: $!";
rmdir ("${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}") || die "Removing Directory fail: $!";
goto VPNCONF_ERROR;
}
if ($cgiparams{'DEST_PORT'} <= 1023) {
$errormessage = $Lang::tr{'ovpn port in root range'};
unlink ("${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}/$cgiparams{'NAME'}.conf") or die "Removing Configfile fail: $!";
rmdir ("${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}") || die "Removing Directory fail: $!";
goto VPNCONF_ERROR;
}
if ($cgiparams{'OVPN_MGMT'} eq '') {
$cgiparams{'OVPN_MGMT'} = $cgiparams{'DEST_PORT'};
}
if ($cgiparams{'OVPN_MGMT'} <= 1023) {
$errormessage = $Lang::tr{'ovpn mgmt in root range'};
unlink ("${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}/$cgiparams{'NAME'}.conf") or die "Removing Configfile fail: $!";
rmdir ("${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}") || die "Removing Directory fail: $!";
goto VPNCONF_ERROR;
}
#Check if remote subnet is used elsewhere
my ($n2nip,$n2nsub)=split("/",$cgiparams{'REMOTE_SUBNET'});
$warnmessage=&General::checksubnets('',$n2nip,'ovpn');
if ($warnmessage){
$warnmessage=$Lang::tr{'remote subnet'}." ($cgiparams{'REMOTE_SUBNET'}) ".$warnmessage;
}
}
# Check if there is no other entry with this name
if (! $cgiparams{'KEY'}) {
foreach my $key (keys %confighash) {
if ($confighash{$key}[1] eq $cgiparams{'NAME'}) {
$errormessage = $Lang::tr{'a connection with this name already exists'};
if ($cgiparams{'TYPE'} eq 'net') {
unlink ("${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}/$cgiparams{'NAME'}.conf") or die "Removing Configfile fail: $!";
rmdir ("${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}") || die "Removing Directory fail: $!";
}
goto VPNCONF_ERROR;
}
}
}
# Check if a remote host/IP has been set for the client.
if ($cgiparams{'TYPE'} eq 'net') {
if ($cgiparams{'SIDE'} ne 'server' && $cgiparams{'REMOTE'} eq '') {
$errormessage = $Lang::tr{'invalid input for remote host/ip'};
# Check if this is a N2N connection and drop temporary config.
unlink ("${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}/$cgiparams{'NAME'}.conf") or die "Removing Configfile fail: $!";
rmdir ("${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}") || die "Removing Directory fail: $!";
goto VPNCONF_ERROR;
}
# Check if a remote host/IP has been configured - the field can be empty on the server side.
if ($cgiparams{'REMOTE'} ne '') {
# Check if the given IP is valid - otherwise check if it is a valid domain.
if (! &General::validip($cgiparams{'REMOTE'})) {
# Check for a valid domain.
if (! &General::validfqdn ($cgiparams{'REMOTE'})) {
$errormessage = $Lang::tr{'invalid input for remote host/ip'};
# Check if this is a N2N connection and drop temporary config.
unlink ("${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}/$cgiparams{'NAME'}.conf") or die "Removing Configfile fail: $!";
rmdir ("${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}") || die "Removing Directory fail: $!";
goto VPNCONF_ERROR;
}
}
}
}
if ($cgiparams{'TYPE'} ne 'host') {
unless (&General::validipandmask($cgiparams{'LOCAL_SUBNET'})) {
$errormessage = $Lang::tr{'local subnet is invalid'};
if ($cgiparams{'TYPE'} eq 'net') {
unlink ("${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}/$cgiparams{'NAME'}.conf") or die "Removing Configfile fail: $!";
rmdir ("${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}") || die "Removing Directory fail: $!";
}
goto VPNCONF_ERROR;}
}
# Check if there is no other entry without IP-address and PSK
if ($cgiparams{'REMOTE'} eq '') {
foreach my $key (keys %confighash) {
if(($cgiparams{'KEY'} ne $key) &&
($confighash{$key}[4] eq 'psk' || $cgiparams{'AUTH'} eq 'psk') &&
$confighash{$key}[10] eq '') {
$errormessage = $Lang::tr{'you can only define one roadwarrior connection when using pre-shared key authentication'};
goto VPNCONF_ERROR;
}
}
}
if (($cgiparams{'TYPE'} eq 'net') && (! &General::validipandmask($cgiparams{'REMOTE_SUBNET'}))) {
$errormessage = $Lang::tr{'remote subnet is invalid'};
unlink ("${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}/$cgiparams{'NAME'}.conf") or die "Removing Configfile fail: $!";
rmdir ("${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}") || die "Removing Directory fail: $!";
goto VPNCONF_ERROR;
}
# Check for N2N that OpenSSL maximum of valid days will not be exceeded
if ($cgiparams{'TYPE'} eq 'net') {
if ($cgiparams{'DAYS_VALID'} >= '999999') {
$errormessage = $Lang::tr{'invalid input for valid till days'};
unlink ("${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}/$cgiparams{'NAME'}.conf") or die "Removing Configfile fail: $!";
rmdir ("${General::swroot}/ovpn/n2nconf/$cgiparams{'NAME'}") || die "Removing Directory fail: $!";
goto VPNCONF_ERROR;
}
}
if ($cgiparams{'ENABLED'} !~ /^(on|off|)$/) {
$errormessage = $Lang::tr{'invalid input'};
goto VPNCONF_ERROR;
}
if ($cgiparams{'AUTH'} eq 'certreq') {
if ($cgiparams{'KEY'}) {
$errormessage = $Lang::tr{'cant change certificates'};
goto VPNCONF_ERROR;
}
unless (ref ($cgiparams{'FH'})) {
$errormessage = $Lang::tr{'there was no file upload'};
goto VPNCONF_ERROR;
}
# Move uploaded certificate request to a temporary file
(my $fh, my $filename) = tempfile( );
if (copy ($cgiparams{'FH'}, $fh) != 1) {
$errormessage = $!;
goto VPNCONF_ERROR;
}
# Sign the certificate request and move it
# Sign the host certificate request
# The system call is safe, because all arguments are passed as an array.
system('/usr/bin/openssl', 'ca', '-days', "$cgiparams{'DAYS_VALID'}",
'-batch', '-notext',
'-in', $filename,
'-out', "${General::swroot}/ovpn/certs/$cgiparams{'NAME'}cert.pem",
'-config', "/usr/share/openvpn/ovpn.cnf");
if ($?) {
$errormessage = "$Lang::tr{'openssl produced an error'}: $?";
unlink ($filename);
unlink ("${General::swroot}/ovpn/certs/$cgiparams{'NAME'}cert.pem");
&cleanssldatabase();
goto VPNCONF_ERROR;
} else {
unlink ($filename);
&deletebackupcert();
}
my @temp = &General::system_output("/usr/bin/openssl", "x509", "-text", "-in", "${General::swroot}/ovpn/certs/$cgiparams{'NAME'}cert.pem");
my $temp;
foreach my $line (@temp) {
if ($line =~ /Subject:.*CN\s?=\s?(.*)[\n]/) {
$temp = $1;
$temp =~ s+/Email+, E+;
$temp =~ s/ ST=/ S=/;
last;
}
}
$cgiparams{'CERT_NAME'} = $temp;
$cgiparams{'CERT_NAME'} =~ s/,//g;
$cgiparams{'CERT_NAME'} =~ s/\'//g;
if ($cgiparams{'CERT_NAME'} eq '') {
$errormessage = $Lang::tr{'could not retrieve common name from certificate'};
goto VPNCONF_ERROR;
}
} elsif ($cgiparams{'AUTH'} eq 'certfile') {
if ($cgiparams{'KEY'}) {
$errormessage = $Lang::tr{'cant change certificates'};
goto VPNCONF_ERROR;
}
unless (ref ($cgiparams{'FH'})) {
$errormessage = $Lang::tr{'there was no file upload'};
goto VPNCONF_ERROR;
}
# Move uploaded certificate to a temporary file
(my $fh, my $filename) = tempfile( );
if (copy ($cgiparams{'FH'}, $fh) != 1) {
$errormessage = $!;
goto VPNCONF_ERROR;
}
# Verify the certificate has a valid CA and move it
my $validca = 0;
my @test = &General::system_output("/usr/bin/openssl", "verify", "-CAfile", "${General::swroot}/ovpn/ca/cacert.pem", "$filename");
if (grep(/: OK/, @test)) {
$validca = 1;
} else {
foreach my $key (keys %cahash) {
@test = &General::system_output("/usr/bin/openssl", "verify", "-CAfile", "${General::swroot}/ovpn/ca/$cahash{$key}[0]cert.pem", "$filename");
if (grep(/: OK/, @test)) {
$validca = 1;
}
}
}
if (! $validca) {
$errormessage = $Lang::tr{'certificate does not have a valid ca associated with it'};
unlink ($filename);
goto VPNCONF_ERROR;
} else {
unless(move($filename, "${General::swroot}/ovpn/certs/$cgiparams{'NAME'}cert.pem")) {
$errormessage = "$Lang::tr{'certificate file move failed'}: $!";
unlink ($filename);
goto VPNCONF_ERROR;
}
}
my @temp = &General::system_output("/usr/bin/openssl", "x509", "-text", "-in", "${General::swroot}/ovpn/certs/$cgiparams{'NAME'}cert.pem");
my $temp;
foreach my $line (@temp) {
if ($line =~ /Subject:.*CN\s?=\s?(.*)[\n]/) {
$temp = $1;
$temp =~ s+/Email+, E+;
$temp =~ s/ ST=/ S=/;
last;
}
}
$cgiparams{'CERT_NAME'} = $temp;
$cgiparams{'CERT_NAME'} =~ s/,//g;
$cgiparams{'CERT_NAME'} =~ s/\'//g;
if ($cgiparams{'CERT_NAME'} eq '') {
unlink ("${General::swroot}/ovpn/certs/$cgiparams{'NAME'}cert.pem");
$errormessage = $Lang::tr{'could not retrieve common name from certificate'};
goto VPNCONF_ERROR;
}
} elsif ($cgiparams{'AUTH'} eq 'certgen') {
if ($cgiparams{'KEY'}) {
$errormessage = $Lang::tr{'cant change certificates'};
goto VPNCONF_ERROR;
}
# Validate input since the form was submitted
if (length($cgiparams{'CERT_NAME'}) >60) {
$errormessage = $Lang::tr{'name too long'};
goto VPNCONF_ERROR;
}
if ($cgiparams{'CERT_NAME'} eq '' || $cgiparams{'CERT_NAME'} !~ /^[a-zA-Z0-9 ,\.\-_]+$/) {
$errormessage = $Lang::tr{'invalid input for name'};
goto VPNCONF_ERROR;
}
if ($cgiparams{'CERT_EMAIL'} ne '' && (! &General::validemail($cgiparams{'CERT_EMAIL'}))) {
$errormessage = $Lang::tr{'invalid input for e-mail address'};
goto VPNCONF_ERROR;
}
if (length($cgiparams{'CERT_EMAIL'}) > 40) {
$errormessage = $Lang::tr{'e-mail address too long'};
goto VPNCONF_ERROR;
}
if ($cgiparams{'CERT_OU'} ne '' && $cgiparams{'CERT_OU'} !~ /^[a-zA-Z0-9 ,\.\-_]*$/) {
$errormessage = $Lang::tr{'invalid input for department'};
goto VPNCONF_ERROR;
}
if (length($cgiparams{'CERT_ORGANIZATION'}) >60) {
$errormessage = $Lang::tr{'organization too long'};
goto VPNCONF_ERROR;
}
if ($cgiparams{'CERT_ORGANIZATION'} !~ /^[a-zA-Z0-9 ,\.\-_]+$/) {
$errormessage = $Lang::tr{'invalid input for organization'};
goto VPNCONF_ERROR;
}
if ($cgiparams{'CERT_CITY'} ne '' && $cgiparams{'CERT_CITY'} !~ /^[a-zA-Z0-9 ,\.\-_]*$/) {
$errormessage = $Lang::tr{'invalid input for city'};
goto VPNCONF_ERROR;
}
if ($cgiparams{'CERT_STATE'} ne '' && $cgiparams{'CERT_STATE'} !~ /^[a-zA-Z0-9 ,\.\-_]*$/) {
$errormessage = $Lang::tr{'invalid input for state or province'};
goto VPNCONF_ERROR;
}
if ($cgiparams{'CERT_COUNTRY'} !~ /^[A-Z]*$/) {
$errormessage = $Lang::tr{'invalid input for country'};
goto VPNCONF_ERROR;
}
if ($cgiparams{'CERT_PASS1'} ne '' && $cgiparams{'CERT_PASS2'} ne ''){
if (length($cgiparams{'CERT_PASS1'}) < 5) {
$errormessage = $Lang::tr{'password too short'};
goto VPNCONF_ERROR;
}
}
if ($cgiparams{'CERT_PASS1'} ne $cgiparams{'CERT_PASS2'}) {
$errormessage = $Lang::tr{'passwords do not match'};
goto VPNCONF_ERROR;
}
if ($cgiparams{'DAYS_VALID'} eq '' && $cgiparams{'DAYS_VALID'} !~ /^[0-9]+$/) {
$errormessage = $Lang::tr{'invalid input for valid till days'};
goto VPNCONF_ERROR;
}
# Check for RW that OpenSSL maximum of valid days will not be exceeded
if ($cgiparams{'TYPE'} eq 'host') {
if ($cgiparams{'DAYS_VALID'} >= '999999') {
$errormessage = $Lang::tr{'invalid input for valid till days'};
goto VPNCONF_ERROR;
}
}
# Check for RW if client name is already set
if ($cgiparams{'TYPE'} eq 'host') {
foreach my $key (keys %confighash) {
if ($confighash{$key}[1] eq $cgiparams{'NAME'}) {
$errormessage = $Lang::tr{'a connection with this name already exists'};
goto VPNCONF_ERROR;
}
}
}
# Check if there is no other entry with this common name
if ((! $cgiparams{'KEY'}) && ($cgiparams{'AUTH'} ne 'psk')) {
foreach my $key (keys %confighash) {
if ($confighash{$key}[2] eq $cgiparams{'CERT_NAME'}) {
$errormessage = $Lang::tr{'a connection with this common name already exists'};
goto VPNCONF_ERROR;
}
}
}
# Replace empty strings with a .
(my $ou = $cgiparams{'CERT_OU'}) =~ s/^\s*$/\./;
(my $city = $cgiparams{'CERT_CITY'}) =~ s/^\s*$/\./;
(my $state = $cgiparams{'CERT_STATE'}) =~ s/^\s*$/\./;
# Create the Host certificate request client
my $pid = open(OPENSSL, "|-");
$SIG{ALRM} = sub { $errormessage = $Lang::tr{'broken pipe'}; goto VPNCONF_ERROR;};
if ($pid) { # parent
print OPENSSL "$cgiparams{'CERT_COUNTRY'}\n";
print OPENSSL "$state\n";
print OPENSSL "$city\n";
print OPENSSL "$cgiparams{'CERT_ORGANIZATION'}\n";
print OPENSSL "$ou\n";
print OPENSSL "$cgiparams{'CERT_NAME'}\n";
print OPENSSL "$cgiparams{'CERT_EMAIL'}\n";
print OPENSSL ".\n";
print OPENSSL ".\n";
close (OPENSSL);
if ($?) {
$errormessage = "$Lang::tr{'openssl produced an error'}: $?";
unlink ("${General::swroot}ovpn/certs/$cgiparams{'NAME'}key.pem");
unlink ("${General::swroot}ovpn/certs/$cgiparams{'NAME'}req.pem");
goto VPNCONF_ERROR;
}
} else { # child
unless (exec ('/usr/bin/openssl', 'req', '-nodes',
'-newkey', 'rsa:4096',
'-keyout', "${General::swroot}/ovpn/certs/$cgiparams{'NAME'}key.pem",
'-out', "${General::swroot}/ovpn/certs/$cgiparams{'NAME'}req.pem",
'-config', "/usr/share/openvpn/ovpn.cnf")) {
$errormessage = "$Lang::tr{'cant start openssl'}: $!";
unlink ("${General::swroot}/ovpn/certs/$cgiparams{'NAME'}key.pem");
unlink ("${General::swroot}/ovpn/certs/$cgiparams{'NAME'}req.pem");
goto VPNCONF_ERROR;
}
}
# Sign the host certificate request
# The system call is safe, because all arguments are passed as an array.
system('/usr/bin/openssl', 'ca', '-days', "$cgiparams{'DAYS_VALID'}",
'-batch', '-notext',
'-in', "${General::swroot}/ovpn/certs/$cgiparams{'NAME'}req.pem",
'-out', "${General::swroot}/ovpn/certs/$cgiparams{'NAME'}cert.pem",
'-config', "/usr/share/openvpn/ovpn.cnf");
if ($?) {
$errormessage = "$Lang::tr{'openssl produced an error'}: $?";
unlink ("${General::swroot}/ovpn/certs/$cgiparams{'NAME'}key.pem");
unlink ("${General::swroot}/ovpn/certs/$cgiparams{'NAME'}req.pem");
unlink ("${General::swroot}/ovpn/certs/$cgiparams{'NAME'}cert.pem");
&cleanssldatabase();
goto VPNCONF_ERROR;
} else {
unlink ("${General::swroot}/ovpn/certs/$cgiparams{'NAME'}req.pem");
&deletebackupcert();
}
# Create the pkcs12 file
# The system call is safe, because all arguments are passed as an array.
system('/usr/bin/openssl', 'pkcs12', '-export',
'-inkey', "${General::swroot}/ovpn/certs/$cgiparams{'NAME'}key.pem",
'-in', "${General::swroot}/ovpn/certs/$cgiparams{'NAME'}cert.pem",
'-name', $cgiparams{'NAME'},
'-passout', "pass:$cgiparams{'CERT_PASS1'}",
'-certfile', "${General::swroot}/ovpn/ca/cacert.pem",
'-caname', "$vpnsettings{'ROOTCERT_ORGANIZATION'} CA",
'-out', "${General::swroot}/ovpn/certs/$cgiparams{'NAME'}.p12");
if ($?) {
$errormessage = "$Lang::tr{'openssl produced an error'}: $?";
unlink ("${General::swroot}/ovpn/certs/$cgiparams{'NAME'}key.pem");
unlink ("${General::swroot}/ovpn/certs/$cgiparams{'NAME'}cert.pem");
unlink ("${General::swroot}/ovpn/certs/$cgiparams{'NAME'}.p12");
goto VPNCONF_ERROR;
} else {
unlink ("${General::swroot}/ovpn/certs/$cgiparams{'NAME'}key.pem");
}
} elsif ($cgiparams{'AUTH'} eq 'cert') {
;# Nothing, just editing
} else {
$errormessage = $Lang::tr{'invalid input for authentication method'};
goto VPNCONF_ERROR;
}
# Save the config
my $key = $cgiparams{'KEY'};
if (! $key) {
$key = &General::findhasharraykey (\%confighash);
foreach my $i (0 .. 43) { $confighash{$key}[$i] = "";}
}
$confighash{$key}[0] = $cgiparams{'ENABLED'};
$confighash{$key}[1] = $cgiparams{'NAME'};
if ((! $cgiparams{'KEY'}) && $cgiparams{'AUTH'} ne 'psk') {
$confighash{$key}[2] = $cgiparams{'CERT_NAME'};
}
$confighash{$key}[3] = $cgiparams{'TYPE'};
if ($cgiparams{'AUTH'} eq 'psk') {
$confighash{$key}[4] = 'psk';
$confighash{$key}[5] = $cgiparams{'PSK'};
} else {
$confighash{$key}[4] = 'cert';
}
if ($cgiparams{'TYPE'} eq 'net') {
$confighash{$key}[6] = $cgiparams{'SIDE'};
$confighash{$key}[11] = $cgiparams{'REMOTE_SUBNET'};
}
$confighash{$key}[8] = $cgiparams{'LOCAL_SUBNET'};
$confighash{$key}[10] = $cgiparams{'REMOTE'};
if ($cgiparams{'OVPN_MGMT'} eq '') {
$confighash{$key}[22] = $confighash{$key}[29];
} else {
$confighash{$key}[22] = $cgiparams{'OVPN_MGMT'};
}
$confighash{$key}[23] = $cgiparams{'MSSFIX'};
$confighash{$key}[24] = $cgiparams{'FRAGMENT'};
$confighash{$key}[25] = $cgiparams{'REMARK'};
$confighash{$key}[26] = $cgiparams{'INTERFACE'};
# new fields
$confighash{$key}[27] = $cgiparams{'OVPN_SUBNET'};
$confighash{$key}[28] = $cgiparams{'PROTOCOL'};
$confighash{$key}[29] = $cgiparams{'DEST_PORT'};
$confighash{$key}[30] = $cgiparams{'COMPLZO'};
$confighash{$key}[31] = $cgiparams{'MTU'};
$confighash{$key}[32] = $cgiparams{'CHECK1'};
$name=$cgiparams{'CHECK1'};
$confighash{$key}[33] = $cgiparams{$name};
$confighash{$key}[34] = $cgiparams{'RG'};
$confighash{$key}[35] = $cgiparams{'CCD_DNS1'};
$confighash{$key}[36] = $cgiparams{'CCD_DNS2'};
$confighash{$key}[37] = $cgiparams{'CCD_WINS'};
$confighash{$key}[39] = $cgiparams{'DAUTH'};
$confighash{$key}[40] = $cgiparams{'DCIPHER'};
if ($confighash{$key}[41] eq "") {
if (($cgiparams{'TYPE'} eq 'host') && ($cgiparams{'CERT_PASS1'} eq "")) {
$confighash{$key}[41] = "no-pass";
} elsif (($cgiparams{'TYPE'} eq 'host') && ($cgiparams{'CERT_PASS1'} ne "")) {
$confighash{$key}[41] = "pass";
} elsif ($cgiparams{'TYPE'} eq 'net') {
$confighash{$key}[41] = "no-pass";
}
}
$confighash{$key}[42] = 'HOTP/T30/6';
$confighash{$key}[43] = $cgiparams{'OTP_STATE'};
if (($confighash{$key}[43] eq 'on') && ($confighash{$key}[44] eq '')) {
my @otp_secret = &General::system_output("/usr/bin/openssl", "rand", "-hex", "20");
chomp($otp_secret[0]);
$confighash{$key}[44] = $otp_secret[0];
} elsif ($confighash{$key}[43] eq '') {
$confighash{$key}[44] = '';
}
&General::writehasharray("${General::swroot}/ovpn/ovpnconfig", \%confighash);
# Rewrite the server configuration
&writeserverconf();
if ($cgiparams{'TYPE'} eq 'net') {
if (-e "/var/run/$confighash{$key}[1]n2n.pid") {
&General::system("/usr/local/bin/openvpnctrl", "n2n", "stop", "$confighash{$cgiparams{'KEY'}}[1]");
&General::readhasharray("${General::swroot}/ovpn/ovpnconfig", \%confighash);
my $key = $cgiparams{'KEY'};
if (! $key) {
$key = &General::findhasharraykey (\%confighash);
foreach my $i (0 .. 31) {
$confighash{$key}[$i] = "";
}
}
$confighash{$key}[0] = 'on';
&General::writehasharray("${General::swroot}/ovpn/ovpnconfig", \%confighash);
&General::system("/usr/local/bin/openvpnctrl", "n2n", "start", "$confighash{$cgiparams{'KEY'}}[1]");
}
}
goto VPNCONF_END;
} else {
$cgiparams{'ENABLED'} = 'on';
$cgiparams{'MSSFIX'} = 'on';
$cgiparams{'FRAGMENT'} = '1300';
$cgiparams{'DAUTH'} = 'SHA512';
$cgiparams{'SIDE'} = 'left';
if ( ! -f "${General::swroot}/ovpn/ca/cakey.pem" ) {
$cgiparams{'AUTH'} = 'psk';
} elsif ( ! -f "${General::swroot}/ovpn/ca/cacert.pem") {
$cgiparams{'AUTH'} = 'certfile';
} else {
$cgiparams{'AUTH'} = 'certgen';
}
$cgiparams{'LOCAL_SUBNET'} ="$Network::ethernet{'GREEN_NETADDRESS'}/$Network::ethernet{'GREEN_NETMASK'}";
$cgiparams{'CERT_ORGANIZATION'} = $vpnsettings{'ROOTCERT_ORGANIZATION'};
$cgiparams{'CERT_CITY'} = $vpnsettings{'ROOTCERT_CITY'};
$cgiparams{'CERT_STATE'} = $vpnsettings{'ROOTCERT_STATE'};
$cgiparams{'CERT_COUNTRY'} = $vpnsettings{'ROOTCERT_COUNTRY'};
$cgiparams{'DAYS_VALID'} = $vpnsettings{'DAYS_VALID'} = '730';
}
VPNCONF_ERROR:
$checked{'ENABLED'}{'off'} = '';
$checked{'ENABLED'}{'on'} = '';
$checked{'ENABLED'}{$cgiparams{'ENABLED'}} = 'CHECKED';
$checked{'OTP_STATE'}{$cgiparams{'OTP_STATE'}} = 'CHECKED';
$selected{'SIDE'}{'server'} = '';
$selected{'SIDE'}{'client'} = '';
$selected{'SIDE'}{$cgiparams{'SIDE'}} = 'SELECTED';
$selected{'PROTOCOL'}{'udp'} = '';
$selected{'PROTOCOL'}{'tcp'} = '';
$selected{'PROTOCOL'}{$cgiparams{'PROTOCOL'}} = 'SELECTED';
$checked{'AUTH'}{'psk'} = '';
$checked{'AUTH'}{'certreq'} = '';
$checked{'AUTH'}{'certgen'} = '';
$checked{'AUTH'}{'certfile'} = '';
$checked{'AUTH'}{$cgiparams{'AUTH'}} = 'CHECKED';
$selected{'INTERFACE'}{$cgiparams{'INTERFACE'}} = 'SELECTED';
$checked{'COMPLZO'}{'off'} = '';
$checked{'COMPLZO'}{'on'} = '';
$checked{'COMPLZO'}{$cgiparams{'COMPLZO'}} = 'CHECKED';
$checked{'MSSFIX'}{'off'} = '';
$checked{'MSSFIX'}{'on'} = '';
$checked{'MSSFIX'}{$cgiparams{'MSSFIX'}} = 'CHECKED';
$selected{'DCIPHER'}{'AES-256-GCM'} = '';
$selected{'DCIPHER'}{'AES-192-GCM'} = '';
$selected{'DCIPHER'}{'AES-128-GCM'} = '';
$selected{'DCIPHER'}{'CAMELLIA-256-CBC'} = '';
$selected{'DCIPHER'}{'CAMELLIA-192-CBC'} = '';
$selected{'DCIPHER'}{'CAMELLIA-128-CBC'} = '';
$selected{'DCIPHER'}{'AES-256-CBC'} = '';
$selected{'DCIPHER'}{'AES-192-CBC'} = '';
$selected{'DCIPHER'}{'AES-128-CBC'} = '';
$selected{'DCIPHER'}{'DESX-CBC'} = '';
$selected{'DCIPHER'}{'SEED-CBC'} = '';
$selected{'DCIPHER'}{'DES-EDE3-CBC'} = '';
$selected{'DCIPHER'}{'DES-EDE-CBC'} = '';
$selected{'DCIPHER'}{'CAST5-CBC'} = '';
$selected{'DCIPHER'}{'BF-CBC'} = '';
$selected{'DCIPHER'}{'DES-CBC'} = '';
$selected{'DCIPHER'}{$cgiparams{'DCIPHER'}} = 'SELECTED';
$selected{'DAUTH'}{'whirlpool'} = '';
$selected{'DAUTH'}{'SHA512'} = '';
$selected{'DAUTH'}{'SHA384'} = '';
$selected{'DAUTH'}{'SHA256'} = '';
$selected{'DAUTH'}{'SHA1'} = '';
$selected{'DAUTH'}{$cgiparams{'DAUTH'}} = 'SELECTED';
$checked{'TLSAUTH'}{'off'} = '';
$checked{'TLSAUTH'}{'on'} = '';
$checked{'TLSAUTH'}{$cgiparams{'TLSAUTH'}} = 'CHECKED';
if (1) {
&Header::showhttpheaders();
&Header::openpage($Lang::tr{'ovpn'}, 1, '');
&Header::openbigbox('100%', 'LEFT', '', $errormessage);
# Show any errors
&Header::errorbox($errormessage);
if ($warnmessage) {
&Header::openbox('100%', 'LEFT', "$Lang::tr{'warning messages'}:");
print "$warnmessage";
print " ";
&Header::closebox();
}
print "";
&Header::closebigbox();
&Header::closepage();
exit (0);
}
VPNCONF_END:
}
%cahash = ();
%confighash = ();
&General::readhasharray("${General::swroot}/ovpn/caconfig", \%cahash);
&General::readhasharray("${General::swroot}/ovpn/ovpnconfig", \%confighash);
my @status = ();
# Only load status when the RW server is enabled
if ($vpnsettings{'ENABLED'} eq 'on') {
open(FILE, "/usr/local/bin/openvpnctrl rw log |");
@status = ;
close(FILE);
}
$checked{'ENABLED'}{'off'} = '';
$checked{'ENABLED'}{'on'} = '';
$checked{'ENABLED'}{$vpnsettings{'ENABLED'}} = 'CHECKED';
&Header::showhttpheaders();
&Header::openpage($Lang::tr{'status ovpn'}, 1, '');
&Header::openbigbox('100%', 'LEFT', '', $errormessage);
# Show any errors and warnings
&Header::errorbox($errormessage);
if ($warnmessage) {
&Header::openbox('100%', 'LEFT', $Lang::tr{'warning messages'});
print "$warnmessage ";
print "$Lang::tr{'fwdfw warn1'} ";
&Header::closebox();
print"
";
&Header::closepage();
exit 0;
}
&Header::openbox('100%', 'LEFT', $Lang::tr{'ovpn roadwarrior settings'});
# Show the service status
&Header::ServiceStatus({
$Lang::tr{'ovpn roadwarrior server'} => {
"process" => "openvpn",
"pidfile" => "/var/run/openvpn-rw.pid",
}
});
print <
$Lang::tr{'enabled'}
$Lang::tr{'ovpn fqdn'}
$Lang::tr{'ovpn dynamic client subnet'}
END
&Header::closebox();
&Header::openbox('100%', 'LEFT', $Lang::tr{'connection status and controlc' });
print <
$Lang::tr{'name'}
$Lang::tr{'type'}
$Lang::tr{'remark'}
$Lang::tr{'status'}
$Lang::tr{'action'}
END
my $gif;
foreach my $key (sort { ncmp ($confighash{$a}[1],$confighash{$b}[1]) } keys %confighash) {
my $status = $confighash{$key}[0];
my $name = $confighash{$key}[1];
my $type = $confighash{$key}[3];
# Create some simple booleans to check the status
my $hasExpired = 0;
my $expiresSoon = 0;
# Fetch information about the certificate for non-N2N connections only
if ($confighash{$key}[3] ne 'net') {
my @cavalid = &General::system_output("/usr/bin/openssl", "x509", "-text",
"-in", "${General::swroot}/ovpn/certs/$confighash{$key}[1]cert.pem");
my $expiryDate = 0;
# Parse the certificate information
foreach my $line (@cavalid) {
if ($line =~ /Not After : (.*)[\n]/) {
$expiryDate = &Date::Parse::str2time($1);
last;
}
}
# Calculate the remaining time
my $remainingTime = $expiryDate - time();
# Determine whether the certificate has already expired, or will so soon
$hasExpired = ($remainingTime <= 0);
$expiresSoon = ($remainingTime <= 30 * 24 * 3600);
}
my @classes = ();
# Highlight the row if the certificate has expired/will expire soon
if ($hasExpired || $expiresSoon) {
push(@classes, "is-warning");
}
# Start a new row
print "
END
;
my $col1="bgcolor='$Header::color{'color22'}'";
my $col2="bgcolor='$Header::color{'color20'}'";
# DH parameter line
my $col3="bgcolor='$Header::color{'color22'}'";
# ta.key line
my $col4="bgcolor='$Header::color{'color20'}'";
if (-f "${General::swroot}/ovpn/ca/cacert.pem") {
my @casubject = &General::system_output("/usr/bin/openssl", "x509", "-text", "-in", "${General::swroot}/ovpn/ca/cacert.pem");
my $casubject;
foreach my $line (@casubject) {
if ($line =~ /Subject: (.*)[\n]/) {
$casubject = $1;
$casubject =~ s+/Email+, E+;
$casubject =~ s/ ST=/ S=/;
last;
}
}
print <