From 9d38669861117bc30253483cd314ff6ed563ff02 Mon Sep 17 00:00:00 2001 From: Alexander Marx Date: Tue, 14 Jan 2014 21:57:26 +0100 Subject: [PATCH] index.cgi: Re-design index page. --- html/cgi-bin/gpl.cgi | 82 +++++++++ html/cgi-bin/index.cgi | 367 ++++++++++++++++++++--------------------- langs/de/cgi-bin/de.pl | 2 + langs/en/cgi-bin/en.pl | 2 + 4 files changed, 265 insertions(+), 188 deletions(-) create mode 100644 html/cgi-bin/gpl.cgi diff --git a/html/cgi-bin/gpl.cgi b/html/cgi-bin/gpl.cgi new file mode 100644 index 000000000..7589054b6 --- /dev/null +++ b/html/cgi-bin/gpl.cgi @@ -0,0 +1,82 @@ +#!/usr/bin/perl +############################################################################### +# # +# IPFire.org - A linux based firewall # +# Copyright (C) 2007-2012 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; +# enable only the following on debugging purpose +#use warnings; +#use CGI::Carp 'fatalsToBrowser'; + +require '/var/ipfire/general-functions.pl'; +require "${General::swroot}/lang.pl"; +require "${General::swroot}/header.pl"; +require "/opt/pakfire/lib/functions.pl"; + + +my %cgiparams; +my $refresh; + +if ( -e "/var/ipfire/main/gpl_accepted" ) { + print "Status: 302 Moved Temporarily\n"; + print "Location: index.cgi\n\n"; + exit (0); +} +&Header::showhttpheaders(); + +$cgiparams{'ACTION'} = ''; +&Header::getcgihash(\%cgiparams); + +&Header::openpage($Lang::tr{'main page'}, 1, $refresh); +&Header::openbigbox('', 'center'); + +# licence agreement +if ($cgiparams{'ACTION'} eq $Lang::tr{'yes'} && $cgiparams{'gpl_accepted'} eq '1') { + system('touch /var/ipfire/main/gpl_accepted'); +} + +&Header::openbox('100%', 'left', $Lang::tr{'gpl license agreement'}); +print <
+END +; +if ( -e "/usr/share/doc/licenses/GPLv3" ) { + print ''; +} +else { + print '
GNU GENERAL PUBLIC LICENSE
'; +} +print < +
+ $Lang::tr{'gpl i accept these terms and conditions'}. +
+ +
+

+ $Lang::tr{'gpl unofficial translation of the general public license v3'} + +END + +&Header::closebox(); +&Header::closebigbox(); +&Header::closepage(); diff --git a/html/cgi-bin/index.cgi b/html/cgi-bin/index.cgi index d6115bdb0..3b44baad3 100644 --- a/html/cgi-bin/index.cgi +++ b/html/cgi-bin/index.cgi @@ -21,6 +21,7 @@ use strict; use Net::Telnet; +use Sort::Naturally; # enable only the following on debugging purpose #use warnings; @@ -36,12 +37,22 @@ my %pppsettings=(); my %modemsettings=(); my %netsettings=(); my %ddnssettings=(); +my %proxysettings=(); +my %vpnsettings=(); +my %vpnconfig=(); +my %ovpnconfig=(); my $warnmessage = ''; my $refresh = ""; my $ipaddr=''; - -my $haveipsec=0; -my $haveovpn=0; +my $showbox=0; +my $showipsec=0; +my $showovpn=0; + +if ( ! -e "/var/ipfire/main/gpl_accepted" ) { + print "Status: 302 Moved Temporarily\n"; + print "Location: gpl.cgi\n\n"; + exit (0); +} &Header::showhttpheaders(); @@ -53,6 +64,8 @@ $pppsettings{'PROFILENAME'} = 'None'; &General::readhash("${General::swroot}/modem/settings", \%modemsettings); &General::readhash("${General::swroot}/ethernet/settings", \%netsettings); &General::readhash("${General::swroot}/ddns/settings", \%ddnssettings); +&General::readhash("${General::swroot}/proxy/advanced/settings", \%proxysettings); +&General::readhash("${General::swroot}/vpn/settings", \%vpnsettings); my %color = (); my %mainsettings = (); @@ -61,12 +74,12 @@ my %mainsettings = (); my $connstate = &Header::connectionstatus(); - if ( -e "/var/ipfire/main/gpl-accepted" ) { -if ($connstate =~ /$Lang::tr{'connecting'}/ || /$Lang::tr{'connection closed'}/ ){ - $refresh = ""; -} elsif ($connstate =~ /$Lang::tr{'dod waiting'}/ || -e "${General::swroot}/main/refreshindex") { - $refresh = ""; -} +if ( -e "/var/ipfire/main/gpl-accepted" ) { + if ($connstate =~ /$Lang::tr{'connecting'}/ || /$Lang::tr{'connection closed'}/ ){ + $refresh = ""; + }elsif ($connstate =~ /$Lang::tr{'dod waiting'}/ || -e "${General::swroot}/main/refreshindex") { + $refresh = ""; + } } if ($cgiparams{'ACTION'} eq $Lang::tr{'dial profile'}) @@ -81,8 +94,8 @@ if ($cgiparams{'ACTION'} eq $Lang::tr{'dial profile'}) unlink("${General::swroot}/ppp/settings"); link("${General::swroot}/ppp/settings-$cgiparams{'PROFILE'}", "${General::swroot}/ppp/settings"); - system ("/usr/bin/touch", "${General::swroot}/ppp/updatesettings"); - + open (TMP, ">${General::swroot}/ppp/updatesettings"); + close TMP; # read in the new params "early" so we can write secrets. %cgiparams = (); &General::readhash("${General::swroot}/ppp/settings", \%cgiparams); @@ -106,10 +119,11 @@ if ($cgiparams{'ACTION'} eq $Lang::tr{'dial profile'}) if ($cgiparams{'ACTION'} eq $Lang::tr{'dial'}) { system('/usr/local/bin/redctrl start > /dev/null') == 0 - or &General::log("Dial failed: $?"); sleep 1;} -elsif ($cgiparams{'ACTION'} eq $Lang::tr{'hangup'}) { + or &General::log("Dial failed: $?"); sleep 1; +}elsif ($cgiparams{'ACTION'} eq $Lang::tr{'hangup'}) { system('/usr/local/bin/redctrl stop > /dev/null') == 0 - or &General::log("Hangup failed: $?"); sleep 1;} + or &General::log("Hangup failed: $?"); sleep 1; +} my $c; my $maxprofiles = 5; @@ -129,29 +143,21 @@ for ($c = 1; $c <= $maxprofiles; $c++) { $selected{'PROFILE'}{$pppsettings{'PROFILE'}} = "selected='selected'"; my $dialButtonDisabled = "disabled='disabled'"; - &Header::openpage($Lang::tr{'main page'}, 1, $refresh); &Header::openbigbox('', 'center'); +if (open(IPADDR,"${General::swroot}/red/local-ipaddress")) { + $ipaddr = ; + close IPADDR; + chomp ($ipaddr); + } -# licence agreement -if ($cgiparams{'ACTION'} eq $Lang::tr{'yes'} && $cgiparams{'gpl_accepted'} eq '1') { - system('touch /var/ipfire/main/gpl_accepted') -} -if ( -e "/var/ipfire/main/gpl_accepted" ) { &Header::openbox('100%', 'center', ''); - - if ( ( $pppsettings{'VALID'} eq 'yes' && $modemsettings{'VALID'} eq 'yes' ) || ( $netsettings{'CONFIG_TYPE'} =~ /^(1|2|3|4)$/ && $netsettings{'RED_TYPE'} =~ /^(DHCP|STATIC)$/ )) { if (open(IPADDR,"${General::swroot}/ddns/ipcache")) { $ipaddr = ; close IPADDR; chomp ($ipaddr); } - if (open(IPADDR,"${General::swroot}/red/local-ipaddress")) { - $ipaddr = ; - close IPADDR; - chomp ($ipaddr); - } } elsif ($modemsettings{'VALID'} eq 'no') { print "$Lang::tr{'modem settings have errors'}\n \n"; } else { @@ -160,45 +166,54 @@ if ( ( $pppsettings{'VALID'} eq 'yes' && $modemsettings{'VALID'} eq 'yes' ) || ( print < - +
- END my $HOSTNAME = (gethostbyaddr(pack("C4", split(/\./, $ipaddr)), 2))[0]; if ( "$HOSTNAME" ne "" ) { print < END } - if ( -e "/var/ipfire/red/remote-ipaddress" ) { - my $GATEWAY = `cat /var/ipfire/red/remote-ipaddress`; + if ( -e "${General::swroot}/red/remote-ipaddress" ) { + open (TMP, "<${General::swroot}/red/remote-ipaddress"); + my $GATEWAY = ; chomp($GATEWAY); + close TMP; print < END } - - my $DNS1 = `cat /var/ipfire/red/dns1`; - my $DNS2 = `cat /var/ipfire/red/dns2`; - chomp($DNS1); + #Read DNS server 1 + open (DNS1, "<${General::swroot}/red/dns1"); + my $DNS1 = ; chomp($DNS1); + close DNS1; + #Read DNS server 2 + open (DNS2, "<${General::swroot}/red/dns2"); + my $DNS2 = ; + chomp($DNS2); + close DNS2; if ( $DNS1 ) { print < END } if ( $DNS2 ) { print <$DNS2 + +
$Lang::tr{'network'} $Lang::tr{'ip address'} $Lang::tr{'status'}
$Lang::tr{'internet'}
$ipaddr $connstate + $connstate
Hostname:$HOSTNAME  +
Hostname:$HOSTNAMEGateway:$GATEWAY  +
Gateway:$GATEWAY
DNS-Server:$DNS1 +
$Lang::tr{'dns server'}1:$DNS1
$Lang::tr{'dns server'}2:$DNS2
END } else { print <  + + END } @@ -255,9 +270,9 @@ END $netsettings{'GREEN_ADDRESS'}/$sub END - if ( `cat /var/ipfire/proxy/advanced/settings | grep ^ENABLE=on` ) { - print $Lang::tr{'advproxy on'}; - if ( `cat /var/ipfire/proxy/advanced/settings | grep ^TRANSPARENT=on` ) { print " (transparent)"; } + if ( $proxysettings{'ENABLE'} eq 'on' ) { + print $Lang::tr{'advproxy on'}; + if ( $proxysettings{'TRANSPARENT'} eq 'on' ) { print " (transparent)"; } } else { print $Lang::tr{'advproxy off'}; } } if ( $netsettings{'BLUE_DEV'} ) { @@ -267,9 +282,9 @@ END $netsettings{'BLUE_ADDRESS'}/$sub END - if ( `cat /var/ipfire/proxy/advanced/settings | grep ^ENABLE_BLUE=on` ) { - print $Lang::tr{'advproxy on'}; - if ( `cat /var/ipfire/proxy/advanced/settings | grep ^TRANSPARENT_BLUE=on` ) { print " (transparent)"; } + if ( $proxysettings{'ENABLE_BLUE'} eq 'on' ) { + print $Lang::tr{'advproxy on'}; + if ( $proxysettings{'TRANSPARENT_BLUE'} eq 'on' ) { print " (transparent)"; } } else { print $Lang::tr{'advproxy off'}; } } if ( $netsettings{'ORANGE_DEV'} ) { @@ -281,10 +296,8 @@ END END } #check if IPSEC is running -if ( `cat /var/ipfire/vpn/settings | grep ^ENABLED=on` || - `cat /var/ipfire/vpn/settings | grep ^ENABLED_BLUE=on` ) { - $haveipsec=1; - my $ipsecip = `cat /var/ipfire/vpn/settings | grep ^VPN_IP= | cut -c 8-`; +if ( $vpnsettings{'ENABLED'} eq 'on' || $vpnsettings{'ENABLED_BLUE'} eq 'on' ) { + my $ipsecip = $vpnsettings{'VPN_IP'}; print<$Lang::tr{'ipsec'}
$ipsecip @@ -302,7 +315,6 @@ if (($confighash{'ENABLED'} eq "on") || my ($ovpnip,$sub) = split("/",$confighash{'DOVPN_SUBNET'}); $sub=&General::iporsubtocidr($sub); $ovpnip="$ovpnip/$sub"; - $haveovpn=1; print < @@ -315,53 +327,73 @@ END print""; &Header::closebox(); -# Start of Box wich contains all vpn connections - &Header::openbox('100%', 'center', $Lang::tr{'vpn'}) if ($haveipsec || $haveovpn); +#Check if there are any vpns configured (ipsec and openvpn) +&General::readhasharray("${General::swroot}/vpn/config", \%vpnconfig); +foreach my $key (sort { ncmp($vpnconfig{$a}[1],$vpnconfig{$b}[1]) } keys %vpnconfig) { + if ($vpnconfig{$key}[0] eq 'on'){ + $showipsec=1; + $showbox=1; + last; + } +} +&General::readhasharray("${General::swroot}/ovpn/ovpnconfig", \%ovpnconfig); +foreach my $dkey (sort { ncmp($ovpnconfig{$a}[1],$ovpnconfig{$b}[1])} keys %ovpnconfig) { + if (($ovpnconfig{$dkey}[3] eq 'net') && (-e "/var/run/$ovpnconfig{$dkey}[1]n2n.pid")){ + $showbox=1; + $showovpn=1; + last; + } +} +if ($showbox){ +# Start of Box wich contains all vpn connections + &Header::openbox('100%', 'center', $Lang::tr{'vpn'}); #show ipsec connectiontable - if ( $haveipsec ) { - my $ipsecip = `cat /var/ipfire/vpn/settings | grep ^VPN_IP= | cut -c 8-`; + if ( $showipsec ) { + my $ipsecip = $vpnsettings{'VPN_IP'}; my @status = `/usr/local/bin/ipsecctrl I`; my %confighash = (); - &General::readhasharray("${General::swroot}/vpn/config", \%confighash); + my $id = 0; + my $gif; + my $col=""; + my $count=0; print < - - - + + + END - my $id = 0; - my $gif; - my $col=""; - foreach my $key (sort { uc($confighash{$a}[1]) cmp uc($confighash{$b}[1]) } keys %confighash) { - if ($confighash{$key}[0] eq 'on') { $gif = 'on.gif'; } else { $gif = 'off.gif'; } - my ($vpnip,$vpnsub) = split("/",$confighash{$key}[11]); - $vpnsub=&General::iporsubtocidr($vpnsub); - $vpnip="$vpnip/$vpnsub"; - if ($id % 2) { - $col="bgcolor='$color{'color20'}'"; - print ""; - } else { - $col="bgcolor='$color{'color22'}'"; - print ""; - } - - my $active = ""; - if ($confighash{$key}[0] eq 'off') { - $active = ""; - } else { - foreach my $line (@status) { - if (($line =~ /\"$confighash{$key}[1]\".*IPsec SA established/) || - ($line =~/$confighash{$key}[1]\{.*INSTALLED/ )) - { - $active = "
$Lang::tr{'ipsec network'}$Lang::tr{'ip address'}$Lang::tr{'status'}$Lang::tr{'ipsec network'}$Lang::tr{'ip address'}$Lang::tr{'status'}
$confighash{$key}[1] / " . $Lang::tr{"$confighash{$key}[3]"} . " (" . $Lang::tr{"$confighash{$key}[4]"} . ")$vpnip
$confighash{$key}[1] / " . $Lang::tr{"$confighash{$key}[3]"} . " (" . $Lang::tr{"$confighash{$key}[4]"} . ")$vpnip$Lang::tr{'capsclosed'}$Lang::tr{'capsclosed'}
$Lang::tr{'capsopen'}
"; + foreach my $key (sort { uc($vpnconfig{$a}[1]) cmp uc($vpnconfig{$b}[1]) } keys %vpnconfig) { + if ($vpnconfig{$key}[0] eq 'on') { + $count++; + my ($vpnip,$vpnsub) = split("/",$vpnconfig{$key}[11]); + $vpnsub=&General::iporsubtocidr($vpnsub); + $vpnip="$vpnip/$vpnsub"; + if ($count % 2){ + $col="bgcolor='$color{'color22'}'"; + }else{ + $col="bgcolor='$color{'color20'}'"; + } + if ($id % 2) { + print "$vpnconfig{$key}[1]$vpnip"; + } else { + print "$vpnconfig{$key}[1]$vpnip"; + } + my $active = "$Lang::tr{'capsclosed'}"; + if ($vpnconfig{$key}[0] eq 'off') { + $active = "$Lang::tr{'capsclosed'}"; + } else { + foreach my $line (@status) { + if (($line =~ /\"$vpnconfig{$key}[1]\".*IPsec SA established/) || ($line =~/$vpnconfig{$key}[1]\{.*INSTALLED/ )){ + $active = "$Lang::tr{'capsopen'}"; + } + } } - } + print "$active"; } - print "$active"; } print ""; } @@ -370,77 +402,67 @@ END # Check if there is any OpenVPN connection configured. ### -if ( $haveovpn ) -{ - print < - - - - - + if ( $showovpn ){ + print < +
$Lang::tr{'openvpn network'}$Lang::tr{'ip address'}$Lang::tr{'status'}
+ + + + END - # Check if the OpenVPN server for Road Warrior Connections is running and display status information. - my %confighash=(); - &General::readhash("${General::swroot}/ovpn/settings", \%confighash); - # Print the OpenVPN N2N connection status. - if ( -d "${General::swroot}/ovpn/n2nconf") { - my %confighash=(); - - &General::readhasharray("${General::swroot}/ovpn/ovpnconfig", \%confighash); - my $lines; - my $col=""; - foreach my $dkey (keys %confighash) { - $lines++; - if (($confighash{$dkey}[3] eq 'net') && (-e "/var/run/$confighash{$dkey}[1]n2n.pid")) { - my $tport = $confighash{$dkey}[22]; - next if ($tport eq ''); - - my $tnet = new Net::Telnet ( Timeout=>5, Errmode=>'return', Port=>$tport); - $tnet->open('127.0.0.1'); - my @output = $tnet->cmd(String => 'state', Prompt => '/(END.*\n|ERROR:.*\n)/'); - my @tustate = split(/\,/, $output[1]); - - my $display; - my $display_colour = $Header::colourred; - if (($tustate[1] eq 'CONNECTED') || ($tustate[1] eq 'WAIT')) { - $display_colour = $Header::colourgreen; - $display = $Lang::tr{'capsopen'}; - } else { - $display = $tustate[1]; - } - if ($lines %2){ - $col="bgcolor='$color{'color20'}'"; - }else{ - $col="bgcolor='$color{'color22'}'"; - } - #make cidr from ip - my ($vpnip,$vpnsub) = split("/",$confighash{$dkey}[11]); - my $vpnsub=&General::iporsubtocidr($vpnsub); - my $vpnip="$vpnip/$vpnsub"; - print < - - - - + # Check if the OpenVPN server for Road Warrior Connections is running and display status information. + my $active; + my $count=0; + # Print the OpenVPN N2N connection status. + if ( -d "${General::swroot}/ovpn/n2nconf") { + my $col=""; + foreach my $dkey (sort { ncmp ($ovpnconfig{$a}[1],$ovpnconfig{$b}[1])} keys %ovpnconfig) { + if (($ovpnconfig{$dkey}[3] eq 'net') && (-e "/var/run/$ovpnconfig{$dkey}[1]n2n.pid")){ + $count++; + my $tport = $ovpnconfig{$dkey}[22]; + next if ($tport eq ''); + my $tnet = new Net::Telnet ( Timeout=>5, Errmode=>'return', Port=>$tport); + $tnet->open('127.0.0.1'); + my @output = $tnet->cmd(String => 'state', Prompt => '/(END.*\n|ERROR:.*\n)/'); + my @tustate = split(/\,/, $output[1]); + my $display; + my $display_colour = $Header::colourred; + if ( $tustate[1] eq 'CONNECTED' || ($tustate[1] eq 'WAIT')) { + $display_colour = $Header::colourgreen; + $display = $Lang::tr{'capsopen'}; + } else { + $display = $tustate[1]; + } + if ($count %2){ + $col="bgcolor='$color{'color22'}'"; + }else{ + $col="bgcolor='$color{'color20'}'"; + } + $active='off'; + #make cidr from ip + my ($vpnip,$vpnsub) = split("/",$ovpnconfig{$dkey}[11]); + my $vpnsub=&General::iporsubtocidr($vpnsub); + my $vpnip="$vpnip/$vpnsub"; + print < + + + + END + } } } + if ($active ne 'off'){ + print ""; + } + print"
$Lang::tr{'openvpn network'}$Lang::tr{'ip address'}$Lang::tr{'status'} - $confighash{$dkey}[1] - - $vpnip - - - - $display - - -
$ovpnconfig{$dkey}[1]$vpnip$display
$Lang::tr{'ovpn no connections'}
"; } -} &Header::closebox(); +} + # Fireinfo if ( ! -e "/var/ipfire/main/send_profile") { $warnmessage .= "
  • $Lang::tr{'fireinfo please enable'}
  • "; @@ -501,52 +523,21 @@ foreach my $disk (@files) { $warnmessage .= "
  • $disk - $Lang::tr{'deprecated fs warn'}
  • \n\n"; } - if ($warnmessage) { + &Header::openbox('100%','center', ); + print ""; + print ""; print "
    $Lang::tr{'fwhost hint'}
    $warnmessage
    "; + &Header::closebox(); } -print < -END -; + +print "
    "; &Pakfire::dblist("upgrade", "notice"); -print <
    $Lang::tr{'needreboot'}!"; } -&Header::closebox(); -} -else { -&Header::openbox('100%', 'left', $Lang::tr{'gpl license agreement'}); -print <
    -END -; -if ( -e "/usr/share/doc/licenses/GPLv3" ) { - print ''; -} -else { - print '
    GNU GENERAL PUBLIC LICENSE
    '; -} -print < -
    - $Lang::tr{'gpl i accept these terms and conditions'}. -
    - -
    -

    - $Lang::tr{'gpl unofficial translation of the general public license v3'} - -END - -&Header::closebox(); -} +print "
    "; &Header::closebigbox(); &Header::closepage(); diff --git a/langs/de/cgi-bin/de.pl b/langs/de/cgi-bin/de.pl index b4753a6fd..eb11acc91 100644 --- a/langs/de/cgi-bin/de.pl +++ b/langs/de/cgi-bin/de.pl @@ -1273,6 +1273,7 @@ 'iptmangles' => 'IPTable Mangles', 'ipsec' => 'IPsec', 'ipsec network' => 'IPsec-Netzwerke', +'ipsec no connections' => 'Keine aktiven IPsec Verbindungen', 'iptnats' => 'IPTable Network Address Translation', 'ipts' => 'iptables', 'isdn' => 'ISDN', @@ -1603,6 +1604,7 @@ 'ovpn mtu-disc off' => 'Deaktiviert', 'ovpn mtu-disc with mssfix or fragment' => 'Path MTU Discovery kann nicht gemeinsam mit mssfix oder fragment verwendet werden.', 'ovpn mtu-disc yes' => 'Forciert', +'ovpn no connections' => 'Keine aktiven OpenVPN Verbindungen', 'ovpn on blue' => 'OpenVPN auf BLAU', 'ovpn on orange' => 'OpenVPN auf ORANGE', 'ovpn on red' => 'OpenVPN auf ROT', diff --git a/langs/en/cgi-bin/en.pl b/langs/en/cgi-bin/en.pl index 3d9a5eb4d..8f8356b4b 100644 --- a/langs/en/cgi-bin/en.pl +++ b/langs/en/cgi-bin/en.pl @@ -1300,6 +1300,7 @@ 'ipinfo' => 'IP info', 'ipsec' => 'IPsec', 'ipsec network' => 'IPsec network', +'ipsec no connections' => 'No active IPsec connections', 'iptable rules' => 'IPTable rules', 'iptmangles' => 'IPTable Mangles', 'iptnats' => 'IPTable Network Address Translation', @@ -1633,6 +1634,7 @@ 'ovpn mtu-disc off' => 'Disabled', 'ovpn mtu-disc with mssfix or fragment' => 'Path MTU Discovery cannot be used with mssfix or fragment.', 'ovpn mtu-disc yes' => 'Forced', +'ovpn no connections' => 'No active OpenVPN connections', 'ovpn on blue' => 'OpenVPN on BLUE', 'ovpn on orange' => 'OpenVPN on ORANGE', 'ovpn on red' => 'OpenVPN on RED', -- 2.39.2