#!/bin/sh

: <<SCRIPTHEADER
Description: VPN client/server helper
Version: 1.0.16
Copyright: GNU GPL (2021-2024) Narcis Garcia
Homepage: https://www.somtecnologia.com
License: GNU GPL
 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 <http://www.gnu.org/licenses/>.
SCRIPTHEADER

# Program development at programacio/projectes_publics/durruter/gesvipen
# Software releases can be downloaded from: https://...##

# Script template version: 1.5.7
# Copyright 2017-2024 (GNU GPL) Narcis Garcia
# Compatible with package managers integration: Dpkg/deb

# NOTES:
#	- To make use of easy-rsa it must be version 3 or newer
#	- See for supported options by OpenVPN:
#		https://community.openvpn.net/openvpn/wiki/ChangesInOpenvpn20
#		https://community.openvpn.net/openvpn/wiki/ChangesInOpenvpn21
#		https://community.openvpn.net/openvpn/wiki/ChangesInOpenvpn22
#		https://community.openvpn.net/openvpn/wiki/ChangesInOpenvpn23
#		https://community.openvpn.net/openvpn/wiki/ChangesInOpenvpn24
#		https://community.openvpn.net/openvpn/wiki/ChangesInOpenvpn25
#		https://community.openvpn.net/openvpn/wiki/ChangesInOpenvpn26
#	- OpenVPN v2.3.18 was the last version that officially supports Windows XP and Windows Server 2003
#	  https://build.openvpn.net/downloads/releases/

# Program development ToDo:
#	- Register cron.hourly task to restart dead links (p.e. due to temporary network failure)
#		Alternative: daemonize a kee-alive process to react faster
#	  Network failure can cause if-down.d to trigger durruter to disable concerned iptables too
#	- Prevent importing client account from served itself
#	- Export mode to .ovpn format for non-openvpn clients and Windows-OpenVPN clients
#	- Try to migrate to p2p topology
#	- Support non-Systemd environments
#	- Test non-static keys (debian <10)
#	- Client_account_remove does not remove sesele-created certificate files
#	- "passwd" action to update a client account password
#	- Test by serving through TCP/443 port
#	- "rename" function to rename client & server profiles.


# ProgramName: Brief and compact code name that needs to be unique in the software world. Will be used for filenames and some directories.
ProgramName="gesvipen"
# DependsOnSoftware: Space separated list of required programs. Each item's syntaxes: executable/package ExecutableOption1:ExecutableOption2/PackageOption1|PackageOption2...
DependsOnSoftware="tr:cat/coreutils|debianutils grep sed perl/perl-base openvpn find/findutils expect telnet sudo"
RecommendedSoftware="ip:brctl/iproute2|bridge-utils ip:ifconfig/iproute2|net-tools durruter apg xxd"
SuggestedSoftware="make-cadir/easy-rsa sesele"
SystemConfigDir="/etc/${ProgramName}"
# ProgramInstaller: If integrated install/uninstall functions will be available (1) or not (0)
ProgramInstaller=1
# InstalledExpected: If help information drives user to install program before using it (1) or not (0)
InstalledExpected=1
# RootRequired: Prevent to run without superuser permissions? (1=Yes 0=No). This also determines program FHS location (sbin/ or bin/).
RootRequired=1


##### TEMPLATE FUNCTIONS from @-funcions 0:2022.12.15 #####

ParO='(' ; ParC=')' ; Tab="$(printf '\t')"
if [ "$TERM" != "" ] ; then
	perl -e "exit${ParO}-p STDOUT ? 1 : 0${ParC};"
	RedirectedStdout=$?
fi
if [ "$TERM" != "" ] && [ $RedirectedStdout -eq 0 ] ; then
	cbDGRAY="$(tput setab 0)"
	cbRED="$(tput setab 196)"
	cbGREEN="$(tput setab 46)"
	cbCBROWN="$(tput setab 3)"
	cbVIOLET="$(tput setab 5)"
	cbBLUEGREEN="$(tput setab 6)"
	cbCGRAY="$(tput setab 7)"
	cbGRAY="$(tput setab 8)"
	cbCGREEN="$(tput setab 10)"
	cbCYELLOW="$(tput setab 11)"
	cbYELLOW="$(tput setab 226)"
	cbCBLUE="$(tput setab 12)"
	cbCVIOLET="$(tput setab 13)"
	cbCYAN="$(tput setab 51)"
	cbWHITE="$(tput setab 15)"
	cbBLACK="$(tput setab 16)"
	cbBLUE="$(tput setab 21)"
	cbPINK="$(tput setab 26)"
	
	cfDGRAY="$(tput setaf 0)"
	cfRED="$(tput setaf 196)"
	cfGREEN="$(tput setaf 46)"
	cfCBROWN="$(tput setaf 3)"
	cfVIOLET="$(tput setaf 5)"
	cfBLUEGREEN="$(tput setaf 6)"
	cfCGRAY="$(tput setaf 7)"
	cfGRAY="$(tput setaf 8)"
	cfCGREEN="$(tput setaf 10)"
	cfCYELLOW="$(tput setaf 11)"
	cfYELLOW="$(tput setaf 226)"
	cfCBLUE="$(tput setaf 12)"
	cfCVIOLET="$(tput setaf 13)"
	cfCYAN="$(tput setaf 51)"
	cfWHITE="$(tput setaf 15)"
	cfBLACK="$(tput setaf 16)"
	cfBLUE="$(tput setaf 21)"
	cfPINK="$(tput setaf 26)"
	
	fBOLD="$(tput bold)"
	fREVERSEC="$(tput rev)"
	fLOW="$(tput dim)"
	fUNDERL="$(tput smul)"
	fUNDERLx="$(tput rmul)"
	fRESET="$(tput sgr0)"
	
	sPROMPT="${cfWHITE}${cbDGRAY}${fBOLD}$(tput bel)"
	sHEAD0="${cfCYAN}${cbBLACK}${fBOLD}${fUNDERL}"
	sHEAD1="${cfCYAN}${cbDGRAY}${fBOLD}"
	sWARN="${cfYELLOW}${cbDGRAY}${fBOLD}"
	sERROR="${cfRED}${cbBLACK}${fBOLD}"
	sINFO="${cfWHITE}${cbDGRAY}"
	sPROGRESS="${cfCGREEN}${cbDGRAY}"
	sVALUE="${cfVIOLET}${cbDGRAY}${fBOLD}"
	sGOOD="${cfWHITE}${cbBLUEGREEN}${fBOLD}"
	sDISABLED="${fLOW}"
else
	cbDGRAY=''
	cbRED=''
	cbGREEN=''
	cbCBROWN=''
	cbVIOLET=''
	cbBLUEGREEN=''
	cbCGRAY=''
	cbGRAY=''
	cbCGREEN=''
	cbCYELLOW=''
	cbYELLOW=''
	cbCBLUE=''
	cbCVIOLET=''
	cbCYAN=''
	cbWHITE=''
	cbBLACK=''
	cbBLUE=''
	cbPINK=''
	
	cfDGRAY=''
	cfRED=''
	cfGREEN=''
	cfCBROWN=''
	cfVIOLET=''
	cfBLUEGREEN=''
	cfCGRAY=''
	cfGRAY=''
	cfCGREEN=''
	cfCYELLOW=''
	cfYELLOW=''
	cfCBLUE=''
	cfCVIOLET=''
	cfCYAN=''
	cfWHITE=''
	cfBLACK=''
	cfBLUE=''
	cfPINK=''
	
	fBOLD=''
	fREVERSEC=''
	fLOW=''
	fUNDERL=''
	fUNDERLx=''
	fRESET=''
	
	sPROMPT=''
	sHEAD0=''
	sHEAD1=''
	sWARN=''
	sERROR=''
	sINFO=''
	sPROGRESS=''
	sVALUE=''
	sGOOD=''
	sDISABLED=''
fi

Is_Executable ()
# Syntax as a function: Is_Executable $Command
# Description: Returns (exitcode 0) TRUE if specified command's argument is a directly available executable file, internal program or function; or FALSE otherwise.
# Use example (without brackets []):
#	if Is_Executable nano ; then echo "Program exists." ; fi
# Depends on functions: (none)
# Depends on software packages: (none)
{
	ie__Program="$1"
	ie__TrueCode=254  # 254=FALSE
	ie__TestValue=''
	
	ie__TestValue="$(command -v "$ie__Program" 2>/dev/null)"
	IsEmptyString () { WordsNr () { printf '%s' $#; }; return $(WordsNr $*); }
	if ! IsEmptyString "$ie__TestValue" ; then
		# Some scenarios "command" returns directories as executables.
		if [ ! -d "$ie__TestValue" ] || [ "$(printf '%s' "$ie__TestValue" | tr '/' ' ')" = "$ie__TestValue"  ] ; then
			ie__TrueCode=0
		fi
	fi
	return $ie__TrueCode
}

Is_IntegerNr ()
# Syntax as a function: Is_IntegerNr $Valor
# Description: Returns (exitcode 0) TRUE if specified value is an integer number, or FALSE otherwise. (IsNumeric/EsNumerico)
# Use example (without brackets []):
#	if Is_IntegerNr $Respuesta ; then echo "Correcto." ; fi
# Use example verifying also there is only 1 word:
#	if Is_IntegerNr "$Respuesta" ; then echo "Correcto." ; fi
# Notes:
#	- Only first parameter is evaluated. No matter next parameters contain.
# Depends on functions: (none)
# Depends on software packages: (none)
{
	iin__TestValue="$1"
	iin__TrueCode=254  # 254=FALSE
	iin__LastStatus=0
	
	iin__TestValue="$(expr "$iin__TestValue" : '[ ]*\(.*[^ ]\)[ ]*$')"	# Trim spaces
	if [ "$iin__TestValue" = "" ] ; then iin__TestValue='.' ; fi
	[ "$iin__TestValue" -eq "$iin__TestValue" ] > /dev/null 2>&1
	iin__LastStatus=$?
	if [ $iin__LastStatus -eq 0 ] ; then	iin__TrueCode=0 ; fi
	return $iin__TrueCode
}

Dirname ()
# Independent version of GNU dirname
# Useful for Busybox and other minimalistic environments
# Notes:
#	- Only allows 1 path specified
#	- Does not allow any option (such as -z or --version)
# Depends on functions: (none)
# Depends on software packages: (none)
{
	local Path="$1"
	local NrElement=0
	local TotalElements=0
	local CurrentElement=''
	local PreviousElement=''
	local Value=''
	
	if [ "$Path" != "" ] ; then
		if [ "$Path" != "/" ] ; then
			IFS="/" ; for CurrentElement in $Path ; do
				NrElement=$((NrElement + 1))
				if [ $NrElement -gt 1 ] ; then
					if [ $NrElement -gt 2 ] ; then
						if [ "$Value" != "/" ] ; then Value="${Value}/" ; fi
					fi
					Value="${Value}${PreviousElement}"
				else
					if [ "$CurrentElement" = "" ] ; then Value="/" ; fi
				fi
				PreviousElement="$CurrentElement"
			done
			unset IFS
			if [ $NrElement -eq 1 ] && [ "$Value" = "" ] ; then Value="." ; fi
		else
			Value="/"
		fi
	else
		Value="."
	fi
	if [ "$Value" != "" ] ; then printf '%s\n' "$Value" ; fi
}

ReadlinkF ()
# Old compatibility replacement for modern "readlink -f"
# "canonicalize by following every symlink in every component of the given name recursively; all but the last component must exist"
# Depends on functions: Dirname
# Depends on software packages: grep, sed
{
	local Element="$1"
	local PreviousDir=''
	local CurrentElement=''
	local CurrentPath=''
	local TmpElement=''
	local BaseElement=''
	local ReadlinkStatus=0
	local LastPath=''
	local CurStep=''
	local Value=''

	if [ "$Element" != "" ] ; then
		PreviousDir="$(pwd)"
		CurrentRelElement="$Element"
		if [ "$(ls "$CurrentRelElement" 2>/dev/null)" = "" ] && [ -d "$(Dirname "$CurrentRelElement")" ] ; then
			# Same as "readlink -f" behaviour: If element doesn't exist (but path yes), return as if existed.
			cd "$(Dirname "$CurrentRelElement")"
			Value="$(pwd | sed -e 's|/$||g')"
			Value="${Value}/$(printf '%s\n' "$CurrentRelElement" | tr -s '/' '\n' | tail -n 1)"
		fi
		while [ "$(ls "$CurrentRelElement" 2>/dev/null)" != "" ] ; do	# A broken link is not detected with -e
			if [ -d "$(Dirname "$CurrentRelElement")" ] ; then
				cd "$(Dirname "$CurrentRelElement")"
				CurrentPath="$(pwd)"
			else
				CurrentPath="$(Dirname "$CurrentRelElement")"
				if [ "$(printf '%s\n' "$CurrentPath" | grep -e '^/')" = "" ] ; then
					# Relative path
					LastPath="$(pwd)"
					IFS="/" ; for CurStep in $CurrentPath ; do unset IFS
						if [ "$CurStep" = ".." ] ; then
							LastPath="$(Dirname "$LastPath")"
						else
							if [ "$CurStep" = "." ] ; then
								LastPath="$LastPath"
							else
								if [ "$CurStep" = "" ] ; then
									LastPath="$LastPath"
								else
									LastPath="$(printf '%s\n' "$LastPath" | sed -e 's|/$||g')/${CurStep}"
								fi
							fi
						fi
					done
					CurrentPath="$LastPath"
				fi
				CurrentPath="$(printf '%s\n' "$CurrentPath" | sed -e 's|/$||g')"
			fi
			BaseElement="$(printf '%s\n' "$CurrentRelElement" | tr -s '/' '\n' | tail -n 1)"	# Problems with a path begun with "-" in old GNU basename versions
			if [ "$CurrentPath" = "/" ] ; then CurrentPath="" ; fi
			Value="${CurrentPath}/${BaseElement}"
			NextRelElement="$(readlink "$BaseElement" 2>/dev/null)"
			ReadlinkStatus=$?
			if [ "$NextRelElement" != "" ] ; then
				CurrentRelElement="$NextRelElement"
			else
				if [ $ReadlinkStatus -eq 0 ] || [ $ReadlinkStatus -eq 1 ] ; then
					# Old readlink (such as v1.13.3 in Potato debianutils) returns exitcode 1 if element is not a link.
					CurrentRelElement=""
				else
					NextRelElement="$(stat "$BaseElement" | grep -ie 'File:' | cut -f 4 -d '"')"	#'
					if [ "$NextRelElement" != "" ] ; then
						CurrentRelElement="$NextRelElement"
					else
						CurrentRelElement="$(stat "$BaseElement" | grep -ie 'File:' | cut -f 4 -d '"')"	#'
					fi
				fi
			fi
			if [ "$CurrentRelElement" != "" ] && [ "$(ls "$CurrentRelElement" 2>/dev/null)" = "" ] ; then
				# Broken link
				BaseElement="$(printf '%s\n' "$CurrentRelElement" | tr -s '/' '\n' | tail -n 1)"	# Problems with a path begun with "-" in old GNU basename versions
				CurrentPath="$(Dirname "$CurrentRelElement")"
				if [ "$(printf '%s\n' "$CurrentPath" | grep -e '^/')" = "" ] ; then
					# Relative path
					LastPath="$(pwd)"
					IFS="/" ; for CurStep in $CurrentPath ; do unset IFS
						if [ "$CurStep" = ".." ] ; then
							LastPath="$(Dirname "$LastPath")"
						else
							if [ "$CurStep" = "." ] ; then
								LastPath="$LastPath"
							else
								if [ "$CurStep" = "" ] ; then
									LastPath="$LastPath"
								else
									LastPath="$(printf '%s\n' "$LastPath" | sed -e 's|/$||g')/${CurStep}"
								fi
							fi
						fi
					done
					CurrentPath="$LastPath"
				fi
				CurrentPath="$(printf '%s\n' "$CurrentPath" | sed -e 's|/$||g')"
				Value="${CurrentPath}/${BaseElement}"
			fi
		done
		cd "$PreviousDir"
		if [ "$Value" != "" ] ; then printf '%s\n' "$Value" ; fi
	fi
}

Lowercase ()
# Description: Converts specified string to lower letter case, using found tools.
# Depends on functions: Is_Executable
# Depends on software packages: grep, tr|awk|sed
{
	local OriginalString="$1"
	
	if [ "$OriginalString" != "" ] ; then
		# Busybox tr has no upper/lower conversion
		if Is_Executable tr && [ "$(tr --help 2>&1 | grep -e ':lower:')" != "" ] ; then
			printf '%s\n' "$OriginalString" | tr '[:upper:]' '[:lower:]'
		else
			if Is_Executable awk ; then
				printf '%s\n' "$OriginalString" | awk '{print tolower($0)}'
			else
				if Is_Executable sed && [ "$(sed --help 2>&1 | grep -e 'GNU')" != "" ] ; then
					printf '%s\n' "$OriginalString" | sed -re 's/([[:upper:]]?)/\L\1/g'
				fi
			fi
		fi
	fi
}

PrepareExitcodeToFile ()
# Syntax as a sentence: PrepareExitcodeToFile
# Description: Prepares environment variables to catch exitcode from a file. Useful when pipelining functions output. Supports cascade calls and recursion.
# NOTES:
#	- Use this function just before desired execution. It's expected that execution runs some function that saves exitcode to $NozeroStatusToFile file.
#	- If called execution does not generate any file. Exitcode 0 will be assumed.
#	- Exports done in a subshell $(...) do not survive out of it, but most of times this technique works to catch exitcode from subshell executions.
{
	petf__LastStatus=0
	petf__LegatedIndex=0
	petf__CurLegatedName=''
	if [ "$NozeroStatusToFile" != "" ] ; then
		petf__CurLegatedName="NozeroStatusToFile_Legated_${petf__LegatedIndex}"
		while [ "$(eval "printf '%s' \"\$$petf__CurLegatedName\"")" != "" ] ; do
			petf__LegatedIndex=$((petf__LegatedIndex + 1))
			petf__CurLegatedName="NozeroStatusToFile_Legated_${petf__LegatedIndex}"
		done
		eval "$petf__CurLegatedName=\"$NozeroStatusToFile\""
		NozeroStatusToFile=''
	fi
	if [ -d "/run/shm" ] ; then
		NozeroStatusToFile="$(mktemp -u -q -p /run/shm 2>/dev/null)"
		petf__LastStatus=$?
	else
		NozeroStatusToFile="$(mktemp -u 2>/dev/null)"
		petf__LastStatus=$?
	fi
	if [ $petf__LastStatus -ne 0 ] ; then
		NozeroStatusToFile="$(mktemp 2>/dev/null)"
		petf__LastStatus=$?
	fi
	if [ $petf__LastStatus -ne 0 ] || [ "$NozeroStatusToFile" = "" ] ; then
		NozeroStatusToFile=/tmp/exitcode.$$
	fi
	export NozeroStatusToFile="$NozeroStatusToFile"
}

GetFiledExitcode ()
# Syntax as a sentence: GetFiledExitcode $?
# Description: Prepares environment variables to catch exitcode from a file. Useful when pipelining functions output. Supports cascade calls and recursion.
# NOTES:
#	- Use this function just after desired execution. It's expected that execution runs some function that saves exitcode to $NozeroStatusToFile file.
#	- If called execution does not generate any file. Exitcode 0 is assumed.
#	- This also accumulates pipelining exitcode.
#	- Exports done in a subshell $(...) do not survive out of it, but most of times this technique works to catch exitcode from subshell executions.
# Depends on functions: Is_IntegerNr
# Depends on software packages: (none)
{
	gfe__LastStatus="$1"
	gfe__StatusCode=0
	gfe__LegatedIndex=0
	gfe__CurLegatedName=''
	gfe__LastLegatedName=''
	gfe__LastLegatedValue=''
	
	# Get value
	gfe__StatusCode=$(cat "$NozeroStatusToFile" 2>/dev/null)
	if ! Is_IntegerNr "$gfe__StatusCode" ; then gfe__StatusCode=0 ; fi
	rm -f "$NozeroStatusToFile"
	if [ $gfe__StatusCode -eq 0 ] && Is_IntegerNr "$gfe__LastStatus" ; then gfe__StatusCode=$gfe__LastStatus ; fi
	# Pop legacies array stack
	gfe__CurLegatedName="NozeroStatusToFile_Legated_${gfe__LegatedIndex}"
	while [ "$(eval "printf '%s' \"\$$gfe__CurLegatedName\"")" != "" ] ; do
		gfe__LastLegatedName="$gfe__CurLegatedName"
		gfe__LegatedIndex=$((gfe__LegatedIndex + 1))
		gfe__CurLegatedName="NozeroStatusToFile_Legated_${gfe__LegatedIndex}"
	done
	if [ "$gfe__LastLegatedName" != "" ] ; then
		eval "gfe__LastLegatedValue=\"\$$gfe__LastLegatedName\""
	fi
	if [ "$gfe__LastLegatedValue" != "" ] ; then
		export NozeroStatusToFile="$gfe__LastLegatedValue"
	else
		if [ "$(help export 2>/dev/null)" ] ; then
			# Bashism to remove variable
			export -n NozeroStatusToFile
		else
			# Other POSIX store empty variables
			export NozeroStatusToFile=
		fi
	fi
	return $gfe__StatusCode
}

DependenciasFaltan ()
# Sintaxis como función: $(DependenciasFaltan "$ListaDependencias")
# Descripción:
#	Comprueba la disponibilidad de los programas especificados y
#	devuelve (stdout) una lista de los que faltan. Si se encuentra todo
#	no devuelve nada.
# Parámetros esperados:
#	$1	Lista separada por espacios de cada ejecutable y paquete respectivo,
#		separados entre si por "/"
#		Para varias opciones de ejecutable, separarlos entre ":"
#	$2	(opcional) Especifique "0" para evitar que a cada mención de paquete se [añada] el nombre del ejecutable que falta.
# Ejemplo1:	$(DependenciasFaltan "cat/coreutils grep/grep dpkg-deb/dpkg")
#		puede devolver "grep dpkg[dpkg-deb]" si faltan grep y dpkg-deb
# Ejemplo2:	$(DependenciasFaltan "insserv:update-rc.d/sysv-rc cat/coreutils")
#		puede devolver "sysv-rc[update-rc.d]" si tanto falta insserv como update-rc.d
# ToDo:
#	- Cambiar la sintaxis para permitir rutas de ejecutable: /bin/gzip
#	  Alternativas a la barra /: ? ! ;
#	- Para un ejecutable que condiciona requerir otro, separarlos entre "¡"
#	  Por ejemplo: postfix¡policyd-spf/postfix-policyd-spf-python
#	  significaría: SI existe "postfix" entonces debe existir "policyd-spf" y ello depende del paquete postfix-policyd-spf-python
#	- Para un ejecutable que es incompatible con otro, separarlos entre "!"
#	  Por ejemplo: postfix!exigrep/exim4-base
#	  significaría: SI existe "postfix" entonces NO debe existir "exigrep" y ello se resuelve desinstalando el paquete exim4-base
# Depends on functions: Is_Executable
# Depends on software packages: grep, sed
{
	local ListaDependencias="$1"
	local SenyalarEjecutable="$2"
	local DependenciaActual=''
	local EjecutablesActuales=''
	local EjecutableActual=''
	local EncontradoActual=''
	local PaqueteActual=''
	local Value=''
	
	for DependenciaActual in $ListaDependencias ; do
		EjecutablesActuales="$(printf '%s' "$DependenciaActual" | cut -f 1 -d '/')"
		PaqueteActual="$(printf '%s' "$DependenciaActual" | cut -f 2 -d '/')"
		EjecutablesActuales="$(printf '%s' "$EjecutablesActuales" | tr -s ':' ' ')"
		EncontradoActual=''
		for EjecutableActual in $EjecutablesActuales ; do
			if Is_Executable "$EjecutableActual" ; then
				EncontradoActual="1"
			fi
		done
		if [ "$EncontradoActual" = "" ] ; then
			if [ "$(printf '%s' " $Value " | grep -e " $PaqueteActual\[")" = "" ] || [ "$(printf '%s' " $Value " | grep -e " $PaqueteActual ")" = "" ] ; then
				if [ "$EjecutableActual" = "$PaqueteActual" ] || [ "$EjecutableActual" != "$EjecutablesActuales" ] || [ "$SenyalarEjecutable" = "0" ] ; then
					Value="$Value $PaqueteActual"
				else
					Value="$Value $PaqueteActual[$EjecutableActual]"
				fi
			fi
		fi
	done
	Value="$(echo TrimAndSingle $Value | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g')"
	if [ "$Value" != "" ] ; then printf '%s\n' "$Value" ; fi
}

NuevoTemporalSeguro ()
# Sintaxis como función: $(NuevoTemporalSeguro "$NombreBase" "$Extension" "$DirTemp")
# Descripción:
#	Crea y devuelve (stdout) la ruta de un fichero de tipo
#	/run/shm/usuario/NombreBase/20120821-091624.qBG8.ext con permisos rwX------
#	/tmp/usuario/NombreBase/20120821-091624.qBG8.ext con permisos rwX------
# Parámetros esperados:
#	$1	(opcional) Etiqueta ilustrativa
#	$2	(opcional) Extensión a añadir al nombre del fichero
#	$3	(opcional) Directorio-madre preferido en lugar de /run/shm o /tmp
# Notas:
#	- Si necesita ejecutar el fichero, debe evitar su creación en puntos como /run (noexec)
#	  especificando DirTemp
#	- Tambien es útil porque salva la falta de opciones --xx de versiones antiguas de mktemp
#	- Para directorios es mejor usar: TempName="${DirTemp}/${ProgramName}.$(id -un).$$.funcion" ; MkdirPP "$TempName" '' u=rw,g=r,o=
{
	local NombreBase="$1"
	local Extension="$2"
	local DirTemp="$3"
	local RutaDir=''
	local Tiempo=''
	local Valor=''
	local ModernTool=''
	local LastStatus=0
	local StatusCode=0
	
	RutaDir="${DirTemp}/$(id -un)"
	if [ "$NombreBase" != "" ] ; then
		RutaDir="${RutaDir}/${NombreBase}"
	fi
	mkdir -p "$RutaDir"
	chmod u=rwX,go= "${DirTemp}/$(id -un)"
	Tiempo="$(date +'%Y%m%d-%H%M%S')"
	if [ "$Extension" = "" ] ; then
		Valor="$(env TMPDIR="$RutaDir" mktemp -t ${Tiempo}.XXXX)"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	else
		ModernTool="$(mktemp --help 2>&1 | grep -e "--suffix")"
		if [ "$ModernTool" != "" ] ; then
			Valor="$(env TMPDIR="$RutaDir" mktemp -t ${Tiempo}.XXXX --suffix=.${Extension})"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		else
			Valor="$(env TMPDIR="$RutaDir" mktemp -t ${Tiempo}.XXXX)"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			if [ $StatusCode -eq 0 ] ; then
				mv "$Valor" "${Valor}.${Extension}"
				Valor="${Valor}.${Extension}"
			fi
		fi
	fi
	if [ "$Valor" != "" ] ; then
		if [ -f "$Valor" ] ; then
			printf '%s\n' "$Valor"
		fi
	fi
	if [ "$NozeroStatusToFile" != "" ] && [ ! -f "$NozeroStatusToFile" ] && [ $StatusCode -ne 0 ] ; then echo $StatusCode > "$NozeroStatusToFile" ; fi
	return $StatusCode
}

ExecuteString ()
# Description: Writes specified parameters to a shell script, and runs the script.
# Notes: In some rare case, neither eval/command/exec may work well
# Depends on functions: NuevoTemporalSeguro
# Depends on software packages: (none)
{
	local ScriptFile=""
	local ArgumentNr=0
	local CurrentArgument=""
	local LastStatus=0
	local StatusCode=0
	
	if [ ! -d "$DirTempX" ] ; then DirTempX="/tmp" ; fi
	ScriptFile="$(NuevoTemporalSeguro '' sh "$DirTempX")"
	printf '%s\n' '#!/bin/sh' > "$ScriptFile"
	while [ $# -gt 0 ] ; do
		CurrentArgument="$1"
		ArgumentNr=$((ArgumentNr + 1))
		if [ $ArgumentNr -gt 1 ] ; then
			printf ' ' >> "$ScriptFile"
		fi
		if [ "$CurrentArgument" = "" ] ; then CurrentArgument="''" ; fi
		if [ "$CurrentArgument" != "\"\"" ] && [ "$CurrentArgument" != "''" ] ; then
			EvalArgument="$(eval printf '%s' "$CurrentArgument" 2>/dev/null)"
			LastStatus=$?
			if [ $LastStatus -eq 0 ] ; then CurrentArgument="$EvalArgument" ; fi
		fi
		printf '%s' "$CurrentArgument" >> "$ScriptFile"
		shift
	done
	printf '\n' >> "$ScriptFile"
	printf '%s\n' 'exit $?' >> "$ScriptFile"
	chmod u+x "$ScriptFile"
	"$ScriptFile"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	rm "$ScriptFile"
	if [ "$NozeroStatusToFile" != "" ] && [ ! -f "$NozeroStatusToFile" ] && [ $StatusCode -ne 0 ] ; then echo $StatusCode > "$NozeroStatusToFile" ; fi
	return $StatusCode
}

IniSectionContent ()
# Syntax as a function: $(IniSectionContent "$FileOrContent" "$SectionName")
# Expected parameters:
#	$1	Path/name of file to query. If not exists as a file, string will be treated as content to look into.
#	$2	[section name] without brackets []. Example: "global"
#		specifying "[]" returns file content before any section declaration.
# Notes:
#	- Includes section header 
# Depends on functions:	(none)
# Depends on software packages: grep, sed
{
	local FileOrContent="$1"
	local SectionName="$2"
	local LinesNr=0
	local Value=''
	
	if [ "$FileOrContent" != "" ] && [ "$SectionName" != "" ] ; then
		if [ -f "$FileOrContent" ] ; then
			Value="$(cat "$FileOrContent")"
		else
			Value="$FileOrContent"
		fi
		if [ "$SectionName" != "[]" ] ; then
			Value="$(printf '%s\n' "$Value" | sed -rne "/^[[:blank:]]*[[]${SectionName}[]]/,//p")"
			if [ "$Value" != "" ] ; then
				printf '%s\n' "$Value" | head -n 1	# Print before being filtered
				LinesNr="$(printf '%s\n' "$Value" | wc -l)"
				# Omit current section header
				Value="$(printf '%s\n' "$Value" | tail -n $((LinesNr - 1)))"
				Value="$(printf '%s\n' "$Value" | sed -re '/^[[:blank:]]*[[].*[]]/q')"
				LinesNr="$(printf '%s\n' "$Value" | wc -l)"
				if [ "$(printf '%s\n' "$Value" | tail -n 1 | grep -e '^ [[].*[]]' -e '^[[].*[]]')" != "" ] && [ $LinesNr -ge 2 ] ; then
					# Omit next section header
					Value="$(printf '%s\n' "$Value" | head -n $((LinesNr - 1)))"
				else
					# Omit when desired section is empty and first line is next section's header
					Value="$(printf '%s\n' "$Value" | grep -ve '^ [[].*[]]' -ve '^[[].*[]]')"
				fi
			fi
		else
			Value="$(printf '%s\n' "$Value" | sed -re '/^[[:blank:]]*[[].*[]]/q' | grep -ve '^ [[].*[]]' -ve '^[[].*[]]')"
		fi
	fi
	if [ "$Value" != "" ] ; then printf '%s\n' "$Value" ; fi
}

IniVarValue ()
# Syntax as a function: $(IniVarValue "$FileOrContent" "$VariableName" "$SectionName" "$NotFoundValue" "$NameValueSeparator" "$EndVariableSymbol" "$DefaultsFile")
# Expected parameters:
#	$1	Path/name of file to query. If not exists as a file, string will be treated as content to look into.
#	$2	Variable name to query (case insensitive)
#	$3	(optional or empty) [section name] without brackets []. Default (empty): no section consideration. Examples: "global" ""
#	$4	(optional or empty) Value to return in case of not finding the variable in any file. Default; empty string.
#	$5	(optional or empty) Separator between variable name and assigned value. Default: "=". Examples: "=" ":="
#	$6	(optional or empty) End variable/assignation mark, to be omited from value. Default (empty): No end mark. Example: ";"
#	$7	(optional or empty) File path where to get DefaultValue from, if found. A found value prevails over 4th parameter (DefaultValue)
# To Do:
#	- This (or another function) to load more than one variable at once.
# Notes:
#	- if section is not specified or empty, returns last match from whole file content.
#	- if section is "[]", queries only file content before any [section] declaration.
# Depends on functions: IniSectionContent
# Depends on software packages: grep, sed
{
	local FileOrContent="$1"
	local VariableName="$2"
	local SectionName="$3"
	local NotFoundValue="$4"
	local NameValueSeparator="$5"
	local EndVariableSymbol="$6"
	local DefaultsFile="$7"
	local LinesNr=0
	local SectionContent=''
	local SeparatorMask='IVVtmpbHckF2LMB4tmpWz2coasdb3tmpX7LuyGTvrW'
	local NotFoundKey='IVVtmpStZrypNMzntmpgKqLEd5E5ttmpIWW5wemyCW'
	local Value=''
	
	if [ "$FileOrContent" != "" ] ; then
		if [ "$NameValueSeparator" = "" ] ; then NameValueSeparator='=' ; fi
		if [ "$SectionName" = "" ] ; then
			if [ -f "$FileOrContent" ] ; then
				SectionContent="$(cat "$FileOrContent")"
			else
				SectionContent="$FileOrContent"
			fi
		else
			SectionContent="$(IniSectionContent "$FileOrContent" "$SectionName")"
		fi
		Value="$(printf '%s\n' "$SectionContent" | tr -s '\t' ' ' | grep -ie "^${VariableName}${NameValueSeparator}" -ie "^ ${VariableName}${NameValueSeparator}" -ie "^ ${VariableName} ${NameValueSeparator}" | tail -n 1 | sed -e "s|${NameValueSeparator}|${SeparatorMask}|")"
		if [ "$Value" != "" ] ; then
			# Variable found; let's separate value.
			Value="$(printf '%s\n' "$Value" | sed -e "s|.*${SeparatorMask}||")"
			Value="$(expr "$Value" : "[ ]*\(.*[^ ]\)[ ]*$")"	# Trim
			if [ "$EndVariableSymbol" != "" ] ; then
				Value="$(printf '%s\n' "$Value" | sed -e "s|${EndVariableSymbol}$||")"
				Value="$(expr "$Value" : "[ ]*\(.*[^ ]\)[ ]*$")"	# Trim
			fi
			if [ "$(printf '%s\n' "$Value" | grep -e '^"' | grep -e '"$')" != "" ] ; then
				# Double quotes
				Value="$(printf '%s\n' "$Value" | cut -f 2- -d '"' | sed -e 's|"$||')"
			else
				if [ "$(printf '%s\n' "$Value" | grep -e "^'" | grep -e "'$")" != "" ] ; then
					# Single quotes
					Value="$(printf '%s\n' "$Value" | cut -f 2- -d "'" | sed -e "s|'$||")"
				fi
			fi
		else
			Value="$NotFoundKey"
		fi
	else
		Value="$NotFoundKey"
	fi
	if [ "$Value" = "$NotFoundKey" ] ; then
		if [ "$DefaultsFile" != "$FileOrContent" ] && [ -f "$DefaultsFile" ] ; then
			Value="$(IniVarValue "$DefaultsFile" "$VariableName" "$SectionName" "$NotFoundValue" "$NameValueSeparator" "$EndVariableSymbol" '')"
		else
			Value="$NotFoundValue"
		fi
	fi
	if [ "$Value" != "" ] ; then printf '%s\n' "$Value" ; fi
}

SetIniVarValue ()
# Syntax as a sentence: SetIniVarValue "$File" "$VariableName" "$SectionName" "$NewValue" "$NameValueSeparator" "$PreComment"
# Expected parameters:
#	$1	Path/name of file to query
#	$2	Variable name to query
#	$3	(optional or empty) [section name] without brackets []. Examples: "global" ""
#	$4	(optional or empty) Value to write for the variable entry.
#	$5	(optional or empty) Separator between variable name and assigned value. Examples: "=" ":=". Default is "="
#	$6	(optional or empty) Line to precede variable's line if it must be added. Comment mark should be included.
#		PreComment can contain escaped \n to be converted into line breaks.
# To Do:
#	- Process content separated by section (treat different variable as in different section)
#	- This (or another function) to set more than one variable at once.
# Notes:
#	- if section is not specified or empty, sets matches in the whole file.
#	- if section is "[]", sets variable only before any [section] declaration in the file.
#	- Some blank lines can be lost in resulting file content
# Depends on functions: Dirname IniSectionContent
# Depends on software packages: grep, sed
{
	local File="$1"
	local VariableName="$2"
	local SectionName="$3"	# Optional or empty (examples: "global" "")
	local NewValue="$4"	# If quotes are needed ("value") must be already contained in the supplied value ("\"value\""). Same with EndVariableSymbol.
	local NameValueSeparator="$5"	# Optional or empty (example: ":=") Default is "="
	local PreComment="$6"	# Optional line to precede variable's line if it must be added. Comment mark should be included.
	local OldContent=''
	local Part1=''
	local SectionContent=''
	local Part2=''
	local LastStatus=0
	local StatusCode=0
	
	if [ "$File" != "" ] && [ "$VariableName" != "" ] ; then
		if [ ! -f "$File" ] ; then
			mkdir -p "$(Dirname "$File")"
			touch "$File"
		fi
		if [ -f "$File" ] ; then
			if [ "$NameValueSeparator" = "" ] ; then NameValueSeparator="=" ; fi
			OldContent="$(cat "$File")"
			if [ "$SectionName" != "" ] ; then
				OldContentLinesNr=$(printf '%s\n' "$OldContent" | wc -l)
				if [ "$SectionName" = "[]" ] ; then
					SectionContent="$(printf '%s\n' "$OldContent" | sed -re '/^[[:blank:]]*[[].*[]]/q' | grep -ve '^[[].*[]]' -ve '^ [[].*[]]')"
					Part1LinesNr=$(printf '%s\n' "$SectionContent" | wc -l)
					cat /dev/null > "$File"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					if [ "$(printf '%s\n' "$SectionContent" | tr -s '\t' ' ' | tr -s ' ' | grep -ie "^${VariableName}${NameValueSeparator}" -ie "^ ${VariableName}${NameValueSeparator}" -ie "^ ${VariableName}${NameValueSeparator}" -ie "^ ${VariableName} ${NameValueSeparator}")" = "" ] ; then
						if [ "$SectionContent" != "" ] ; then
							printf '%s\n' "$SectionContent" >> "$File"
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						fi
						if [ "$PreComment" != "" ] ; then
							if [ "$(cat "$File" 2>/dev/null)" != "" ] && [ "$(cat "$File" | tail -n 1)" != "" ] ; then
								printf '%s\n' "" >> "$File"
								LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
							fi
							printf '%s\n' "$PreComment" | sed -e 's|\\n|\n|g' >> "$File"
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						fi
						printf '%s\n' "${VariableName}${NameValueSeparator}${NewValue}" >> "$File"
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					else
						printf '%s\n' "$SectionContent" | sed -re "s|^([[:blank:]]*)(${VariableName})([[:blank:]]*)(${NameValueSeparator})([[:blank:]]*).*|\1\2\3\4\5${NewValue}|" >> "$File"
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					fi
					printf '%s\n' "$OldContent" | tail -n $((OldContentLinesNr - Part1LinesNr)) >> "$File"
				else
					if [ "$(printf '%s\n' "$OldContent" | grep -e "^[[]${SectionName}[]]" -e "^ [[]${SectionName}[]]")" != "" ] ; then
						# Case sensitive to match sed that cannot be insensitive with q/p commands
						Part1="$(printf '%s\n' "$OldContent" | sed -re "/^[[:blank:]]*[[]${SectionName}[]]/q")"
						Part1LinesNr=$(printf '%s\n' "$Part1" | wc -l)
						Part1LinesNr=$((Part1LinesNr - 1))
						Part1="$(printf '%s\n' "$Part1" | grep -ive "^[[]${SectionName}[]]" -ive "^ [[]${SectionName}[]]")"
						SectionContent="$(IniSectionContent "$File" "$SectionName")"
						SectionLinesNr=$(printf '%s\n' "$SectionContent" | wc -l)
						Part2="$(printf '%s\n' "$OldContent" | tail -n $((OldContentLinesNr - Part1LinesNr - SectionLinesNr)))"
						cat /dev/null > "$File"
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						if [ "$Part1" != "" ] ; then
							printf '%s\n' "$Part1" >> "$File"
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						fi
						if [ "$(printf '%s\n' "$SectionContent" | tr -s '\t' ' ' | tr -s ' '| grep -ie "^${VariableName}${NameValueSeparator}" -ie "^ ${VariableName}${NameValueSeparator}" -ie "^ ${VariableName}${NameValueSeparator}" -ie "^ ${VariableName} ${NameValueSeparator}")" = "" ] ; then
							printf '%s\n' "$SectionContent" >> "$File"
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
							if [ "$PreComment" != "" ] ; then
								printf '%s\n' "$PreComment" | sed -e 's|\\n|\n|g' >> "$File"
								LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
							fi
							printf '%s\n' "${VariableName}${NameValueSeparator}${NewValue}" >> "$File"
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						else
							printf '%s\n' "$SectionContent" | sed -re "s|^([[:blank:]]*)(${VariableName})([[:blank:]]*)(${NameValueSeparator})([[:blank:]]*).*|\1\2\3\4\5${NewValue}|" >> "$File"
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						fi
						if [ "$Part2" != "" ] ; then
							printf '%s\n' "$Part2" >> "$File"
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						fi
					else
						printf '%s\n' "[${SectionName}]" >> "$File"
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						if [ "$PreComment" != "" ] ; then
							printf '%s\n' "$PreComment" | sed -e 's|\\n|\n|g' >> "$File"
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						fi
						printf '%s\n' "${VariableName}${NameValueSeparator}${NewValue}" >> "$File"
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					fi
				fi
			else
				if [ "$(printf '%s\n' "$OldContent" | tr -s '\t' ' ' | tr -s ' '| grep -ie "^${VariableName}${NameValueSeparator}" -ie "^ ${VariableName}${NameValueSeparator}" -ie "^ ${VariableName}${NameValueSeparator}" -ie "^ ${VariableName} ${NameValueSeparator}")" = "" ] ; then
					if [ "$PreComment" != "" ] ; then
						printf '%s\n' "$PreComment" | sed -e 's|\\n|\n|g' >> "$File"
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					fi
					printf '%s\n' "${VariableName}${NameValueSeparator}${NewValue}" >> "$File"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				else
					printf '%s\n' "$OldContent" | sed -re "s|^([[:blank:]]*)(${VariableName})([[:blank:]]*)(${NameValueSeparator})([[:blank:]]*).*|\1\2\3\4\5${NewValue}|i" > "$File"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
			fi
		fi
	else
		printf '%s\n' "${sERROR}E: SetIniVarValue: File and/or VariableName not specified.${fRESET}" 1>&2
		LastStatus=81 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ "$NozeroStatusToFile" != "" ] && [ ! -f "$NozeroStatusToFile" ] && [ $StatusCode -ne 0 ] ; then echo $StatusCode > "$NozeroStatusToFile" ; fi
	return $StatusCode
}

GetOrSetIniVarValue ()
# Syntax as a function: $(GetOrSetIniVarValue "$File" "$VariableName" "$SectionName" "$DefaultValue" "$NameValueSeparator" "$EndVariableSymbol" "$PreComment" "$ReadOnly" "$DefaultsFile")
# Expected parameters:
#	$1	Path/name of file to query
#	$2	Variable name to query (case insensitive)
#	$3	(optional or empty) [section name] without brackets []. Examples: "global" ""
#	$4	(optional or empty) Value to return in case of not finding the variable in any file. It's used to write the new variable entry too.
#	$5	(optional or empty) Separator between variable name and assigned value. Examples: "=" ":=". Default is "="
#	$6	(optional or empty) End variable/assignation mark, to be omited from value and to be written when needed. Example: ";"
#	$7	(optional or empty) Line to precede variable's line if it must be added. Comment mark should be included.
#	$8	(optional or empty) "1" or "ro" if $File must not be modified
#	$9	(optional or empty) File path where to get DefaultValue from, if found. A found value prevails over 4th parameter (DefaultValue)
# Notes:
#	- if section is not specified or empty, treats whole file content.
#	- if section is "[]", treats only file content before any [section] declaration.
#	- PreComment can contain escaped \n to be converted into line breaks.
# Depends on functions: IniVarValue SetIniVarValue
# Depends on software packages: grep, sed
{
	local File="$1"
	local VariableName="$2"
	local SectionName="$3"
	local DefaultValue="$4"	# If quotes are needed ("value") must be already contained in the supplied value ("\"value\""). Same with EndVariableSymbol.
	local NameValueSeparator="$5"
	local EndVariableSymbol="$6"
	local PreComment="$7"
	local ReadOnly="$8"
	local DefaultsFile="$9"
	local NotFoundKey='GOSIVVtmpStZrypNMzntmpgKqLEd5E5ttmpIWW5wemyCW'
	local Value=''
	local LastStatus=0
	local StatusCode=0
	
	Value="$(IniVarValue "$File" "$VariableName" "$SectionName" "$NotFoundKey" "$NameValueSeparator" "$EndVariableSymbol" "$DefaultsFile")"
	if [ "$Value" = "$NotFoundKey" ] ; then
		if [ "$ReadOnly" != "1" ] && [ "$ReadOnly" != "ro" ] ; then
			SetIniVarValue "$File" "$VariableName" "$SectionName" "${DefaultValue}${EndVariableSymbol}" "$NameValueSeparator" "$PreComment"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
		Value="$DefaultValue"
		# EndVariableSymbol does not come with DefaultValue
		if [ "$(printf '%s\n' "$Value" | grep -e '^"' | grep -e '"$')" != "" ] ; then
			# Double quotes
			Value="$(printf '%s\n' "$Value" | cut -f 2- -d '"' | sed -e 's|"$||')"
		else
			if [ "$(printf '%s\n' "$Value" | grep -e "^'" | grep -e "'$")" != "" ] ; then
				# Single quotes
				Value="$(printf '%s\n' "$Value" | cut -f 2- -d "'" | sed -e "s|'$||")"
			fi
		fi
	fi
	if [ "$Value" != "" ] ; then printf '%s\n' "$Value" ; fi
	if [ "$NozeroStatusToFile" != "" ] && [ ! -f "$NozeroStatusToFile" ] && [ $StatusCode -ne 0 ] ; then echo $StatusCode > "$NozeroStatusToFile" ; fi
	return $StatusCode
}

LoadVarsValues ()
# Syntax as a sentence: LoadVarsValues $FileOrContent $VariablesNames $VarsRequired $DefaultsFileOrContent
# Descripcion: Runs global variables assignation same as specified in $FileOrContent
# Expected parameters:
#	$1	Path/name of file to query. If not exists as a file, string will be treated as content to look into.
#	$3	Variable names to filter (case sensitive, space separated). If empty "", all file variables will be loaded.
#	$2	0 = No error on variables lack. 1 = Only error if all variables lack. 2 = Result error on any variable lack (it only returns exitcode 104; no messages)
#	$4	(optional or empty) File path (or vars string) to do a previous load from, if found.
# To Do:
#	- 
# Notes:
#	- Indentation are supported (initial tabs and/or spaces).
# Depends on functions: (none)
# Depends on software packages: grep, sed
{
	lvv__FileOrContent="$1"
	lvv__VariablesNames="$2"
	lvv__VarsRequired="$3"
	lvv__DefaultsFileOrContent="$4"
	lvv__CurVarName=''
	lvv__VarsFilter=''
	lvv__LoadedContent=''
	lvv__LastStatus=0
	lvv__StatusCode=0
	
	if [ "$lvv__VariablesNames" != "" ] ; then
		for lvv__CurVarName in $lvv__VariablesNames ; do
			lvv__VarsFilter="$lvv__VarsFilter -e ^${lvv__CurVarName}="
		done
	else
		lvv__VarsFilter=" -ie ^[a-z].*="
	fi
	if [ "$lvv__DefaultsFileOrContent" != "" ] ; then
		if [ -f "$lvv__DefaultsFileOrContent" ] ; then
			lvv__DefaultsFileOrContent="$(cat "$lvv__DefaultsFileOrContent")"
			lvv__LastStatus=$? ; if [ $lvv__StatusCode -eq 0 ] ; then lvv__StatusCode=$lvv__LastStatus ; fi
		fi
		lvv__LoadedContent="$(printf '%s\n' "$lvv__DefaultsFileOrContent" | sed -e 's|^[ \t]*||g' | grep $lvv__VarsFilter)"
		if [ "$lvv__LoadedContent" != "" ] ; then
			eval $lvv__LoadedContent
		fi
	fi
	if [ "$lvv__FileOrContent" != "" ] ; then
		if [ -f "$lvv__FileOrContent" ] ; then
			lvv__FileOrContent="$(cat "$lvv__FileOrContent")"
			lvv__LastStatus=$? ; if [ $lvv__StatusCode -eq 0 ] ; then lvv__StatusCode=$lvv__LastStatus ; fi
		fi
		lvv__LoadedContent="$(printf '%s\n' "$lvv__FileOrContent" | sed -e 's|^[ \t]*||g' | grep $lvv__VarsFilter)"
		if [ "$lvv__LoadedContent" != "" ] ; then
			eval $lvv__LoadedContent
			if [ "$lvv__VarsRequired" = "2" ] && [ "$lvv__VariablesNames" != "" ] ; then
				for lvv__CurVarName in $lvv__VariablesNames ; do
					if [ "$(printf '%s' "$lvv__LoadedContent" | grep -e "^${lvv__CurVarName}=")" = "" ] ; then
						lvv__LastStatus=104 ; if [ $lvv__StatusCode -eq 0 ] ; then lvv__StatusCode=$lvv__LastStatus ; fi
					fi
				done
			fi
		else
			if [ "$lvv__VarsRequired" = "1" ] || [ "$lvv__VarsRequired" = "2" ] ; then
				lvv__LastStatus=104 ; if [ $lvv__StatusCode -eq 0 ] ; then lvv__StatusCode=$lvv__LastStatus ; fi
			fi
		fi
	fi
	return $lvv__StatusCode
}

LogToFile ()
# Syntax as a sentence: LogToFile $LogFile $DatetimeStamp $MaxLogLines $Message
# Description: Appends message to text file
# Expected parameters:
#	$1	File path to write to
#	$2	"0" to not prepend line with date-time stamp. Any other value (1) to yes write it.
#	$3	"0" to infinite log lines or a greater number to leave limited log tail
#	(rest)	String for stdout/stderr/log
#		Escaped \n are replaced by line breaks.
# Notes:
#	- This function can be called without message to only cut file to last $MaxLogLines
# Depends on functions: Is_IntegerNr Dirname
# Depends on software packages: sed
{
	local LogFile="$1"
	if [ $# -gt 0 ] ; then shift ; fi
	local DatetimeStamp="$1"
	if [ $# -gt 0 ] ; then shift ; fi
	local MaxLogLines="$1"
	if [ $# -gt 0 ] ; then shift ; fi
	local AskedText=''
	local TextLinesNr=0
	local CurString=''
	local LoggingLog=''
	local LastStatus=0
	local StatusCode=0
	
	if [ "$LogFile" != "" ] ; then
		if [ "$DatetimeStamp" = "0" ] ; then
			DatetimeStamp=''
		else
			DatetimeStamp="$(date '+%Y-%m-%dT%T%z') "
		fi
		for CurString in "$@" ; do	# Simple $* sometimes results in comma-separated words
			if [ "$AskedText" != "" ] ; then AskedText="${AskedText} " ; fi
			AskedText="${AskedText}$(printf '%s\n' "$CurString" | sed -e 's|\\n|\n|g')"
		done
		if [ "$AskedText" != "" ] ; then
			TextLinesNr=$(printf '%s\n' "$AskedText" | wc -l)
		else
			TextLinesNr=0
		fi
		mkdir -p "$(Dirname "$LogFile")"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		if [ $StatusCode -eq 0 ] ; then
			if Is_IntegerNr "$MaxLogLines" && [ $MaxLogLines -gt 0 ] && [ $(cat "$LogFile" 2>&1 | wc -l) -gt $((MaxLogLines+$TextLinesNr)) ] ; then
				LoggingLog="$(cp --attributes-only -a "$LogFile" "${LogFile}.tmp" 2>&1)"
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				if [ $StatusCode -eq 0 ] ; then
					cat "$LogFile" | tail -n $((MaxLogLines-TextLinesNr)) > "${LogFile}.tmp"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
				if [ $StatusCode -eq 0 ] ; then
					mv "${LogFile}.tmp" "$LogFile"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
				rm -f "${LogFile}.tmp"
				if [ $StatusCode -eq 0 ] ; then
					if [ $TextLinesNr -gt 0 ] ; then
						printf '%s\n' "${DatetimeStamp}${AskedText}" >> "$LogFile"
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					fi
				else
					# If it's out of space, then try to overwrite previous log.
					printf '%s\n' "$LoggingLog" > "$LogFile"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					if [ $TextLinesNr -gt 0 ] ; then
						printf '%s\n' "${DatetimeStamp}${AskedText}" >> "$LogFile"
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					fi
				fi
			else
				if [ $TextLinesNr -gt 0 ] ; then
					printf '%s\n' "${DatetimeStamp}${AskedText}" >> "$LogFile"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
			fi
		fi
	else
		printf '%s\n' "E: Log file not specified" 1>&2
		LastStatus=97 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	return $StatusCode
}

LogProgram ()
# Syntax as a sentence: LogProgram $ThisLevel $Message
# Description: Writes message to configured log file, depending on configured LogLevel vs ThisLevel for message.
# Expected parameters:
#	$1	Level number of the message:
#		0=NothingToSay 1=Error 2=Warning 3=NormalInfo 4=DebugInfo
#		(same numbers in negative sign to not write date-time stamp)
#	(rest)	String for stdout/stderr/log
#		Escaped \n are replaced by line breaks.
# Depends on functions: Is_IntegerNr Dirname LogToFile
# Depends on software packages: perl-base sed
# Supports environment variables: LogLevel MainLog MainControllerLog INIT_SCRIPT_InitCall MaxLogLines
{
	local ThisLevel="$1"
	if [ $# -gt 0 ] ; then shift ; fi
	local CallType=''
	local LogFile=''
	local DatetimeStamp=0
	local AskedText=''
	local CurString=''
	local ProgramLogLevel=3
	local RedirectedStdout=''
	local LoggingLog=''
	local LastStatus=0
	local StatusCode=0
	
	perl -e "exit${ParO}-p STDOUT ? 1 : 0${ParC};"  #'
	RedirectedStdout=$?
	if [ $RedirectedStdout -ne 0 ] ; then
		sWARN=''
		fRESET=''
		sERROR=''
		sGOOD=''
	fi
	if [ $ThisLevel -ge 0 ] ; then
		DatetimeStamp=1
	fi
	ThisLevel="$(printf '%s\n' "$ThisLevel" | sed -e 's|^-||g')"
	if Is_IntegerNr "$LogLevel" ; then ProgramLogLevel=$LogLevel ; fi
	if [ "$*" != "" ] && [ $ProgramLogLevel -ge $ThisLevel ] && [ $ThisLevel -gt 0 ] ; then
		for CurString in "$@" ; do	# Simple $* sometimes results in comma-separated words
			if [ "$AskedText" != "" ] ; then AskedText="${AskedText} " ; fi
			AskedText="${AskedText}$(printf '%s\n' "$CurString" | sed -e 's|\\n|\n|g')"
		done
		if [ $ThisLevel -eq 3 ] ; then
			printf '%s\n' "$AskedText"
		else
			if [ $ThisLevel -eq 2 ] ; then
				printf '%s\n' "${sWARN}${AskedText}${fRESET}" 1>&2
			else
				if [ $ThisLevel -eq 1 ] ; then
					printf '%s\n' "${sERROR}${AskedText}${fRESET}" 1>&2
				else
					printf '%s\n' "$AskedText" 1>&2
				fi
			fi
		fi
		LogFile="$MainLog"
		if [ "$LogFile" = "" ] && [ "$MainControllerLog" != "" ] ; then
			LogFile="$MainControllerLog"
			if [ "$INIT_SCRIPT_InitCall" != "1" ] ; then
				CallType=" ${ParO} Manual ${ParC} "
			fi
		fi
		if [ "$LogFile" != "" ] ; then
			LoggingLog="$(LogToFile "$LogFile" "$DatetimeStamp" "$MaxLogLines" "${CallType}${AskedText}")"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			if [ $LastStatus -ne 0 ] ; then
				printf '%s\n' "${sERROR}${LoggingLog}${fRESET}" 1>&2
			fi
		fi
	fi
	return $StatusCode
}


##### PROGRAM TEMPLATE BASE FUNCTIONS #####

BreakingControls ()
{
#[gesvipen]
#	if [ $(id -g) -ne 0 ] && [ "$RootRequired" != "0" ] ; then
	if [ $(id -g) -ne 0 ] && [ "$RootRequired" != "0" ] && [ "$(id -un)" != "openvpn" ] ; then
#[/gesvipen]
		printf '%s\n' "${sERROR}E: This program needs to be run with superuser ${ParO}root${ParC} permissions.${fRESET}" 1>&2
		exit 45
	fi
}

DeleteIfNotMe ()
{
	local FileToDelete="$1"
	local LastStatus=0
	
	if [ "$FileToDelete" != "" ] && [ "$FileToDelete" != "$MeCallFile" ] && [ "$(ReadlinkF "$FileToDelete")" != "$MeCallFile" ] && [ "$FileToDelete" != "$MeExecutable" ] && [ "$(ReadlinkF "$FileToDelete")" != "$MeExecutable" ] ; then
		rm -fr "$FileToDelete"
		LastStatus=$?
	fi
	return $LastStatus
}

FoundProgramScript ()
{
	local Value=""
	if [ -x "/bin/${ProgramName}" ] ; then Value="/bin/${ProgramName}" ; fi
	if [ -x "/sbin/${ProgramName}" ] ; then Value="/sbin/${ProgramName}" ; fi
	if [ -x "/usr/bin/${ProgramName}" ] ; then Value="/usr/bin/${ProgramName}" ; fi
	if [ -x "/usr/sbin/${ProgramName}" ] ; then Value="/usr/sbin/${ProgramName}" ; fi
	if [ -x "/usr/local/bin/${ProgramName}" ] ; then Value="/usr/local/bin/${ProgramName}" ; fi
	if [ -x "/usr/local/sbin/${ProgramName}" ] ; then Value="/usr/local/sbin/${ProgramName}" ; fi
	if [ -x "/bin/${ProgramName}.sh" ] ; then Value="/bin/${ProgramName}.sh" ; fi
	if [ -x "/sbin/${ProgramName}.sh" ] ; then Value="/sbin/${ProgramName}.sh" ; fi
	if [ -x "/usr/bin/${ProgramName}.sh" ] ; then Value="/usr/bin/${ProgramName}.sh" ; fi
	if [ -x "/usr/sbin/${ProgramName}.sh" ] ; then Value="/usr/sbin/${ProgramName}.sh" ; fi
	if [ -x "/usr/local/bin/${ProgramName}.sh" ] ; then Value="/usr/local/bin/${ProgramName}.sh" ; fi
	if [ -x "/usr/local/sbin/${ProgramName}.sh" ] ; then Value="/usr/local/sbin/${ProgramName}.sh" ; fi
	if [ "$Value" != "" ] ; then printf '%s\n' "$Value" ; fi
}

ScriptHeaderValue ()
# Note: it's absolutely case-sensitive
{
	local HeaderKey="$1"
	local VariableKey=""
	local Value=""
	
	if [ "$(cat "$MeExecutable" | grep -e '^:.*SCRIPTHEADER$')" != "" ] && [ "$(cat "$MeExecutable" | grep -e '^SCRIPTHEADER$')" != "" ] ; then
		Value="$(echo aaa$(cat "$MeExecutable" | sed -ne '/^:.*SCRIPTHEADER$/,//p' | sed -e '/^SCRIPTHEADER$/q' | grep -e "^${HeaderKey}:" | head -n 1 | cut -f 2- -d ':') | sed -e 's|^aaa||g' | sed -e 's|^ ||g')"
		if [ "$Value" != "" ] ; then printf '%s\n' "$Value" ; fi
	fi
}

MeInstalled ()
# Returns 1 if this script is placed as system installed path.
{
	local Value=0
	
	if [ "$(printf '%s\n' "$MeExecutable" | grep -e 'bin/')" != "" ] ; then
		Value=1
	fi
	printf '%s\n' "$Value"
}

Install_precp ()
# WARNING: Package manager cannot invoke this action before this script file is installed. Install_postcp() calls this with argument "postcp" if didn't run.
{
	local Mode="$1"
	local Mode2="$2"
	local TheFoundProgramScript=''
	local LastStatus=0
	local StatusCode=0
	
	if [ $StatusCode -eq 0 ] ; then
		Install_precp_Pre "$(Dirname "$MeExecutable")" "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	TheFoundProgramScript="$(FoundProgramScript)"
	if [ $StatusCode -eq 0 ] && [ -x "$TheFoundProgramScript" ] ; then
		if [ "$Mode" != "postcp" ] ; then
			if [ "$PackageManager_Call" = "" ] && [ "$Mode" != "force" ] && [ "$Mode2" != "force" ] ; then
#				if [ "$(IniVarValue "$(cat "$TheFoundProgramScript" | grep -e '^ProgramInstaller=' | head -n 1)" ProgramInstaller '' '' = '' '')" = "0" ] ; then
				if [ "$(cat "$TheFoundProgramScript" | grep -e '^ProgramInstaller=' | head -n 1 | grep -e '^ProgramInstaller=0')" != "" ] ; then
					printf '%s\n' "${sERROR}E: Program already installed. Use your package manager to un/install $(printf '%s\n' "$TheFoundProgramScript" | tr -s '/' '\n' | tail -n 1).${fRESET}" 1>&2
					LastStatus=99 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
			fi
			if [ $StatusCode -eq 0 ] ; then
				printf '%s\n' "Trying to uninstall previous $ProgramName"
				"$TheFoundProgramScript" uninstall "$@"
				LastStatus=$?
				if [ $LastStatus -eq 87 ] || [ $LastStatus -eq 127 ] ; then # Unknown action
					rm "$TheFoundProgramScript"
					LastStatus=$?
				fi
				if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				if [ -x "$TheFoundProgramScript" ] ; then
					printf '%s\n' "${sWARN}W: old executable $TheFoundProgramScript not removed.${fRESET}" 1>&2
					printf '%s\n' "Actions recommended:"
					printf '%s\n' "	1. Retry un/installation"
					printf '%s\n' "	2. Remove old program manually if necessary"
				fi
				if [ $StatusCode -eq 0 ] ; then
					# Important to do this after uninstalling previous version.
					Configuration "$@"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					if [ -d "$SystemConfigDir" ] ; then
						printf '%s\n' "System level configurations directory is: $SystemConfigDir"
					fi
					printf '%s\n' "System level parameters can be at: $SystemConfigFile"
				fi
			fi
		fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		Install_precp_Post "$(Dirname "$MeExecutable")" "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		# Old executables were a duplicity of program script.
		DeleteIfNotMe "/bin/${ProgramName}"
		DeleteIfNotMe "/sbin/${ProgramName}"
		DeleteIfNotMe "/usr/bin/${ProgramName}"
		DeleteIfNotMe "/usr/sbin/${ProgramName}"
		DeleteIfNotMe "/usr/local/bin/${ProgramName}"
		DeleteIfNotMe "/usr/local/sbin/${ProgramName}"
		DeleteIfNotMe "/bin/${ProgramName}.sh"
		DeleteIfNotMe "/sbin/${ProgramName}.sh"
		DeleteIfNotMe "/usr/bin/${ProgramName}.sh"
		DeleteIfNotMe "/usr/sbin/${ProgramName}.sh"
		DeleteIfNotMe "/usr/local/bin/${ProgramName}.sh"
		DeleteIfNotMe "/usr/local/sbin/${ProgramName}.sh"
		DeleteIfNotMe "$InitExecutableLink"
		DeleteIfNotMe "$ProgramExecutablePath"
	fi
	touch "${DirTemp}/${ProgramName}.install-precp.done"
	return $StatusCode
}

Install_cp ()
{
	local SourceScript="$1"
	local LastStatus=0
	local StatusCode=0
	
	if [ $StatusCode -eq 0 ] ; then
		if [ "$SourceScript" != "$ProgramExecutablePath" ] && [ "$SourceScript" != "$InitExecutableLink" ] ; then
			PreviousUmask="$(umask)"
			umask u=rwx,go=rx
			mkdir -p "$(Dirname "$ProgramExecutablePath")"
			umask "$PreviousUmask"
			cp "$SourceScript" "$ProgramExecutablePath"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			chown root:root "$ProgramExecutablePath"
			chmod u=rwx,go=rx "$ProgramExecutablePath"
		else
			printf '%s\n' "${sERROR}E: Cannot install from destination path.${fRESET}" 1>&2
			LastStatus=94 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
	fi
	return $StatusCode
}

Install_postcp ()
{
	local LastStatus=0
	local StatusCode=0
	
	if [ ! -f "${DirTemp}/${ProgramName}.install-precp.done" ] ; then
		Install_precp postcp
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		rm -f "${DirTemp}/${ProgramName}.install-precp.done"
	fi
	if [ $StatusCode -eq 0 ] ; then
		Install_postcp_More "$(Dirname "$MeExecutable")" "$@"	#"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		# Writing system-wide defaults
		Configuration "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	return $StatusCode
}

Uninstall_predel ()
{
	local LastStatus=0
	local StatusCode=0
	
	Uninstall_predel_More "$@"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	return $StatusCode
}

Uninstall_del ()
{
	local MeVersion=''
	MeVersion="$(ScriptHeaderValue Version)"
#	DeleteIfNotMe "$ProgramExecutablePath"
#	DeleteIfNotMe "/bin/${ProgramName}"
#	DeleteIfNotMe "/sbin/${ProgramName}"
#	DeleteIfNotMe "/usr/bin/${ProgramName}"
#	DeleteIfNotMe "/usr/sbin/${ProgramName}"
#	DeleteIfNotMe "/usr/local/bin/${ProgramName}"
#	DeleteIfNotMe "/usr/local/sbin/${ProgramName}"
	rm -f "/bin/${ProgramName}"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	rm -f "/sbin/${ProgramName}"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	rm -f "/usr/bin/${ProgramName}"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	rm -f "/usr/sbin/${ProgramName}"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	rm -f "/usr/local/bin/${ProgramName}"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	rm -f "/usr/local/sbin/${ProgramName}"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	rm -f "/bin/${ProgramName}.sh"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	rm -f "/sbin/${ProgramName}.sh"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	rm -f "/usr/bin/${ProgramName}.sh"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	rm -f "/usr/sbin/${ProgramName}.sh"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	rm -f "/usr/local/bin/${ProgramName}.sh"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	rm -f "/usr/local/sbin/${ProgramName}.sh"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	rm -f "$ProgramExecutablePath"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	if [ $StatusCode -eq 0 ] ; then
		printf '%s\n' "$ProgramName $MeVersion : Program files removed."
	fi
}

Uninstall_postdel ()
{
	local LastStatus=0
	local StatusCode=0
	
	Uninstall_Postdeletion "$@"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	return $StatusCode
}

Uninstall_purge ()
{
	local LastStatus=0
	local StatusCode=0
	
	rm -f "${DirTempX}/${ProgramName}.uninstall.tmp" "/var/tmp/${ProgramName}.uninstall.tmp"
	Uninstall_purge_MorePre "$@"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	if [ -f "$SystemConfigFile" ] ; then
		printf '%s\n' "Removing: $SystemConfigFile"
		rm "$SystemConfigFile"
	fi
	if [ -d "$SystemConfigDir" ] ; then
		printf '%s\n' "Removing: $SystemConfigDir"
		rm -r "$SystemConfigDir"
	fi
	if [ "$ChildsPluralName" != "" ] ; then
		rm -fr "$Childs_logs"
	fi
	rm -f "$MainLog"
	rm -f "/var/log/upstart/${ProgramName}."*
	Uninstall_purge_MorePost "$@"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	if [ $LastStatus -eq 0 ] ; then
		printf '%s\n' "Configurations and logs cleaned."
	fi
	return $StatusCode
}

GetSetLocalConfig ()
# Syntax as a function: $(GetSetLocalConfig "$ReadOnly" "$VariableName" "$SectionName" "$DefaultValue" "$PreComment")

# Function taken from script.sh template.

# Description: Gets the most local value for a configuration (1:user 2:system 3:filedefault 4:specifieddefault) and optionally writes if nowhere found.
# Expected parameters:
#	$1	"1" to not write anything at files; "0" to save variable when not found.
#	$2	Variable name to query
#	$3	(optional or empty) [section name] without brackets []. Examples: "global" ""
#	$4	(optional or empty) Value to return in case of not finding the variable in any file. It's used to write the new variable entry too.
#	$5	(optional or empty) Line to precede variable's line if it must be added. Comment mark should be included.
# Notes:
#	- NOT USEFUL when default value can be invalid for unprivileged user, such as file/dir paths to write: [system.conf=]/etc/custom.list vs [user.conf=]~/custom.list
#	  NOT USEFUL for other config files but SystemConfigFile & UserConfigFile
#	  In these cases, it's convenient to use GetOrSetIniVarValue()
#	- USEFUL when need to use system-level value on user-level lack.
#	- $SystemDefaults is used as DefaultsFile. A found value here prevails over 4th parameter (DefaultValue)
#	- If DefaultValue is to be written (because no value found anywhere and ReadOnly==0), root user will write to $SystemConfigFile and others will write to $UserConfigFile
#	- if section is not specified or empty, treats whole file content.
#	- if section is "[]", treats only file content before any [section] declaration.
#	- Separator between variable name and assigned value is assumed to be '='
#	- No end variable/assignation mark assumed.
# Depends on functions: IniVarValue SetIniVarValue
# Depends on other software: grep
{
	local ReadOnly="$1"
	local VariableName="$2"
	local SectionName="$3"
	local DefaultValue="$4"	# If quotes are needed ("value") must be already contained in the supplied value ("\"value\""). Same with EndVariableSymbol.
	local PreComment="$5"
	local DefaultsFile="$SystemDefaults"
	local NotFoundKey=''
	local Value=''
	local LastStatus=0
	local StatusCode=0
	
	NotFoundKey="#GOSIVV$(dd if=/dev/urandom count=1 2> /dev/null | cksum | cut -f1 -d ' ')#"
	Value="$(IniVarValue "$UserConfigFile" "$VariableName" "$SectionName" "$NotFoundKey" = '' '')"
	if [ "$Value" = "$NotFoundKey" ] ; then
		Value="$(IniVarValue "$SystemConfigFile" "$VariableName" "$SectionName" "$NotFoundKey" = '' "$DefaultsFile")"
		if [ "$Value" = "$NotFoundKey" ] ; then
			if [ $ReadOnly -eq 0 ] ; then
				if [ $(id -u) -eq 0 ] ; then
					SetIniVarValue "$SystemConfigFile" "$VariableName" "$SectionName" "$DefaultValue" '=' "$PreComment"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				else
					SetIniVarValue "$UserConfigFile" "$VariableName" "$SectionName" "$DefaultValue" '=' "$PreComment"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
			fi
			Value="$DefaultValue"
			if [ "$(printf '%s\n' "$Value" | grep -e '^".*"$')" != "" ] ; then Value="$(printf '%s\n' "$Value" | cut -f 2 -d '"')" ; fi	#'
		fi
	fi
	if [ "$Value" != "" ] ; then printf '%s' "$Value" ; fi
	return $StatusCode
}

Configuration ()
{
	local ReadOnly=0
	local BaseName=''
	local LastStatus=0
	local StatusCode=0

	if [ "$1" = "ro" ] ; then ReadOnly=1 ; shift ; fi
	MeExecutable="$(ReadlinkF "$MeCallFile")"
	if [ ! -d "$DirTemp" ] ; then DirTemp="/run/shm" ; fi
	if [ ! -d "$DirTemp" ] ; then DirTemp="/tmp" ; fi
	if [ ! -d "$DirTempX" ] ; then DirTempX="/tmp" ; fi
	BreakingControls
	if [ "$1" = "pmc" ] ; then
		PackageManager_Call="$2"
		PackageManager_Mode="$3"
		PackageManager_ForVersion="$4"
	else
		PackageManager_Call=''
	fi
	if [ "$RootRequired" = "0" ] ; then
		ProgramExecutablePath="/bin/${ProgramName}"
	else
		ProgramExecutablePath="/sbin/${ProgramName}"
	fi
#	if [ "$EssentialAtBoot" != "1" ] ; then  # FHS /usr Merge
		ProgramExecutablePath="/usr${ProgramExecutablePath}"
#	fi
	Configuration_Saved_Pre "$@"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	if [ ! -d "$SystemConfigDir" ] && [ $ReadOnly -eq 0 ] && [ -w "$(Dirname "$SystemConfigDir")" ] ; then
		mkdir -p "$SystemConfigDir"
		chmod u=rwX,go=rX "$SystemConfigDir"
	fi
	SystemDefaults="${SystemConfigDir}/system.defaults"	# This file is never written by program; only provided by package manager and updates
	SystemConfigFile="${SystemConfigDir}/system.conf"
	if [ -f "${SystemConfigDir}/default.conf" ] ; then
		# Files structure upgrade
		if [ $ReadOnly -eq 0 ] && [ $(id -u) -eq 0 ] ; then
			mv "${SystemConfigDir}/default.conf" "$SystemConfigFile"
		else
			SystemConfigFile="${SystemConfigDir}/default.conf"
		fi
	fi
	BaseName="$(printf '%s\n' "$SystemConfigDir" | tr -s '/' '\n' | tail -n 1)"
	UserDir="$HOME"
	if [ "$(printf '%s\n' "$BaseName" | grep -e '^\.')" != "" ] ; then
		UserConfigDir="${UserDir}/${BaseName}"
	else
		UserConfigDir="${UserDir}/.${BaseName}"
	fi
	if [ ! -d "$UserDir" ] ; then
		UserDir="$(pwd)"
		UserConfigDir="${UserDir}/${BaseName}"
	fi
	if [ ! -w "$UserDir" ] ; then
		UserDir="$(pwd)"
		UserConfigDir="${UserDir}/${BaseName}"
	fi
#[gesvipen]
	if [ "$(id -un)" = "openvpn" ] && [ -d "${SystemConfigDir}/openvpn" ] ; then
		# This is because /etc/openvpn and /var/log/openvpn are created with root-only write permission, and /etc/openvpn is the home directory of "openpvn"
		UserConfigDir="${SystemConfigDir}/openvpn"
	fi
#[/gesvipen]
	UserDefaults="${UserConfigDir}/user.defaults"	# This file is never written by program; only provided by package manager/skel
	UserConfigFile="${UserConfigDir}/user.conf"
	if [ $ReadOnly -eq 0 ] && [ ! -d "$UserConfigDir" ] ; then
		mkdir -p "$UserConfigDir"
	fi
	if [ -f "${UserConfigDir}/default.conf" ] ; then
		# Files structure upgrade
		if [ $ReadOnly -eq 0 ] ; then
			mv "${UserConfigDir}/default.conf" "$UserConfigFile"
		else
			UserConfigFile="${UserConfigDir}/default.conf"
		fi
	fi
	if [ $ReadOnly -eq 0 ] && [ -w "$(Dirname "$SystemConfigFile")" ] ; then
		if [ ! -f "$SystemConfigFile" ] ; then
			printf '%s\n' "# $(ScriptHeaderValue Description)" >> "$SystemConfigFile"
			if [ "$RootRequired" = "0" ] ; then
				printf '%s\n' "# $ProgramName system-wide and user-default working parameters" >> "$SystemConfigFile"
			else
				printf '%s\n' "# $ProgramName working parameters" >> "$SystemConfigFile"
			fi
			printf '%s\n' "" >> "$SystemConfigFile"
		fi
	fi
	CurrentConfigDir="$SystemConfigDir"
	CurrentConfigFile="$SystemConfigFile"
	if [ $(id -u) -ne 0 ] && [ "$RootRequired" = "0" ] ; then
		CurrentConfigDir="$UserConfigDir"
		CurrentConfigFile="$UserConfigFile"
		if [ $ReadOnly -eq 0 ] && [ -w "$(Dirname "$UserConfigFile")" ] ; then
			if [ ! -f "$UserConfigFile" ] ; then
				printf '%s\n' "# $(ScriptHeaderValue Description)" >> "$UserConfigFile"
				printf '%s\n' "# $ProgramName user working parameters. This overrides configurations over $SystemConfigFile" >> "$UserConfigFile"
				printf '%s\n' "" >> "$UserConfigFile"
			fi
		fi
	fi
	MainLog="/var/log/${ProgramName}.log"
	if [ $(id -u) -ne 0 ] ; then
		MainLog="${UserConfigDir}/main.log"
	fi
	if [ "$LogLevel" != "4" ] ; then	# Debugging probably a nested call (exported LogLevel)
		LoadVarsValues "$UserConfigFile" 'LogLevel MaxLogLines' 2 "$SystemConfigFile"
		LastStatus=$?
		if [ $LastStatus -eq 104 ] ; then
			LogLevel="$(GetSetLocalConfig "$ReadOnly" LogLevel '' 3 '# LogLevel: 0=Nothing 1=Errors 2=Warnings+E 3=Info+W+E 4=Debug')"
			MaxLogLines="$(GetSetLocalConfig "$ReadOnly" MaxLogLines '' 0 '# MaxLogLines: 0=Unlimited')"
		else
			if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
		if ! Is_IntegerNr "$LogLevel" ; then LogLevel=3 ; fi
	fi
	if [ $ReadOnly -eq 0 ] ; then
		Configuration_Saved_Post "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	else
		Configuration_Saved_Post ro "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	return $StatusCode
}


##### PROGRAM FUNCTIONS from @-funcions 0:2023.08.29 #####

#[gesvipen]
WhereProgram ()
# Syntax as a function: $(WhereProgram $Command)
# Description: Returns existing program filepath from specified command, or internal shell program or function
# Notes:
#	- Similar to the debianutils' "which" but also walks through typical OS binaries paths.
# Depends on functions: (none)
# Depends on software packages: (none)
{
	wp__Program="$1"
	wp__CurDir=''
	wp__Value=''
	
	wp__Value="$(command -v "$wp__Program" 2>/dev/null)"
	if [ "$wp__Value" = "" ] && [ "$(printf '%s' "$wp__Program" | tr '/' ' ')" = "$wp__Program"  ] ; then
		# Walk through typical path
		if [ $(id -u) -eq 0 ] ; then
			for wp__CurDir in /root/.local/bin /usr/local/bin /usr/local/sbin /usr/bin /usr/sbin /bin /sbin /system/bin /system/xbin ; do
				if [ "$wp__Value" = "" ] && [ -x "{wp__CurDir}/${wp__Program}" ] ; then
					wp__Value="{wp__CurDir}/${wp__Program}"
				fi
			done
		else
			for wp__CurDir in ~/.local/bin /usr/local/bin /usr/bin /bin /usr/local/sbin /usr/sbin /sbin /system/bin /system/xbin ; do
				if [ "$wp__Value" = "" ] && [ -x "{wp__CurDir}/${wp__Program}" ] ; then
					wp__Value="{wp__CurDir}/${wp__Program}"
				fi
			done
		fi
	fi
	if [ "$wp__Value" != "" ] ; then
		# Some scenarios "command" returns directories as executables.
		if [ -d "$wp__Value" ] && [ "$(printf '%s' "$wp__Value" | tr '/' ' ')" != "$wp__Value"  ] ; then wp__Value='' ; fi
	fi
	if [ "$wp__Value" != "" ] ; then
		printf '%s\n' "$wp__Value"
	fi
}

RespostaLletra ()
# Sintaxi com a funcio: $(RespostaLletra $JaEspecificada)
# Descripció: Reads a keyboard-typed answer (ENTER must be pressed) and returns first answer letter (lower case if possible - not in Maemo)
# Paràmetres esperats:
#	$1	(opcional) Tractar aquesta cadena en comptes de demanar-la de teclat.
# Depends on functions: Lowercase
# Depends on software packages: (none)
{
	JaEspecificada="$1"
	if [ "$JaEspecificada" = "" ] ; then read JaEspecificada ; fi
	JaEspecificada="$(printf '%s' "$JaEspecificada" | cut -c 1)"
	JaEspecificada="$(Lowercase "$JaEspecificada")"
	if [ "$JaEspecificada" != "" ] ; then printf '%s\n' "$JaEspecificada" ; fi
}

DescartarNoimprimibles ()
# Syntax as a function: $(DescartarNoimprimibles "$CadenaOriginal")
# Retorna la cadena descartant els caràcters no imprimibles, però incloent tabulacions i salts de línia.
# Nota: els salts de línia de tipus Microsoft (CRLF) els converteix a Unix (LF)
# Depends on functions: (none)
# Depends on software packages: xxd, vim-common
{
	local RetornFet="0"
	local CadenaOriginal="$1"
	if [ "$CadenaOriginal" != "" ] ; then
		TiraNova=''
		TiraVella=$(printf '%s' "$CadenaOriginal" | od -tx1 | cut -c8- | tr -d '\n')
		for HexaActual in $TiraVella ; do
			HexaDecimalActual="0x$HexaActual"
			DecimalActual=$((HexaDecimalActual))
			if [ $DecimalActual -lt 32 ] || [ $DecimalActual -eq 127 ] ; then
				case "$HexaActual" in
					"09" )	TiraNova="$TiraNova $HexaActual"
						RetornFet="0"
						;;
					"0d" )	TiraNova="$TiraNova 0a"
						RetornFet="1"
						;;
					"0a" )	if [ "$RetornFet" != "1" ] ; then TiraNova="$TiraNova $HexaActual" ; fi
						RetornFet="0"
						;;
				esac
			else
				TiraNova="$TiraNova $HexaActual"
				RetornFet="0"
			fi
		done
		printf '%s\n' "$TiraNova" | xxd -r -p
	fi
}

NumeroResumit ()
# Syntax as a function: $(NumeroResumit $Numero $Estil)
# Retorna el número humanament llegible, segons $Estil:
# 	"2" :	Amb factors de 1024 (10,5b; 10,5k; 10,5m; 10,5g)
# 	"2L" :	Amb factors de 1024 i notació llarga (10,5B; 10,5KiB; 10,5MiB; 10,5GiB)
# 	"1" :	Amb factors de 1000 (10,5B; 10,5K; 10,5M; 10,5G) (SI)
# 	"." :	Utilitzar el separador decimal "." en comptes del predeterminat ","
#	Si s'afegeix un espai, se separa el número de la notació amb un espai:
#		"2 "   10,5 g
#		"2L "  9,5 MiB
#		"1 "   8300,5 B
#	Si s'afegeix una "A", aleshores s'alineen les dades:
#		"A2"     10,5g
#		"A2L"     9,5MiB
#		"A1"   8300,5B
#	Si s'afegeix un "0", aleshores no s'expressen decimals (només l'enter arrodonit a la baixa):
#		"02"  10g
#		"02L" 9MiB
#		"01"  8300B
#	Tot es pot combinar:
#		"A02L "     9 MiB
#		"A01L "  8300 B
#		"2L."     9.5MiB
# Nota: el $Numero s'espera en bytes (octets)
# ToDo:
#	- Try to not depend on bc
# Depends on functions: (none)
# Depends on software packages: grep, bc
{
	local Numero=$1
	local Estil="$2"
	local LlindarResum=2000
	local DecimalSeparator=''
	local CurLocale=''
	local EnterVisible=''
	local DecimalVisible=''
	local Sufix=''
	local Factor=0
	
	if [ "$(printf '%s' "$Estil" | grep "\.")" != "" ] ; then DecimalSeparator="." ; fi
	if [ "$(printf '%s' "$Estil" | grep ",")" != "" ] ; then DecimalSeparator="," ; fi
	if [ "$(printf '%s' "$Estil" | grep "'")" != "" ] ; then DecimalSeparator="'" ; fi	#"
	if [ "$DecimalSeparator" = "" ] ; then
		DecimalSeparator=','
		for CurLocale in "$LC_NUMERIC" "$LC_ALL" "$LANG" "$LANGUAGE" ; do
			CurLanguage="$(printf '%s' "$LC_NUMERIC" | cut -f 1 -d '_' | cut -f 1 -d '-')"
			if [ "$CurLanguage" = "en" ] || [ "$CurLanguage" = "EN" ] ; then
				DecimalSeparator='.'
			fi
		done
	fi
	if [ "$(printf '%s' "$Estil" | grep "2")" != "" ] ; then
		Factor=1024
	else
		if [ "$(printf '%s' "$Estil" | grep "1")" != "" ] ; then
			Factor=1000
		else
			return 1
		fi
	fi
	EnterVisible="$Numero"
	Sufix="B"
	# Resum
	if [ $Numero -ge $LlindarResum ] || [ $(printf '%s\n' "$Numero" | sed -e 's|.....$||g') -ge $LlindarResum ] ; then
		EnterVisible="$(echo "$Numero / $Factor" | bc)"
		DecimalVisible="$(echo "$(echo "$(echo "$Numero * 10" | bc) / $Factor" | bc) % 10" | bc)"
		Numero=$(echo "$Numero / $Factor" | bc)
		Sufix="K"
		if [ $Numero -ge $LlindarResum ] ; then
			EnterVisible="$(echo "$Numero / $Factor" | bc)"
			DecimalVisible="$(echo "$(echo "$(echo "$Numero * 10" | bc) / $Factor" | bc) % 10" | bc)"
			Numero=$(echo "$Numero / $Factor" | bc)
			Sufix="M"
			if [ $Numero -ge $LlindarResum ] ; then
				EnterVisible="$(echo "$Numero / $Factor" | bc)"
				DecimalVisible="$(echo "$(echo "$(echo "$Numero * 10" | bc) / $Factor" | bc) % 10" | bc)"
				Numero=$(echo "$Numero / $Factor" | bc)
				Sufix="G"
				if [ $Numero -ge $LlindarResum ] ; then
					EnterVisible="$(echo "$Numero / $Factor" | bc)"
					DecimalVisible="$(echo "$(echo "$(echo "$Numero * 10" | bc) / $Factor" | bc) % 10" | bc)"
					Numero=$(echo "$Numero / $Factor" | bc)
					Sufix="T"
					if [ $Numero -ge $LlindarResum ] ; then
						EnterVisible="$(echo "$Numero / $Factor" | bc)"
						DecimalVisible="$(echo "$(echo "$(echo "$Numero * 10" | bc) / $Factor" | bc) % 10" | bc)"
						Numero=$(echo "$Numero / $Factor" | bc)
						Sufix="P"
						if [ $Numero -ge $LlindarResum ] ; then
							EnterVisible="$(echo "$Numero / $Factor" | bc)"
							DecimalVisible="$(echo "$(echo "$(echo "$Numero * 10" | bc) / $Factor" | bc) % 10" | bc)"
							Numero=$(echo "$Numero / $Factor" | bc)
							Sufix="E"
							if [ $Numero -ge $LlindarResum ] ; then
								EnterVisible="$(echo "$Numero / $Factor" | bc)"
								DecimalVisible="$(echo "$(echo "$(echo "$Numero * 10" | bc) / $Factor" | bc) % 10" | bc)"
								Numero=$(echo "$Numero / $Factor" | bc)
								Sufix="Z"
								if [ $Numero -ge $LlindarResum ] ; then
									EnterVisible="$(echo "$Numero / $Factor" | bc)"
									DecimalVisible="$(echo "$(echo "$(echo "$Numero * 10" | bc) / $Factor" | bc) % 10" | bc)"
									Numero=$(echo "$Numero / $Factor" | bc)
									Sufix="Y"
								fi
							fi
						fi
					fi
				fi
			fi
		fi
	fi
	# Decimal invisible
	if [ "$(printf '%s' "$Estil" | grep "0")" != "" ] ; then
		DecimalVisible=''
	else
		if [ "$DecimalVisible" = "" ] ; then DecimalVisible="0" ; fi
		DecimalVisible="$DecimalSeparator$DecimalVisible"
	fi
	# Notació llarga
	if [ "$(printf '%s' "$Estil" | grep "2")" != "" ] ; then  # factor binari, sufix en minuscula
		case "$Sufix" in
			"B" )	Sufix="b"	;;
			"K" )	Sufix="k"	;;
			"M" )	Sufix="m"	;;
			"G" )	Sufix="g"	;;
			"T" )	Sufix="t"	;;
			"P" )	Sufix="p"	;;
			"E" )	Sufix="e"	;;
			"Z" )	Sufix="z"	;;
			"Y" )	Sufix="y"	;;
		esac
		if [ "$(printf '%s' "$Estil" | grep "L")" != "" ] ; then
			case "$Sufix" in
				"b" )	if [ "$(printf '%s' "$Estil" | grep "A")" != "" ] ; then
						Sufix="B  "	# Amb aliniació
					else
						Sufix="B"
					fi
					;;
				"k" )	Sufix="KiB"	;;
				"m" )	Sufix="MiB"	;;
				"g" )	Sufix="GiB"	;;
				"t" )	Sufix="TiB"	;;
				"p" )	Sufix="PiB"	;;
				"e" )	Sufix="EiB"	;;
				"z" )	Sufix="ZiB"	;;
				"y" )	Sufix="YiB"	;;
			esac
		fi
	fi
	# Espai
	if [ "$(printf '%s' "$Estil" | grep -e ' ')" != "" ] ; then
		Sufix=" $Sufix"
	fi
	# Aliniació
	if [ "$(printf '%s' "$Estil" | grep "A")" != "" ] ; then
		while [ ${#EnterVisible} -lt 4 ] ; do
			EnterVisible=" $EnterVisible"
		done
	fi
	printf '%s\n' "$EnterVisible$DecimalVisible$Sufix"
}

DfOutputValue ()
# Description: Parser for DF command, that emulates --output option for old versions without support for it. Only returns (stdout) the one asked output value.
# ToDo:
#	- Implement "no Path" to list all volumes for specified OutputField
#	- Implement alternative method to get fstype when using BusyBox (eg. in Maemo)
#	- Try to not depend on bc
# Notes:
#	- If no third parameter is specified (-b -k -m -g), sizes are returned in bytes.
#	- As of df v8, some filesystems (eg: btrfs) don't report inodes information and returned value can be "-"
# Depends on functions: Is_Executable ReadlinkF
# Depends on software packages: perl-base, grep, bc, sed
{
	local OutputField="$1"
	local Path="$2"
	local Unit="$3"
	local BlockSize=1
	local BlockFactor=1
	local LineInodes=''
	local LineSpace=''
	local WordsInodes=''
	local WordsSpace=''
	local ConvertSize=0
	local LastStatus=0
	local Posix='P'
	local PrintType='T'
	local Value=''
	
	case "$Unit" in
		"-b" ) BlockSize=1 ;;
		"-k" ) BlockSize=1024 ;;
		"-m" ) BlockSize=$((1024*1024)) ;;
		"-g" ) BlockSize=$((1024*1024*1024)) ;;
		* ) BlockSize=1 ;;
	esac
	df "$Path" >/dev/null 2>&1
	LastStatus=$?
	if [ $LastStatus -ne 0 ] ; then
		# /dev/simfs (VPS) doesn't work; let's detect mountpoint
		Path="$(df | grep -e "^${Path} " | tr -s '%' '\n' | sed -e 's|^ ||g' | tail -n 1)"
	fi
	if [ "$(df --help 2>&1 | grep -e '--output')" != "" ] ; then
		df "--output=${OutputField}" "$Path" >/dev/null 2>&1
		LastStatus=$?
		if [ $LastStatus -eq 0 ] ; then
			Value="$(env LANG=en df --block-size=$BlockSize "--output=${OutputField}" "$Path" 2>/dev/null | tail -n 1)"
		fi
	else
		df -P "$Path" >/dev/null 2>&1
		LastStatus=$?
		if [ $LastStatus -ne 0 ] ; then
			Posix=''
			df "$Path" >/dev/null 2>&1
			LastStatus=$?
		fi
		if [ $LastStatus -eq 0 ] ; then
			# Note: Different GNU distributions have complete different alignment policy.
			#       Most of fields is better to get counting words backwards since the common % symbol.
			#	This method is incompatible with devices with % symbol in path.
			df -T "$Path" >/dev/null 2>&1
			LastStatus=$?
			if [ $LastStatus -ne 0 ] ; then
				PrintType=''
			fi
			df -i "$Path" >/dev/null 2>&1
			LastStatus=$?
			if [ $LastStatus -eq 0 ] ; then
				LineInodes="$(env LANG=en df -${Posix}${PrintType}i "$Path" | tail -n 1)"
			fi
			if [ "${Posix}${PrintType}" != "" ] ; then
				LineSpace="$(env LANG=en df -${Posix}${PrintType} --block-size=$BlockSize "$Path" 2>/dev/null)"
			else
				LineSpace="$(env LANG=en df --block-size=$BlockSize "$Path" 2>/dev/null)"
			fi
			LastStatus=$?
			if [ $LastStatus -ne 0 ] ; then
				if [ "${Posix}${PrintType}" != "" ] ; then
					LineSpace="$(env LANG=en df -${Posix}${PrintType} -k "$Path")"
				else
					LineSpace="$(env LANG=en df -k "$Path")"
				fi
				BlockFactor=1024
				if [ $BlockSize -ge $BlockFactor ] ; then
					BlockFactor=$((BlockSize / BlockFactor))
				else
					BlockFactor=-1024
				fi
			fi
			LineSpace="$(printf '%s\n' "$LineSpace" | tail -n 1)"
			if [ "$(printf '%s' "$LineInodes" | grep -e '%')" = "" ] ; then
				# Optical media
				LineInodes="$(printf '%s' "$LineInodes" | sed -e 's| - | 100% |g')"
			fi
			WordsInodes="$(echo TrimAndSingle $LineInodes | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g' | cut -f 2- -d ' ' | cut -f 1 -d '%' | tr -s ' ' '\n')"
			WordsSpace="$(echo TrimAndSingle $LineSpace | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g' | cut -f 2- -d ' ' | cut -f 1 -d '%' | tr -s ' ' '\n')"
			case "$OutputField" in
				"source" )
					Value=$(printf '%s' "$WordsInodes" | tail -n 5 | head -n 1)	# Get fstype to use as field splitter
					Value="$(printf '%s' "$LineInodes" | perl -pe "s| ${Value} |\n|g" | head -n 1)"
					;;
				"fstype" )
					if [ "$PrintType" != "" ] ; then
						Value=$(printf '%s' "$WordsInodes" | tail -n 5 | head -n 1)
					fi
					;;
				"itotal" )
					Value=$(printf '%s' "$WordsInodes" | tail -n 4 | head -n 1)
					;;
				"iused" )
					Value=$(printf '%s' "$WordsInodes" | tail -n 3 | head -n 1)
					;;
				"iavail" )
					Value=$(printf '%s' "$WordsInodes" | tail -n 2 | head -n 1)
					;;
				"ipcent" )
					Value=$(echo TrimAndSingle $LineInodes | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g' | cut -f 2- -d ' ' | tr -s ' ' '\n' | grep -e '%' | head -n 1)
					;;
				"size" )
					Value=$(printf '%s' "$WordsSpace" | tail -n 4 | head -n 1)
					ConvertSize=1
					;;
				"used" )
					Value=$(printf '%s' "$WordsSpace" | tail -n 3 | head -n 1)
					ConvertSize=1
					;;
				"avail" )
					Value=$(printf '%s' "$WordsSpace" | tail -n 2 | head -n 1)
					ConvertSize=1
					;;
				"pcent" )
					Value=$(echo TrimAndSingle $LineSpace | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g' | cut -f 2- -d ' ' | tr -s ' ' '\n' | grep -e '%' | head -n 1)
					;;
				"target" )
					Value=$(echo TrimAndSingle $LineInodes | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g' | cut -f 2- -d ' ' | cut -f 2- -d '%')
					;;
			esac
		fi
		if [ $ConvertSize -eq 1 ] && [ $BlockFactor -ne 1 ] ; then
			if Is_Executable bc ; then
				if [ $BlockFactor -ge 0 ] ; then
					Value=$(printf '%s\n' "$Value / $BlockFactor" | bc)
				else
					Value=$(printf '%s\n' "$Value * $BlockFactor" | bc)
				fi
			else
				if [ $BlockFactor -ge 0 ] ; then
					Value=$((Value / BlockFactor))
				else
					Value=$((Value * BlockFactor))
				fi
			fi
		fi
	fi
	if [ "$Value" != "" ] ; then
		if [ "$OutputField" = "source" ] && [ "$(printf '%s' "$Value" | grep -e '^/')" != "" ] && [ "$(ReadlinkF "$Value" 2>/dev/null)" != "" ] ; then
			Value="$(ReadlinkF "$Value")"
		fi
		# Trim:
		Value="$(expr "$Value" : "[ ]*\(.*[^ ]\)[ ]*$")"
		printf '%s\n' "$Value"
	fi
}

StatFormatValue ()
# Description: Parser for STAT command, that emulates --format option for old versions without support for it. Only returns (stdout) the one asked output value.
# Notes:
#	--file-system is not used.
# ToDo:
#	- Try to not depend on bc
# Depends on functions: DfOutputValue Lowercase
# Depends on software packages: grep, sed, bc
{
	local FormatField="$1"
	local Path="$2"
	local Value=''
	
	if [ -e "$Path" ] ; then
		if [ "$(stat --help 2>&1 | grep -e '--format')" != "" ] ; then
			stat "--format=${FormatField}" "$Path"
		else
			FormatField="$(printf '%s\n' "$FormatField" | sed -e 's|^%||g')"
			case "$FormatField" in
				"a" )	# access rights in octal
					Value="$(env LANG=en stat "$Path" | grep -ie 'Access:.*(.*/.*)' | cut -f 2 -d '(' | cut -f 1 -d '/')"
					;;
				"A" )	# access rights in human readable form
					Value="$(env LANG=en stat "$Path" | grep -ie 'Access:.*(.*/.*)' | cut -f 1 -d ')' | cut -f 2 -d '/')"
					;;
				"b" )	# number of blocks allocated (see %B)
					Value="$(env LANG=en stat "$Path" | grep -e 'Blocks:' | sed -e 's|.*Blocks:||g' | tr -s '\t' ' ' | sed -e 's|^ ||g' | cut -f 1 -d ' ')"
#					Value=$(SomeWord () { printf '%s' $1; }; SomeWord $Value)
					;;
				"B" )	# the size in bytes of each block reported by %b
					Value=''
					;;
				"C" )	# SELinux security context string
					Value='?'
					;;
				"d" )	# device number in decimal
					Value="$(env LANG=en stat "$Path" | grep -e 'Device:' | sed -e 's|.*Device:||g' | tr -s '\t' ' ' | sed -e 's|^ ||g' | cut -f 1 -d ' ')"
#					Value=$(SomeWord () { printf '%s' $1; }; SomeWord $Value)
					Value="$(printf '%s\n' "$Value" | sed -e 's|.*/||g' | sed -e 's|d$||g')"
					;;
				"D" )	# device number in hex
					Value="$(env LANG=en stat "$Path" | grep -e 'Device:' | sed -e 's|.*Device:||g' | tr -s '\t' ' ' | sed -e 's|^ ||g' | cut -f 1 -d ' ')"
#					Value=$(SomeWord () { printf '%s' $1; }; SomeWord $Value)
					Value="$(printf '%s\n' "$Value" | sed -e 's|/.*||g')"	# Only when stat presents it
					Value="$(printf '%s\n' "$Value" | grep -ie 'h$' | sed -e 's|h$||g')"
					if [ "$Value" = "" ] ; then
						Value="$(env LANG=en stat "$Path" | grep -e 'Device:' | sed -e 's|.*Device:||g' | tr -s '\t' ' ' | sed -e 's|^ ||g' | cut -f 1 -d ' ')"
#						Value=$(SomeWord () { printf '%s' $1; }; SomeWord $Value)
						Value="$(printf '%s\n' "$Value" | sed -e 's|.*/||g' | sed -e 's|d$||g')"
						if [ "$Value" != "" ] ; then
							Value="$(printf '%s\n' "obase=16; $Value" | bc 2>/dev/null)"
							if [ "$Value" != "" ] ; then Value="$(Lowercase "$Value")" ; fi
						fi
					fi
					;;
				"f" )	# raw mode in hex
					Value=''
					;;
				"F" )	# file type
					Value="$(env LANG=en stat "$Path" | grep -e 'Blocks:' | sed -e 's|.*:||g')"
					Value=$(NextWords () { shift ; printf '%s' "$*"; }; NextWords $Value)
					;;
				"g" )	# group ID of owner
					Value="$(env LANG=en stat "$Path" | grep -ie 'Uid:.*(.*/.*)' | perl -pe 's|.*Uid:||gi' | cut -f 2 -d '(' | cut -f 1 -d '/' | tr -s '\t' ' ' | sed -e 's|^ ||g' | cut -f 1 -d ' ')"
#					Value=$(SomeWord () { printf '%s' $1; }; SomeWord $Value)
					;;
				"G" )	# group name of owner
					Value="$(env LANG=en stat "$Path" | grep -ie 'Uid:.*(.*/.*)' | perl -pe 's|.*Uid:||gi' | cut -f 1 -d ')' | cut -f 2 -d '/')"
					Value="$(echo TrimAndSingle $Value | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g')"
					;;
				"h" )	# number of hard links
					Value="$(env LANG=en stat "$Path" | grep -ie 'Links:..*' | perl -pe 's|.*Links:||gi' | tr -s '\t' ' ' | sed -e 's|^ ||g' | cut -f 1 -d ' ')"
#					Value=$(SomeWord () { printf '%s' $1; }; SomeWord $Value)
					;;
				"i" )	# inode number
					Value="$(env LANG=en stat "$Path" | grep -ie 'Inode:..*' | perl -pe 's|.*Inode:||gi' | tr -s '\t' ' ' | sed -e 's|^ ||g' | cut -f 1 -d ' ')"
#					Value=$(SomeWord () { printf '%s' $1; }; SomeWord $Value)
					;;
				"m" )	# mount point
					Value="$(DfOutputValue target "$Path" | grep -ve '^/dev$')"
					;;
				"n" )	# file name
					Value="$(env LANG=en stat "$Path" | grep -ie 'File:.*"' | cut -f 2 -d '"')"
					;;
				"N" )	# quoted file name with dereference if symbolic link
					Value="'$(env LANG=en stat "$Path" | grep -ie 'File:.*"' | cut -f 2 -d '"')'"
					if [ "$(ls -l "$Path" | grep -e "$Path -> ")" != "" ] ; then
						Value="${Value} -> '$(ls -l "$Path" | sed -e "s|.*${Path} -> ||g")'"	#'
					fi
					;;
				"o" )	# optimal I/O transfer size hint
					Value=''
					;;
				"s" )	# total size, in bytes
					Value="$(env LANG=en stat "$Path" | grep -ie 'Size:..*' | perl -pe 's|.*Size:||gi' | tr -s '\t' ' ' | sed -e 's|^ ||g' | cut -f 1 -d ' ')"
#					Value=$(SomeWord () { printf '%s' $1; }; SomeWord $Value)
					;;
				"t" )	# major device type in hex
					Value=''
					;;
				"T" )	# minor device type in hex
					Value=''
					;;
				"u" )	# user ID of owner
					Value="$(env LANG=en stat "$Path" | grep -ie 'Uid:..*' | perl -pe 's|.*Uid:||gi' | tr -s '\t' ' ')"
					Value="$(printf '%s\n' "$Value" | cut -f 2 -d '(' | cut -f 1 -d '/' | sed -e 's|^ ||g' | cut -f 1 -d ' ')"
#					Value=$(SomeWord () { printf '%s' $1; }; SomeWord $Value)
					;;
				"U" )	# user name of owner
					Value="$(env LANG=en stat "$Path" | grep -ie 'Uid:..*' | perl -pe 's|.*Uid:||gi')"
					Value="$(printf '%s\n' "$Value" | cut -f 1 -d ')' | cut -f 2 -d '/')"
					Value="$(echo TrimAndSingle $Value | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g')"
					;;
				"w" )	# time of file birth, human-readable; - if unknown
					Value="$(env LANG=en stat "$Path" | grep -ie 'Birth:..*' | perl -pe 's|.*Birth:||gi')"
					Value="$(echo TrimAndSingle $Value | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g')"
					if [ "$Value" = "" ] ; then Value="-" ; fi
					;;
				"W" )	# time of file birth, seconds since Epoch; 0 if unknown
					Value="$(env LANG=en stat "$Path" | grep -ie 'Birth:..*' | perl -pe 's|.*Birth:||gi')"
					Value="$(echo TrimAndSingle $Value | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g')"
					if [ ${#Value} -gt 4 ] ; then
						Value="$(date -d "$Value" '+%s')"
					else
						Value=0
					fi
					;;
				"x" )	# time of last access, human-readable [file content read, except if 'noatime' in effect]
					Value="$(env LANG=en stat "$Path" | grep -ie 'Access:..*'  | grep -ive 'Access:.*(.*/.*)' | perl -pe 's|.*Access:||gi')"
					Value="$(echo TrimAndSingle $Value | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g')"
					;;
				"X" )	# time of last access, seconds since Epoch [file content read, except if 'noatime' in effect]
					Value="$(env LANG=en stat "$Path" | grep -ie 'Access:..*'  | grep -ive 'Access:.*(.*/.*)' | perl -pe 's|.*Access:||gi')"
					Value="$(echo TrimAndSingle $Value | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g')"
					if [ ${#Value} -gt 4 ] ; then
						Value="$(date -d "$Value" '+%s')"
					else
						Value=0
					fi
					;;
				"y" )	# time of last modification, human-readable [file content changes]
					Value="$(env LANG=en stat "$Path" | grep -ie 'Modify:..*' | perl -pe 's|.*Modify:||gi')"
					Value="$(echo TrimAndSingle $Value | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g')"
					;;
				"Y" )	# time of last modification, seconds since Epoch [file content changes]
					Value="$(env LANG=en stat "$Path" | grep -ie 'Modify:..*' | perl -pe 's|.*Modify:||gi')"
					Value="$(echo TrimAndSingle $Value | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g')"
					if [ ${#Value} -gt 4 ] ; then
						Value="$(date -d "$Value" '+%s')"
					else
						Value=0
					fi
					;;
				"z" )	# time of last change, human-readable [both file content changes and metadata only changes: owner, time, permissions, etc.]
					Value="$(env LANG=en stat "$Path" | grep -ie 'Change:..*' | perl -pe 's|.*Change:||gi')"
					Value="$(echo TrimAndSingle $Value | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g')"
					;;
				"Z" )	# time of last change, seconds since Epoch [both file content changes and metadata only changes: owner, time, permissions, etc.]
					Value="$(env LANG=en stat "$Path" | grep -ie 'Modify:..*' | perl -pe 's|.*Modify:||gi')"
					Value="$(echo TrimAndSingle $Value | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g')"
					if [ ${#Value} -gt 4 ] ; then
						Value="$(date -d "$Value" '+%s')"
					else
						Value=0
					fi
					;;
			esac
		fi
	fi
	if [ "$Value" != "" ] ; then printf '%s\n' "$Value" ; fi
}

MkdirPP ()
# Syntax as a sentence: MkdirPP $NewPath [$OwningForNewElements] [$PermissionsForNewElements] [$MorePermissions]
# Description: Same job as "mkdir -p" but setting specified owner and permissions to each new subdirectory
# Expected parameters:
#	$1	Directory path to create as necessary
#	$2	(optional) User, :Group or User:Group specification as allowed by chown. If no specified, will take setting from existing tree
#	$3	(optional) Permissions specification as allowed by chmod. If no specified, will take setting from existing tree
#	$4	(optional) Additional permissions specification to set (useful for g+s not being applied by MkfilePP)
# Notes:
#	- Owners/permissions are only set when creating subdirectories.
# Depends on functions: StatFormatValue
# Depends on software packages: (none)
{
	local NewPath="$1"
	local OwningForNewElements="$2"
	local PermissionsForNewElements="$3"
	local MorePermissions="$4"
	local CurrentElement=''
	local PreviousDir=''
	local LastStatus=0
	local StatusCode=0
	
	PreviousDir="$(pwd)"
	IFS="$(printf "/")" ; for CurrentElement in $NewPath ; do unset IFS
		if [ $StatusCode -eq 0 ] ; then
			if [ "$CurrentElement" = "" ] ; then
				cd /
			else
				if [ -e "$CurrentElement" ] ; then
					cd "$CurrentElement"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				else
					if [ "$PermissionsForNewElements" = "" ] || [ "$PermissionsForNewElements" = "." ] ; then
						PermissionsForNewElements="$(StatFormatValue %a .)"
					fi
					if [ "$OwningForNewElements" = "" ] || [ "$OwningForNewElements" = "." ] ; then
						OwningForNewElements="$(StatFormatValue %U .)"
						OwningForNewElements="${OwningForNewElements}:$(StatFormatValue %G .)"
					fi
					mkdir "$CurrentElement"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					if [ $StatusCode -eq 0 ] ; then
						if [ "$OwningForNewElements" != "" ] && [ "$OwningForNewElements" != "." ] ; then
							chown "$OwningForNewElements" "$CurrentElement"
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						fi
						if [ "$PermissionsForNewElements" != "" ] && [ "$PermissionsForNewElements" != "." ] ; then
							chmod "$PermissionsForNewElements" "$CurrentElement"
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						fi
						if [ "$MorePermissions" != "" ] && [ "$MorePermissions" != "." ] ; then
							chmod "$MorePermissions" "$CurrentElement"
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						fi
						cd "$CurrentElement"
					fi
				fi
			fi
		fi
	done
	cd "$PreviousDir"
	if [ "$NozeroStatusToFile" != "" ] && [ ! -f "$NozeroStatusToFile" ] && [ $StatusCode -ne 0 ] ; then echo $StatusCode > "$NozeroStatusToFile" ; fi
	return $StatusCode
}

MkfilePP ()
# Syntax as a sentence: MkfilePP $NewFile [$OwningForNewElements] [$PermissionsForNewElements] [$MorePermissions]
# Description: Creates a file only if not exists, and then sets it specified owner and permissions
# Expected parameters:
#	$1	Path to create as necessary
#	$2	(optional) User, :Group or User:Group specification as allowed by chown. If no specified, will take setting from existing tree
#	$3	(optional) Permissions specification as allowed by chmod. If no specified, will take setting from existing tree
#	$4	(optional) Additional permissions specification to set (useful for g+s only being to directories)
# Notes:
#	- Owners/permissions are only set when creating the file.
#	- If directory path does not exist, it's created with MkdirPP using same parameters.
# Depends on functions: MkdirPP Dirname StatFormatValue
# Depends on software packages: (none)
{
	local NewFile="$1"
	local OwningForNewElements="$2"
	local PermissionsForNewElements="$3"
	local MorePermissions="$4"
	local TheDirname=''
	local LastStatus=0
	local StatusCode=0
	
	if [ ! -e "$NewFile" ] && [ ! -L "$NewFile" ] ; then
		TheDirname="$(Dirname "$NewFile")"
		MkdirPP "$TheDirname" "$OwningForNewElements" "$PermissionsForNewElements" "$MorePermissions" 2>/dev/null
		touch "$NewFile"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		if [ "$OwningForNewElements" = "" ] || [ "$OwningForNewElements" = "." ] ; then
			OwningForNewElements="$(StatFormatValue %U "$TheDirname")"
			OwningForNewElements="${OwningForNewElements}:$(StatFormatValue %G "$TheDirname")"
		fi
		if [ "$OwningForNewElements" != "" ] && [ "$OwningForNewElements" != "." ] ; then
			chown "$OwningForNewElements" "$NewFile"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
		if [ "$PermissionsForNewElements" = "" ] || [ "$PermissionsForNewElements" = "." ] ; then
			PermissionsForNewElements="$(StatFormatValue %a "$TheDirname")"
		fi
		if [ "$PermissionsForNewElements" != "" ] && [ "$PermissionsForNewElements" != "." ] ; then
			chmod "$PermissionsForNewElements" "$NewFile"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
		if [ "$MorePermissions" != "" ] && [ "$MorePermissions" != "." ] && [ "$MorePermissions" != "g+s" ] ; then
			chmod "$MorePermissions" "$NewFile"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
	fi
	if [ "$NozeroStatusToFile" != "" ] && [ ! -f "$NozeroStatusToFile" ] && [ $StatusCode -ne 0 ] ; then echo $StatusCode > "$NozeroStatusToFile" ; fi
	return $StatusCode
}

EsIP ()
# Sintaxis como función: $(EsIP $Address)
# Descripción: Devuelve (stdout) "1" en caso de que el dato proporcionado valga como dirección IPv4
# Notas:
#	- Direcciones validas desde 0.0.0.0 hasta 255.255.255.255
# Depends on functions: Is_IntegerNr
# Depends on software packages: grep
{
	local Address="$1"
#	local NumeroActual=''
	local Address_b1=''
	local Address_b2=''
	local Address_b3=''
	local Address_b4=''
	local Value=0
	
	# Expresión encontrada por la web, pero que no funciona bien con todos los extremos numéricos.
	# Puede que sea un problema de desbordamiento de expresiones cuando hay pocos recursos.
	# echo "$Value" | grep -E "\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)"
	# PENDIENTE: Puede que sea más sencillo con la técnica de Is_IntegerNr()
	
	if [ "$Address" != "" ] && [ "$(printf '%s' "$Address" | grep -e ' ')" = "" ] ; then
#		Value="$(printf '%s' "$Address" | tr -s '.' ' ')"
#		NumeroActual="$(UnaPalabra () { printf '%s' $1; }; UnaPalabra $Value)"
#		NumeroActual="$(printf '%s' "$NumeroActual" | grep -E "^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$")"
#		if [ "$NumeroActual" != "" ] ; then
#			NumeroActual="$(UnaPalabra () { printf '%s' $2; }; UnaPalabra $Value)"
#			NumeroActual="$(printf '%s' "$NumeroActual" | grep -E "^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$")"
#			if [ "$NumeroActual" != "" ] ; then
#				NumeroActual="$(UnaPalabra () { printf '%s' $3; }; UnaPalabra $Value)"
#				NumeroActual="$(printf '%s' "$NumeroActual" | grep -E "^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$")"
#				if [ "$NumeroActual" != "" ] ; then
#					NumeroActual="$(UnaPalabra () { printf '%s' $4; }; UnaPalabra $Value)"
#					NumeroActual="$(printf '%s' "$NumeroActual" | grep -E "^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$")"
#				fi
#			fi
#		fi
		Address_b1="$(printf '%s\n' "$Address" | cut -f 1 -d '.')"
		Address_b2="$(printf '%s\n' "$Address" | cut -sf 2 -d '.')"
		Address_b3="$(printf '%s\n' "$Address" | cut -sf 3 -d '.')"
		Address_b4="$(printf '%s\n' "$Address" | cut -sf 4 -d '.')"
		if Is_IntegerNr $Address_b1 && Is_IntegerNr $Address_b2 && Is_IntegerNr $Address_b3 && Is_IntegerNr $Address_b4 ; then
			if [ $Address_b1 -ge 0 ] && [ $Address_b2 -ge 0 ] && [ $Address_b3 -ge 0 ] && [ $Address_b4 -ge 0 ] ; then
				if [ $Address_b1 -le 255 ] && [ $Address_b2 -le 255 ] && [ $Address_b3 -le 255 ] && [ $Address_b4 -le 255 ] ; then
					Value=1
				fi
			fi
		fi
	fi
	printf '%s\n' "$Value"
}

ListaDispositivosRed ()
# Sintaxis como funcion: $(ListaDispositivosRed)
# Descripcion: Devuelve (stdout) una lista de todos los dispositivos de red disponibles en el sistema, separados por espacios.
# Depends on functions:	WhereProgram
# Depends on software packages: grep, sed, iproute|net-tools
{
	local Value=''
	local IpRoute=''
	
	IpRoute="$(WhereProgram ip)"
	if [ "$IpRoute" = "" ] && [ -x /sbin/ip ] ; then IpRoute="/sbin/ip" ; fi
	if [ "$IpRoute" = "" ] && [ -x /bin/ip ] ; then IpRoute="/bin/ip" ; fi
	if [ "$IpRoute" = "" ] && [ -x /usr/sbin/ip ] ; then IpRoute="/usr/sbin/ip" ; fi
	if [ "$IpRoute" = "" ] && [ -x /usr/bin/ip ] ; then IpRoute="/usr/bin/ip" ; fi
	if [ "$IpRoute" != "" ] ; then
		export LANG=en
		Value="$($IpRoute link 2>/dev/null | grep -ve '^ ' | grep -e '..*: ..*: ' | cut -f 2 -d ':')"
		if [ "$Value" = "" ] ; then
			Value="$(env LANG=en netstat --interfaces --all 2>/dev/null | grep -ive "^Iface" | grep -ive "^Kernel" | cut -f 1 -d ' ')"
		fi
		Value="$(echo TrimAndSingle $Value | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g')"
		if [ "$Value" != "" ] ; then printf '%s\n' "$Value" ; fi
	fi
}

AllIPv4 ()
# Syntax as a function: $(AllIPv4 $LimitNr "$NIC" "$ExcludedAddresses")
# Description: Returns (stdout) active IP addresses on system (one per line)
# Parametres esperats:
#	$1	Numero màxim d'adreces a llistar. Especifiqueu 0 per a totes.
#	$2	Nom de dispositiu de xarxa a analitzar. "eth" es valid per tots els "eth.."; "." o "*" serveix per a tots
#	$3	(opcional) Patro d'adreces a excloure de la llista. Per exemple "^127\."
# Notes:
#	- 
# Depends on functions: ListaDispositivosRed IPCDIRFromDevice
# Depends on software packages: grep (>= 2.0), sed
{
	# Variables locales
	local LimitNr=$1
	local NIC="$2"
	local ExcludedAddresses="$3"
	local FoundNr=0
	local NICs=''
	local CurDevice=''
	local CurIPs=''
	local CurIP=''
	local LastStatus=0
	local StatusCode=0
	
	if ! Is_IntegerNr "$LimitNr" ; then LimitNr=0 ; fi
	NICs="$(ListaDispositivosRed)"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	for CurDevice in $NICs ; do
		CurIPs="$(IPCDIRFromDevice $CurDevice)"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		CurIPs="$(printf '%s\n' "$CurIPs" | cut -f 1 -d '/')"
		for CurIP in $CurIPs ; do
			if [ "$ExcludedAddresses" = "" ] || [ "$(printf '%s' "$CurIP" | grep -ve "$ExcludedAddresses")" != "" ] ; then
				FoundNr=$((FoundNr + 1))
				if [ $FoundNr -le $LimitNr ] || [ $LimitNr -eq 0 ] ; then
					printf '%s\n' "$CurIP"
				fi
			fi
		done
	done
	if [ "$NozeroStatusToFile" != "" ] && [ ! -f "$NozeroStatusToFile" ] && [ $StatusCode -ne 0 ] ; then echo $StatusCode > "$NozeroStatusToFile" ; fi
	return $StatusCode
}

IPCDIRFromDevice ()
# Sintaxis como funcion: $(IPCDIRFromDevice $NIC)
# Descripcion: Devuelve (stdout) todas las IPv4 asignadas al dispositivo especificado, una dirección por línea, en formato CDIR (192.168.1.33/24)
# Depends on functions: WhereProgram
# Depends on software packages: grep, sed, iproute
{
	local NIC="$1"
	local IpRoute=''
	local SelectedLines=''
	local CurLine=''
	local CurIP=''
	local CurMask=''
	
	if [ "$NIC" != "" ] ; then
		IpRoute="$(WhereProgram ip)"
		if [ "$IpRoute" = "" ] && [ -x /sbin/ip ] ; then IpRoute="/sbin/ip" ; fi
		if [ "$IpRoute" = "" ] && [ -x /bin/ip ] ; then IpRoute="/bin/ip" ; fi
		if [ "$IpRoute" = "" ] && [ -x /usr/sbin/ip ] ; then IpRoute="/usr/sbin/ip" ; fi
		if [ "$IpRoute" = "" ] && [ -x /usr/bin/ip ] ; then IpRoute="/usr/bin/ip" ; fi
		if [ "$IpRoute" != "" ] ; then
			export LANG=en
#			Value="$($IpRoute address show $NIC | grep -e 'inet ..*\...*\...*\...*/..* .')"
#			Value="$(printf '%s\n' "$Value" | tr -s '\t' ' ' | tr -s ' ' | sed -e 's|^ ||g' | cut -f 2 -d ' ')"
#			if [ "$Value" != "" ] ; then printf '%s\n' "$Value" ; fi
#			$IpRoute address show dev "$NIC" | sed -e 's|.* inet |inet |g' | grep -e '^inet ' | cut -f 2 -d ' '
			SelectedLines="$($IpRoute address show dev "$NIC" | sed -e 's|.* inet |inet |g' | grep -e '^inet ')"
			IFS="$(printf '\n\b')" ; for CurLine in $SelectedLines ; do unset IFS
				CurIP="$(printf '%s' "$CurLine" | tr ' ' '\n' | grep -e '..*\...*\...*\..' | head -n 1)"
				# Debian 7 in Netwerkvereniging Coloclue returns IP with no mask but peer with mask at same line.
				CurMask="$(printf '%s' "$CurLine" | tr ' ' '\n' | grep -e '..*\...*\...*\...*/.' | head -n 1 | cut -f 2 -d '/')"
				if [ "$CurIP" != "" ] ; then
					if [ "$CurMask" != "" ] ; then
						printf '%s\n' "${CurIP}/${CurMask}"
					else
						printf '%s\n' "$CurIP"
					fi
				fi
			done
		fi
	fi
}

IpDeDispositivo ()
# Sintaxis como funcion: $(IpDeDispositivo $NIC)
# Descripcion: Devuelve (stdout) una IP que tiene asignado el dispositivo especificada, lo más probable es que sea la predeterminada.
# Notes:
#	- Use IPv4FromDevice() or LlistarIPpropies() to get all IPs
# Depends on functions: WhereProgram
# Depends on software packages: grep, iproute
{
	local NIC="$1"
	local IpRoute=''
	
	if [ "$NIC" != "" ] ; then
		IpRoute="$(WhereProgram ip)"
		if [ "$IpRoute" = "" ] && [ -x /sbin/ip ] ; then IpRoute="/sbin/ip" ; fi
		if [ "$IpRoute" = "" ] && [ -x /bin/ip ] ; then IpRoute="/bin/ip" ; fi
		if [ "$IpRoute" = "" ] && [ -x /usr/sbin/ip ] ; then IpRoute="/usr/sbin/ip" ; fi
		if [ "$IpRoute" = "" ] && [ -x /usr/bin/ip ] ; then IpRoute="/usr/bin/ip" ; fi
		if [ "$IpRoute" != "" ] ; then
			export LANG=en
#			Value="$($IpRoute address show $NIC | grep -e 'inet .* brd ' | head -n 1)"
			Value="$($IpRoute address show $NIC | grep -e "inet .* $NIC" | head -n 1 | tr -s '\t' ' ' | sed -e 's|^ ||g' | cut -f 2 -d ' ')"
#			Value=$(SomeWord () { printf '%s' $2; }; SomeWord $Value)
			Value="$(printf '%s\n' "$Value" | cut -f 1 -d '/')"
			if [ "$Value" != "" ] ; then printf '%s\n' "$Value" ; fi
		fi
	fi
}

Is_PrivateIPv4 ()
# Syntax as a function: Is_PrivateIPv4 $Address
# Description: Returns (exitcode 0) TRUE if specified address is assigned by IANA as in class A/B/C
# https://en.wikipedia.org/wiki/Private_network
# Use example (without brackets []):
#	if Is_PrivateIPv4 192.168.1.33 ; then echo "This is LAN" ; fi
# Notes:
#	Class A: 10.0.0.0/8
#	Class B: 172.16.0.0/12
#	Class C: 192.168.0.0/16
#	Link-local: 169.254.0.0/16
#	- carrier-grade NAT (100.64.0.0/10) is not be used on private networks or on the public Internet -> FALSE
#	- Way to detect 100.64.0.0/10 :
#	  ! Is_PublicIPv4 && ! Is_PrivateIPv4
# Depends on functions: EsIP
# Depends on software packages: grep
{
	local Address="$1"
	local AnalyzedPart=''
	local TrueCode=254 # 254=FALSE
	
	if [ "$(EsIP "$Address")" = "1" ] ; then
		AnalyzedPart="$(printf '%s' "$Address" | cut -f 1 -d '.')"
		if [ "$AnalyzedPart" = "172" ] ; then
			AnalyzedPart="$(printf '%s' "$Address" | cut -sf 2 -d '.')"
			if [ $AnalyzedPart -ge 16 ] && [ $AnalyzedPart -le 31 ] ; then AnalyzedPart='172.16' ; fi
		fi
		if [ "$(printf '%s' "$Address" | grep -e '^10\.' -e '^192\.168\.' -e '^169\.254\.')" ] || [ "$AnalyzedPart" = "172.16" ] ; then
			TrueCode=0
		fi
	fi
	return $TrueCode
}

Is_PublicIPv4 ()
# Syntax as a function: Is_PublicIPv4 $Address
# Description: Returns (exitcode 0) TRUE if specified address can be assigned by an ISP for Internet host
# https://en.wikipedia.org/wiki/Private_network
# Use example (without brackets []):
#	if Is_PrivateIPv4 1.2.3.4 ; then echo "This is an Internet address" ; fi
# Notes:
#	- carrier-grade NAT (100.64.0.0/10) is not be used on private networks or on the public Internet -> FALSE
#	- Way to detect 100.64.0.0/10 :
#	  ! Is_PublicIPv4 && ! Is_PrivateIPv4
# Depends on functions: EsIP Is_PrivateIPv4
# Depends on software packages: grep
{
	local Address="$1"
	local AnalyzedPart=''
	local TrueCode=254 # 254=FALSE
	
	if [ "$(EsIP "$Address")" = "1" ] ; then
		if ! Is_PrivateIPv4 "$Address" ; then
			TrueCode=0
			AnalyzedPart="$(printf '%s' "$Address" | cut -f 1 -d '.')"
			if [ "$AnalyzedPart" = "100" ] ; then
				# carrier-grade NAT?
				AnalyzedPart="$(printf '%s' "$Address" | cut -sf 2 -d '.')"
				if [ $AnalyzedPart -ge 64 ] && [ $AnalyzedPart -le 127 ] ; then
					TrueCode=254
				fi
			fi
		fi
	fi
	return $TrueCode
}

BitsMascara ()
# Syntax as a function: $(BitsMascara "$SubnetMask")
# Description: Converts old subnet mask into CIDR bits number.
# $(BitsMascara "255.255.255.0") => 24
# Depends on functions: Is_IntegerNr
# Depends on software packages: grep
{
	local SubnetMask="$1"
	local CurByte=''
	local Value=0
	
	if [ "$(printf '%s\n' "$SubnetMask" | grep -e '..*\...*\...*\...*')" != "" ] ; then
#		SubnetMask="$(printf '%s' "$SubnetMask" | tr -s '.' ' ')"
#		for CurByte in $(echo $SubnetMask) ; do
		IFS="." ; for CurByte in $SubnetMask ; do unset IFS
			if Is_IntegerNr "$CurByte" && [ "$Value" != "" ] ; then 
				if [ $CurByte -ge 128 ] ; then Value=$((Value + 1)) ; fi
				if [ $CurByte -ge 192 ] ; then Value=$((Value + 1)) ; fi
				if [ $CurByte -ge 224 ] ; then Value=$((Value + 1)) ; fi
				if [ $CurByte -ge 240 ] ; then Value=$((Value + 1)) ; fi
				if [ $CurByte -ge 248 ] ; then Value=$((Value + 1)) ; fi
				if [ $CurByte -ge 252 ] ; then Value=$((Value + 1)) ; fi
				if [ $CurByte -ge 254 ] ; then Value=$((Value + 1)) ; fi
				if [ $CurByte -ge 255 ] ; then Value=$((Value + 1)) ; fi
			else
				Value=''
			fi
		done
		if [ "$Value" != "" ] ; then
			printf '%s\n' "$Value"
		else
			printf '%s\n' "${sERROR}E: Bad IPv4 numbers for BitsMascara()${fRESET}" 1>&2
		fi
	fi
}

IPv4ToDecimal ()
# Syntax as a function: $(IPv4ToDecimal "$IP")
# Description: Returns (stdout) the decimal numeric representation of IPv4 address (unsigned number)
# Depends on functions: EsIP
# Depends on software packages: bc
# Suggests software packages: awk/gawk
{
	local IP="$1"
	local Nr1=''
	local Nr2=''
	local Nr3=''
	local Nr4=''
	local Value=''

	if [ "$(EsIP "$IP")" = "1" ] ; then
		Nr1="$(printf '%s' "$IP" | cut -f 1 -d '.')"
		Nr2="$(printf '%s' "$IP" | cut -f 2 -d '.')"
		Nr3="$(printf '%s' "$IP" | cut -f 3 -d '.')"
		Nr4="$(printf '%s' "$IP" | cut -f 4 -d '.')"
		# Sometimes awk does not get stdin as expected, and can cache & repeat data
#		Value="$(printf '%s' "$IP" | awk -F '.' '{printf "%d\n", ($1 * 2^24) + ($2 * 2^16) + ($3 * 2^8) + $4}')"
		Value="$(printf '%s\n' "($Nr1 * 2^24) + ($Nr2 * 2^16) + ($Nr3 * 2^8) + $Nr4" | bc)"
		printf '%s\n' "$Value"
	fi
}

IPv4ToBinary ()
# Syntax as a function: $(IPv4ToBinary "$IP")
# Description: Returns (stdout) the binary representation of IPv4 address (unsigned numbers)
# Depends on functions: IPv4ToDecimal
# Depends on software packages: bc
{
	local IP="$1"
	local Value=''

	Value="$(IPv4ToDecimal "$IP")"
	if [ "$Value" != "" ] ; then
		Value="$(echo "obase=2;$Value" | bc)"
		if [ "$Value" != "" ] ; then
			Value="$(printf '%32s' "$Value" | tr ' ' '0')"
			printf '%s\n' "$Value"
		fi
	fi
}

BinaryToIPv4 ()
# Syntax as a function: $(BinaryToIPv4 "$BinaryDigits")
# Description: Returns (stdout) the IPv4 address of a 32-digit binary representation
# Note: Specified string MUST be 32 characters long.
# Depends on functions: (none)
# Depends on software packages: bc
{
	local BinaryDigits="$1"
	local Nr1=''
	local Nr2=''
	local Nr3=''
	local Nr4=''
	local Value=''

	if [ ${#BinaryDigits} -eq 32 ] && [ "$(printf '%s' "$BinaryDigits" | tr -s '1' '0')" = "0" ] ; then
		Nr1="$(printf '%s' "$BinaryDigits" | cut -c 1-8)"
		Nr1="$(echo "obase=10;ibase=2;${Nr1}" | bc)"
		Value="$Nr1"
		Nr2="$(printf '%s' "$BinaryDigits" | cut -c 9-16)"
		Nr2="$(echo "obase=10;ibase=2;${Nr2}" | bc)"
		Value="${Value}.${Nr2}"
		Nr3="$(printf '%s' "$BinaryDigits" | cut -c 17-24)"
		Nr3="$(echo "obase=10;ibase=2;${Nr3}" | bc)"
		Value="${Value}.${Nr3}"
		Nr4="$(printf '%s' "$BinaryDigits" | cut -c 25-32)"
		Nr4="$(echo "obase=10;ibase=2;${Nr4}" | bc)"
		Value="${Value}.${Nr4}"
		printf '%s\n' "$Value"
	fi
}

IPv4NetworkAddress ()
# Syntax as a function: $(IPv4NetworkAddress "$CIDR")
# Description: Returns (stdout) the base address of CIDR specification
# Example: 192.168.1.150/24 -> 192.168.1.0
# Depends on functions: Is_IntegerNr IPv4ToBinary BinaryToIPv4
# Depends on software packages: bc
{
	local CIDR="$1"
	local Address=''
	local Binary=''
	local Mask=''

	Address="$(printf '%s' "$CIDR" | cut -f 1 -d '/')"
	Mask="$(printf '%s' "$CIDR" | cut -f 2 -d '/')"
	if [ "$Address" != "$Mask" ] && [ "$Mask" != "" ] && Is_IntegerNr "$Mask" && [ $Mask -ge 0 ] && [ $Mask -le 32 ] ; then
		Binary="$(IPv4ToBinary "$Address")"
		if [ "$Binary" != "" ] ; then
			if [ $Mask -gt 0 ] ; then
				Binary="$(printf '%s' "$Binary" | cut -c 1-${Mask})"
			else
				Binary=''
			fi
			Binary="$(printf '%-32s' "$Binary" | tr ' ' '0')"
			BinaryToIPv4 "$Binary"
		fi
	fi
}

BridgeMembers ()
# Syntax as a function: "$(BridgeMembers "$TheBridge")"
# Description: Returns (stdout) names of attached NICs; one name per line
# Expected parameters:
#	$1	Existing bridge name
# Depends on functions: WhereProgram Is_Executable
# Depends on software packages: grep, sed, iproute2|net-tools&bridge-utils
{
	local TheBridge="$1"
	local CurrentLine=''
	local CurrentMac=''
	local CurrentNIC=''
	local IfConfig=''
	local BrCtl=''
	local Value=''
	
	if Is_Executable bridge && [ "$(ip -V 2>/dev/null | grep -e iproute2)" != "" ] ; then
		Value="$(bridge link show | grep -e ": .*:.*<.*>.* master $TheBridge " | cut -f 2 -d ' ' | cut -f 1 -d ':' | cut -f 1 -d '@')"
	else
		IfConfig="$(WhereProgram ifconfig)"
		if [ "$IfConfig" = "" ] && [ -x /sbin/ifconfig ] ; then IfConfig="/sbin/ifconfig" ; fi
		if [ "$IfConfig" = "" ] && [ -x /bin/ifconfig ] ; then IfConfig="/bin/ifconfig" ; fi
		if [ "$IfConfig" = "" ] && [ -x /usr/sbin/ifconfig ] ; then IfConfig="/usr/sbin/ifconfig" ; fi
		if [ "$IfConfig" = "" ] && [ -x /usr/bin/ifconfig ] ; then IfConfig="/usr/bin/ifconfig" ; fi
		BrCtl="$(WhereProgram brctl)"
		if [ "$BrCtl" = "" ] && [ -x /sbin/brctl ] ; then BrCtl="/sbin/brctl" ; fi
		if [ "$BrCtl" = "" ] && [ -x /bin/brctl ] ; then BrCtl="/bin/brctl" ; fi
		if [ "$BrCtl" = "" ] && [ -x /usr/sbin/brctl ] ; then BrCtl="/usr/sbin/brctl" ; fi
		if [ "$BrCtl" = "" ] && [ -x /usr/bin/brctl ] ; then BrCtl="/usr/bin/brctl" ; fi
		if [ "$IfConfig" != "" ] ; then
			NICs="$($IfConfig 2>/dev/null | grep -ve '^ ' -ve '^$' | cut -f 1 -d ' ' | cut -f 1 -d ':')"
		fi
		IFS="$(printf '\n\b')" ; for CurrentLine in $($BrCtl showmacs "$TheBridge" 2>/dev/null | tail -n +2 | tr -s '\t' ' ') ; do unset IFS
			IsLocal="$(SomeWord () { printf '%s' $3; }; SomeWord $CurrentLine)"
			if [ "$IsLocal" = "yes" ] ; then
				CurrentMac="$(SomeWord () { printf '%s' $2; }; SomeWord $CurrentLine)"
				for CurNIC in $NICs ; do
					NicInfo="$(env LANG=en $IfConfig $CurNIC 2>/dev/null)"
					NicInfo="$(echo TrimAndSingle $NicInfo | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g')"
					if [ "$(printf '%s\n' "$NicInfo" | grep -ie " HWaddr.${CurrentMac}" -ie " ether ${CurrentMac}")" != "" ] ; then
						#printf '%s\n' "$CurNIC"
						if [ "$(printf '%s' "$Value" | grep -e "^${CurNIC}$")" = "" ] ; then
							Value="$(printf '%s\n' "$Value" ; printf '%s\n' "$CurNIC")"
						fi
					fi
				done
			fi
		done
	fi
	if [ "$Value" != "" ] ; then
		printf '%s' "$Value" | grep -ve '^$'
	fi
}

Is_NetBridge ()
# Syntax as a function: Is_Bridge $NetDevice
# Description: Returns (exitcode 0) TRUE if $NetDevice is a network bridge
# Use example (without brackets []):
#	if Is_NetBridge br0 ; then echo "Yes" ; fi
# Expected parameters:
#	$1	Network device name
# Depends on functions: Is_Executable
# Depends on software packages: grep, sed, iproute2|bridge-utils
{
	local NetDevice="$1"
	local Value=254

	if Is_Executable bridge && [ "$(ip -V 2>/dev/null | grep -e iproute2)" != "" ] ; then
		if [ "$(bridge fdb show dev $NetDevice 2>/dev/null | grep -e ":.. master $NetDevice " -e ":.. master ${NetDevice}$")" != "" ] || [ "$(ip link show type bridge dev $NetDevice 2>/dev/null)" != "" ] ; then
			Value=0
		fi
	else
		if [ "$(brctl show $NetDevice | grep -ie 'Operation not supported' -ie 'No such device')" = "" ] ; then
			Value=0
		fi
	fi
	return $Value
}

UpNetBridge ()
# Syntax as a sentence: UpNetBridge $BridgeName
# Description: Rises specified bridge up (if not already) and/or creates it as necessary
# Expected parameters:
#	$1	Network device name
#	$2	Software bridge name
# Notes:
#	- First place it's tried with ifup/ifconfig to use registered NIC configuration
#	- If bridge does not exist, it's created and enabled.
#	- Requires superuser permissions (root)
# TO DO:
#	- Allow specify IP/Mask to be set IP when creating bridge
# Depends on functions: Is_Executable Is_NetBridge BridgeMembers ListaDispositivosRed
# Depends on software packages: grep, sed, iproute2|bridge-utils, net-tools
{
	local BridgeName="$1"
	local LastStatus=0
	local StatusCode=0
	
	if ! Is_NetBridge "$BridgeName" ; then
		if [ "$(ListaDispositivosRed | grep -e "^${BridgeName}$" -e " ${BridgeName}$" -e "^${BridgeName} " -e " $BridgeName ")" = "" ] ; then
			ifconfig $BridgeName down >/dev/null 2>&1
			if Is_Executable ifup ; then
				ifup $BridgeName >/dev/null 2>&1
			else
				ifconfig $BridgeName up >/dev/null 2>&1
			fi
			if ! Is_NetBridge "$BridgeName" ; then
				if Is_Executable bridge && [ "$(ip -V 2>/dev/null | grep -e iproute2)" != "" ] ; then
					ip link add name $BridgeName type bridge
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					if [ $StatusCode -eq 0 ] ; then
						ip link set $BridgeName up
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					fi
				else
					brctl addbr $BridgeName
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
			fi
		else
			printf '%s\n' "${sERROR}E: A NIC already exists with name of pretended bridge: $BridgeName${fRESET}" 1>&2
			LastStatus=113 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
	fi
	return $StatusCode
}

AttachToNetBrigde ()
# Syntax as a sentence: AttachToNetBrigde $NetDevice $TheBridge
# Description: Adds specified NIC to specified bridge
# Expected parameters:
#	$1	Network device name
#	$2	Software bridge name
# Notes:
#	- If bridge does not exist, it's created and enabled.
#	- $NetDevice is enabled because its state must be up.
#	- Requires superuser permissions (root)
# TO DO:
#	- Allow br0/192.168.1.250/24 syntax to set IP when creating bridge
# Depends on functions: Is_Executable Is_NetBridge BridgeMembers UpNetBridge
# Depends on software packages: grep, sed, iproute2|net-tools&bridge-utils
{
	local NetDevice="$1"
	local TheBridge="$2"
	local LastStatus=0
	local StatusCode=0

	if [ "$NetDevice" = "" ] || [ "$TheBridge" = "" ] ; then
		printf '%s\n' "${sERROR}E: Net device or bridge name not specified when asking an attachment.${fRESET}" 1>&2
		LastStatus=82 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		UpNetBridge $TheBridge
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ $StatusCode -eq 0 ] && [ "$(BridgeMembers $TheBridge | grep -e "^${NetDevice}$")" = "" ] ; then
		if Is_Executable bridge && [ "$(ip -V 2>/dev/null | grep -e iproute2)" != "" ] ; then
			ip link set $NetDevice master $TheBridge
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		else
			brctl addif $TheBridge $NetDevice
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			if [ $StatusCode -eq 0 ] ; then
				ifconfig $TheBridge up
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
		fi
	fi
	if [ "$NozeroStatusToFile" != "" ] && [ ! -f "$NozeroStatusToFile" ] && [ $StatusCode -ne 0 ] ; then echo $StatusCode > "$NozeroStatusToFile" ; fi
	return $StatusCode
}

DetachFromNetBrigde ()
# Syntax as a sentence: DetachFromNetBrigde $NetDevice $TheBridge $RemoveEmpty
# Description: Removes specified NIC from specified bridge
# Expected parameters:
#	$1	Network device name
#	$2	Software bridge name
#	$3	(optional) "1" to remove bridge if has no members
# Notes:
#	- Requires superuser permissions (root)
# Depends on functions: Is_Executable Is_NetBridge BridgeMembers
# Depends on software packages: grep, sed, iproute2|net-tools&bridge-utils
{
	local NetDevice="$1"
	local TheBridge="$2"
	local RemoveEmpty="$3"
	local LastStatus=0
	local StatusCode=0

	if [ "$NetDevice" = "" ] || [ "$TheBridge" = "" ] ; then
		printf '%s\n' "${sERROR}E: Net device or bridge name not specified when asking a detachment.${fRESET}" 1>&2
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		if Is_Executable bridge && [ "$(ip -V 2>/dev/null | grep -e iproute2)" != "" ] ; then
			ip link set $NetDevice nomaster
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			if [ $StatusCode -eq 0 ] && [ "$RemoveEmpty" = "1" ] && Is_NetBridge $TheBridge && [ "$(BridgeMembers $TheBridge)" = "" ] ; then
				ip link delete $TheBridge type bridge
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
		else
			brctl delif $TheBridge $NetDevice
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			if [ $StatusCode -eq 0 ] && [ "$RemoveEmpty" = "1" ] && Is_NetBridge $TheBridge && [ "$(BridgeMembers $TheBridge)" = "" ] ; then
				ifconfig $TheBridge down
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				if [ $StatusCode -eq 0 ] ; then
					brctl delbr $TheBridge
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
			fi
		fi
	fi
	if [ "$NozeroStatusToFile" != "" ] && [ ! -f "$NozeroStatusToFile" ] && [ $StatusCode -ne 0 ] ; then echo $StatusCode > "$NozeroStatusToFile" ; fi
	return $StatusCode
}

RemoteFunctionTelnet ()
# Syntax as a function: $(RemoteFunctionTelnet Timeout Host Port [Username] Password Command [ReadyPrompt] [CompletedSignal])
# Description: Retorna únicament el resultat de l'execució remota (sense el requeriment de credencials)
# Depends on functions: Is_Executable Is_IntegerNr DescartarNoimprimibles
# Depends on software packages: expect telnet grep sed
# Notes:	- Filtra la paraula "password"
#		- Accepts "sudo", then if remote asks for password we send same again.
#		- És important que "sudo" vingui amb la instrució principal, perquè si un programet remot fa "sudo" per primera vegada hi ha problemes (tty/askpass)
# Expected parameters:
#	$1	Seconds to execute expect code. Specify -1 for infinite. Default is 10.
#	$2	Hostname/IP of telnet server to operate with.
#	$3	Telnet server's TCP port. Default is 23.
#	$4	(optional) login username to be typed to telnet service when asked.
#	$5	password to be typed to telnet service when asked.
#	$6	Command to be executed to telnet service after login
#	$7	String to be expected from telnet service before sending remote command.
#	$8	String to be expected from telnet service before assuming job done.
# Pendent:
#	- Permetre multilínia a "$Comanda" amb \n (símplement cal aquí un for amb \n i que la crida es faci amb "$(echo "..." ; echo "...")")
#	- Support specify a text file instead of single command.
{
	local TheTimeout="$1"
	local TheHost="$2"
	local ThePort="$3"
	local TheUser="$4"
	local ThePassword="$5"
	local TheCommand="$6"
	local ReadyPrompt="$7"
	local CompletedSignal="$8"
	local Log=''
	local Log2=''
	local CurLine=''
	local ReservedLine=''
	local LastStatus=0
	local StatusCode=0
	
	Execution_Username_Ready_Completed ()
	{
		local TheTimeout="$1"
		local TheHost="$2"
		local ThePort="$3"
		local TheUser="$4"
		local ThePassword="$5"
		local TheCommand="$6"
		local ReadyPrompt="$7"
		local CompletedSignal="$8"
		local TelnetCommand="telnet"
		if ! Is_IntegerNr "$TheTimeout" ; then TheTimeout=10 ; fi
		if ! Is_IntegerNr "$ThePort" ; then ThePort=23 ; fi
		# El 'timeout' ha de preveure la comunicació per internet O amb un equip saturat
		expect <<RFT_BeginEndExpect_Username_Ready_Completed
		set timeout $TheTimeout
		spawn -noecho env LANG=en $TelnetCommand $TheHost $ThePort
		expect -nocase "Login:"
		send "$TheUser\r"
		expect -nocase "Password:"
		send "$ThePassword\r"
		expect {
			-nocase -re ".*Login incorrect.*" {
				close
			}
			-nocase -re ".*sudo.*password.*" {
				send "$ThePassword\r"
				exp_continue
			}
			"$ReadyPrompt" {
				send "$TheCommand\r"
				expect "$CompletedSignal"
				send "quit\r"
			}
			timeout { close }
		}
RFT_BeginEndExpect_Username_Ready_Completed
	}
	Execution_NoUsername_Ready_Completed ()
	{
		local TheTimeout="$1"
		local TheHost="$2"
		local ThePort="$3"
		local TheUser="$4"
		local ThePassword="$5"
		local TheCommand="$6"
		local ReadyPrompt="$7"
		local CompletedSignal="$8"
		local TelnetCommand="telnet"
		if ! Is_IntegerNr "$TheTimeout" ; then TheTimeout=10 ; fi
		if ! Is_IntegerNr "$ThePort" ; then ThePort=23 ; fi
		# El 'timeout' ha de preveure la comunicació per internet O amb un equip saturat
		expect <<RFT_BeginEndExpect_NoUsername_Ready_Completed
		set timeout $TheTimeout
		spawn -noecho env LANG=en $TelnetCommand $TheHost $ThePort
		expect -nocase "Password:"
		send "$ThePassword\r"
		expect {
			-nocase -re ".*Login incorrect.*" {
				close
			}
			-nocase -re ".*sudo.*password.*" {
				send "$ThePassword\r"
				exp_continue
			}
			"$ReadyPrompt" {
				send "$TheCommand\r"
				expect "$CompletedSignal"
				send "quit\r"
			}
			timeout { close }
		}
RFT_BeginEndExpect_NoUsername_Ready_Completed
	}
	Execution_Username_NoReady_Completed ()
	{
		local TheTimeout="$1"
		local TheHost="$2"
		local ThePort="$3"
		local TheUser="$4"
		local ThePassword="$5"
		local TheCommand="$6"
		local ReadyPrompt="$7"
		local CompletedSignal="$8"
		local TelnetCommand="telnet"
		if ! Is_IntegerNr "$TheTimeout" ; then TheTimeout=10 ; fi
		if ! Is_IntegerNr "$ThePort" ; then ThePort=23 ; fi
		# El 'timeout' ha de preveure la comunicació per internet O amb un equip saturat
		expect <<RFT_BeginEndExpect_Username_NoReady_Completed
		set timeout $TheTimeout
		spawn -noecho env LANG=en $TelnetCommand $TheHost $ThePort
		expect -nocase "Login:"
		send "$TheUser\r"
		expect -nocase "Password:"
		send "$ThePassword\r"
		expect {
			-nocase -re ".*Login incorrect.*" {
				close
			}
			-nocase -re ".*sudo.*password.*" {
				send "$ThePassword\r"
				exp_continue
			}
			"> " {
				send "$TheCommand\r"
				expect "$CompletedSignal"
				send "quit\r"
			}
			-re "\# " {
				send "$TheCommand\r"
				expect "$CompletedSignal"
				send "quit\r"
			}
			timeout { close }
		}
RFT_BeginEndExpect_Username_NoReady_Completed
	}
	Execution_NoUsername_NoReady_Completed ()
	{
		local TheTimeout="$1"
		local TheHost="$2"
		local ThePort="$3"
		local TheUser="$4"
		local ThePassword="$5"
		local TheCommand="$6"
		local ReadyPrompt="$7"
		local CompletedSignal="$8"
		local TelnetCommand="telnet"
		if ! Is_IntegerNr "$TheTimeout" ; then TheTimeout=10 ; fi
		if ! Is_IntegerNr "$ThePort" ; then ThePort=23 ; fi
		# El 'timeout' ha de preveure la comunicació per internet O amb un equip saturat
		expect <<RFT_BeginEndExpect_NoUsername_NoReady_Completed
		set timeout $TheTimeout
		spawn -noecho env LANG=en $TelnetCommand $TheHost $ThePort
		expect -nocase "Password:"
		send "$ThePassword\r"
		expect {
			-nocase -re ".*Login incorrect.*" {
				close
			}
			-nocase -re ".*sudo.*password.*" {
				send "$ThePassword\r"
				exp_continue
			}
			"> " {
				send "$TheCommand\r"
				expect "$CompletedSignal"
				send "quit\r"
			}
			-re "\# " {
				send "$TheCommand\r"
				expect "$CompletedSignal"
				send "quit\r"
			}
			timeout { close }
		}
RFT_BeginEndExpect_NoUsername_NoReady_Completed
	}
	Execution_Username_Ready_NoCompleted ()
	{
		local TheTimeout="$1"
		local TheHost="$2"
		local ThePort="$3"
		local TheUser="$4"
		local ThePassword="$5"
		local TheCommand="$6"
		local ReadyPrompt="$7"
		local CompletedSignal="$8"
		local TelnetCommand="telnet"
		if ! Is_IntegerNr "$TheTimeout" ; then TheTimeout=10 ; fi
		if ! Is_IntegerNr "$ThePort" ; then ThePort=23 ; fi
		# El 'timeout' ha de preveure la comunicació per internet O amb un equip saturat
		expect <<RFT_BeginEndExpect_Username_Ready_NoCompleted
		set timeout $TheTimeout
		spawn -noecho env LANG=en $TelnetCommand $TheHost $ThePort
		expect -nocase "Login:"
		send "$TheUser\r"
		expect -nocase "Password:"
		send "$ThePassword\r"
		expect {
			-nocase -re ".*Login incorrect.*" {
				close
			}
			-nocase -re ".*sudo.*password.*" {
				send "$ThePassword\r"
				exp_continue
			}
			"> " {
				send "$TheCommand\r"
				expect "> "
				send "quit\r"
			}
			-re "\# " {
				send "$TheCommand\r"
				expect -re "\# "
				send "quit\r"
			}
			timeout { close }
		}
RFT_BeginEndExpect_Username_Ready_NoCompleted
	}
	Execution_NoUsername_Ready_NoCompleted ()
	{
		local TheTimeout="$1"
		local TheHost="$2"
		local ThePort="$3"
		local TheUser="$4"
		local ThePassword="$5"
		local TheCommand="$6"
		local ReadyPrompt="$7"
		local CompletedSignal="$8"
		local TelnetCommand="telnet"
		if ! Is_IntegerNr "$TheTimeout" ; then TheTimeout=10 ; fi
		if ! Is_IntegerNr "$ThePort" ; then ThePort=23 ; fi
		# El 'timeout' ha de preveure la comunicació per internet O amb un equip saturat
		expect <<RFT_BeginEndExpect_NoUsername_Ready_NoCompleted
		set timeout $TheTimeout
		spawn -noecho env LANG=en $TelnetCommand $TheHost $ThePort
		expect -nocase "Password:"
		send "$ThePassword\r"
		expect {
			-nocase -re ".*Login incorrect.*" {
				close
			}
			-nocase -re ".*sudo.*password.*" {
				send "$ThePassword\r"
				exp_continue
			}
			"$ReadyPrompt" {
				send "$TheCommand\r"
				expect "$ReadyPrompt"
				send "quit\r"
			}
			timeout { close }
		}
RFT_BeginEndExpect_NoUsername_Ready_NoCompleted
	}
	Execution_Username_NoReady_NoCompleted ()
	{
		local TheTimeout="$1"
		local TheHost="$2"
		local ThePort="$3"
		local TheUser="$4"
		local ThePassword="$5"
		local TheCommand="$6"
		local ReadyPrompt="$7"
		local CompletedSignal="$8"
		local TelnetCommand="telnet"
		if ! Is_IntegerNr "$TheTimeout" ; then TheTimeout=10 ; fi
		if ! Is_IntegerNr "$ThePort" ; then ThePort=23 ; fi
		# El 'timeout' ha de preveure la comunicació per internet O amb un equip saturat
		expect <<RFT_BeginEndExpect_Username_NoReady_NoCompleted
		set timeout $TheTimeout
		spawn -noecho env LANG=en $TelnetCommand $TheHost $ThePort
		expect -nocase "Login:"
		send "$TheUser\r"
		expect -nocase "Password:"
		send "$ThePassword\r"
		expect {
			-nocase -re ".*Login incorrect.*" {
				close
			}
			-nocase -re ".*sudo.*password.*" {
				send "$ThePassword\r"
				exp_continue
			}
			"$ReadyPrompt" {
				send "$TheCommand\r"
				expect "$ReadyPrompt"
				send "quit\r"
			}
			timeout { close }
		}
RFT_BeginEndExpect_Username_NoReady_NoCompleted
	}
	Execution_NoUsername_NoReady_NoCompleted ()
	{
		local TheTimeout="$1"
		local TheHost="$2"
		local ThePort="$3"
		local TheUser="$4"
		local ThePassword="$5"
		local TheCommand="$6"
		local ReadyPrompt="$7"
		local CompletedSignal="$8"
		local TelnetCommand="telnet"
		if ! Is_IntegerNr "$TheTimeout" ; then TheTimeout=10 ; fi
		if ! Is_IntegerNr "$ThePort" ; then ThePort=23 ; fi
		# El 'timeout' ha de preveure la comunicació per internet O amb un equip saturat
		expect <<RFT_BeginEndExpect_NoUsername_NoReady_NoCompleted
		set timeout $TheTimeout
		spawn -noecho env LANG=en $TelnetCommand $TheHost $ThePort
		expect -nocase "Password:"
		send "$ThePassword\r"
		expect {
			-nocase -re ".*Login incorrect.*" {
				close
			}
			-nocase -re ".*sudo.*password.*" {
				send "$ThePassword\r"
				exp_continue
			}
			"> " {
				send "$TheCommand\r"
				expect "> "
				send "quit\r"
			}
			-re "\# " {
				send "$TheCommand\r"
				expect -re "\# "
				send "quit\r"
			}
			timeout { close }
		}
RFT_BeginEndExpect_NoUsername_NoReady_NoCompleted
	}
	if ! Is_Executable expect ; then
		printf '%s\n' "${sERROR}E: expect utility not found.${fRESET}" 1>&2
		LastStatus=54 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if ! Is_Executable telnet ; then
		printf '%s\n' "${sERROR}E: telnet utility not found.${fRESET}" 1>&2
		LastStatus=54 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		if [ "$TheUser" != "" ] ; then
			if [ "$ReadyPrompt" != "" ] ; then
				if [ "$CompletedSignal" != "" ] ; then
					Log="$(Execution_Username_Ready_Completed "$@" 2>&1)"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				else
					Log="$(Execution_Username_Ready_NoCompleted "$@" 2>&1)"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
			else
				if [ "$CompletedSignal" != "" ] ; then
					Log="$(Execution_Username_NoReady_Completed "$@" 2>&1)"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				else
					Log="$(Execution_Username_NoReady_NoCompleted "$@" 2>&1)"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
			fi
		else
			if [ "$ReadyPrompt" != "" ] ; then
				if [ "$CompletedSignal" != "" ] ; then
					Log="$(Execution_NoUsername_Ready_Completed "$@" 2>&1)"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				else
					Log="$(Execution_NoUsername_Ready_NoCompleted "$@" 2>&1)"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
			else
				if [ "$CompletedSignal" != "" ] ; then
					Log="$(Execution_NoUsername_NoReady_Completed "$@" 2>&1)"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				else
					Log="$(Execution_NoUsername_NoReady_NoCompleted "$@" 2>&1)"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
			fi
		fi
		Log="$(DescartarNoimprimibles "$Log")"	# This first, to allow good text filtering.
		IFS="$(printf '\n\b')" ; for CurLine in $Log ; do unset IFS
			if [ "$CurLine" = " > " ] ; then
				# To exclude last " > " line
				ReservedLine=' > '
			else
				if [ "$CurLine" = " > $TheCommand" ] && [ "$TheCommand" != "" ] ; then
					Log2=''
				else
					if [ "$ReservedLine" != "" ] ; then
						if [ "$Log2" != "" ] ; then
							Log2="$(printf '%s\n' "$Log2" ; printf '%s\n' "$ReservedLine")"
						else
							Log2="$ReservedLine"
						fi
						ReservedLine=''
					fi
					if [ "$Log2" != "" ] ; then
						Log2="$(printf '%s\n' "$Log2" ; printf '%s\n' "$CurLine")"
					else
						Log2="$CurLine"
					fi
				fi
			fi
		done
		Log="$Log2"
		Log="$(printf '%s' "$Log" | grep -ive '^Trying ....*...$' -ive '^Connected to ....*\.$' -ive '^Escape character is ..*\.$' -ive '^Login: ' -ive "password" -ive '^Connection closed.*\.$' | sed -e "s|${ThePassword}|*****|g" | grep -ve "^ > $" -ve "^ > ${TheCommand}$")"
		if [ "$(echo TrimAndSingle $Log | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g' | grep "spawn id exp6 not open while executing")" != "" ] ; then
			# Descartem els errors del mateix expect
			Log="$(printf '%s' "$Log" | grep -v "spawn id exp6 not open" | grep -v "while executing" | grep -v '""')"
			LastStatus=110 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
		if [ "$Log" != "" ] ; then
			printf '%s' "$Log"
		fi
	fi
	if [ "$NozeroStatusToFile" != "" ] && [ ! -f "$NozeroStatusToFile" ] && [ $StatusCode -ne 0 ] ; then echo $StatusCode > "$NozeroStatusToFile" ; fi
	return $StatusCode
}

EmpaquetarContenido ()
# Crea un fichero comprimido (como nuevo; no agrega contenido)
# En lugar de especificar "*" en el contenido, es mejor especificar el directorio, y asi incluira elementos ocultos.
# EJEMPLOS:
#	EmpaquetarContenido "/tmp/paquete.tar.bz2" .
#	EmpaquetarContenido "/tmp/paquete.tar.bz2" file1 dir1 dir2
#	EmpaquetarContenido "/tmp/paquete.tar.7z" secret dir1
# Opcionalmente se puede especificar "--dereference" como primer parámetro, para tratar los enlaces como contenidos (almacenar los contenidos en lugar de los enlaces)
# EJEMPLOS:
#	EmpaquetarContenido --dereference "/tmp/paquete.tar.bz2" .
#	EmpaquetarContenido --dereference "/tmp/paquete.tar.bz2" file1 dir1 dir2
# Notas:
#	- Empaqueta con la ruta especificada, es decir, que no quedan igual estos dos ejemplos:
#	  EmpaquetarContenido "paquete.tar.bz2" /home/usuario/Documentos
#	  cd /home/usuario ; EmpaquetarContenido "paquete.tar.bz2" Documentos
#	- Con el formato tar.7z el segundo parámetro DEBE ser la contraseña de encriptado. Si está vacía ("") entonces no se encriptará.
# TO DO:
#	- Support .7z same as DesempaquetarContenido() does
#	- Support .tar.xz same as DesempaquetarContenido() does
#	- Support .zip same as DesempaquetarContenido() does
# Depends on functions: Is_Executable Dirname Is_IntegerNr
# Depends on software packages: sed, tar
# Recommends other software: cstream (>= 2.0), gzip (>= 1.0), bzip2 (>= 1.0), lzma | xz-utils, p7zip | p7zip-full
{
	local RutaPaquete="$1"
	local Contenido1="$2"
	local RutaDatos=''
	local EncryptionPassword=''
	local ExtensionPaquete=''
	local SubExtensionPaquete=''
	local Deference1="l"
	local Deference2=''
	local BandwidthLimit="$bwlimit"
	local CompressBinary=''
	local ComposedSpecs=''
	local LastStatus=0
	local StatusCode=0
	
	if [ ! -d "$DirTemp" ] ; then DirTemp="/run/shm" ; fi
	if [ ! -d "$DirTemp" ] ; then DirTemp="/tmp" ; fi
	if [ ! -d "$DirTempX" ] ; then DirTempX="/tmp" ; fi
	if [ "$1" = "--dereference" ] ; then
		Deference1="h"
		Deference2="--hard-dereference"
		shift
		RutaPaquete="$1"
		Contenido1="$2"
	fi
	if [ "$1" = "--no-recursion" ] ; then
		Deference2="--no-recursion"
		shift
		RutaPaquete="$1"
		Contenido1="$2"
	fi
	if ! Is_IntegerNr "$BandwidthLimit" ; then BandwidthLimit=0 ; fi
	if [ $BandwidthLimit -gt 0 ] && ! Is_Executable cstream ; then BandwidthLimit=0 ; fi
	ExtensionPaquete="$(printf '%s' "$RutaPaquete" | sed -e "s/\./\n/g" | tail -n 1)"
	SubExtensionPaquete="$(printf '%s' "$RutaPaquete" | sed -e "s/\./\n/g" | tail -n 2 | head -n 1)"
	if [ "$SubExtensionPaquete" = "tar" ] && [ "$ExtensionPaquete" != "tar" ] ; then ExtensionPaquete="${SubExtensionPaquete}.${ExtensionPaquete}" ; fi
	case "$ExtensionPaquete" in
		"tar" )
			if ! Is_Executable tar ; then
				printf '%s\n' "PROBLEMA: No se encuentra el programa de empaquetado 'tar'" 1>&2
				LastStatus=54 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
			;;
		"tar.gz" )
			if ! Is_Executable tar || ! Is_Executable gzip ; then
				printf '%s\n' "PROBLEMA: No se encuentra el programa de compresión 'gzip' o 'tar'" 1>&2
				LastStatus=54 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
			;;
		"tar.bz2" )
			if ! Is_Executable tar || ! Is_Executable bzip2 ; then
				printf '%s\n' "PROBLEMA: No se encuentra el programa de compresión 'bzip2' o 'tar'" 1>&2
				LastStatus=54 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
			;;
		"tar.lzma" )
			if ! Is_Executable tar || ! Is_Executable lzma ; then
				printf '%s\n' "PROBLEMA: No se encuentra el programa de compresión 'lzma' o 'tar'" 1>&2
				LastStatus=54 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
			;;
		"tar.7z" )
			# 7-zip does not store the owner/group of the file; tar needed.
			if ! Is_Executable tar ; then
				printf '%s\n' "PROBLEMA: No se encuentra el programa de empaquetado 'tar'" 1>&2
				LastStatus=54 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
			if ! Is_Executable 7z && ! Is_Executable 7zr && ! Is_Executable 7za ; then
				printf '%s\n' "PROBLEMA: No se encuentra el programa de compresión '7z'" 1>&2
				LastStatus=54 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
			EncryptionPassword="$2"
			if [ $# -gt 0 ] ; then shift ; fi
			Contenido1="$2"
			if [ "$Contenido1" = "" ] ; then
				printf '%s\n' "PROBLEMA: Contraseña de encriptación no especificada." 1>&2
				printf '%s\n' "          Con el formato tar.7z hay que especificar la contraseña de encriptado después de la ruta del paquete. Si se especifica vacía ${ParO}""${ParC} entonces no se encriptará" 1>&2
				LastStatus=79 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
			;;
		* )
			printf '%s\n' "PROBLEMA: Extension de fichero \"$ExtensionPaquete\" no reconocida." 1>&2
			printf '%s\n' "SOLUCION: Especifique un nombre de paquete con una de las extensiones:" 1>&2
			printf '%s\n' "          .tar" 1>&2
			printf '%s\n' "          .tar.gz" 1>&2
			printf '%s\n' "          .tar.bz2" 1>&2
			printf '%s\n' "          .tar.lzma" 1>&2
			printf '%s\n' "          .tar.7z" 1>&2
			LastStatus=47 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			;;
	esac
	if [ $StatusCode -eq 0 ] ; then
		if [ $# -gt 0 ] ; then shift ; fi
		rm -f "$RutaPaquete"
		mkdir -p "$(Dirname "$RutaPaquete")"
		case "$ExtensionPaquete" in
			"tar" )
				if [ $BandwidthLimit -le 0 ] ; then
					if [ "$Contenido1" = "*" ] ; then
						tar -c${Deference1}pf "$RutaPaquete" $Deference2 *
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					else
						tar -c${Deference1}pf "$RutaPaquete" $Deference2 "$@"
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					fi
				else
					if [ "$Contenido1" = "*" ] ; then
						tar -c${Deference1}pf - $Deference2 * 2>"${DirTemp}/$$.log" | cstream -t $((BandwidthLimit * 1024)) -c 1 > "$RutaPaquete"
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						if [ "$(cat "${DirTemp}/$$.log")" != "" ] ; then StatusCode=222 ; fi ; rm "${DirTemp}/$$.log"
					else
						tar -c${Deference1}pf - $Deference2 "$@" 2>"${DirTemp}/$$.log" | cstream -t $((BandwidthLimit * 1024)) -c 1 > "$RutaPaquete"
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						if [ "$(cat "${DirTemp}/$$.log")" != "" ] ; then StatusCode=222 ; fi ; rm "${DirTemp}/$$.log"
					fi
				fi
				;;
			"tar.gz" )
				ComposedSpecs="-cz${Deference1}pf"
				if Is_Executable pigz ; then ComposedSpecs="-I pigz -c${Deference1}pf" ; fi
				if [ $BandwidthLimit -le 0 ] ; then
					if [ "$Contenido1" = "*" ] ; then
						tar $ComposedSpecs "$RutaPaquete" $Deference2 *
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					else
						tar $ComposedSpecs "$RutaPaquete" $Deference2 "$@"
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					fi
				else
					if [ "$Contenido1" = "*" ] ; then
						tar $ComposedSpecs - $Deference2 * 2>"${DirTemp}/$$.log" | cstream -t $((BandwidthLimit * 1024)) -c 1 > "$RutaPaquete"
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						if [ "$(cat "${DirTemp}/$$.log")" != "" ] ; then StatusCode=222 ; fi ; rm "${DirTemp}/$$.log"  #"
					else
						tar $ComposedSpecs - $Deference2 "$@" 2>"${DirTemp}/$$.log" | cstream -t $((BandwidthLimit * 1024)) -c 1 > "$RutaPaquete"
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						if [ "$(cat "${DirTemp}/$$.log")" != "" ] ; then StatusCode=222 ; fi ; rm "${DirTemp}/$$.log"
					fi
				fi
				;;
			"tar.bz2" )
				ComposedSpecs="-cj${Deference1}pf"
				if Is_Executable pbzip2 ; then ComposedSpecs="-I \"pbzip2 --best\" -c${Deference1}pf" ; fi
				if [ $BandwidthLimit -le 0 ] ; then
					if [ "$Contenido1" = "*" ] ; then
						eval tar $ComposedSpecs "$RutaPaquete" $Deference2 *
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					else
						eval tar $ComposedSpecs "$RutaPaquete" $Deference2 "$@"
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					fi
				else
					if [ "$Contenido1" = "*" ] ; then
						eval tar $ComposedSpecs - $Deference2 * 2>"${DirTemp}/$$.log" | cstream -t $((BandwidthLimit * 1024)) -c 1 > "$RutaPaquete"
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						if [ "$(cat "${DirTemp}/$$.log")" != "" ] ; then StatusCode=222 ; fi ; rm "${DirTemp}/$$.log"  #"
					else
						eval tar $ComposedSpecs - $Deference2 "$@" 2>"${DirTemp}/$$.log" | cstream -t $((BandwidthLimit * 1024)) -c 1 > "$RutaPaquete"
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						if [ "$(cat "${DirTemp}/$$.log")" != "" ] ; then StatusCode=222 ; fi ; rm "${DirTemp}/$$.log"
					fi
				fi
				;;
			"tar.lzma" )
				if [ $BandwidthLimit -le 0 ] ; then
					if [ "$Contenido1" = "*" ] ; then
						tar -c${Deference1}pf - $Deference2 * | lzma -zfc -v -6 > "$RutaPaquete"
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					else
						tar -c${Deference1}pf - $Deference2 "$@" | lzma -zfc -v -6 > "$RutaPaquete"
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					fi
				else
					if [ "$Contenido1" = "*" ] ; then
						tar -c${Deference1}pf - $Deference2 * 2>"${DirTemp}/$$.log" | cstream -t $((BandwidthLimit * 1024)) -c 0 | lzma -zfc -v -6 > "$RutaPaquete"
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						if [ "$(cat "${DirTemp}/$$.log")" != "" ] ; then StatusCode=222 ; fi ; rm "${DirTemp}/$$.log"  #"
					else
						tar -c${Deference1}pf - $Deference2 "$@" 2>"${DirTemp}/$$.log" | cstream -t $((BandwidthLimit * 1024)) -c 0 | lzma -zfc -v -6 > "$RutaPaquete"
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						if [ "$(cat "${DirTemp}/$$.log")" != "" ] ; then StatusCode=222 ; fi ; rm "${DirTemp}/$$.log"
					fi
				fi
				;;
			"tar.7z" )
				# 7-zip does not store the owner/group of the file; tar needed.
				CompressBinary='7zr'
				if ! Is_Executable "$CompressBinary" ; then CompressBinary='7z' ; fi  #"
				if ! Is_Executable "$CompressBinary" ; then CompressBinary='7za' ; fi  #"
				if [ "$EncryptionPassword" = "" ] ; then
					if [ $BandwidthLimit -le 0 ] ; then
						if [ "$Contenido1" = "*" ] ; then
							tar -c${Deference1}pf - $Deference2 * | $CompressBinary a -si "$RutaPaquete" -m0=lzma2 -mx=9 -mmt=on
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						else
							tar -c${Deference1}pf - $Deference2 "$@" | $CompressBinary a -si "$RutaPaquete" -m0=lzma2 -mx=9 -mmt=on
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						fi
					else
						if [ "$Contenido1" = "*" ] ; then
							tar -c${Deference1}pf - $Deference2 * 2>"${DirTemp}/$$.log" | cstream -t $((BandwidthLimit * 1024)) -c 0 | $CompressBinary a -si "$RutaPaquete" -m0=lzma2 -mx=9 -mmt=on
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
							if [ "$(cat "${DirTemp}/$$.log")" != "" ] ; then StatusCode=222 ; fi ; rm "${DirTemp}/$$.log"	#"
						else
							tar -c${Deference1}pf - $Deference2 "$@" 2>"${DirTemp}/$$.log" | cstream -t $((BandwidthLimit * 1024)) -c 0 | $CompressBinary a -si "$RutaPaquete" -m0=lzma2 -mx=9 -mmt=on
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
							if [ "$(cat "${DirTemp}/$$.log")" != "" ] ; then StatusCode=222 ; fi ; rm "${DirTemp}/$$.log"	#"
						fi
					fi
				else
					if [ $BandwidthLimit -le 0 ] ; then
						if [ "$Contenido1" = "*" ] ; then
							tar -c${Deference1}pf - $Deference2 * | $CompressBinary a -si "$RutaPaquete" -m0=lzma2 -mx=9 -mmt=on "-p${EncryptionPassword}" -mhe=on
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						else
							tar -c${Deference1}pf - $Deference2 "$@" | $CompressBinary a -si "$RutaPaquete" -m0=lzma2 -mx=9 -mmt=on "-p${EncryptionPassword}" -mhe=on
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						fi
					else
						if [ "$Contenido1" = "*" ] ; then
							tar -c${Deference1}pf - $Deference2 * 2>"${DirTemp}/$$.log" | cstream -t $((BandwidthLimit * 1024)) -c 0 | $CompressBinary a -si "$RutaPaquete" -m0=lzma2 -mx=9 -mmt=on "-p${EncryptionPassword}" -mhe=on
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
							if [ "$(cat "${DirTemp}/$$.log")" != "" ] ; then StatusCode=222 ; fi ; rm "${DirTemp}/$$.log"	#"
						else
							tar -c${Deference1}pf - $Deference2 "$@" 2>"${DirTemp}/$$.log" | cstream -t $((BandwidthLimit * 1024)) -c 0 | $CompressBinary a -si "$RutaPaquete" -m0=lzma2 -mx=9 -mmt=on "-p${EncryptionPassword}" -mhe=on
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
							if [ "$(cat "${DirTemp}/$$.log")" != "" ] ; then StatusCode=222 ; fi ; rm "${DirTemp}/$$.log"	#"
						fi
					fi
				fi
				;;
		esac
	fi
	if [ "$NozeroStatusToFile" != "" ] && [ ! -f "$NozeroStatusToFile" ] && [ $StatusCode -ne 0 ] ; then echo $StatusCode > "$NozeroStatusToFile" ; fi
	return $StatusCode
}

DesempaquetarContenido ()
# Extrae un fichero comprimido en el directorio actual
# Depends on functions: Is_Executable Is_IntegerNr
# Depends on software packages: sed, tar
# Recommends other software: cstream (>= 2.0), gzip (>= 1.0), bzip2 (>= 1.0), lzma | xz-utils, p7zip | p7zip-full
{
	local RutaPaquete="$1"
	local DecryptionPassword="$2"
	local ExtensionPaquete=''
	local SubExtensionPaquete=''
	local BandwidthLimit="$bwlimit"
	local CompressBinary=''
	local ComposedSpecs=''
	local LastStatus=0
	local StatusCode=0
	
	if [ ! -d "$DirTemp" ] ; then DirTemp="/run/shm" ; fi
	if [ ! -d "$DirTemp" ] ; then DirTemp="/tmp" ; fi
	if [ ! -d "$DirTempX" ] ; then DirTempX="/tmp" ; fi
	if ! Is_IntegerNr "$BandwidthLimit" ; then BandwidthLimit=0 ; fi
	if [ $BandwidthLimit -gt 0 ] && ! Is_Executable cstream ; then BandwidthLimit=0 ; fi
	ExtensionPaquete="$(printf '%s' "$RutaPaquete" | sed -e "s/\./\n/g" | tail -n 1)"
	SubExtensionPaquete="$(printf '%s' "$RutaPaquete" | sed -e "s/\./\n/g" | tail -n 2 | head -n 1)"
	if [ "$SubExtensionPaquete" = "tar" ] && [ "$ExtensionPaquete" != "tar" ] ; then ExtensionPaquete="${SubExtensionPaquete}.${ExtensionPaquete}" ; fi
	case "$ExtensionPaquete" in
		"tar" )
			if ! Is_Executable tar ; then
				printf '%s\n' "${sERROR}E: No se encuentra el programa de empaquetado 'tar'${fRESET}" 1>&2
				LastStatus=54 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
			;;
		"tar.gz" )
			if ! Is_Executable tar || ! Is_Executable gzip ; then
				printf '%s\n' "${sERROR}E: No se encuentra el programa de compresión 'gzip' o 'tar'${fRESET}" 1>&2
				LastStatus=54 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
			;;
		"tar.bz2" )
			if ! Is_Executable tar || ! Is_Executable bzip2 ; then
				printf '%s\n' "${sERROR}E: No se encuentra el programa de compresión 'bzip2' o 'tar'${fRESET}" 1>&2
				LastStatus=54 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
			;;
		"tar.lzma" )
			if ! Is_Executable tar || ! Is_Executable lzma ; then
				printf '%s\n' "${sERROR}E: No se encuentra el programa de compresión 'lzma' o 'tar'${fRESET}" 1>&2
				LastStatus=54 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
			;;
		"tar.7z" )
			# 7-zip does not store the owner/group of the file; tar needed.
			if ! Is_Executable tar ; then
				printf '%s\n' "${sERROR}E: No se encuentra el programa de empaquetado 'tar'${fRESET}" 1>&2
				LastStatus=54 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
			if ! Is_Executable 7z  && ! Is_Executable 7zr && ! Is_Executable 7za ; then
				printf '%s\n' "${sERROR}E: No se encuentra el programa de compresión '7z' o 'tar'${fRESET}" 1>&2
				LastStatus=54 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
			;;
		"7z" )
			# 7-zip does not store the owner/group of the file.
			if ! Is_Executable 7z  && ! Is_Executable 7zr && ! Is_Executable 7za ; then
				printf '%s\n' "${sERROR}E: No se encuentra el programa de compresión '7z' o 'tar'${fRESET}" 1>&2
				LastStatus=54 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
			;;
		"tar.xz" )
			if ! Is_Executable tar || ! Is_Executable xz ; then
				printf '%s\n' "${sERROR}E: No se encuentra el programa de compresión 'xz' o 'tar'${fRESET}" 1>&2
				LastStatus=54 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
			;;
		"zip" )
			# 7-zip does not store the owner/group of the file; tar needed.
			if ! Is_Executable unzip ; then
				printf '%s\n' "${sERROR}E: No se encuentra el programa de compresión 'unzip'${fRESET}" 1>&2
				LastStatus=54 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
			;;
		* )
			printf '%s\n' "${sERROR}E: Extension de fichero \"$ExtensionPaquete\" no reconocida." 1>&2
			printf '%s\n' "SOLUCION: Especifique un nombre de paquete con una de las extensiones:" 1>&2
			printf '%s\n' "          .tar" 1>&2
			printf '%s\n' "          .tar.gz" 1>&2
			printf '%s\n' "          .tar.bz2" 1>&2
			printf '%s\n' "          .tar.lzma" 1>&2
			printf '%s\n' "          .tar.7z" 1>&2
			printf '%s\n' "          .7z" 1>&2
			printf '%s\n' "          .tar.xz" 1>&2
			printf '%s\n' "          .zip${fRESET}" 1>&2
			LastStatus=47 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			;;
	esac
	if [ $StatusCode -eq 0 ] ; then
		case "$ExtensionPaquete" in
			"tar" )
				if [ $BandwidthLimit -le 0 ] ; then
					tar --same-owner -xf "$RutaPaquete" ${MoreTarParameters}
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				else
					cat "$RutaPaquete" 2>"${DirTemp}/$$.log" | cstream -t $((BandwidthLimit * 1024)) -c 0 | tar --same-owner -x ${MoreTarParameters}
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					if [ "$(cat "${DirTemp}/$$.log")" != "" ] ; then StatusCode=222 ; fi ; rm "${DirTemp}/$$.log"  #"
				fi
				;;
			"tar.gz" )
				if [ $BandwidthLimit -le 0 ] ; then
					ComposedSpecs="--same-owner -xzf"
					if Is_Executable pigz ; then ComposedSpecs="-I pigz --same-owner -xf" ; fi
					# Syntax is right but some environment condition can result in error:
					# tar: You must specify one of the '-Acdtrux', '--delete' or '--test-label' options
					# Workaround is to execute in a subshell (sh)
					sh -c "tar $ComposedSpecs "$RutaPaquete" ${MoreTarParameters}"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				else
					ComposedSpecs="gzip"
					if Is_Executable pigz ; then ComposedSpecs="pigz" ; fi
					$ComposedSpecs -dcq "$RutaPaquete" 2>"${DirTemp}/$$.log" | cstream -t $((BandwidthLimit * 1024)) -c 0 | tar --same-owner -x ${MoreTarParameters}
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					if [ "$(cat "${DirTemp}/$$.log")" != "" ] ; then StatusCode=222 ; fi ; rm "${DirTemp}/$$.log"  #"
				fi
				;;
			"tar.bz2" )
				if [ $BandwidthLimit -le 0 ] ; then
					ComposedSpecs="--same-owner -xjf"
					if Is_Executable pbzip2 ; then ComposedSpecs="-I pbzip2 --same-owner -xf" ; fi
					# Syntax is right but some environment condition can result in error:
					# tar: You must specify one of the '-Acdtrux', '--delete' or '--test-label' options
					# Workaround is to execute in a subshell (sh)
					sh -c "tar $ComposedSpecs "$RutaPaquete" ${MoreTarParameters}"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				else
					ComposedSpecs="bzip2"
					if Is_Executable pbzip2 ; then ComposedSpecs="pbzip2" ; fi
					$ComposedSpecs -dcq "$RutaPaquete" 2>"${DirTemp}/$$.log" | cstream -t $((BandwidthLimit * 1024)) -c 0 | tar --same-owner -x ${MoreTarParameters}
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					if [ "$(cat "${DirTemp}/$$.log")" != "" ] ; then StatusCode=222 ; fi ; rm "${DirTemp}/$$.log"  #"
				fi
				;;
			"tar.lzma" )
				if [ $BandwidthLimit -le 0 ] ; then
					lzma -dcq "$RutaPaquete" | tar --same-owner -x ${MoreTarParameters}
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				else
					lzma -dcq "$RutaPaquete" 2>"${DirTemp}/$$.log" | cstream -t $((BandwidthLimit * 1024)) -c 0 | tar --same-owner -x ${MoreTarParameters}
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					if [ "$(cat "${DirTemp}/$$.log")" != "" ] ; then StatusCode=222 ; fi ; rm "${DirTemp}/$$.log"  #"
				fi
				;;
			"tar.7z" )
				CompressBinary='7zr'
				if ! Is_Executable "$CompressBinary" ; then CompressBinary='7z' ; fi  #"
				if ! Is_Executable "$CompressBinary" ; then CompressBinary='7za' ; fi  #"
				if [ "$DecryptionPassword" = "" ] ; then
					if [ $BandwidthLimit -le 0 ] ; then
						$CompressBinary x -so "$RutaPaquete" | tar --same-owner -x ${MoreTarParameters}
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					else
						$CompressBinary x -so "$RutaPaquete" | cstream -t $((BandwidthLimit * 1024)) -c 0 | tar --same-owner -x ${MoreTarParameters}
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					fi
				else
					if [ $BandwidthLimit -le 0 ] ; then
						$CompressBinary x -so "$RutaPaquete" "-p${DecryptionPassword}" | tar --same-owner -x ${MoreTarParameters}
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					else
						$CompressBinary x -so "$RutaPaquete" "-p${DecryptionPassword}" | cstream -t $((BandwidthLimit * 1024)) -c 0 | tar --same-owner -x ${MoreTarParameters}
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					fi
				fi
				;;
			"7z" )
				CompressBinary='7zr'
				if ! Is_Executable "$CompressBinary" ; then CompressBinary='7z' ; fi  #"
				if ! Is_Executable "$CompressBinary" ; then CompressBinary='7za' ; fi  #"
				if [ "$DecryptionPassword" = "" ] ; then
					$CompressBinary x "$RutaPaquete"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				else
					$CompressBinary x "$RutaPaquete" "-p${DecryptionPassword}"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
				;;
			"tar.xz" )
				if [ $BandwidthLimit -le 0 ] ; then
					xz --decompress --stdout "$RutaPaquete" | tar --same-owner -x ${MoreTarParameters}
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				else
					xz --decompress --stdout "$RutaPaquete" | cstream -t $((BandwidthLimit * 1024)) -c 0 | tar --same-owner -x ${MoreTarParameters}
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
				;;
			"zip" )
				if [ "$DecryptionPassword" = "" ] ; then
					unzip -q -o -X "$RutaPaquete"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				else
					unzip -q -o -X "$RutaPaquete" -P "$DecryptionPassword"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
				;;
		esac
	fi
	if [ "$NozeroStatusToFile" != "" ] && [ ! -f "$NozeroStatusToFile" ] && [ $StatusCode -ne 0 ] ; then echo $StatusCode > "$NozeroStatusToFile" ; fi
	return $StatusCode
}

ComparaVersions ()
# Sintaxis com a funció: $(ComparaVersions $Versio1 $Versio2)
# Descripció:	Torna (stdout) un d'aquests tres valors: "<" "=" ">" en funció de si la $Versio1 és inferior
#		, igual o superior a la $Versio2
# Notes:
#	- S'esperen valors de tipus "2.6.32" (números separats entre punts)
#	- Alternativa Debian: dpkg --compare-versions $Versio1 $Comparison $Versio2 && echo true
#	  on $Comparison pot ser:
#		lt le eq ne ge gt	(versió en blanc és anterior a les altres)
#		lt-nl le-nl ge-nl gt-nl	(versió en blanc és posterior a les altres)
#		< << <= = >= >> >
# Depends on functions: Is_IntegerNr
# Depends on software packages: grep, sed
{
	local Versio1="$1"
	local Versio2="$2"
	local InternalEnd="$3"
	local Versio1Numeros=''
	local Versio2Numeros=''
	local Numero1Actual=''
	local Numero2Actual=''
	local Newest=''
	local LastStatus=0
	local StatusCode=0
	local Valor=''
	
	if [ "$Versio1" != "" ] && [ "$Versio2" != "" ] ; then
		if [ "$InternalEnd" = "" ] ; then
			Versio1Numeros="$(printf '%s' "$Versio1" | tr -s '.' ' ' | tr -s '\t' ' ')"
			Versio2Numeros="$(printf '%s' "$Versio2" | tr -s '.' ' ' | tr -s '\t' ' ')"
			for Numero1Actual in $Versio1Numeros ; do
				if [ "$Valor" = "" ] ; then
					Numero2Actual="$(SomeWord () { printf '%s' $1; }; SomeWord $Versio2Numeros)"
					Numero1Actual="$(printf '%s' "$Numero1Actual" | tr -s '.' ' ' | sed -e 's|\([a-z]\)| \1 |g' | tr -s '+:~-' ' ' | tr -s ' ' '.')"
					Numero2Actual="$(printf '%s' "$Numero2Actual" | tr -s '.' ' ' | sed -e 's|\([a-z]\)| \1 |g' | tr -s '+:~-' ' ' | tr -s ' ' '.')"
					if [ "$Numero2Actual" != "" ] ; then
						if [ "$Numero1Actual" != "$Numero2Actual" ] ; then
							Valor="$(ComparaVersions $Numero1Actual $Numero2Actual end)"
						fi
						Versio2Numeros="$(RestaParaules () { shift ; echo TrimAndSingle $* | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g' ; }; RestaParaules $Versio2Numeros)"
					else
						# versio1 té més números que versio2
						if [ "$Numero1Actual" != "0" ] ; then
							Valor=">"
						fi
					fi
				fi
			done
			if [ "$Valor" = "" ] ; then
				Numero2Actual="$(SomeWord () { printf '%s' $1; }; SomeWord $Versio2Numeros)"
				if [ "$Numero2Actual" != "" ] && [ "$Numero2Actual" != "0" ] ; then
					# versio2 té més números que versio1
					Valor="<"
				else
					Valor="="
				fi
			fi
		else
			Versio1Numeros="$(printf '%s' "$Versio1" | tr -s '.' ' ' | sed -e 's|\([a-z]\)| \1 |g' | tr -s '+:~-' ' ' | tr -s '\t' ' ')"
			Versio2Numeros="$(printf '%s' "$Versio2" | tr -s '.' ' ' | sed -e 's|\([a-z]\)| \1 |g' | tr -s '+:~-' ' ' | tr -s '\t' ' ')"
			for Numero1Actual in $Versio1Numeros ; do
				if [ "$Valor" = "" ] ; then
					Numero2Actual="$(SomeWord () { printf '%s' $1; }; SomeWord $Versio2Numeros)"
					if [ "$Numero2Actual" != "" ] ; then
						if [ "$Numero1Actual" != "$Numero2Actual" ] ; then
							if Is_IntegerNr $Numero1Actual ; then
								if Is_IntegerNr $Numero2Actual ; then
									if [ $Numero1Actual -lt $Numero2Actual ] ; then Valor="<" ; fi
									if [ $Numero1Actual -gt $Numero2Actual ] ; then Valor=">" ; fi
								else
									Valor="<"
								fi
							else
								if Is_IntegerNr $Numero2Actual ; then
									Valor=">"
								else
									Newest="$(printf '%s\n' "$Numero1Actual $Numero2Actual" | tr -s ' ' '\n' | grep -ve '^$' | sort | tail -n 1)"
									if [ "$Newest" = "$Numero1Actual" ] ; then
										Valor=">"
									else
										Valor="<"
									fi
								fi
							fi
						fi
						Versio2Numeros="$(RestaParaules () { shift ; echo TrimAndSingle $* | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g' ; }; RestaParaules $Versio2Numeros)"
					else
						# versio1 té més números que versio2
						if [ "$Numero1Actual" != "0" ] ; then
							Valor=">"
						fi
					fi
				fi
			done
			if [ "$Valor" = "" ] ; then
				Numero2Actual="$(SomeWord () { printf '%s' $1; }; SomeWord $Versio2Numeros)"
				if [ "$Numero2Actual" != "" ] && [ "$Numero2Actual" != "0" ] ; then
					# versio2 té més números que versio1
					Valor="<"
				else
					Valor="="
				fi
			fi
		fi
	else
		if [ "${Versio1}${Versio2}" = "" ] ; then
			# No sabem cap versio
			Valor=""
		else
			# Versio coneguda sempre és més nova que un programa que no donava versió.
			if [ "$Versio1" = "" ] ; then
				Valor="<"
			else
				if [ "$Versio2" = "" ] ; then
					Valor=">"
				fi
			fi
		fi
	fi
	if [ "$Valor" != "" ] && [ $StatusCode -eq 0 ] ; then printf '%s\n' "$Valor" ; fi
	if [ "$NozeroStatusToFile" != "" ] && [ ! -f "$NozeroStatusToFile" ] && [ $StatusCode -ne 0 ] ; then echo $StatusCode > "$NozeroStatusToFile" ; fi
	return $StatusCode
}

MailNotify ()
# Syntax as a sentence: MailNotify "$From" "$To" "$Subject" "$BodyFile" "$SmtpLogin" "$SmtpService"
# Description: Send an e-mail
# Expected parameters:
#	$1	(from) Address and/or name of sender: "John Doe <john@example.net>" (session user&domain by default)
#		Optionally, a second recipient can be specified to set "Reply-To:" header
#		example: "website@www.example.net, John Doe <john@example.com>"
#	$2	(to) Addresses and/or mames of deliver destinations: "Malcolm <malcolm@example.net>, maria@example.com"
#	$3	(subject) Brief text line as title (recommended)
#	$4	(body file) Path to the text file with content for the letter. If is not a file, will be used as body text.
#	$5	(login for swaks) Username and password delimited with "|". Example: "john|1234"
#	$6	(service for swaks) Server and SMTP port delimited with ":". Example: smtp.example.net:587
# Notes:
#	- One individual sending will be done for each destination.
#	- If $SmtpLogin or $SmtpService aren't specified, the mail will be sent with *mail* command.
# TO DO:
#	- Polish the resulting Content-Type, charset, Format and Content-Transfer-Encoding headers.
#	  Them affect to mailman2, that inserts a garbage top-line to body.
#	- Get unspecified SmtpService from From's domain MX record.
#	- If STARTTLS fails, try SSL.
#	- Allow attachments
# Depends on functions: Is_Executable
# Depends on software packages: grep, sed, swaks|mailx
{
	local From="$1"
	local To="$2"
	local Subject="$3"
	local BodyFile="$4"
	local SmtpLogin="$5"
	local SmtpService="$6"
	local ReplyTo=''
	local SmtpUSR=''
	local SmtpPWD=''
	local SmtpSVR=''
	local SmtpPOR=''
	local BodyTemp=''
	local MailHelp=''
	local LastStatus=0
	local StatusCode=0
	
	if [ ! -d "$DirTemp" ] ; then DirTemp="/run/shm" ; fi
	if [ ! -d "$DirTemp" ] ; then DirTemp="/tmp" ; fi
	if [ ! -d "$DirTempX" ] ; then DirTempX="/tmp" ; fi
	if [ "$(printf '%s' "$From" | grep -e ';')" != "" ] ; then
		ReplyTo="$(printf '%s' "$From" | cut -f 2 -d ';')"
		From="$(printf '%s' "$From" | cut -f 1 -d ';')"
	else
		if [ "$(printf '%s' "$From" | grep -e ',')" != "" ] ; then
			ReplyTo="$(printf '%s' "$From" | cut -f 2 -d ',')"
			From="$(printf '%s' "$From" | cut -f 1 -d ',')"
		fi
	fi
	if [ "$To" = "" ] ; then
		printf '%s\n' 'E: Destination parameter cannot be empty (To)' 1>&2
		LastStatus=81 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ "$SmtpLogin" != "" ] && [ "$SmtpService" != "" ] ; then
		if [ "$From" = "" ] ; then
			printf '%s\n' 'E: Sender parameter cannot be empty for an SMTP sending (From)' 1>&2
			LastStatus=81 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
		if ! Is_Executable swaks ; then
			printf '%s\n' 'E: swaks SMTP tool not found.' 1>&2
			LastStatus=54 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
		SmtpUSR="$(printf '%s' "$SmtpLogin" | cut -f 1 -d '|')"
		SmtpPWD="$(printf '%s' "$SmtpLogin" | cut -f 2- -d '|')"
		SmtpSVR="$(printf '%s' "$SmtpService" | cut -f 1 -d ':')"
		SmtpPOR="$(printf '%s' "$SmtpService" | cut -f 2 -d ':')"
	else
		if ! Is_Executable mail ; then
			printf '%s\n' 'E: mail command not found (and no smtp data specified for swaks).' 1>&2
			LastStatus=54 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		BodyTemp="${DirTemp}/MailNotify.$$"
		cat /dev/null > "$BodyTemp" ; chmod u=rw,g=r,o= "$BodyTemp"
		if [ -f "$BodyFile" ] ; then
			cat "$BodyFile" > "$BodyTemp"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		else
			printf '%s\n' "$BodyFile" | sed -e 's|\\n|\n|g' > "$BodyTemp"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
		if [ "$(printf '%s' "$To" | grep -e ';')" != "" ] ; then
			To="$(printf '%s' "$To" | tr -s ';' '\n')"
		else
			To="$(printf '%s' "$To" | tr -s ',' '\n')"
		fi
		IFS="$(printf '\n\b')" ; for CurrentTo in $To ; do unset IFS
			CurrentTo="$(echo TrimAndSingle $CurrentTo | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g')"
			if [ "$SmtpLogin" != "" ] && [ "$SmtpService" != "" ] ; then
				if [ "$ReplyTo" != "" ] ; then
					if [ "$LogLevel" != "" ] && [ $LogLevel -ge 4 ] ; then
						printf '%s\n' "\$ swaks --silent 1 -f \"$From\" --body \"$BodyTemp\" -s \"$SmtpSVR\" -p $SmtpPOR -tlso -aos -au \"$SmtpUSR\" -ap \"*****\" --header 'Content-Type: text/plain; charset=UTF-8' --header 'Content-Transfer-Encoding: 8bit' --header \"Reply-To: $ReplyTo\" --h-Subject \"$Subject\" -t \"$CurrentTo\""
					fi
					swaks --silent 1 -f "$From" --body "$BodyTemp" -s "$SmtpSVR" -p $SmtpPOR -tlso -aos -au "$SmtpUSR" -ap "$SmtpPWD" --header 'Content-Type: text/plain; charset=UTF-8' --header 'Content-Transfer-Encoding: 8bit' --header "Reply-To: $ReplyTo" --h-Subject "$Subject" -t "$CurrentTo"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				else
					if [ "$LogLevel" != "" ] && [ $LogLevel -ge 4 ] ; then
						printf '%s\n' "\$ swaks --silent 1 -f \"$From\" --body \"$BodyTemp\" -s \"$SmtpSVR\" -p $SmtpPOR -tlso -aos -au \"$SmtpUSR\" -ap \"$SmtpPWD\" --header 'Content-Type: text/plain; charset=UTF-8' --header 'Content-Transfer-Encoding: 8bit' --h-Subject \"$Subject\" -t \"$CurrentTo\""
					fi
					swaks --silent 1 -f "$From" --body "$BodyTemp" -s "$SmtpSVR" -p $SmtpPOR -tlso -aos -au "$SmtpUSR" -ap "$SmtpPWD" --header 'Content-Type: text/plain; charset=UTF-8' --header 'Content-Transfer-Encoding: 8bit' --h-Subject "$Subject" -t "$CurrentTo"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
			else
				CurrentTo="$(printf '%s' "$CurrentTo" | sed -e 's| <|<|g' | sed -e 's|> |>|g' | sed -e 's|< |<|g' | sed -e 's| >|>|g')"	# mail uses spaces to separate recipients.
				CurrentTo="$(printf '%s' "$CurrentTo" | tr -s ' ' '_')"	# mail uses spaces to separate recipients.
				if [ "$From" = "" ] ; then
					mail -V > /dev/null 2>&1
					LastStatus=$?
					if [ $LastStatus -eq 0 ] ; then
						MailHelp="$(LANG=en mail --help 2>/dev/null)"
						if [ "$(printf '%s' "$MailHelp" | grep -ie '-a.*header' | grep -ie 'append')" != "" ] ; then
							# GNU Mailutils
							if [ "$(printf '%s' "$MailHelp" | grep -ie '-E.*command')" != "" ] ; then
								# Avoid X-Mailer header with agent fingerprint.
								if [ "$ReplyTo" != "" ] ; then
									if [ "$LogLevel" != "" ] && [ $LogLevel -ge 4 ] ; then
										printf '%s\n' "\$ cat \"\$BodyTemp\" | mail -E \"set noxmailer\" -a 'Content-Type: text/plain; charset=\"utf-8\"' -a 'Content-Transfer-Encoding: 8bit' -a \"Reply-To: $ReplyTo\" -s \"$Subject\" \"$CurrentTo\""
									fi
									cat "$BodyTemp" | mail -E "set noxmailer" -a 'Content-Type: text/plain; charset="utf-8"' -a 'Content-Transfer-Encoding: 8bit' -a "Reply-To: $ReplyTo" -s "$Subject" "$CurrentTo"
									LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
								else
									if [ "$LogLevel" != "" ] && [ $LogLevel -ge 4 ] ; then
										printf '%s\n' "\$ cat \"\$BodyTemp\" | mail -E \"set noxmailer\" -a 'Content-Type: text/plain; charset=\"utf-8\"' -a 'Content-Transfer-Encoding: 8bit' -s \"$Subject\" \"$CurrentTo\""
									fi
									cat "$BodyTemp" | mail -E "set noxmailer" -a 'Content-Type: text/plain; charset="utf-8"' -a 'Content-Transfer-Encoding: 8bit' -s "$Subject" "$CurrentTo"
									LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
								fi
							else
								if [ "$ReplyTo" != "" ] ; then
									if [ "$LogLevel" != "" ] && [ $LogLevel -ge 4 ] ; then
										printf '%s\n' "\$ cat \"\$BodyTemp\" | mail -a 'Content-Type: text/plain; charset=\"utf-8\"' -a 'Content-Transfer-Encoding: 8bit' -a \"Reply-To: $ReplyTo\" -s \"$Subject\" \"$CurrentTo\""
									fi
									cat "$BodyTemp" | mail -a 'Content-Type: text/plain; charset="utf-8"' -a 'Content-Transfer-Encoding: 8bit' -a "Reply-To: $ReplyTo" -s "$Subject" "$CurrentTo"
									LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
								else
									if [ "$LogLevel" != "" ] && [ $LogLevel -ge 4 ] ; then
										printf '%s\n' "\$ cat \"\$BodyTemp\" | mail -a 'Content-Type: text/plain; charset=\"utf-8\"' -a 'Content-Transfer-Encoding: 8bit' -s \"$Subject\" \"$CurrentTo\""
									fi
									cat "$BodyTemp" | mail -a 'Content-Type: text/plain; charset="utf-8"' -a 'Content-Transfer-Encoding: 8bit' -s "$Subject" "$CurrentTo"
									LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
								fi
							fi
						else
							# heirloom-mailx
							if [ "$LogLevel" != "" ] && [ $LogLevel -ge 4 ] ; then
								printf '%s\n' "\$ cat \"\$BodyTemp\" | mail -s \"$Subject\" \"$CurrentTo\""
							fi
							cat "$BodyTemp" | mail -s "$Subject" "$CurrentTo"
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						fi
					else
						# bsd-mailx
						if [ "$LogLevel" != "" ] && [ $LogLevel -ge 4 ] ; then
							printf '%s\n' "\$ cat \"\$BodyTemp\" | mail -s \"$Subject\" \"$CurrentTo\""
						fi
						cat "$BodyTemp" | mail -s "$Subject" "$CurrentTo"
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					fi
				else
					mail -V > /dev/null 2>&1
					LastStatus=$?
					if [ $LastStatus -eq 0 ] ; then
						MailHelp="$(LANG=en mail --help 2>/dev/null)"
						if [ "$(printf '%s' "$MailHelp" | grep -ie '-a.*header' | grep -ie 'append')" != "" ] ; then
							# GNU Mailutils
							if [ "$(printf '%s' "$MailHelp" | grep -ie '-E.*command')" != "" ] ; then
								# Avoid X-Mailer header with agent fingerprint.
								if [ "$ReplyTo" != "" ] ; then
									if [ "$LogLevel" != "" ] && [ $LogLevel -ge 4 ] ; then
										printf '%s\n' "\$ cat \"\$BodyTemp\" | mail -E \"set noxmailer\" -a \"From: $From\" -a 'Content-Type: text/plain; charset=\"utf-8\"' -a 'Content-Transfer-Encoding: 8bit' -a \"Reply-To: $ReplyTo\" -s \"$Subject\" \"$CurrentTo\""
									fi
									cat "$BodyTemp" | mail -E "set noxmailer" -a "From: $From" -a 'Content-Type: text/plain; charset="utf-8"' -a 'Content-Transfer-Encoding: 8bit' -a "Reply-To: $ReplyTo" -s "$Subject" "$CurrentTo"
									LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
								else
									if [ "$LogLevel" != "" ] && [ $LogLevel -ge 4 ] ; then
										printf '%s\n' "\$ cat \"\$BodyTemp\" | mail -E \"set noxmailer\" -a \"From: $From\" -a 'Content-Type: text/plain; charset=\"utf-8\"' -a 'Content-Transfer-Encoding: 8bit' -s \"$Subject\" \"$CurrentTo\""
									fi
									cat "$BodyTemp" | mail -E "set noxmailer" -a "From: $From" -a 'Content-Type: text/plain; charset="utf-8"' -a 'Content-Transfer-Encoding: 8bit' -s "$Subject" "$CurrentTo"
									LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
								fi
							else
								if [ "$ReplyTo" != "" ] ; then
									if [ "$LogLevel" != "" ] && [ $LogLevel -ge 4 ] ; then
										printf '%s\n' "\$ cat \"\$BodyTemp\" | mail -a \"From: $From\" -a 'Content-Type: text/plain; charset=\"utf-8\"' -a 'Content-Transfer-Encoding: 8bit' -a \"Reply-To: $ReplyTo\" -s \"$Subject\" \"$CurrentTo\""
									fi
									cat "$BodyTemp" | mail -a "From: $From" -a 'Content-Type: text/plain; charset="utf-8"' -a 'Content-Transfer-Encoding: 8bit' -a "Reply-To: $ReplyTo" -s "$Subject" "$CurrentTo"
									LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
								else
									if [ "$LogLevel" != "" ] && [ $LogLevel -ge 4 ] ; then
										printf '%s\n' "\$ cat \"\$BodyTemp\" | mail -a \"From: $From\" -a 'Content-Type: text/plain; charset=\"utf-8\"' -a 'Content-Transfer-Encoding: 8bit' -s \"$Subject\" \"$CurrentTo\""
									fi
									cat "$BodyTemp" | mail -a "From: $From" -a 'Content-Type: text/plain; charset="utf-8"' -a 'Content-Transfer-Encoding: 8bit' -s "$Subject" "$CurrentTo"
									LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
								fi
							fi
						else
							# heirloom-mailx
							if [ "$LogLevel" != "" ] && [ $LogLevel -ge 4 ] ; then
								printf '%s\n' "\$ cat \"\$BodyTemp\" | mail -r \"$From\" -s \"$Subject\" \"$CurrentTo\""
							fi
							cat "$BodyTemp" | mail -r "$From" -s "$Subject" "$CurrentTo"
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						fi
					else
						# bsd-mailx
						if [ "$LogLevel" != "" ] && [ $LogLevel -ge 4 ] ; then
							printf '%s\n' "\$ cat \"\$BodyTemp\" | mail -a \"From: $From\" -s \"$Subject\" \"$CurrentTo\""
						fi
						cat "$BodyTemp" | mail -a "From: $From" -s "$Subject" "$CurrentTo"
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					fi
				fi
			fi
		done
		if Is_Executable shred ; then
			shred -fuz -n 0 "$BodyTemp"
		else
			rm "$BodyTemp"
		fi
	fi
	if [ "$NozeroStatusToFile" != "" ] && [ ! -f "$NozeroStatusToFile" ] && [ $StatusCode -ne 0 ] ; then echo $StatusCode > "$NozeroStatusToFile" ; fi
	return $StatusCode
}

#[/gesvipen]


##### SPECIFIC FUNCTIONS TO THIS SCRIPT #####

#[gesvipen]
ServiceProfileFile ()
# Description: Returns full filepath of specified VPN-Server profile name.
{
	local ServiceName="$1"
	find /etc/openvpn/server 2>/dev/null | grep -e "/${ServiceName}.conf$"
}

ClientProfileFile ()
# Description: Returns full filepath of specified VPN-Client profile name.
{
	local AccessName="$1"
	find /etc/openvpn/client 2>/dev/null | grep -e "/${AccessName}.conf$" -e "/${AccessName}.ovpn$" | head -n 1
}

ServiceProfiles ()
# Description: Served list. Returns a list of valid VPN services names. One name per line.
# Returns a simple names list with VPN server registered profiles. One name per line.
{
	local Value=''
	Value="$(ls -1 /etc/openvpn/server/ 2>/dev/null | grep -e '\.conf$' | sed -e 's|\.conf$||')"
	if [ "$Value" != "" ] ; then printf '%s\n' "$Value" ; fi
}

ClientProfiles ()
# Client accesses list. Simple list with profiles names, one per line.
{
	local ProfilesFiles=''
	local CurProfile_File=''
	
	if [ -d /etc/openvpn/client ] ; then
		ProfilesFiles="$(ls -1 /etc/openvpn/client | grep -ie '\.conf$' -ie '\.ovpn$')"
		IFS="$(printf '\n\b')" ; for CurProfile_File in $ProfilesFiles ; do unset IFS
			printf '%s\n' "$CurProfile_File" | sed -e 's|\.conf$||gi' | sed -e 's|\.ovpn$||gi'
		done
	fi
}

Event_ClientUp ()
{
	local LastStatus=0
	local StatusCode=0
	
	if [ "$VpnDevice" = "" ] ; then
		printf '%s\n' "${sERROR}E: VPN device name not detected.${fRESET}" 1>&2
		LastStatus=101 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		if [ $MakeGateway -eq 1 ] ; then
			if [ "$VpnGateway" = "" ] ; then
				printf '%s\n' "${sERROR}E: VPN gateway IP not detected.${fRESET}" 1>&2
				LastStatus=101 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
			if [ $StatusCode -eq 0 ] ; then
				GatewayRegexp1="$(printf '%s\n' "$VpnGateway" | sed -e 's|\.|\\.|g')"
				if [ "$("$NetstatExecutable" -rn | sed -e 's|\t| |g' | tr -s ' ' | grep -e "^0\.0\.0\.0 ${GatewayRegexp1} .* ${VpnDevice}$")" = "" ] ; then
					# Add (not replace) a new gateway to response to traffic from remote WAN
#					if [ $Verbosity -gt 1 ] ; then
#						printf '%s\n' "$(date '+%F %T') Adding $VpnDevice a new gateway ${ParO}${VpnGateway}${ParC} to allow traffic from remote WAN" | tee -a "$LogFile"
#					fi
					LogProgram 3 "Adding $VpnDevice a new gateway ${ParO}${VpnGateway}${ParC} to allow traffic from remote WAN"
					"$RouteExecutable" add default gw $VpnGateway dev $VpnDevice
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
			fi
			if [ $StatusCode -eq 0 ] && [ "$trusted_ip" != "" ] && [ "$route_net_gateway" != "" ] ; then
				IpRegexp1="$(printf '%s\n' "$trusted_ip" | sed -e 's|\.|\\.|g')"
				GatewayRegexp2="$(printf '%s\n' "$route_net_gateway" | sed -e 's|\.|\\.|g')"
				OldList="$("$NetstatExecutable" -rn | sed -e 's|\t| |g' | tr -s ' ' | grep -e "^0\.0\.0\.0 ${GatewayRegexp2} " | grep -ve " ${VpnDevice}$")"
				DedicatedResult=0
				if [ "$OldList" != "" ] && [ "$("$NetstatExecutable" -rn | sed -e 's|\t| |g' | tr -s ' ' | grep -e "^${IpRegexp1} ")" = "" ] ; then
					# Create a dedicated route to maintain connection with VPN peer, before removing default gateway
					OldDev="$(printf '%s\n' "$OldList" | head -n 1 | tr -s ' ' '\n' | tail -n 1)"
#					if [ $Verbosity -gt 1 ] ; then
#						printf '%s\n' "Adding a dedicated route through $OldDev to maintain connection with VPN peer $trusted_ip" | tee -a "$LogFile"
#					fi
					LogProgram 3 "Adding a dedicated route through $OldDev to maintain connection with VPN peer $trusted_ip"
					"$RouteExecutable" add $trusted_ip gw $route_net_gateway dev $OldDev
					DedicatedResult=$?
				fi
				if [ $DedicatedResult -eq 0 ] ; then
					# Remove previous local gateways, to programs access WAN: only through VPN
					IFS="$(printf "\n\b")" ; for CurRoute in $OldList ; do unset IFS
						CurDev="$(printf '%s\n' "$CurRoute" | tr -s ' ' '\n' | tail -n 1)"
#						if [ $Verbosity -gt 1 ] ; then
#							printf '%s\n' "$(date '+%F %T') Removing previous default route through $OldDev, to programs use only $VpnDevice to reach WAN" | tee -a "$LogFile"
#						fi
						LogProgram 3 "Removing previous default route through $OldDev, to programs use only $VpnDevice to reach WAN"
						"$RouteExecutable" del default gw $route_net_gateway dev $CurDev
					done
				fi
			fi
		fi
		if [ $StatusCode -eq 0 ] && [ "$DurruterExecutable" != "" ] ; then
			LogProgram 4 "\$ \"$DurruterExecutable\" start \"$VpnDevice\""
#			if [ $Verbosity -gt 2 ] ; then
			if [ $LogLevel -ge 4 ] ; then
				# Problem: as of durruter 2.2.6 cannot filter device to enable NAT entries
#				"$DurruterExecutable" nat enable all
#				"$DurruterExecutable" dnat enable "$VpnDevice" all all all
#				echo "$DurruterExecutable" start "$VpnDevice" | tee -a "$LogFile"
#				"$DurruterExecutable" start "$VpnDevice" | tee -a "$LogFile"
				"$DurruterExecutable" start "$VpnDevice" 2>&1 | tee -a "$MainLog"
			else
				# Problem: as of durruter 2.2.6 cannot filter device to enable NAT entries
#				"$DurruterExecutable" nat enable all >/dev/null
#				"$DurruterExecutable" dnat enable "$VpnDevice" all all all >/dev/null
				"$DurruterExecutable" start "$VpnDevice" >/dev/null
			fi
		fi
	fi
	return $StatusCode
}

Event_ClientDown ()
{
	local LastStatus=0
	local StatusCode=0
	
#	if [ "$VpnGateway" != "" ] && [ "$VpnDevice" != "" ] ; then
	if [ "$VpnDevice" != "" ] ; then
		if [ "$DurruterExecutable" != "" ] ; then
			LogProgram 4 "\$ \"$DurruterExecutable\" dnat disable \"$VpnDevice\" all all all"
#			if [ $Verbosity -gt 2 ] ; then
			if [ $LogLevel -ge 4 ] ; then
#				echo "$DurruterExecutable" dnat disable "$VpnDevice" all all all | tee -a "$LogFile"
#				"$DurruterExecutable" dnat disable "$VpnDevice" all all all | tee -a "$LogFile"
				"$DurruterExecutable" dnat disable "$VpnDevice" all all all 2>&1 | tee -a "$MainLog"
			else
				"$DurruterExecutable" dnat disable "$VpnDevice" all all all >/dev/null
			fi
		fi
		if [ $MakeGateway -eq 1 ] ; then
			OldGw=""
			PeerIp="$trusted_ip"
			if [ "$PeerIp" = "" ] ; then PeerIp="$untrusted_ip" ; fi
			PeerRegexp1="$(printf '%s\n' "$PeerIp" | sed -e 's|\.|\\.|g')"
			RestoreResult=0
			if [ "$PeerIp" != "" ] ; then
				# Dedicated gateway
				OldGw="$("$NetstatExecutable" -rn | sed -e 's|\t| |g' | tr -s ' ' | grep -e "^${PeerRegexp1} " | grep -ve " ${VpnDevice}$" | head -n 1)"
				if [ "$OldGw" = "" ] ; then
					# Shared gateway
					GatewayRegexp1="$(printf '%s\n' "$route_net_gateway" | sed -e 's|\.|\\.|g')"
					OldGw="$("$NetstatExecutable" -rn | sed -e 's|\t| |g' | tr -s ' ' | grep -e "^0\.0\.0\.0 ${GatewayRegexp1} " | grep -ve " ${VpnDevice}$" | head -n 1)"
				fi
				if [ "$OldGw" = "" ] ; then
					# Any other gateway
					GatewayRegexp1="$(printf '%s\n' "$route_net_gateway" | sed -e 's|\.|\\.|g')"
					OldGw="$("$NetstatExecutable" -rn | sed -e 's|\t| |g' | tr -s ' ' | grep -e "^0\.0\.0\.0 " | grep -ve " ${VpnDevice}$" | head -n 1)"
				fi
				if [ "$OldGw" != "" ] ; then
					OldDev="$(printf '%s\n' "$OldGw" | tr -s ' ' '\n' | tail -n 1)"
					OldGw="$(printf '%s\n' "$OldGw" | cut -f 2 -d ' ')"
				fi
			fi
			OldGwRegexp1="$(printf '%s\n' "$OldGw" | sed -e 's|\.|\\.|g')"
			if [ "$OldGw" != "" ] && [ "$OldDev" != "" ] ; then
				if [ "$("$NetstatExecutable" -rn | sed -e 's|\t| |g' | tr -s ' ' | grep -e "^0\.0\.0\.0 ${OldGwRegexp1} .* ${OldDev}$")" = "" ] ; then
#					if [ $Verbosity -gt 1 ] ; then
#						printf '%s\n' "$(date '+%F %T') Restoring $OldDev previous gateway ${ParO}${OldGw}${ParC} to allow default traffic with local WAN" | tee -a "$LogFile"
#					fi
					LogProgram 3 "Restoring $OldDev previous gateway ${ParO}${OldGw}${ParC} to allow default traffic with local WAN"
					"$RouteExecutable" add default gw $OldGw dev $OldDev
					RestoreResult=$?
				fi
			else
#				if [ $Verbosity -gt 0 ] ; then
#					printf '%s\n' "$(date '+%F %T') W: Previous gateway not found when trying to restore it." | tee -a "$LogFile" 1>&2
#				fi
				LogProgram 2 "W: Previous gateway not found when trying to restore it."
				RestoreResult=104
			fi
			GatewayRoutes="$("$NetstatExecutable" -rn | sed -e 's|\t| |g' | tr -s ' ' | grep -e "^0\.0\.0\.0 .* ${VpnDevice}$")"
			if [ "$GatewayRoutes" != "" ] ; then
				# Remove all default routes for this device
				IFS="$(printf "\n\b")" ; for CurRoute in $GatewayRoutes ; do unset IFS
					CurGateway="$(printf '%s\n' "$CurRoute" | cut -f 2 -d ' ')"
#					if [ $Verbosity -gt 1 ] ; then
#						printf '%s\n' "$(date '+%F %T') Removing $VpnDevice old gateway ${ParO}${VpnGateway}${ParC}" | tee -a "$LogFile"
#					fi
					LogProgram 3 "Removing $VpnDevice old gateway ${ParO}${VpnGateway}${ParC}"
					"$RouteExecutable" del default gw $CurGateway dev $VpnDevice
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				done
			fi
			if [ $RestoreResult -eq 0 ] && [ "$PeerIp" != "" ] ; then
				DedicatedList="$("$NetstatExecutable" -rn | sed -e 's|\t| |g' | tr -s ' ' | grep -e "^${PeerRegexp1} ")"
				IFS="$(printf "\n\b")" ; for CurRoute in $DedicatedList ; do unset IFS
					CurGateway="$(printf '%s\n' "$CurRoute" | cut -f 2 -d ' ')"
					CurDev="$(printf '%s\n' "$CurRoute" | tr -s ' ' '\n' | tail -n 1)"
#					if [ $Verbosity -gt 1 ] ; then
#						printf '%s\n' "$(date '+%F %T') Removing $CurDev old dedicated route ${ParO}${PeerIp} to ${CurGateway}${ParC}" | tee -a "$LogFile"
#					fi
					LogProgram 3 "Removing $CurDev old dedicated route ${ParO}${PeerIp} to ${CurGateway}${ParC}"
					"$RouteExecutable" del $PeerIp gw $CurGateway dev $CurDev
				done
			fi
		fi
	else
#		printf '%s\n' "${sERROR}E: VPN gateway IP or device name not detected.${fRESET}" 1>&2
		printf '%s\n' "${sERROR}E: VPN device name not detected.${fRESET}" 1>&2
		LastStatus=101 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	return $StatusCode
}

Event_ServerUp ()
{
	local LastStatus=0
	local StatusCode=0
	
	if [ $StatusCode -eq 0 ] && [ "$DurruterExecutable" != "" ] ; then
		LogProgram 4 "\$ \"$DurruterExecutable\" dnat enable \"$VpnDevice\" all all all"
#		if [ $Verbosity -gt 2 ] ; then
		if [ $LogLevel -ge 4 ] ; then
#			echo "$DurruterExecutable" dnat enable "$VpnDevice" all all all | tee -a "$LogFile"
#			"$DurruterExecutable" dnat enable "$VpnDevice" all all all | tee -a "$LogFile"
			"$DurruterExecutable" dnat enable "$VpnDevice" all all all 2>&1 | tee -a "$MainLog"
		else
			"$DurruterExecutable" dnat enable "$VpnDevice" all all all >/dev/null
		fi
	fi
	return $StatusCode
}

Event_ServerDown ()
{
	local LastStatus=0
	local StatusCode=0
	
	if [ $StatusCode -eq 0 ] && [ "$DurruterExecutable" != "" ] ; then
		LogProgram 4 "\$ \"$DurruterExecutable\" dnat disable \"$VpnDevice\" all all all"
#		if [ $Verbosity -gt 2 ] ; then
		if [ $LogLevel -ge 4 ] ; then
#			echo "$DurruterExecutable" dnat disable "$VpnDevice" all all all | tee -a "$LogFile"
#			"$DurruterExecutable" dnat disable "$VpnDevice" all all all | tee -a "$LogFile"
			"$DurruterExecutable" dnat disable "$VpnDevice" all all all 2>&1 | tee -a "$MainLog"
		else
			"$DurruterExecutable" dnat disable "$VpnDevice" all all all >/dev/null
		fi
	fi
	return $StatusCode
}

Event_GatewayUpDown ()
# Additional actions for VPN up/down: WAN gateway
{
	local LastStatus=0
	local StatusCode=0

	# This script is called: On server when OpenVPN profile starts; On Client when per connection is stablished.
	# The only explicit parameter expected is first one. Expected values:
	#	BridgeName	Bridge to attach TAP device to (unexisting is created).
	#	BridgeName+	Bridge to attach TAP device to + make TAP device default gateway (redirect WAN traffic to TAP device)
	#	.		(literal dot) Not bridging and not forwarding anything.
	#	+		Only make TUN/TAP device default gateway (redirect WAN traffic to this virtual NIC) ; No bridge.
	#	@		Only forward DNAT/NAT traffic between WAN IP and VPN client IP.
	# Additional parameters from OpenVPN for TUN device: tun_dev tun_mtu link_mtu ifconfig_local_ip ifconfig_remote_ip [ init | restart ]
	# Additional parameters from OpenVPN for TAP device: tap_dev tap_mtu link_mtu ifconfig_local_ip ifconfig_netmask [ init | restart ]
	
	# TO DO:
	#	- https://openvpn.net/community-resources/ethernet-bridging/
	#	- Allow syntax for "gw-updown" to only perform this actions (gateway for only selected subnet):
	#		SelectedSubnet=172.16.74.100/32
	#		TableName=myfriend
	#		# Register table to /etc/iproute2/rt_tables
	#		ip rule add from $SelectedSubnet table $TableName
	#		ip route add default via $VpnDevice_IP dev $VpnDevice table $TableName
	#	  Then support "down" actions too
	#	  See [lxc50.bell]/etc/openvpn/scripts/subnet-gateway.sh
	
	ExecutionParms="$*"
	
	# Custom from profile.conf call
	AskedBridge="$1"
	if [ $# -gt 0 ] ; then shift ; fi
	
	# Added by OpenVPN
	VpnDevice="$1"
	if [ $# -gt 0 ] ; then shift ; fi
	Dev_MTU="$1"
	if [ $# -gt 0 ] ; then shift ; fi
	Link_MTU="$1"
	if [ $# -gt 0 ] ; then shift ; fi
	VpnDevice_IP="$1"
	if [ $# -gt 0 ] ; then shift ; fi
	OpenVPN_Parameter5="$1"
	if [ $# -gt 0 ] ; then shift ; fi
	CallContext="$1"
	if [ $# -gt 0 ] ; then shift ; fi
	
	# Taken from environment variables
	# script_type: [ up | down ]
	# untrusted_ip: Actual IP address of connecting peer which has not been authenticated yet
	# trusted_ip: Actual IP address of connecting peer which has been authenticated


	# Enhance detection with environment variables
	if [ "$dev" != "" ] ; then VpnDevice="$dev" ; fi
	if [ "$tun_mtu" != "" ] ; then Dev_MTU="$tun_mtu" ; fi
	if [ "$tap_mtu" != "" ] ; then Dev_MTU="$tap_mtu" ; fi
	if [ "$link_mtu" != "" ] ; then Link_MTU="$link_mtu" ; fi
	if [ "$ifconfig_local" != "" ] ; then VpnDevice_IP="$ifconfig_local" ; fi
	if [ "$script_context" != "" ] ; then CallContext="$script_context" ; fi
	Role="client"
	if [ "$(env | grep -e '^proto.*=.*server$')" != "" ] ; then Role="server" ; fi
	if [ "$dev_type" = "" ] ; then
		dev_type="tap"
		if [ "$tun_mtu" != "" ] ; then dev_type="tun" ; fi
	fi
	if [ "$dev_type" = "tap" ] ; then
		ifconfig_netmask="$OpenVPN_Parameter5"
	else
		# At least with a client tun "up" ifconfig_remote_ip is not an IP to reach server, but only to reach WAN/internet
		VpnGateway="$OpenVPN_Parameter5"
	fi
	if [ "$route_vpn_gateway" != "" ] ; then
		VpnGateway="$route_vpn_gateway"
	else
		if [ "$ifconfig_remote" != "" ] ; then
			VpnGateway="$ifconfig_remote"
		fi
	fi
#	if Is_IntegerNr $verb ; then
#		if [ $verb -gt 2 ] ; then
#			echo Verbosity=$verb
#		fi
#		Verbosity=$verb
#	else
#		Verbosity=0
#	fi
	MakeGateway=0
	if [ "$(printf '%s' "$AskedBridge" | grep -e '+$')" != "" ] ; then
		MakeGateway=1
		AskedBridge="$(printf '%s' "$AskedBridge" | sed -e 's|+$||')"
	fi
#	ConfigFile="$(printf '%s' "$0" | sed -e 's|\.sh$||gi').conf"
#	LogFile="/var/log/openvpn/${ProgramName}.log"
#	if [ $Verbosity -eq 0 ] ; then LogFile=/dev/null ; fi
#	printf '%s\n' "$(date '+%F %T') $0 $ExecutionParms" >> "$LogFile"
	
	RouteExecutable="$(command -v route 2>/dev/null)"
	if [ ! -x "$RouteExecutable" ] ; then RouteExecutable="/sbin/route" ; fi
	if [ ! -x "$RouteExecutable" ] ; then RouteExecutable="/bin/route" ; fi
	if [ ! -x "$RouteExecutable" ] ; then RouteExecutable="/usr/sbin/route" ; fi
	if [ ! -x "$RouteExecutable" ] ; then RouteExecutable="/usr/bin/route" ; fi
	if [ ! -x "$RouteExecutable" ] ; then
		printf '%s\n' "${sERROR}E: route command not found ${ParO}net-tools${ParC}.${fRESET}" 1>&2
		exit 52
	fi
	NetstatExecutable="$(command -v netstat 2>/dev/null)"
	if [ ! -x "$NetstatExecutable" ] ; then NetstatExecutable="/sbin/netstat" ; fi
	if [ ! -x "$NetstatExecutable" ] ; then NetstatExecutable="/bin/netstat" ; fi
	if [ ! -x "$NetstatExecutable" ] ; then NetstatExecutable="/usr/sbin/netstat" ; fi
	if [ ! -x "$NetstatExecutable" ] ; then NetstatExecutable="/usr/bin/netstat" ; fi
	if [ ! -x "$NetstatExecutable" ] ; then
		printf '%s\n' "${sERROR}E: netstat command not found ${ParO}net-tools${ParC}.${fRESET}" 1>&2
		exit 52
	fi
	DurruterExecutable="$(command -v durruter 2>/dev/null)"
	if [ ! -x "$DurruterExecutable" ] ; then DurruterExecutable="/sbin/durruter" ; fi
	if [ ! -x "$DurruterExecutable" ] ; then DurruterExecutable="/bin/durruter" ; fi
	if [ ! -x "$DurruterExecutable" ] ; then DurruterExecutable="/usr/sbin/durruter" ; fi
	if [ ! -x "$DurruterExecutable" ] ; then DurruterExecutable="/usr/bin/durruter" ; fi
	if [ ! -x "$DurruterExecutable" ] ; then DurruterExecutable="" ; fi
	
#	if [ ! -f "$ConfigFile" ] ; then
#		printf '%s\n' "# Notes:" >> "$ConfigFile"
#		printf '%s\n' "#	- Bridges specified here are overriden by specified at profile.conf call" >> "$ConfigFile"
#		printf '%s\n' "#	- Blank values override wider scope values. Example: tapdev_vpn0_bridge= means don't bridge although tapdev_defaultbridge is specified." >> "$ConfigFile"
#		printf '%s\n' "" >> "$ConfigFile"
#		printf '%s\n' "# If TAP devices should be attached to specified bridge by default" >> "$ConfigFile"
#		printf '%s\n' "tapdev_defaultbridge=" >> "$ConfigFile"
#		printf '%s\n' "" >> "$ConfigFile"
#		printf '%s\n' "# If named TAP device should be attached to specified bridge" >> "$ConfigFile"
#		printf '%s\n' "#tapdev_vpn0_bridge=" >> "$ConfigFile"
#		printf '%s\n' "#${ParO}any variable-dislike character parses to underscore${ParC} Example for vpn-tun0" >> "$ConfigFile"
#		printf '%s\n' "#tapdev_vpn_tun0_bridge=" >> "$ConfigFile"
#		printf '%s\n' "" >> "$ConfigFile"
#		printf '%s\n' "# If TAP device with named local IP should be attached to specified bridge ${ParO}overrides NIC name${ParC}" >> "$ConfigFile"
#		printf '%s\n' "#tapip_192_168_1_250_bridge=" >> "$ConfigFile"
#	fi
	
	BridgeVpn=''
	if [ "$AskedBridge" != "@" ] && [ "$AskedBridge" != "+" ] && [ "$AskedBridge" != "." ] && [ "$AskedBridge" != "" ] ; then BridgeVpn="$AskedBridge" ; fi
	if [ "$BridgeVpn" = "" ] && [ "$VpnDevice" != "" ] ; then
		if [ "$VpnDevice_IP" != "" ] && [ "$(cat "$CurrentConfigFile" | grep -e "^tapip_${VarNameIP}_bridge=")" != "" ] ; then
			VarNameIP="$(printf '%s' "$VpnDevice_IP" | tr -s '.:' '_')"
			BridgeVpn="$(cat "$CurrentConfigFile" | grep -e "^tapip_${VarNameIP}_bridge=" | cut -f 2- -d '=')"
		else
			VarNameDev="$(printf '%s' "$VpnDevice" | tr -s '.:-@#' '_')"
			if [ "$(cat "$CurrentConfigFile" | grep -e "^tapdev_${VarNameDev}_bridge=")" != "" ] ; then
				BridgeVpn="$(cat "$CurrentConfigFile" | grep -e "^tapdev_${VarNameDev}_bridge=" | cut -f 2- -d '=')"
			else
				BridgeVpn="$(cat "$CurrentConfigFile" | grep -e "^tapdev_defaultbridge=" | cut -f 2- -d '=')"
			fi
		fi
	fi
	if [ "$script_type" = "up" ] ; then
		if [ "$BridgeVpn" != "" ] ; then
			if [ "$dev_type" != "tun" ] ; then
#				printf '%s\n' "Attaching $dev_type device $VpnDevice to bridge $BridgeVpn" | tee -a "$LogFile"
				LogProgram 3 "Attaching $dev_type device $VpnDevice to bridge ${BridgeVpn} . Can't log execution."
#				AttachToNetBrigde $VpnDevice $BridgeVpn 2>&1 | tee -a "$LogFile"
				PrepareExitcodeToFile
				AttachToNetBrigde $VpnDevice $BridgeVpn 2>&1 | tee -a "$MainLog"
				GetFiledExitcode $?
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				if [ "$NozeroStatusToFile_Legated" != "" ] ; then export NozeroStatusToFile="$NozeroStatusToFile_Legated" ; else export -n NozeroStatusToFile ; fi
			else
#				printf '%s\n' "Not attaching device $VpnDevice to any bridge because it's TUN type device." | tee -a "$LogFile"
				LogProgram 3 "Not attaching device $VpnDevice to any bridge because it's TUN type device."
			fi
		fi
		if [ "$Role" = "client" ] ; then
			Event_ClientUp
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		else
			Event_ServerUp
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
	else
		if [ "$script_type" = "down" ] ; then
			if [ "$BridgeVpn" != "" ] && [ "$dev_type" != "tun" ] ; then
#				printf '%s\n' "Detaching $dev_type device $VpnDevice from bridge $BridgeVpn" | tee -a "$LogFile"
#				DetachFromNetBrigde $VpnDevice $BridgeVpn 1 2>&1 | tee -a "$LogFile"
				LogProgram 3 "Detaching $dev_type device $VpnDevice from bridge $BridgeVpn"
				PrepareExitcodeToFile
				DetachFromNetBrigde $VpnDevice $BridgeVpn 1 2>&1 | tee -a "$MainLog"
				GetFiledExitcode $?
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
			if [ "$Role" = "client" ] ; then
				Event_ClientDown
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			else
				Event_ServerDown
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
		else
			printf '%s\n' "${sINFO}I: Assuming it has been called only to generate $CurrentConfigFile${fRESET}"
		fi
	fi
	return $StatusCode

#### ENVIRONMENT EXAMPLES FOR OPENVPN 2 (TCP, TUN)

# Server UP (66.77.88.99 = local original gateway; 1194 = OpenVPN server port)
route_vpn_gateway=192.168.250.2
daemon_log_redirect=1
script_type=up
proto_1=tcp-server
daemon=1
dev_type=tun
route_network_1=192.168.250.0
dev=vpn-tun0
remote_port_1=1194
daemon_start_time=1494497867
script_context=init
ifconfig_local=192.168.250.1
verb=1
local_port_1=1194
link_mtu=1500
route_gateway_1=192.168.250.2
tun_mtu=vpn-tun0
route_netmask_1=255.255.255.0
route_net_gateway=66.77.88.99
ifconfig_remote=192.168.250.2
daemon_pid=18655
config=/etc/openvpn/servervpn.conf
PWD=/etc/openvpn

# Server DOWN (66.77.88.99 = local original gateway; 1194 = OpenVPN server port)
route_vpn_gateway=192.168.250.2
daemon_log_redirect=1
script_type=down
proto_1=tcp-server
daemon=1
dev_type=tun
route_network_1=192.168.250.0
dev=vpn-tun0
remote_port_1=1194
redirect_gateway=0
daemon_start_time=1494497867
script_context=init
ifconfig_local=192.168.250.1
verb=1
local_port_1=1194
link_mtu=1500
signal=sigterm
route_gateway_1=192.168.250.2
tun_mtu=vpn-tun0
route_netmask_1=255.255.255.0
route_net_gateway=66.77.88.99
ifconfig_remote=192.168.250.2
daemon_pid=18655
config=/etc/openvpn/servervpn.conf
PWD=/etc/openvpn

# Client UP (6.7.8.9 = Server's IP; 11.22.33.44 = local original gateway; 1194 = OpenVPN server port)
route_vpn_gateway=192.168.250.5
daemon_log_redirect=1
script_type=up
proto_1=tcp-client
daemon=1
route_network_1=192.168.250.0
remote_1=vpnserver.example.net
dev=vpn-tun0
X509_0_CN=cert-cn.example.net
X509_0_C=NT
remote_port_1=1194
X509_1_CN=cert-issuer.example.net
X509_1_C=NT
daemon_start_time=1494496886
script_context=init
ifconfig_local=192.168.250.6
common_name=cert-cn.example.net
verb=1
local_port_1=0
link_mtu=1543
X509_0_O=cert-issuer.example.net
route_gateway_1=192.168.250.5
trusted_ip=6.7.8.9
X509_1_O=cert-issuer.example.net
tun_mtu=1500
route_netmask_1=255.255.255.0
trusted_port=1194
tls_id_0=/C=NT/ST=NT/O=cert-issuer.example.net/CN=cert-cn.example.net
tls_id_1=/C=NT/ST=NT/O=cert-issuer.example.net/CN=cert-issuer.example.net
route_net_gateway=11.22.33.44
ifconfig_remote=192.168.250.5
daemon_pid=110998
config=/etc/openvpn/servervpn.conf
untrusted_ip=6.7.8.9
tls_serial_0=01
tls_digest_0=9e:ed:17:6e:40:54:ea:0f:df:bd:11:8c:42:9b:62:b8:1f:ce:d1:46
X509_0_ST=NT
tls_serial_1=B2497C3BBF130569
tls_digest_1=52:9f:32:7a:c2:28:3d:61:5c:e4:3e:7d:7b:2d:ac:60:48:9c:9b:80
X509_1_ST=NT
untrusted_port=1194
PWD=/etc/openvpn

# Client DOWN (6.7.8.9 = Server's IP; 11.22.33.44 = local (original?) gateway; 1194 = OpenVPN server port)
route_vpn_gateway=192.168.250.5
daemon_log_redirect=1
script_type=down
proto_1=tcp-client
daemon=1
route_network_1=192.168.250.0
remote_1=vpnserver.example.net
dev=vpn-tun0
X509_0_CN=cert-cn.example.net
X509_0_C=NT
remote_port_1=1194
X509_1_CN=cert-issuer.example.net
X509_1_C=NT
daemon_start_time=1494444751
script_context=init
ifconfig_local=192.168.250.6
common_name=cert-cn.example.net
verb=1
local_port_1=0
link_mtu=1543
signal=sigterm
X509_0_O=cert-issuer.example.net
X509_1_O=cert-issuer.example.net
route_gateway_1=192.168.250.5
trusted_ip=6.7.8.9
tun_mtu=1500
route_netmask_1=255.255.255.0
trusted_port=1194
tls_id_0=/C=NT/ST=NT/O=cert-issuer.example.net/CN=cert-cn.example.net
tls_id_1=/C=NT/ST=NT/O=cert-issuer.example.net/CN=cert-issuer.example.net
route_net_gateway=11.22.33.44
ifconfig_remote=192.168.250.5
daemon_pid=108923
config=/etc/openvpn/servervpn.conf
untrusted_ip=6.7.8.9
tls_serial_0=01
tls_digest_0=8e:ed:17:6e:40:54:ea:0f:df:bd:11:8c:42:9b:62:b8:1f:ce:d1:47
X509_0_ST=NT
tls_serial_1=B2497C3BBF130569
tls_digest_1=52:9f:32:7a:c2:28:3d:61:5c:e4:3e:7d:7b:2d:ac:60:48:9c:9b:80
X509_1_ST=NT
untrusted_port=1194
PWD=/etc/openvpn

}

Event_AuthClient ()
{
	local FileIfSpecified="$1"
	local AccountsDir="/etc/openvpn/accounts"
	local AuthResult=1
	local ServiceProfile=''
	local LastStatus=0
	local StatusCode=0
	
	ServiceProfile="$(printf '%s' "$config" | sed -e 's|\.conf$||')"
	if [ -d /etc/openvpn/accounts.d ] && [ ! -d "$AccountsDir" ] ; then
		AccountsDir=/etc/openvpn/accounts.d
	fi
	if [ ! -d "$AccountsDir" ] ; then
		printf '%s\n' "${sERROR}E: Directory not found: $AccountsDir${fRESET}" 1>&2
		LastStatus=95 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		if [ -f "$FileIfSpecified" ] ; then
			# via-file
			username="$(cat "$FileIfSpecified" | head -n 1)"
			password="$(cat "$FileIfSpecified" | head -n 2 | tail -n 1)"
			if [ -f "${AccountsDir}/${username}.auth" ] ; then
				FilePassword="$(cat "${AccountsDir}/${username}.auth" | head -n 2 | tail -n 1)"
				ProvidedMd5="$(printf '%s' "$password" | md5sum | tr -s '\t' ' ' | cut -f 1 -d ' ')"
				if [ "$ProvidedMd5" = "$FilePassword" ] ; then
					LogProgram 3 "Service ${ServiceProfile}: Authentication success for account $username"
					AuthResult=0
				else
					LogProgram 2 "Service ${ServiceProfile}: Authentication failed for account $username" 1>&2
				fi
			else
				printf '%s\n' "Account $username not found."
			fi
		else
			if [ "$username" != "" ] ; then
				# via-env
				if [ -f "${AccountsDir}/${username}.auth" ] ; then
					FilePassword="$(cat "${AccountsDir}/${username}.auth" | head -n 2 | tail -n 1)"
					ProvidedMd5="$(printf '%s' "$password" | md5sum | tr -s '\t' ' ' | cut -f 1 -d ' ')"
					if [ "$ProvidedMd5" = "$FilePassword" ] ; then
						LogProgram 3 "Service ${ServiceProfile}: Authentication success for account $username"
						AuthResult=0
					else
						LogProgram 2 "Service ${ServiceProfile}: Authentication failed for account $username" 1>&2
					fi
				else
					printf '%s\n' "Account $username not found."
				fi
			else
				printf '%s\n' "${sERROR}E: Lack of environment data.${fRESET}" 1>&2
				LastStatus=102 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
		fi
	fi
	LastStatus=$AuthResult ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	return $StatusCode

ENVIRONMENT EXAMPLE IN A VM:

route_vpn_gateway=172.31.0.2
IV_LZ4=1
daemon_log_redirect=0
script_type=user-pass-verify
proto_1=udp
IV_COMP_STUBv2=1
daemon=0
OLDPWD=/usr/sbin
route_network_1=172.31.0.0
dev_type=tun
dev=vpn-tun1
IV_NCP=2
X509_0_CN=client1
remote_port_1=1194
X509_1_CN=debian10.example.net
IV_PLAT=linux
IV_TCPNL=1
redirect_gateway=0
IV_LZ4v2=1
tls_digest_sha256_0=16:55:d7:70:76:10:9e:aa:37:8d:fe:9b:88:a6:38:96:f3:24:06:cf:e3:90:ab:2d:9f:e7:7a:5f:a8:51:6f:97
daemon_start_time=1616187205
common_name=client1
tls_digest_sha256_1=96:25:6a:39:72:73:53:38:08:8e:1c:5b:50:7b:89:fa:68:d1:d4:5c:80:0d:ec:e1:3d:d9:51:22:b1:c6:3a:11
ifconfig_local=172.31.0.1
script_context=init
local_port_1=1194
verb=1
IV_VER=2.4.7
link_mtu=1621
tls_serial_hex_0=0d:88:ff:76:67:f1:f2:a6:3c:59:e3:ce:04:4c:ca:63
route_gateway_1=172.31.0.2
tls_serial_hex_1=1d:8f:a9:56:db:15:63:0c:48:d7:ad:0c:df:b7:51:70:dc:4e:5c:cf
route_netmask_1=255.255.255.0
tun_mtu=1500
tls_id_0=CN=client1
tls_id_1=CN=debian10.example.net
daemon_pid=16575
ifconfig_remote=172.31.0.2
route_net_gateway=10.0.2.2
IV_LZO=1
config=debian10.example.net.conf
untrusted_ip=172.16.20.101
tls_serial_0=17991297713506203777702377660544240227
tls_digest_0=03:37:8b:5e:ae:16:df:c0:05:06:f0:fe:7b:d2:7d:82:6e:44:4b:4c
IV_COMP_STUB=1
IV_PROTO=2
tls_serial_1=168764490449099226237150093656276125682687171791
tls_digest_1=a9:0a:68:2b:83:24:f3:1e:4a:81:d2:5f:ff:31:9f:f0:e5:73:0b:da
untrusted_port=33758
PWD=/etc/openvpn/server
}

CommonPrepare ()
{
	local ElementNr=0
	local LastStatus=0
	local StatusCode=0

	if [ $StatusCode -eq 0 ] && [ ! -e /dev/net/tun ] ; then
		printf '%s\n' "Enabling TUN module"
		modprobe tun
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		if [ $StatusCode -eq 0 ] && [ "$(cat /etc/modules 2>/dev/null | grep -e '^tun$')" = "" ] ; then
			echo tun >> /etc/modules
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
	fi
	if [ $StatusCode -eq 0 ] && [ "$(id -un openvpn 2>/dev/null)" != "openvpn" ] ; then
		printf '%s\n' "Creating OpenVPN system account"
		GecosParm='--comment'
		if [ "$(adduser --help | grep -e '--comment')" = "" ] ; then GecosParm='--gecos' ; fi
		adduser --system --disabled-login $GecosParm '' --home /etc/openvpn --no-create-home --group openvpn
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		ElementNr=0
#		for CurDir in /etc/openvpn/accounts.d /etc/openvpn/peers.d /etc/openvpn/scripts /var/log/openvpn ; do
		for CurDir in /etc/openvpn/accounts /etc/openvpn/scripts /etc/openvpn/remotes-data /var/log/openvpn /etc/openvpn/ccd "${SystemConfigDir}/openvpn" ; do
			ElementNr=$((ElementNr + 1))
			if [ $StatusCode -eq 0 ] && [ ! -d "$CurDir" ] ; then
				if [ $ElementNr -eq 1 ] ; then
					printf '%s\n' "Deploying directories for OpenVPN"
				fi
				if [ "$(printf '%s' "$CurDir" | grep -ie '/log/' -ie '/log$' -ie '/openvpn$')" != "" ] ; then
					MkdirPP "$CurDir" openvpn:openvpn u=rwX,g=rX,o= g+s
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				else
					MkdirPP "$CurDir" :openvpn u=rwX,g=rX,o= g+s
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
			fi
		done
	fi
#	if [ $StatusCode -eq 0 ] && [ ! -x /etc/openvpn/scripts/gw-updown.sh ] ; then
#		printf '%s\n' "Installing session scripts"
#		HttpGetContent https://.../vpn/gw-updown.sh > /etc/openvpn/scripts/gw-updown.sh
#		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
#		chmod u+rwx,g+rx /etc/openvpn/scripts/gw-updown.sh
#		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
#	fi
	if [ $StatusCode -eq 0 ] && [ ! -x /etc/openvpn/scripts/ifup.sh ] ; then
		printf '%s\n' "Installing session scripts"
		printf '%s\n' '#!/bin/sh
# Useful script to be called with a single parameter, although openvpn parses much more parameters related to dev/ip/gw/etc.
/usr/sbin/ifup "$1"
StatusCode=$?
# Clean any error when net device already exists
ip link show dev "$1" >/dev/null 2>&1
LastStatus=$?
if [ $LastStatus -eq 0 ] ; then StatusCode=0 ; fi

# When NIC/bridge is already active, it does not run "up" actions and sometimes it lacks of iproute rules. We run them now.
UpLines="$(cat /etc/network/interfaces.d/$1 | cut -f 1 -d "#" | tr -s "\t" " " | sed -e "s|^ ||g" | grep -e "^up " | cut -f 2- -d " ")"
IFS="$(printf "\n\b")" ; for CurLine in $UpLines ; do unset IFS
        eval $CurLine
done

exit $StatusCode' > /etc/openvpn/scripts/ifup.sh
		printf '%s\n' '#!/bin/sh
# Useful script to be called with a single parameter, although openvpn parses much more parameters related to dev/ip/gw/etc.
/usr/sbin/ifdown "$1"
StatusCode=$?

# Sometimes it does not run "down" actions and does not flush completely. We run them now.
DownLines="$(cat /etc/network/interfaces.d/$1 | cut -f 1 -d "#" | tr -s "\t" " " | sed -e "s|^ ||g" | grep -e "^down " | cut -f 2- -d " ")"
IFS="$(printf "\n\b")" ; for CurLine in $DownLines ; do unset IFS
        eval $CurLine
done

ip link delete $1

exit $StatusCode' > /etc/openvpn/scripts/ifdown.sh
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
#		HttpGetContent https://.../vpn/gw-updown.sh > /etc/openvpn/scripts/gw-updown.sh
#		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
#		chmod u+rwx,g+rx /etc/openvpn/scripts/gw-updown.sh
		chmod u+rwx,g+rx /etc/openvpn/scripts/ifup.sh /etc/openvpn/scripts/ifdown.sh
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	return $StatusCode
}

Server_prepare_CA_StaticKeys ()
{
	local CertsIssuer_CA_CN="$1"
	local PreviousDir=''
	local LastStatus=0
	local StatusCode=0

	PreviousDir="$(pwd)"
	if [ $StatusCode -eq 0 ] && [ ! -f /etc/openvpn/static.key ] ; then
		openvpn --genkey --secret /etc/openvpn/static.key
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		chgrp openvpn /etc/openvpn/static.key
		chmod g+r /etc/openvpn/static.key
	fi
	if [ $StatusCode -eq 0 ] && [ ! -e /etc/openvpn/easy-rsa ] ; then
		make-cadir /etc/openvpn/easy-rsa
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		if [ $StatusCode -eq 0 ] ; then
			cd /etc/openvpn/easy-rsa
			./easyrsa init-pki
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			chgrp -R openvpn /etc/openvpn/easy-rsa
			chmod -R g+r /etc/openvpn/easy-rsa
			find /etc/openvpn/easy-rsa -type d -exec chmod g+s {} +
		fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		cd /etc/openvpn/easy-rsa
		CertsIssuer_CA_FilePub="$(IniVarValue "$CurrentConfigFile" CertsIssuer_CA_FilePub '' "/etc/openvpn/easy-rsa/pki/ca.crt" =)"
		CertsIssuer_CA_FilePriv="$(IniVarValue "$CurrentConfigFile" CertsIssuer_CA_FilePriv '' "/etc/openvpn/easy-rsa/pki/private/ca.key" =)"
		if [ ! -f "$CertsIssuer_CA_FilePub" ] || [ ! -f "$CertsIssuer_CA_FilePriv" ] ; then
			if [ ! -f /etc/openvpn/easy-rsa/pki/.rnd ] ; then touch /etc/openvpn/easy-rsa/pki/.rnd ; fi
			export EASYRSA_REQ_CN="$CertsIssuer_CA_CN"
			export EASYRSA_CA_EXPIRE=$ValidDays
			export EASYRSA_BATCH=1
			printf '%s\n' "CREATING CERTIFICATE FOR CA ISSUER, valid for $((EASYRSA_CA_EXPIRE/365)) years."
#			printf '%s\n' "W: Creating passwordless key" 1>&2
#			./easyrsa build-ca nopass
			./easyrsa build-ca
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			if [ -f "$CertsIssuer_CA_FilePub" ] ; then
				chgrp openvpn "$CertsIssuer_CA_FilePub"
				chmod g+r "$CertsIssuer_CA_FilePub"
			fi
		fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		if [ -f "$CertsIssuer_CA_FilePub" ] ; then
			CertsIssuer_CA_FilePub="$(GetOrSetIniVarValue "$CurrentConfigFile" CertsIssuer_CA_FilePub '' "$CertsIssuer_CA_FilePub" = '' "\n# Default certificate file of CA issuer")"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		else
			printf '%s\n' "W: You must configure CertsIssuer_CA_FilePub at $CurrentConfigFile" 1>&2
		fi
		if [ -f "$CertsIssuer_CA_FilePriv" ] ; then
			CertsIssuer_CA_FilePriv="$(GetOrSetIniVarValue "$CurrentConfigFile" CertsIssuer_CA_FilePriv '' "$CertsIssuer_CA_FilePriv" = '' "\n# Default private key file of CA issuer")"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		else
			printf '%s\n' "W: You must configure CertsIssuer_CA_FilePriv at $CurrentConfigFile" 1>&2
		fi
	fi
	if [ $StatusCode -eq 0 ] && [ ! -f /etc/openvpn/easy-rsa/pki/dh.pem ] ; then
		./easyrsa gen-dh
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		if [ -f /etc/openvpn/easy-rsa/pki/dh.pem ] ; then
			chgrp openvpn /etc/openvpn/easy-rsa/pki/dh.pem
			chmod g+r /etc/openvpn/easy-rsa/pki/dh.pem
		fi
	fi
	cd "$PreviousDir"
	return $StatusCode
}

Server_create_CN_StaticKeys ()
{
	local Server_CN="$1"
	local PreviousDir=''
	local LastStatus=0
	local StatusCode=0

	PreviousDir="$(pwd)"
	if [ $StatusCode -eq 0 ] ; then
		cd /etc/openvpn/easy-rsa
#		Server_FilePub="$(IniVarValue "$CurrentConfigFile" Server_FilePub '' "/etc/openvpn/easy-rsa/pki/issued/${Server_CN}.crt" =)"
#		Server_FilePriv="$(IniVarValue "$CurrentConfigFile" Server_FilePriv '' "/etc/openvpn/easy-rsa/pki/private/${Server_CN}.key" =)"
		Server_FilePub="/etc/openvpn/easy-rsa/pki/issued/${Server_CN}.crt"
		Server_FilePriv="/etc/openvpn/easy-rsa/pki/private/${Server_CN}.key"
		if [ ! -f "$Server_FilePub" ] || [ ! -f "$Server_FilePriv" ] ; then
			export EASYRSA_CERT_EXPIRE=$ValidDays
			printf '\n'
			printf '%s\n' "CREATING CERTIFICATE FOR VPN SERVER, valid for $((EASYRSA_CERT_EXPIRE/365)) years."
			if [ "$ServiceKeysWithPassword" = "1" ] ; then
				printf '%s\n' "${ParO}first passwords are for VPN server key, next one is from CA issuer${ParC}"
			else
				printf '%s\n' "${ParO}asked pass phrase if from CA issuer${ParC}"
			fi
#			printf '%s\n' "W: Signing with passwordless CA" 1>&2
			export EASYRSA_REQ_CN="$Server_CN"
			cp -a vars "$Server_CN"
			sed -i "s|ChangeMe|${Server_CN}|g" "$Server_CN"
			sed -i "s|^#set_var EASYRSA_REQ_CN|set_var EASYRSA_REQ_CN|g" "$Server_CN"
			cp -a easyrsa easyrsa_${Server_CN}
			sed -i "s|ChangeMe|${Server_CN}|g" easyrsa_${Server_CN}
			if [ "$ServiceKeysWithPassword" = "1" ] ; then
				./easyrsa_${Server_CN} build-server-full "$Server_CN"
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				if [ $LastStatus -ne 0 ] ; then
					printf '%s\n' "Working directory was: $(pwd)" 1>&2
					printf '%s\n' "Command was: ./easyrsa build-server-full $Server_CN" 1>&2
				fi
			else
				./easyrsa_${Server_CN} build-server-full "$Server_CN" nopass
#				./easyrsa build-server-full "$Server_CN" nopass
#				# https://forum.openwrt.org/t/error-when-setting-up-openvpn/166188
#				./easyrsa build-server-full server nopass
#				./easyrsa build-server-full vars nopass
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
#				LastStatus=$?
				if [ $LastStatus -ne 0 ] ; then
#					env > /tmp/env.txt
					printf '%s\n' "Working directory was: $(pwd)" 1>&2
					printf '%s\n' "Command was: ./easyrsa build-server-full $Server_CN nopass" 1>&2
				fi
#				if [ $LastStatus -ne 0 ] ; then
#					sh -c "EASYRSA_REQ_CN= ./easyrsa build-server-full $Server_CN nopass"
#					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
#				fi
#				if [ $LastStatus -ne 0 ] ; then
#					printf '%s\n' "Working directory was: $(pwd)" 1>&2
#					printf '%s\n' "Command was: ./easyrsa build-server-full "$Server_CN" nopass" 1>&2
#				fi
			fi
			if [ -f "$Server_FilePub" ] ; then
				chgrp openvpn "$Server_FilePub"
				chmod g+r "$Server_FilePub"
			fi
			if [ -f "$Server_FilePriv" ] ; then
				chgrp openvpn "$Server_FilePriv"
				chmod g+r "$Server_FilePriv"
			fi
			if [ $StatusCode -eq 0 ] ; then
				if [ -f "$Server_FilePub" ] ; then
					GetOrSetIniVarValue "$CurrentConfigFile" Server_FilePub '' "$Server_FilePub" = '' "\n# Default certificate file of VPN server" >/dev/null
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				else
					printf '%s\n' "W: You should configure Server_FilePub at $CurrentConfigFile" 1>&2
				fi
				if [ -f "$Server_FilePriv" ] ; then
					GetOrSetIniVarValue "$CurrentConfigFile" Server_FilePriv '' "$Server_FilePriv" = '' "\n# Default private key file of VPN server" >/dev/null
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				else
					printf '%s\n' "W: You should configure Server_FilePriv at $CurrentConfigFile" 1>&2
				fi
			fi
		fi
	fi
	cd "$PreviousDir"
	return $StatusCode
}

Server_prepare_CA_TLS ()
{
	local CertsIssuer_CA_CN="$1"
	local Answer=''
	local LastStatus=0
	local StatusCode=0

	if [ "$(groups openvpn | cut -f 2- -d ':' | sed -e 's|^| |' | sed -e 's|$| |' | grep -e ' x509 ')" = "" ] && [ "$(cat /etc/group | grep -e '^x509:')" != "" ] ; then
		usermod --append --groups x509 openvpn
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		CertsIssuer_CA_FilePub="$(GetOrSetIniVarValue "$CurrentConfigFile" CertsIssuer_CA_FilePub '' "\"/etc/ssl/certs/CA@${CertsIssuer_CA_CN}.pem\"" = '' "\n# Default certificate file of CA issuer")"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		CertsIssuer_CA_FilePriv="$(GetOrSetIniVarValue "$CurrentConfigFile" CertsIssuer_CA_FilePriv '' "\"/etc/ssl/private/CA@${CertsIssuer_CA_CN}.key\"" = '' "\n# Default private key file of CA issuer")"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		if [ ! -f "$CertsIssuer_CA_FilePub" ] || [ ! -f "$CertsIssuer_CA_FilePriv" ] ; then
			printf '%s\n' "$CurrentConfigFile does not contain functional values for CertsIssuer_CA_FilePub and CertsIssuer_CA_FilePriv"
			printf '%s\n' "What do you want to do?"
			printf '%s\n' "[C]reate a Certification Authority. [Q]uit and configure manually my own."
			Answer="$(RespostaLletra)"
			if [ "$Answer" = "c" ] ; then
				CertsIssuer_CA_FilePub="/etc/ssl/certs/CA@${CertsIssuer_CA_CN}.pem"
				CertsIssuer_CA_FilePriv="/etc/ssl/private/CA@${CertsIssuer_CA_CN}.key"
				sesele self-ca '' "$CertsIssuer_CA_CN"
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				if [ $StatusCode -eq 0 ] ; then
					if [ -f "$CertsIssuer_CA_FilePub" ] && [ -f "$CertsIssuer_CA_FilePriv" ] ; then
						SetIniVarValue "$CurrentConfigFile" CertsIssuer_CA_FilePub '' "\"${CertsIssuer_CA_FilePub}\"" = "\n# Default certificate file of CA issuer"
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						SetIniVarValue "$CurrentConfigFile" CertsIssuer_CA_FilePriv '' "\"${CertsIssuer_CA_FilePriv}\"" = "\n# Default private key file of CA issuer"
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					else
						printf '%s\n' "${sINFO}I: You must configure CertsIssuer_CA_FilePub and CertsIssuer_CA_FilePriv at $CurrentConfigFile${fRESET}"
						LastStatus=254 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					fi
				fi
			else
				LastStatus=130 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
		fi
	fi
	return $StatusCode
}

Server_create_CN_TLS ()
{
	local Server_CN="$1"
	local Answer=''
	local LastStatus=0
	local StatusCode=0

	if [ $StatusCode -eq 0 ] ; then
#		Server_FilePub="$(GetOrSetIniVarValue "$CurrentConfigFile" Server_FilePub '' "/etc/ssl/certs/${Server_CN}.pem" = '' "\n# Default certificate file of VPN server")"
#		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
#		Server_FilePriv="$(GetOrSetIniVarValue "$CurrentConfigFile" Server_FilePriv '' "/etc/ssl/private/${Server_CN}.key" = '' "\n# Default private key file of VPN server")"
#		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		Server_FilePub="/etc/ssl/certs/${Server_CN}.pem"
		Server_FilePriv="/etc/ssl/private/${Server_CN}.key"
		if [ ! -f "$Server_FilePub" ] || [ ! -f "$Server_FilePriv" ] ; then
			printf '%s\n' "$CurrentConfigFile does not contain functional values for Server_FilePub, Server_FilePriv"
			printf '%s\n' "What do you want to do?"
			printf '%s\n' "[C]reate and sign a VPN server certificate. [Q]uit and configure manually my own."
			Answer="$(RespostaLletra)"
			if [ "$Answer" = "c" ] ; then
				Server_FilePub="/etc/ssl/certs/${Server_CN}.pem"
				Server_FilePriv="/etc/ssl/private/${Server_CN}.key"
				sesele self-cn '' "$Server_CN"
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
#				if [ $StatusCode -eq 0 ] ; then
#					if [ -f "$Server_FilePub" ] && [ -f "$Server_FilePriv" ] ; then
#						SetIniVarValue "$CurrentConfigFile" Server_CN '' "\"${Server_CN}\"" = "\n# Default Common Name of VPN server for X.509 certificates"
#						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
#						SetIniVarValue "$CurrentConfigFile" Server_FilePub '' "\"${Server_FilePub}\"" = "\n# Default certificate file of VPN server"
#						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
#						SetIniVarValue "$CurrentConfigFile" Server_FilePriv '' "\"${Server_FilePriv}\"" = "\n# Default private key file of VPN server"
#						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
#					else
#						printf '%s\n' "${sINFO}I: You must configure Server_FilePub and Server_FilePriv at $CurrentConfigFile${fRESET}"
#						LastStatus=254 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
#					fi
#				fi
				if [ $StatusCode -eq 0 ] ; then
					if [ -f "$Server_FilePub" ] ; then
						GetOrSetIniVarValue "$CurrentConfigFile" Server_FilePub '' "$Server_FilePub" = '' "\n# Default certificate file of VPN server" >/dev/null
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					else
						printf '%s\n' "W: You should configure Server_FilePub at $CurrentConfigFile" 1>&2
					fi
					if [ -f "$Server_FilePriv" ] ; then
						GetOrSetIniVarValue "$CurrentConfigFile" Server_FilePriv '' "$Server_FilePriv" = '' "\n# Default private key file of VPN server" >/dev/null
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					else
						printf '%s\n' "W: You should configure Server_FilePriv at $CurrentConfigFile" 1>&2
					fi
				fi
			else
				LastStatus=130 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
		fi
	fi
	return $StatusCode
}

GetDiffieHellmanFile ()
{
	local Value='/etc/openvpn/easy-rsa/pki/dh.pem'
	
	if [ ! -f "$Value" ] ; then
		Value="$(find /etc/openvpn/easy-rsa/pki 2>/dev/null | grep -e '/dh.*\.pem$' | head -n 1)"
	fi
	if [ ! -f "$Value" ] ; then
		Value="$(find /etc/openvpn/easy-rsa 2>/dev/null | grep -e '/dh.*\.pem$' | head -n 1)"
	fi
	if [ ! -f "$Value" ] ; then
		Value="$(find /etc/openvpn 2>/dev/null | grep -e '/dh.*\.pem$' | head -n 1)"
	fi
	if [ ! -f "$Value" ] ; then
		Value="$(find /usr/share/doc/openvpn/examples/sample-keys 2>/dev/null | grep -e '/dh.*\.pem$' | head -n 1)"
	fi
	if [ ! -f "$Value" ] ; then
		Value="$(find /usr/share/doc/openvpn/examples 2>/dev/null | grep -e '/dh.*\.pem$' | head -n 1)"
	fi
	if [ ! -f "$Value" ] ; then
		Value="$(find /usr/share/doc/openvpn 2>/dev/null | grep -e '/dh.*\.pem$' | head -n 1)"
	fi
	if [ -f "$Value" ] ; then
		printf '%s\n' "$Value"
	fi
}

Server_prepare ()
{
#	local CertsIssuer_CA_CN=''
	local Answer=''
	local HostIPs=''
	local HostNames=''
	local CurValue=''
	local DevName=''
	local CurNr=''
	local ProfileFile=''
	local LastStatus=0
	local StatusCode=0

	CommonPrepare
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	if [ "$(command -v ip 2>/dev/null)" = "" ] ; then
		if [ "$(command -v ifconfig 2>/dev/null)" = "" ] || [ "$(command -v brctl 2>/dev/null)" = "" ] ; then
			printf '%s\n' "${sERROR}E: Required software missing: iproute2 OR net-tools bridge-utils${fRESET}" 1>&2
			LastStatus=53 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
	fi
	if [ "$(openvpn --help | grep -e '--genkey')" != "" ] && [ "$(openvpn --help | grep -e '--secret')" != "" ] && [ "$(command -v make-cadir 2>/dev/null)" != "" ] && [ ! -f /usr/share/easy-rsa/clean-all ] ; then
		# Existence of /usr/share/easy-rsa/clean-all indicates easy-rsa < v3
		KeysType='static'
	else
		if [ "$(command -v sesele 2>/dev/null)" != "" ] ; then
			KeysType='TLS'
		else
			printf '%s\n' "${sERROR}E: Required software missing: easy-rsa OR sesele${fRESET}" 1>&2
			LastStatus=53 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		if [ "$CertsIssuer_CA_CN" = "" ] ; then
			CertsIssuer_CA_CN="$(IniVarValue "$CurrentConfigFile" CertsIssuer_CA_CN '' "" =)"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		else
			SetIniVarValue "$CurrentConfigFile" CertsIssuer_CA_CN '' "\"${CertsIssuer_CA_CN}\"" = "\n# Common name of CA issuer for X.509 certificates"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
	fi
	if [ $StatusCode -eq 0 ] && [ "$CertsIssuer_CA_CN" = "" ] ; then
		CertsIssuer_CA_CN="$(hostname -f)"
		if [ "$CertsIssuer_CA_CN" != "" ] ; then
			printf '%s\n' "This is the default common name of CA issuer: $CertsIssuer_CA_CN"
			printf '%s' "Enter a new FQDN name or press ony ENTER to use default: "
		else
			printf '%s\n' "W: Hostname not detected to use it as CA issuer CN." 1>&2
			printf '%s' "Enter a Common Name (FQDN recommended): "
		fi
		read Answer
		if [ "$Answer" != "" ] ; then CertsIssuer_CA_CN="$Answer" ; fi
		if [ "$CertsIssuer_CA_CN" != "" ] ; then
			SetIniVarValue "$CurrentConfigFile" CertsIssuer_CA_CN '' "\"${CertsIssuer_CA_CN}\"" = "\n# Common name of CA issuer for X.509 certificates"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		else
			printf '%s\n' "${sERROR}E: Common Name not specified for CA certificate creation.${fRESET}" 1>&2
			LastStatus=82 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		if [ "$KeysType" = "static" ] ; then
			Server_prepare_CA_StaticKeys "$CertsIssuer_CA_CN"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		else
			Server_prepare_CA_TLS "$CertsIssuer_CA_CN"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
	fi
	return $StatusCode
}

ServiceAction ()
# To do: Parse 'enable' and 'disable' actions
{
	local Action="$1"  # stop|start|status
	local ServiceName="$2"  # Such as: openvpn-server
	local ObjectName="$3"  # Such as a VPN ProfileName
	local LastStatus=0
	local StatusCode=0
	
	if [ -x "/bin/systemctl" ] ; then
		if [ "$ObjectName" != "" ] ; then
			systemctl $Action "${ServiceName}@${ObjectName}"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		else
			systemctl $Action $ServiceName
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
	else
		if [ -x "/usr/sbin/service" ] ; then
			if [ "$ObjectName" != "" ] && [ "$(printf '%s' "$Action" | grep -e '^start$' -e '^restart$' -e '^reload$')" != "" ] ; then
				if [ "$Action" = "start" ] ; then
					service $ServiceName stop "$ObjectName" >/dev/null 2>&1
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				else
					service $ServiceName stop "$ObjectName"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
				service $ServiceName $Action "$ObjectName"
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			else
				service $ServiceName $Action "$ObjectName"
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
		else
			printf '%s\n' "${sERROR}E: Unknown init software${fRESET}" 1>&2
			LastStatus=52 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
	fi
	return $StatusCode
}

Server_create ()
# Setup VPN service per port
{
	local Server_CN="$1"
	local ProfileName="$2"
	local Port="$3"
	local BindAddress="$4"
	local AuthMode="$5"
	local Server_IP="$6"
	local Server_Mask="$7"
	local Server_Mask_Bits=''
	local Server_BaseNet=''
	local Protocol='udp'
	local DevType='tun'
	local Topology='subnet'
	local Answer=''
	local HostIPs=''
	local HostNames=''
	local CurValue1=''
	local CurValue2=''
	local DevName=''
	local CurNr=''
	local ProfileFile=''
	local Server_CN_Default=''
	local WaitForStart=5
	local RemainForStart=0
	local OpenvpnVersion=''
	local Server_FilePriv_Name=''
	local Server_FilePriv_New=''
	local LastStatus=0
	local StatusCode=0

	Server_prepare
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	OpenvpnVersion="$(openvpn --version 2>/dev/null | grep -ie '^OpenVPN [0-9]' | cut -f 2 -d ' ')"
	if [ $StatusCode -eq 0 ] && [ "$Server_CN" = "." ] ; then
		Server_CN="$(IniVarValue "$CurrentConfigFile" Server_CN '' "$(hostname -f)" =)"
	fi
	if [ $StatusCode -eq 0 ] && [ "$Server_CN" = "" ] ; then
		Server_CN_Default="$(IniVarValue "$CurrentConfigFile" Server_CN '' "" =)"
		Server_CN="$Server_CN_Default"
		if [ "$Server_CN" = "" ] ; then Server_CN="$(hostname -f)" ; fi
		if [ "$Server_CN" != "" ] ; then
			printf '%s\n' "This is the default common name of VPN server: $Server_CN"
			printf '%s' "Enter a new FQDN name or press ony ENTER to use default: "
		else
			printf '%s\n' "W: Hostname not detected to use it as VPN server name." 1>&2
			printf '%s' "Enter a Common Name (FQDN recommended): "
		fi
		read Answer
		if [ "$Answer" != "" ] ; then Server_CN="$Answer" ; fi
		if [ "$Server_CN" != "" ] && [ "$Server_CN_Default" = "" ] ; then
			SetIniVarValue "$CurrentConfigFile" Server_CN '' "\"${Server_CN}\"" = "\n# Default Common Name of VPN server for X.509 certificates"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
		if [ "$Server_CN" = "" ] ; then
			printf '%s\n' "${sERROR}E: Common Name not specified for VPN server certificate creation.${fRESET}" 1>&2
			LastStatus=82 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		if [ "$KeysType" = "static" ] ; then
			Server_create_CN_StaticKeys "$Server_CN"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		else
			Server_create_CN_TLS "$Server_CN"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		if [ "$ProfileName" = "" ] ; then
			printf '%s\n' "Server_CN: $Server_CN"
			printf '%s\n' "Enter a name for this VPN server per port or press ony ENTER to use Server_CN:"
			read ProfileName
		fi
		if [ "$ProfileName" = "" ] || [ "$ProfileName" = "." ] ; then ProfileName="$Server_CN" ; fi
		if ! Is_IntegerNr "$Port" && [ "$Port" != "." ] ; then
			printf '%s' "Communication port for new service access (udp default 1194): "
			read Port
		fi
		if ! Is_IntegerNr "$Port" ; then Port=1194 ; fi
		if [ $Port -eq 443 ] ; then
			printf '%s\n' "${sINFO}I: 443 port will be used by TCP protocol, to ease pass through http proxies.${fRESET}"
			Protocol='tcp-server'
		fi
		if [ "$BindAddress" = "" ] ; then
			printf '\n'
			HostNames="$(hostname -A)"
			if [ "$HostNames" = "" ] ; then HostNames="$(hostname -f)" ; fi
			HostNames="$(echo TrimAndSingle $HostNames | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g')"
			HostIPs="$(AllIPv4 0 . '^127\.')"
			HostIPs="$(echo TrimAndSingle $HostIPs | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g')"
#			printf '%s\n' "Current host names: ${sVALUE}${HostNames}${fRESET}"
			printf '%s\n' "Current host IP addresses: ${sVALUE}${HostIPs}${fRESET}"
#			printf '%s' "Local host name or IP address for bind ${ParO}empty for all${ParC}: "
			printf '%s' "Local IP address for bind service ${ParO}empty for all${ParC}: "
			read BindAddress
		fi
		if [ "$BindAddress" = "." ] ; then BindAddress='' ; fi
		if [ "$BindAddress" = "" ] || [ "$BindAddress" = "0.0.0.0" ] ; then
			BindComment='#'
			BindAddress="$(printf '%s' "$HostIPs" | cut -f 1 -d ' ')"
			if [ "$BindAddress" = "" ] ; then BindAddress='1.2.3.4' ; fi
		else
			BindComment=''
		fi
		if [ "$AuthMode" != "p" ] && [ "$AuthMode" != "P" ] && [ "$AuthMode" != "c" ] && [ "$AuthMode" != "C" ] ; then
			printf '%s\n' "What needs to be be the authentication method for clients?"
			printf '%s\n' "C: Client [C]ertificate only and required"
			printf '%s\n' "P: Username and [P]assword"
			AuthMode="$(RespostaLletra)"
			if [ "$AuthMode" != "p" ] && [ "$AuthMode" != "c" ] ; then
				printf '%s\n' "${sERROR}E: Invalid auth mode.${fRESET}" 1>&2
				LastStatus=92 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
		fi
		if [ "$AuthMode" = "p" ] || [ "$AuthMode" = "P" ] ; then
			ClientUserLoginComment=''
			VerifyClientCert='optional'
		else
			ClientUserLoginComment='#'
			VerifyClientCert='require'
		fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		if [ "$Server_IP" = "" ] ; then
			printf '%s\n' ""
			printf '%s\n' "Carrier-grade NAT uses 100.64.0.0/10 addresses."
			printf '%s\n' "Alternative net can be 172.31.0.0/16 addresses."
#			printf '%s\n' "Default IP and mask for server VPN NIC is: 100.64.0.1 255.255.255.0"
			printf '%s\n' "Typical IP and mask for server VPN NIC: 100.64.0.1 255.255.255.0"
#			printf '%s\n' "${ParO}press only ENTER for default values${ParC}"
			printf '%s\n' "${ParO}${sWARN}care${fRESET} to not 2 VPN servers overlap addresses${ParC}"
			printf '%s\n' "What ${sINFO}IP${fRESET} address must be set for server VPN endpoint?"
			read Server_IP
			if [ "$Server_IP" = "" ] || [ "$Server_IP" = "." ] ; then
				Server_IP='100.64.0.1'
				Server_Mask='255.255.255.0'
			else
				Server_Mask=''
			fi
		else
			if [ "$Server_IP" = "." ] ; then
				Server_IP='100.64.0.1'
			fi
		fi
		if [ "$Server_Mask" = "" ] ; then
			Server_Mask='255.255.255.0'
			printf '%s\n' ""
			printf '%s\n' "Default IP and mask for server VPN NIC is: $Server_IP $Server_Mask"
			printf '%s\n' "${ParO}press only ENTER for ${Server_Mask}${ParC}"
			printf '%s\n' "What mask must be set for VPN addresses?"
			read Server_Mask
			if [ "$Server_Mask" = "" ] || [ "$Server_Mask" = "." ] ; then
				Server_Mask='255.255.255.0'
			fi
		else
			if [ "$Server_Mask" = "." ] ; then
#				if Is_PrivateIPv4 "$Server_IP" ; then
#					Server_Mask='255.255.0.0'
#				else
					Server_Mask='255.255.255.0'
#				fi
			fi
		fi
		if [ "$(EsIP "$Server_IP")" != "1" ] ; then
			printf '%s\n' "${sERROR}E: Server address error ${ParO}${Server_IP}${ParC}. This program only supports IPv4 addresses.${fRESET}" 1>&2
			LastStatus=65 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
		if [ "$(EsIP "$Server_Mask")" != "1" ] ; then
			printf '%s\n' "${sERROR}E: Server address error ${ParO}${Server_Mask}${ParC}. This program only supports IPv4 addresses.${fRESET}" 1>&2
			LastStatus=65 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
	fi
	if [ $StatusCode -eq 0 ] ; then
#		Server_BaseNet="$(IPBaseXarxa "$Server_IP" "$Server_Mask")"
		Server_Mask_Bits="$(BitsMascara "$Server_Mask")"
		Server_BaseNet="$(IPv4NetworkAddress "${Server_IP}/${Server_Mask_Bits}")"
		MkdirPP /etc/openvpn/server :openvpn u=rwX,g=rX,o= g+s
		CurValue1="$(cat /etc/openvpn/server/* 2>/dev/null | grep -e '^dev ')"
		CurValue2="$(ListaDispositivosRed)"
		CurNr=1
		DevName="vpn-${DevType}${CurNr}"
		while [ "$(printf '%s\n' "$CurValue1" | grep -e "^dev ${DevName}$")" != "" ] || [ "$(printf '%s' " $CurValue2 " | grep -e " $DevName ")" != "" ] ; do
			CurNr=$((CurNr + 1))
			DevName="vpn-${DevType}${CurNr}"
		done
		if [ "$Topology" = "subnet" ] ; then
			ifconfigLine="ifconfig $Server_IP $Server_Mask"
		else
			CurValue1="$(printf '%s' "$Server_IP" | cut -f 4 -d '.')"
			CurValue1=$((CurValue1 + 1))
			if [ $CurValue1 -gt 254 ] ; then CurValue1=252 ; fi
			CurValue1="$(printf '%s' "$Server_IP" | cut -f 1-3 -d '.').${CurValue1}"
			ifconfigLine="ifconfig $Server_IP $CurValue1"
		fi
		ProfileFile="/etc/openvpn/server/${ProfileName}.conf"
		if [ ! -f "$ProfileFile" ] ; then
			if [ ! -f /etc/openvpn/management.auth ] ; then
				cat /dev/null > /etc/openvpn/management.auth
				chgrp openvpn /etc/openvpn/management.auth
				chmod g+r,o= /etc/openvpn/management.auth
				if Is_Executable apg ; then
					apg -a 0 -M NCL -m 8 -x 10 | head -n 1 > /etc/openvpn/management.auth
				else
					printf '%s\n' "W: apg utility not found to generate better passwords." 1>&2
					dd if=/dev/urandom count=1 2> /dev/null | md5sum | tr -s '\t' ' ' | cut -f 1 -d ' ' > /etc/openvpn/management.auth
				fi
			fi
			MkdirPP "/etc/openvpn/ccd/${ProfileName}" :openvpn u=rwX,g=rX,o= g+s
			cat /dev/null > "$ProfileFile"
			chgrp openvpn "$ProfileFile"
			chmod g+r "$ProfileFile"
			if [ "$(ComparaVersions $OpenvpnVersion 2.3)" != "<" ] ; then
				ManagementUpDown_Line="management-up-down"
			else
				ManagementUpDown_Line="#management-up-down  # REQUIRES OpenVPN VERSION 2.3"
			fi
			if [ "$(ComparaVersions $OpenvpnVersion 2.4)" != "<" ] ; then
				VerifyClientCert_Line="verify-client-cert $VerifyClientCert"
			else
				VerifyClientCert_Line="#verify-client-cert $VerifyClientCert  # REQUIRES OpenVPN VERSION 2.4"
			fi
			if [ "$(printf '%s' "$Server_FilePriv" | grep -e '^/etc/ssl/private/')" ] ; then
				# Place files from /etc/ssl/private to an openvpn readable path
				Server_FilePriv_Name="$(printf '%s\n' "$Server_FilePriv" | tr -s '/' '\n' | tail -n 1)"	# Problems with a path begun with "-" in old GNU basename versions
				if [ -d /etc/openvpn/easy-rsa/pki/private ] ; then
					Server_FilePriv_New="/etc/openvpn/easy-rsa/pki/private/${Server_FilePriv_Name}"
				else
					if [ -d /etc/openvpn/server ] ; then
						Server_FilePriv_New="/etc/openvpn/server/${Server_FilePriv_Name}"
					else
						Server_FilePriv_New="/etc/openvpn/${Server_FilePriv_Name}"
					fi
				fi
				if [ "$Server_FilePriv_New" != "" ] ; then
					if [ ! -f "$Server_FilePriv_New" ] ; then
						cp -a "$Server_FilePriv" "$Server_FilePriv_New"
						LastStatus=$?
						if [ $LastStatus -eq 0 ] ; then
							Server_FilePriv="$Server_FilePriv_New"
							chgrp openvpn "$Server_FilePriv"
							chmod g+r,o= "$Server_FilePriv"
						fi
					else
						Server_FilePriv="$Server_FilePriv_New"
					fi
				fi
			fi
			printf '%s\n' "# Changes in this profile apply by restarting service:
# systemctl restart ${OpenvpnServerName}@${ProfileName}

user openvpn
group openvpn
${BindComment}local $BindAddress
port $Port
proto $Protocol
dev-type $DevType
dev $DevName
persist-tun

mode server
$ifconfigLine
route $Server_BaseNet $Server_Mask
topology $Topology

tls-server
ca   $CertsIssuer_CA_FilePub
cert $Server_FilePub
key  $Server_FilePriv  # keep secret" >> "$ProfileFile"
			DiffieHellmanFile="$(GetDiffieHellmanFile)"
			if [ "$DiffieHellmanFile" != "" ] ; then
				printf '%s\n' "dh   $DiffieHellmanFile" >> "$ProfileFile"
			fi
			printf '%s\n' "persist-key

$VerifyClientCert_Line
${ClientUserLoginComment}auth-user-pass-verify \"$MeCallFile auth-client\" via-file
${ClientUserLoginComment}username-as-common-name

management localhost 7505 /etc/openvpn/management.auth
management-log-cache 10
$ManagementUpDown_Line

keepalive 10 120
#status log/openvpn-status.log
#verb 3  # verbose mode {0=OnlyFatal 1-4=Normal 5=IncludePacketSignals 6-11=Debug} Default is 0
log-append /var/log/openvpn/${ProfileName}.log
machine-readable-output
script-security 2
# up/down script to configure some about client
#up \"${MeCallFile} ...\"
#down-pre
#down \"${MeCallFile} ...\"

# Here optionally be \"ClientCN\" files to be included (matching Client's Common Name) and \"DEFAULT\" file for not matching ones:
client-config-dir /etc/openvpn/ccd/${ProfileName}" >> "$ProfileFile"
			if [ "$(ComparaVersions $OpenvpnVersion 2.4)" = "<" ] ; then
				# Not structured configuration at /etc/openvpn
				# Not sure if it's from v2.4 that implements structured configuration. v2.6 yes it does.
				if [ -L "/etc/openvpn/${ProfileName}.conf" ] ; then rm "/etc/openvpn/${ProfileName}.conf" ; fi
				if [ -e "/etc/openvpn/${ProfileName}.conf" ] ; then mv "/etc/openvpn/${ProfileName}.conf" "/etc/openvpn/${ProfileName}.conf" ; fi
				ln -s "$ProfileFile" "/etc/openvpn/${ProfileName}.conf"
			fi
		else
			printf '%s\n' "${sERROR}E: A service profile already exists as $ProfileFile${fRESET}" 1>&2
			LastStatus=103 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		printf '%s\n' "Starting server VPN service"
		ServiceAction start $OpenvpnServerName "$ProfileName"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		RemainForStart=$WaitForStart
		ServiceAction status $OpenvpnServerName "$ProfileName" >/dev/null 2>&1
		LastStatus=$?
		while [ $LastStatus -eq 3 ] && [ $RemainForStart -gt 0 ] ; do
			printf '.' 1>&2
			sleep 1
			ServiceAction status $OpenvpnServerName "$ProfileName" >/dev/null 2>&1
			LastStatus=$?
			RemainForStart=$((RemainForStart - 1))
		done
		if [ $RemainForStart -ne $WaitForStart ] ; then
			printf '\n' 1>&2
		fi
		if [ $LastStatus -eq 0 ] ; then
			ServiceAction enable $OpenvpnServerName "$ProfileName"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		else
			printf '%s\n' "${sERROR}E: Service ${OpenvpnServerName}@${ProfileName} did not launch ${ParO}${StatusCode}${ParC}.${fRESET}" 1>&2
			LastStatus=108 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
	fi
	return $StatusCode
}

Client_prepare ()
{
	local LastStatus=0
	local StatusCode=0

	CommonPrepare
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	return $StatusCode
}

Server_remove ()
# Delete port service
{
	local LastStatus=0
	local StatusCode=0

	printf '%s\n' "${sERROR}E: Action, parameter or feature not yet implemented${fRESET}" 1>&2
	LastStatus=63 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	return $StatusCode
}

Server_ServiceAction ()
# Start/stop/restart port service
{
	local Action="$1"
	local ProfileName="$2"
	local LastStatus=0
	local StatusCode=0

	if [ "$ProfileName" != "" ] ; then
		ServiceAction $Action $OpenvpnServerName "$ProfileName"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	else
		printf '%s\n' "${sERROR}E: Port service not specified.${fRESET}" 1>&2
		LastStatus=81 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		printf '\n'
		Status servers
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	return $StatusCode
}

Server_start ()
{
	Server_ServiceAction start "$@"
	return $?
}

Server_stop ()
{
	Server_ServiceAction stop "$@"
	return $?
}

Server_restart ()
{
	Server_ServiceAction restart "$@"
	return $?
}

Server_enable ()
{
	Server_ServiceAction enable "$@"
	return $?
}

Server_disable ()
{
	Server_ServiceAction disable "$@"
	return $?
}

Server_list ()
{
	local ServedList=''
	
	ServedList="$(ServiceProfiles)"
	if [ "$ServedList" != "" ] ; then
		printf '%s\n' "${sHEAD1}CONFIGURED VPN SERVICES TO PROVIDE:${fRESET}"
		printf '%s\n' "$ServedList" | sed -e "s|^|${sVALUE}|g" | sed -e "s|$|${fRESET}|g"
	else
		printf '%s\n' "${sINFO}I: No profiles registered as VPN service to allow connections from remote clients.${fRESET}"
	fi
}

Server_tlsversionmin ()
# server tls-version-min
{
	local VersionsList=''
	local Value=''
	
	VersionsList="$(openssl ciphers -v | tr -s '\t' ' ' | cut -f 2 -d ' ' | sort -u)"
	Value="$(printf '%s' "$VersionsList" | grep -ie '^TLS' | head -n 1 | sed -e 's|TLS||gi' -e 's|v||gi')"
	if [ "$Value" != "" ] && [ "$(printf '%s' "$Value" | grep -e '\.')" = "" ] ; then
		Value="${Value}.0"
	fi
	if [ "$Value" != "" ] ; then printf '%s\n' "tls-version-min $Value" ; fi
}

Server_tlsversionmax ()
# server tls-version-max
{
	local VersionsList=''
	local Value=''
	
	VersionsList="$(openssl ciphers -v | tr -s '\t' ' ' | cut -f 2 -d ' ' | sort -u)"
	Value="$(printf '%s' "$VersionsList" | grep -ie '^TLS' | tail -n 1 | sed -e 's|TLS||gi' -e 's|v||gi')"
	if [ "$Value" != "" ] && [ "$(printf '%s' "$Value" | grep -e '\.')" = "" ] ; then
		Value="${Value}.0"
	fi
	if [ "$Value" != "" ] ; then printf '%s\n' "tls-version-max $Value" ; fi
}

Server_tlscipher ()
# server tls-cipher
{
	local AllCiphers=''
	local CurCipher=''
	local TotalLength=0
	local Finish=0
	local ChainMaxLength=''
	local ValueLabel='tls-cipher '
	local Value=''
	
	ChainMaxLength=$((254 - ${#ValueLabel}))
	AllCiphers="$(openvpn --show-tls | grep -ve ' ' -ve '(')"
	for CurCipher in $AllCiphers ; do
		if [ $Finish -eq 0 ] && [ "$(printf '%s' "$CurCipher" | grep -ie AES | grep -ie CBC | grep -ive ECDSA -ive ECDHE)" != "" ] ; then
			if [ "$Value" != "" ] ; then Value="${Value}:" ; fi
			Value="${Value}${CurCipher}"
			if [ ${#Value} -gt $ChainMaxLength ] ; then
				Value="$(printf '%s' "$Value" | sed -e "s|:${CurCipher}$||")"
				Finish=1
			fi
		fi
	done
	for CurCipher in $AllCiphers ; do
		if [ $Finish -eq 0 ] && [ "$(printf '%s' "$CurCipher" | grep -ie AES | grep -ie CBC | grep -ive ECDSA -ive ECDHE)" = "" ] ; then
			if [ "$Value" != "" ] ; then Value="${Value}:" ; fi
			Value="${Value}${CurCipher}"
			if [ ${#Value} -gt $ChainMaxLength ] ; then
				Value="$(printf '%s' "$Value" | sed -e "s|:${CurCipher}$||")"
				Finish=1
			fi
		fi
	done
	if [ "$Value" != "" ] ; then printf '%s\n' "${ValueLabel}${Value}" ; fi
}

Server_ciphermodes ()
# server cipher
{
	local AllCiphers=''
	local CurCipher=''
	local DataValues=''
	local Value=''
	
	AllCiphers="$(openvpn --show-ciphers | grep -e '-.* .*bit.*)' | cut -f 1 -d ' ')"
	for CurCipher in $AllCiphers ; do
		if [ "$(printf '%s' "$CurCipher" | grep -ie AES | grep -ie CBC)" != "" ] ; then
			printf '%s\n' "${sINFO}cipher ${CurCipher}${fRESET}"
			if [ ${#DataValues} -lt 100 ] ; then
				if [ "$DataValues" != "" ] ; then DataValues="${DataValues}:" ; fi
				DataValues="${DataValues}${CurCipher}"
			fi
		else
			printf '%s\n' "cipher ${CurCipher}"
		fi
	done
	printf '%s\n' "#data-ciphers $DataValues"
}

Server_showciphers ()
{
	Server_ciphermodes "$@"
	return $?
}

Server_cipher ()
{
	Server_ciphermodes "$@"
	return $?
}

Server ()
{
	local CalledFunction="$1"
	if [ $# -gt 0 ] ; then shift ; fi
	local AvailableFormat_B=''
	local AvailableFormat_E=''
	local LastStatus=0
	local StatusCode=0

	CalledFunction="$(printf '%s' "$CalledFunction" | sed -e 's|-||g')"
	if [ "$(cat "$MeCallFile" | grep -e "^Server_${CalledFunction} ()" -e "^Server_${CalledFunction}()")" != "" ] ; then
		Server_$CalledFunction "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	else
		if [ "$CalledFunction" != "" ] && [ "$CalledFunction" != "--help" ] ; then
			printf '%s\n' "${sERROR}E: Unknown function or action for \"server\"${fRESET}" 1>&2
			LastStatus=87 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			printf '\n'
		else
			LastStatus=254 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
		if [ "$(ServiceProfiles)" = "" ] ; then
			AvailableFormat_B="${sDISABLED}"
			AvailableFormat_E="${fRESET}"
		fi
		printf '%s\n' "${sHEAD1}Actions for Virtual Private Network service:${fRESET}"
		printf '%s\n' ""
		printf '%s\n' "	server create           ${ParO}Setup VPN service per port${ParC}"
		printf '%s\n' "${AvailableFormat_B}	server remove   ${ParO}Delete port service${ParC}${AvailableFormat_E}"
		printf '%s\n' "${AvailableFormat_B}	server start    ${ParO}Launch port service to be available${ParC}${AvailableFormat_E}"
		printf '%s\n' "${AvailableFormat_B}	server stop     ${ParO}Shutdown port service${ParC}${AvailableFormat_E}"
		printf '%s\n' "${AvailableFormat_B}	server restart  ${ParO}Stop+Start port service${ParC}${AvailableFormat_E}"
		printf '%s\n' "${AvailableFormat_B}	server enable   ${ParO}Make a VPN service autostart on system boot${ParC}${AvailableFormat_E}"
		printf '%s\n' "${AvailableFormat_B}	server disable  ${ParO}Make a VPN service to be only manual${ParC}${AvailableFormat_E}"
		printf '%s\n' "	server tls-version-min  ${ParO}Show the minimum TLS version we will accept from the peer${ParC}"
		printf '%s\n' "	server tls-version-max  ${ParO}Show the maximum TLS version we will use${ParC}"
		printf '%s\n' "	server tls-cipher       ${ParO}Show a brief list of most compatible TLS ciphers welcome from a peer${ParC}"
		printf '%s\n' "	server cipher-modes     ${ParO}Show a list of cipher algorithms to encrypt packets${ParC}"
		printf '%s\n' "	servers list            ${ParO}Show registered VPN services per port${ParC}"
	fi
	return $StatusCode
}

Servers ()
{
	Server "$@"
	return $?
}

Client_account_ServiceAction ()
# Start/stop/restart client access
{
	local Action="$1"
	local ProfileName="$2"
	local LastStatus=0
	local StatusCode=0

	if [ "$ProfileName" != "" ] ; then
		if [ -f "/lib/systemd/system/${OpenvpnClientName}@.service" ] ; then
			ServiceAction $Action $OpenvpnClientName "$ProfileName"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		else
			ServiceAction $Action openvpn "$ProfileName"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
	else
		printf '%s\n' "${sERROR}E: VPN Client access not specified.${fRESET}" 1>&2
		LastStatus=81 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		printf '\n'
		Status servers
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	return $StatusCode
}

Client_account_enable ()
{
	Client_account_ServiceAction enable "$@"
	return $?
}

Client_account_disable ()
{
	Client_account_ServiceAction disable "$@"
	return $?
}

Client_account_start ()
{
	Client_account_ServiceAction start "$@"
	return $?
}

Client_start ()
{
	Client_account_start "$@"
	return $?
}

Client_account_stop ()
{
	Client_account_ServiceAction stop "$@"
	return $?
}

Client_stop ()
{
	Client_account_stop "$@"
	return $?
}

Client_account_restart ()
{
	Client_account_ServiceAction restart "$@"
	return $?
}

Client_restart ()
{
	Client_account_restart "$@"
	return $?
}

Client_account_create ()
# Create a client access to this server
{
	local ServiceProfile="$1"
	local Client_Name="$2"
	local Client_Password="$3"
	local Client_IP="$4"
	local ServerAsGateway="$5"
	local Topology=''
	local GatewayComment=''
	local Server_IP=''
	local Server_Mask=''
	local Server_Mask_Bits=''
	local Server_BaseNet=''
	local PreviousDir=''
	local ServedList=''
	local ServiceProfileFile=''
	local ClientDataDir=''
	local AccountProfileFile=''
	local ProfileFileForClient=''
	local Server_CN=''
	local Server_CN_ForClient=''
	local ProfileName_ForClient=''
	local ServerCertificate=''
	local Port=''
	local Protocol=''
	local DevType='tun'
	local CommentCerts=''
	local CommentPort=''
	local CommentRemote=''
	local CommentUserPass=''
	local CacertFilename=''
	local ClientPrivFilename=''
	local ClientPubFilename=''
	local RemoteAddress=''
	local ClientConfig_FromServerFile=''
	local RegisteredClientEndpoints=''
	local CurClientEndpoints=''
	local LastStatus=0
	local StatusCode=0

	if [ $StatusCode -eq 0 ] ; then
		ServedList="$(ServiceProfiles)"
		if [ "$ServedList" != "" ] ; then
			if [ "$ServiceProfile" = "." ] ; then
				if [ "$ServedList" = "$(printf '%s' "$ServedList" | head -n 1)" ] ; then
					# Accept default specification if there is only one.
					ServiceProfile="$ServedList"
				else
					ServiceProfile=''
				fi
			fi
			if [ "$ServiceProfile" != "" ] && [ "$(printf '%s' "$ServedList" | grep -e "^${ServiceProfile}$")" = "" ] ; then
				printf '%s\n' "${sERROR}E: Service profile \"${ServiceProfile}\" not found.${fRESET}" 1>&2
				printf '%s\n' "   Existing VPN service profiles:" 1>&2
				printf '%s\n' "$ServedList" | sed -e 's|^|\t|' 1>&2
				LastStatus=101 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			else
				if [ "$ServiceProfile" = "" ] ; then
					printf '%s\n' "Existing VPN service profiles:"
					printf '%s\n' "$ServedList" | sed -e 's|^|\t|'
					printf '%s' "Enter which one this account will be associated to: "
					read ServiceProfile
				fi
				ServiceProfileFile="/etc/openvpn/server/${ServiceProfile}.conf"
				if [ ! -f "$ServiceProfileFile" ] ; then
					printf '%s\n' "${sERROR}E: Service profile file \"${ServiceProfileFile}\" not found.${fRESET}" 1>&2
					LastStatus=95 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
			fi
		else
			printf '%s\n' "${sERROR}E: No VPN service configured. Please setup first with action:${fRESET}" 1>&2
			printf '%s\n' "   server create" 1>&2
			LastStatus=101 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		Server_prepare
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		if [ "$Client_Name" = "" ] ; then
			printf '%s\n' "Profile and/or login Name for client access."
			printf '%s' "${ParO}mail-address allowed characters${ParC}: "
			read Client_Name
		fi
		if [ "$Client_Name" != "" ] ; then
			ClientDataDir="/etc/openvpn/data-for-clients/${ServiceProfile}/${Client_Name}"
			if [ -e "$ClientDataDir" ] || [ -e /etc/openvpn/easy-rsa/pki/reqs/client1.req ] || [ -e /etc/openvpn/easy-rsa/pki/private/client1.key ] || [ -e /etc/openvpn/easy-rsa/pki/issued/client1.crt ] ; then
				printf '%s\n' "${sERROR}E: Account data already exists as:${fRESET}" 1>&2
				if [ -e "$ClientDataDir" ] ; then printf '%s\n' "   $ClientDataDir" 1>&2 ; fi
				if [ -e /etc/openvpn/easy-rsa/pki/reqs/client1.req ] ; then printf '%s\n' "   /etc/openvpn/easy-rsa/pki/reqs/client1.req" 1>&2 ; fi
				if [ -e /etc/openvpn/easy-rsa/pki/private/client1.key ] ; then printf '%s\n' "   /etc/openvpn/easy-rsa/pki/private/client1.key" 1>&2 ; fi
				if [ -e /etc/openvpn/easy-rsa/pki/issued/client1.crt ] ; then printf '%s\n' "   /etc/openvpn/easy-rsa/pki/issued/client1.crt" 1>&2 ; fi
				LastStatus=103 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
		else
			printf '%s\n' "${sERROR}E: Client name not specified to create a VPN access.${fRESET}" 1>&2
			LastStatus=82 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		if [ "$Client_Password" = "" ] ; then
			if [ "$(cat "$ServiceProfileFile" | tr -s '\t' ' ' | grep -e '^auth-user-pass-verify ')" = "" ] ; then
				printf '%s\n' "${sINFO}I: This VPN service is not currently using username/password verification, but it's recommended to be configured for anytime it can be required.${fRESET}"
			fi
			printf '%s\n' ""
			printf '%s\n' "${ParO}You can specify \".\" or \"+\" for autogenerated or autogenerated+${ParC}"
			printf '%s\n' "Password for account \"${Client_Name}\" ${ParO}visible here${ParC}"
			read Client_Password
		fi
		if [ "$Client_Password" = "." ] || [ "$Client_Password" = "+" ] ; then
			if [ "$Client_Password" = "+" ] ; then
				if Is_Executable apg ; then
					Client_Password="$(apg -a 0 -M NCL -m 8 -x 10 | head -n 1).$(apg -a 0 -M NCL -m 8 -x 10 | head -n 1)"
				else
					printf '%s\n' "${sWARN}W: apg utility not found to generate better passwords.${fRESET}" 1>&2
					Client_Password="$(dd if=/dev/urandom count=1 2> /dev/null | md5sum | tr -s '\t' ' ' | cut -f 1 -d ' ')"
				fi
			else
				if Is_Executable apg ; then
					Client_Password="$(apg -a 0 -M NCL -m 8 -x 10 | head -n 1)"
				else
					printf '%s\n' "${sWARN}W: apg utility not found to generate better passwords.${fRESET}" 1>&2
					Client_Password="$(dd if=/dev/urandom count=1 2> /dev/null | md5sum | tr -s '\t' ' ' | cut -f 1 -d ' ' | cut -c 1-10)"
				fi
			fi
			printf '%s\n' "Generating new random password: $Client_Password"
		fi
		Server_IP="$(cat "$ServiceProfileFile" | tr -s '\t' ' ' | grep -e '^ifconfig ' | cut -f 2 -d ' ' | tail -n 1)"
		Server_Mask="$(cat "$ServiceProfileFile" | tr -s '\t' ' ' | grep -e '^ifconfig ' | cut -f 3 -d ' ' | tail -n 1)"
		if [ "$Server_IP" = "" ] ; then
			Server_IP="$(cat "$ServiceProfileFile" | tr -s '\t' ' ' | grep -e '^server ' | cut -f 2 -d ' ' | tail -n 1)"
			Server_Mask="$(cat "$ServiceProfileFile" | tr -s '\t' ' ' | grep -e '^server ' | cut -f 3 -d ' ' | tail -n 1)"
		fi
		if [ "$Server_IP" = "" ] ; then
			Server_IP=100.64.0.1
			Server_Mask=255.255.255.0
		fi
		if [ "$(EsIP "$Server_Mask")" != "1" ] ; then Server_Mask=255.255.255.255 ; fi
#		Server_BaseNet="$(IPBaseXarxa "$Server_IP" "$Server_Mask")"
		Server_Mask_Bits="$(BitsMascara "$Server_Mask")"
		Server_BaseNet="$(IPv4NetworkAddress "${Server_IP}/${Server_Mask_Bits}")"
		if [ "$(EsIP "$Client_IP")" != "1" ] ; then
			CurServed_ClientAccounts="$(ls -1 "/etc/openvpn/ccd/${ServiceProfile}"/ 2>/dev/null)"
			IFS="$(printf '\n\b')" ; for CurAccount in $CurServed_ClientAccounts ; do unset IFS
				CurClientEndpoints="$(cat "/etc/openvpn/ccd/${ServiceProfile}/${CurAccount}" | tr -s '\t' ' ' | grep -ie '^ifconfig-push ' | cut -f 2 -d ' ')"
				RegisteredClientEndpoints="${RegisteredClientEndpoints} ${CurAccount}:${CurClientEndpoints}"
			done
			if [ "$RegisteredClientEndpoints" != "" ] ; then
				printf '%s\n' "Already registered clients endpoints for service \"${ServiceProfile}\":${RegisteredClientEndpoints}"
			fi
			printf '%s\n' "Server endpoint IP is $Server_IP with mask $Server_Mask"
			printf '%s\n' "Enter a compatible IP address to assign to client endpoint:"
			read Client_IP
		fi
		if [ "$ServerAsGateway" != "y" ] && [ "$ServerAsGateway" != "Y" ] && [ "$ServerAsGateway" != "s" ] && [ "$ServerAsGateway" != "S" ] && [ "$ServerAsGateway" != "n" ] && [ "$ServerAsGateway" != "N" ] ; then
			printf '%s' "Should client use this service as default gateway [Y/n]? "
			ServerAsGateway="$(RespostaLletra)"
		fi
		if [ "$ServerAsGateway" = "N" ] ; then ServerAsGateway='n' ; fi
		if [ "$ServerAsGateway" != "n" ] ; then
			ServerAsGateway='y'
			GatewayComment=''
			OpenMaskComment=''
			ClosedMaskComment='#'
		else
			GatewayComment='#'
			OpenMaskComment='#'
			ClosedMaskComment=''
		fi
		if [ "$KeysType" = "static" ] ; then
			export EASYRSA_CERT_EXPIRE=$ValidDays
			printf '\n'
			printf '%s\n' "CREATING CERTIFICATE FOR CLIENT, valid for $((EASYRSA_CERT_EXPIRE/365)) years."
#			printf '%s\n' "${ParO}first passwords are for VPN client key, next one is from CA issuer${ParC}"
			printf '%s\n' "${ParO}asked pass phrase if from CA issuer${ParC}"
#			printf '%s\n' "W: Signing with passwordless CA" 1>&2
			PreviousDir="$(pwd)"
			cd /etc/openvpn/easy-rsa
#			printf '%s\n' "PEM pass phrase: It's the needed for VPN client connects to server."
#			printf '%s\n' "You'll probably will be asked for Certification Authority pass phrase ${ParO}CA.key${ParC}"
			./easyrsa build-client-full "$Client_Name" nopass
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			cd "$PreviousDir"
			Client_FilePub="/etc/openvpn/easy-rsa/pki/issued/${Client_Name}.crt"
			Client_FilePriv="/etc/openvpn/easy-rsa/pki/private/${Client_Name}.key"
		else
			sesele self-cn '' "$Client_Name"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
#			Client_FilePub="/etc/ssl/certs/${Server_CN}.pem"
#			Client_FilePriv="/etc/ssl/private/${Server_CN}.key"
			Client_FilePub="/etc/ssl/certs/${Client_Name}.pem"
			Client_FilePriv="/etc/ssl/private/${Client_Name}.key"
		fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		DevType="$(cat "$ServiceProfileFile" | cut -f 1 -d '#' | tr -s '\t' ' ' | grep -e '^dev-type ' | cut -f 2 -d ' ')"
		ServerCertificate="$(cat "$ServiceProfileFile" | cut -f 1 -d '#' | tr -s '\t' ' ' | grep -e '^cert ' | cut -f 2- -d ' ')"
		Server_CN="$(openssl x509 -subject -nameopt RFC2253,sep_multiline,sname -noout -in "$ServerCertificate" | tr -s '\t' ' ' | sed -e 's|^ ||g' | grep -ie 'CN=' | cut -f 2- -d '=')"
		if [ "$Server_CN" != "" ] ; then
			CommentRemote=''
			RemoteAddress="$Server_CN"
		else
			LogProgram 2 "W: Common Name could not be detected from service profile's certificate: $ServerCertificate" 1>&2
			CommentRemote='#'
			RemoteAddress="vpn.example.net"
		fi
		Server_CN_ForClient="$Server_CN"
		if [ "$Server_CN_ForClient" = "" ] ; then Server_CN_ForClient='server' ; fi
		ProfileName_ForClient="$(printf '%s' "$Server_CN_ForClient" | cut -f 1 -d '.')"
		if Is_IntegerNr "$ProfileName_ForClient" ; then ProfileName_ForClient="$Server_CN_ForClient" ; fi
		ProfileName_ForClient="${ProfileName_ForClient}-${Client_Name}"
		Port="$(cat "$ServiceProfileFile" | cut -f 1 -d '#' | tr -s '\t' ' ' | grep -e '^port ' | cut -f 2 -d ' ')"
		if ! Is_IntegerNr "$Port" ; then
			Port="$(cat "$ServiceProfileFile" | cut -f 1 -d '#' | tr -s '\t' ' ' | grep -e '^local ' | cut -f 3 -d ' ')"
		fi
		if Is_IntegerNr "$Port" ; then
			CommentPort=''
		else
			LogProgram 2 '%s\n' "W: UDP/TCP port could not be detected from service profile ${ServiceProfile}. Default is 1194." 1>&2
			CommentPort='#'
			Port=1194
		fi
		Protocol="$(cat "$ServiceProfileFile" | cut -f 1 -d '#' | tr -s '\t' ' ' | grep -e '^proto ' | cut -f 2 -d ' ' | cut -f 1 -d '-')"
		if [ "$Protocol" != "" ] ; then
			CommentProtocol=''
		else
			LogProgram 2 '%s\n' "W: Communication protocol could not be detected from service profile ${ServiceProfile}. Default is UDP." 1>&2
			Protocol='udp'
			CommentProtocol='#'
		fi
		if [ "$Protocol" = "tcp" ] || [ "$Protocol" = "tcp-server" ] ; then Protocol='tcp-client' ; fi
		Topology="$(cat "$ServiceProfileFile" | cut -f 1 -d '#' | tr -s '\t' ' ' | grep -e '^topology ' | cut -f 2 -d ' ')"
		if [ "$Topology" != "net30" ] && [ "$Topology" != "p2p" ] && [ "$Topology" != "subnet" ] ; then
			# OpenVPN's default
			Topology='net30'
		fi
		if [ "$Topology" = "subnet" ] ; then
			ifconfigLine="ifconfig-push $Server_IP $Server_Mask"
		else
			ifconfigLine="ifconfig-push $Client_IP $Server_IP"
		fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		MkdirPP "$ClientDataDir" :openvpn u=rwX,g=rX,o= g+s
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		chmod u+rwX,go= "$ClientDataDir"
		ProfileFileForClient="${ClientDataDir}/${ProfileName_ForClient}.conf"
		CertsIssuer_CA_CN="$(openssl x509 -subject -nameopt RFC2253,sep_multiline,sname -noout -in "$CertsIssuer_CA_FilePub" | tr -s '\t' ' ' | sed -e 's|^ ||g' | grep -ie 'CN=' | cut -f 2- -d '=')"
		if [ "$CertsIssuer_CA_CN" != "" ] ; then
			CacertFilename="CA@${CertsIssuer_CA_CN}.crt"
		else
			CacertFilename="CA@${CertsIssuer_CA_CN}.crt"
		fi
		cp "$CertsIssuer_CA_FilePub" "${ClientDataDir}/${CacertFilename}"
		ClientPrivFilename="$(basename "$Client_FilePriv")"
		cp "$Client_FilePriv" "${ClientDataDir}/${ClientPrivFilename}"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		ClientPubFilename="$(basename "$Client_FilePub")"
		cp "$Client_FilePub" "${ClientDataDir}/${ClientPubFilename}"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		if [ "$(cat "$ServiceProfileFile" | tr -s '\t' ' ' | grep -e '^auth-user-pass-verify ')" != "" ] ; then
			CommentCerts='#'
			CommentUserPass=''
		else
			CommentCerts=''
			CommentUserPass='#'
		fi
		MkfilePP "/etc/openvpn/accounts/${Client_Name}.auth" root:openvpn u=rw,g=r,o=
		printf '%s\n' "$Client_Name" > "/etc/openvpn/accounts/${Client_Name}.auth"
		printf '%s' "$Client_Password" | md5sum | tr -s '\t' ' ' | cut -f 1 -d ' ' >> "/etc/openvpn/accounts/${Client_Name}.auth"
		MkfilePP "${ClientDataDir}/${ProfileName_ForClient}.auth" root:openvpn u=rw,g=r,o=
		printf '%s\n' "$Client_Name" > "${ClientDataDir}/${ProfileName_ForClient}.auth"
		printf '%s\n' "$Client_Password" >> "${ClientDataDir}/${ProfileName_ForClient}.auth"
		chown -R root:openvpn "$ClientDataDir"
		chmod -R u=rwX,g=rX,o= "$ClientDataDir"
		chmod g+x "$ClientDataDir"
		OpenvpnVersion="$(openvpn --version | grep -ie '^OpenVPN [0-9]' | cut -f 2 -d ' ')"
	fi
	if [ $StatusCode -eq 0 ] ; then
		cat /dev/null > "$ProfileFileForClient"
		printf '%s\n' "# Changes in this profile apply by restarting service:
# systemctl restart openvpn-client@${ProfileName_ForClient}

user openvpn
group openvpn
${CommentPort}port $Port
${CommentProtocol}proto $Protocol
dev-type $DevType
dev vpn-${DevType}0
persist-tun

client
${CommentRemote}remote $RemoteAddress
#nobind

remote-cert-tls server
ca   /etc/openvpn/remotes-data/${ProfileName_ForClient}/${CacertFilename}
key /etc/openvpn/remotes-data/${ProfileName_ForClient}/${ClientPrivFilename}
cert  /etc/openvpn/remotes-data/${ProfileName_ForClient}/${ClientPubFilename}  # keep secret
persist-key
tls-exit
$(Server_tlsversionmin)
$(Server_tlsversionmax)
# Uncomment this if this message is logged: \"TLS error: Unsupported protocol\"
#$(Server_tlscipher)

# username/password on 2 lines (clear text; keep secret):
${CommentUserPass}auth-user-pass /etc/openvpn/remotes-data/${ProfileName_ForClient}/${ProfileName_ForClient}.auth

script-security 2
# up/down script to tunnelize traffic of this host
#up \"${MeCallFile} gw-updown +\"
down-pre
#down \"${MeCallFile} gw-updown +\"

keepalive 10 120
#status log/openvpn-status.log
#verb 3  # verbose mode {0=OnlyFatal 1-4=Normal 5=IncludePacketSignals 6-11=Debug} Default is 0
log-append /var/log/openvpn/${ProfileName_ForClient}.log
machine-readable-output
#compat-mode $OpenvpnVersion
#tls-cert-profile insecure
#providers legacy default
#data-ciphers" >> "$ProfileFileForClient"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		ClientConfig_FromServerFile="$(cat "$ServiceProfileFile" | cut -f 1 -d '#' | tr -s '\t' ' ' | grep -e '^client-config-dir ' | cut -f 2- -d ' ')"
		if [ -d "$ClientConfig_FromServerFile" ] ; then
			ClientConfig_FromServerFile="${ClientConfig_FromServerFile}/${Client_Name}"
			if [ ! -f "$ClientConfig_FromServerFile" ] ; then
				MkfilePP "$ClientConfig_FromServerFile" root:openvpn u=rw,g=r,o=
				printf '%s\n' "# Client-specific configuration options to be parsed by OpenVPN server ${ParO}Live applied file; no need to restart server${ParC}
# The following options are legal in a client-specific context: --push , --push-reset , --push-remove , --iroute , --ifconfig-push , and --config .
# This is a partial list of options which can currently be pushed: --route , --route-gateway , --route-delay , --redirect-gateway , --ip-win32 , --dhcp-option , --inactive , --ping , --ping-exit ,
#   --ping-restart , --setenv , --auth-token , --persist-key , --persist-tun , --echo , --comp-lzo , --socket-flags , --sndbuf , --rcvbuf

push \"topology ${Topology}\"
#[p2p] Client and server IPs
#[p2p]ifconfig-push $Client_IP $Server_IP
# Client IP and mask
# Uncomment this to client uses this service as default gateway:
${OpenMaskComment}ifconfig-push $Client_IP $Server_Mask
# Comment this to client uses this service as default gateway:
${ClosedMaskComment}ifconfig-push $Client_IP 255.255.255.255

iroute $Server_BaseNet $Server_Mask

# Uncomment this to client uses this service as default gateway:
${GatewayComment}push \"route-gateway ${Server_IP}\"
# Uncomment this to client uses this service as default gateway:
${GatewayComment}push redirect-gateway
" > "$ClientConfig_FromServerFile"
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				if [ $StatusCode -eq 0 ] ; then
					printf '%s\n' "${sINFO}I: Client configuration from server can be customized at $ClientConfig_FromServerFile${fRESET}"
					printf '%s\n' "   Useful data for client is saved at ${ClientDataDir}/"
					printf '%s\n' "${sINFO}I: Get whole data for client with this command on this server:${fRESET}"
					printf '%s\n' "   $ProgramName client account export $ServiceProfile $Client_Name ${ProfileName_ForClient}.tar.gz"
#					if [ "$(command -v durruter 2>/dev/null)" != "" ] ; then
					if [ "$DurruterExecutable" != "" ] ; then
						printf '%s\n' "${sINFO}I: You can allow Internet access for this client with one of these commands on this server:${fRESET}"
						TheDefaultNIC="$(ip r l | grep -ie '^default ' | tr -s '\t' ' ' | tr -s ' ' '\n' | tail -n 1)"
						WanIP="$(IpDeDispositivo "$TheDefaultNIC")"
						printf '%s\n' "   durruter nat create ${Client_IP}/32 default default \"${Client_Name}\""
						printf '%s\n' "   durruter nat create ${Client_IP}/32 $TheDefaultNIC $WanIP \"${Client_Name}\""
						printf '%s\n' "   ${ParO}customize WAN IP and/or NIC${ParC}"
						printf '%s\n' "${sINFO}I: You can forward WAN ports to this client with one of these commands on this server:${fRESET}"
						printf '%s\n' "   durruter dnat create default any any default $Client_IP = \"${Client_Name}\""
						printf '%s\n' "   durruter dnat create any any any $WanIP $Client_IP = \"${Client_Name}\""
						printf '%s\n' "   ${ParO}customize WAN IP and/or NIC${ParC}"
					else
						printf '%s\n' "${sINFO}I: You can use iptables to forward NAT/DNAT Internet traffic from/to client.${fRESET}"
					fi
					printf '%s\n' "${sINFO}I: After exporting from here, access data can be deployed at client host with this command there:${fRESET}"
					printf '%s\n' "   $ProgramName client account import ${ProfileName_ForClient}.tar.gz"
				fi
			else
				printf '%s\n' "${sINFO}I: Client configuration from server already exists as $ClientConfig_FromServerFile${fRESET}"
			fi
		fi
	fi
	return $StatusCode
}

Server_account_create ()
{
	Client_account_create "$@"
	return $?
}

Client_account_remove ()
# SERVER: Revoke a client access to this server
# CLIENT: Disconnect and remove access data to a service
{
	local Client_Name="$1"
	local SelectedFiles=''
	local CurFile=''
	local CurService=''
	local LastStatus=0
	local StatusCode=0

	if [ "$Client_Name" != "" ] ; then
		SelectedFiles="$(find /etc/openvpn/ccd 2>/dev/null | grep -e "/${Client_Name}$")"
		if [ -f "/etc/openvpn/client/${Client_Name}.conf" ] || [ -f "/etc/openvpn/accounts/${Client_Name}.auth" ] || [ "$SelectedFiles" = "" ] ; then
			if [ -f "/etc/openvpn/client/${Client_Name}.conf" ] ; then
				# Client access
				if [ $StatusCode -eq 0 ] ; then
					printf '%s\n' "Disabling client access from system start"
					ServiceAction disable $OpenvpnClientName "$Client_Name"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
				if [ $StatusCode -eq 0 ] ; then
					printf '%s\n' "Stopping client access"
					ServiceAction stop $OpenvpnClientName "$Client_Name"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
				if [ $StatusCode -eq 0 ] ; then
					printf '%s\n' "Removing client access profile"
					rm "/etc/openvpn/client/${Client_Name}.conf"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
				if [ -e "/etc/openvpn/remotes-data/${Client_Name}" ] ; then
					if [ $StatusCode -eq 0 ] ; then
						printf '%s\n' "Removing access data about remote service"
						rm -rf "/etc/openvpn/remotes-data/${Client_Name}"
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					else
						printf '%s\n' "W: Access data about remote service not removed at:" 1>&2
						printf '%s\n' "   /etc/openvpn/remotes-data/${Client_Name}"
					fi
				fi
			else
				# Service account
				if [ $StatusCode -eq 0 ] ; then
					printf '%s\n' "Removing service account user/password permissions"
					rm -f "/etc/openvpn/accounts/${Client_Name}.auth"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
				SelectedFiles="$(find /etc/openvpn/easy-rsa/pki/issued 2>/dev/null | grep -e "/${Client_Name}\.crt$" -e "/${Client_Name}\.req$" -e "/${Client_Name}\.key$")"
				if [ "$SelectedFiles" != "" ] ; then
					if [ $StatusCode -eq 0 ] ; then
						printf '%s\n' "Removing service account certificate/key files"
						IFS="$(printf '\n\b')" ; for CurFile in $SelectedFiles ; do unset IFS
							rm "$CurFile"
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						done
					else
						IFS="$(printf '\n\b')" ; for CurFile in $SelectedFiles ; do unset IFS
							printf '%s\n' "W: Certification file not removed: $CurFile" 1>&2
						done
					fi
				fi
				SelectedFiles="$(find /etc/openvpn/easy-rsa/pki/reqs 2>/dev/null | grep -e "/${Client_Name}\.crt$" -e "/${Client_Name}\.req$" -e "/${Client_Name}\.key$")"
				if [ "$SelectedFiles" != "" ] ; then
					if [ $StatusCode -eq 0 ] ; then
						IFS="$(printf '\n\b')" ; for CurFile in $SelectedFiles ; do unset IFS
							rm "$CurFile"
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						done
					else
						IFS="$(printf '\n\b')" ; for CurFile in $SelectedFiles ; do unset IFS
							printf '%s\n' "W: Certification file not removed: $CurFile" 1>&2
						done
					fi
				fi
				SelectedFiles="$(find /etc/openvpn/easy-rsa/pki/private 2>/dev/null | grep -e "/${Client_Name}\.crt$" -e "/${Client_Name}\.req$" -e "/${Client_Name}\.key$")"
				if [ "$SelectedFiles" != "" ] ; then
					if [ $StatusCode -eq 0 ] ; then
						IFS="$(printf '\n\b')" ; for CurFile in $SelectedFiles ; do unset IFS
							rm "$CurFile"
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						done
					else
						IFS="$(printf '\n\b')" ; for CurFile in $SelectedFiles ; do unset IFS
							printf '%s\n' "W: Certification file not removed: $CurFile" 1>&2
						done
					fi
				fi
				SelectedFiles="$(find /etc/openvpn/ccd 2>/dev/null | grep -e "/${Client_Name}$")"
				if [ "$SelectedFiles" != "" ] ; then
					if [ $StatusCode -eq 0 ] ; then
						printf '%s\n' "Removing specific account parameters:"
						IFS="$(printf '\n\b')" ; for CurFile in $SelectedFiles ; do unset IFS
							CurService="$(printf '%s' "$CurFile" | sed -e 's|^/etc/openvpn/ccd/||g' | cut -f 1 -d '/')"
							printf '%s\n' "	client-config-dir from service: $CurService"
							rm "$CurFile"
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						done
					else
						IFS="$(printf '\n\b')" ; for CurFile in $SelectedFiles ; do unset IFS
							CurService="$(printf '%s' "$CurFile" | sed -e 's|^/etc/openvpn/ccd/||g' | cut -f 1 -d '/')"
							printf '%s\n' "W: client-config-dir from service $CurService not removed: $CurFile" 1>&2
						done
					fi
				fi
				SelectedFiles="$(find /etc/openvpn/data-for-clients 2>/dev/null | grep -e "^/etc/openvpn/data-for-clients/..*/${Client_Name}$")"  #do
				if [ "$SelectedFiles" != "" ] ; then
					if [ $StatusCode -eq 0 ] ; then
						printf '%s\n' "Removing exportable data for client access:"
						IFS="$(printf '\n\b')" ; for CurFile in $SelectedFiles ; do unset IFS
							CurService="$(printf '%s' "$CurFile" | sed -e 's|^/etc/openvpn/data-for-clients/||g' | cut -f 1 -d '/')"
							printf '%s\n' "	data-for-clients from service: $CurService"
							rm -r "$CurFile"
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						done
					else
						IFS="$(printf '\n\b')" ; for CurFile in $SelectedFiles ; do unset IFS
							CurService="$(printf '%s' "$CurFile" | sed -e 's|^/etc/openvpn/data-for-clients/||g' | cut -f 1 -d '/')"
							printf '%s\n' "W: data-for-clients from service $CurService not removed: $CurFile" 1>&2
						done
					fi
				fi
			fi
		else
			printf '%s\n' "${sERROR}E: Neither service account nor client access found.${fRESET}" 1>&2
			LastStatus=101 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
	else
		printf '%s\n' "${sERROR}E: Account name not specified.${fRESET}" 1>&2
		LastStatus=81 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		printf '\n'
		Status accesses
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	return $StatusCode
}

Server_account_remove ()
{
	Client_account_remove "$@"
	return $?
}

Client_account_export ()
# Package data from this server to be copied to a client host
{
	local ServiceProfile="$1"
	local Account_Name="$2"
	local ExportPackage="$3"
	local ServedList=''
	local ClientDataDir=''
	local PreviousDir=''
	local LastStatus=0
	local StatusCode=0

	if [ $StatusCode -eq 0 ] ; then
		ServedList="$(ServiceProfiles)"
		if [ "$ServedList" != "" ] ; then
			if [ "$ServiceProfile" = "." ] ; then
				if [ "$ServedList" = "$(printf '%s' "$ServedList" | head -n 1)" ] ; then
					# Accept default specification if there is only one.
					ServiceProfile="$ServedList"
				else
					ServiceProfile=''
				fi
			fi
			if [ "$ServiceProfile" != "" ] && [ "$(printf '%s' "$ServedList" | grep -e "^${ServiceProfile}$")" = "" ] ; then
				printf '%s\n' "${sERROR}E: Service profile \"${ServiceProfile}\" not found.${fRESET}" 1>&2
				printf '%s\n' "   Existing VPN service profiles:" 1>&2
				printf '%s\n' "$ServedList" | sed -e 's|^|\t|' 1>&2
				LastStatus=101 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			else
				if [ "$ServiceProfile" = "" ] ; then
					printf '%s\n' "Existing VPN service profiles:"
					printf '%s\n' "$ServedList" | sed -e 's|^|\t|'
					printf '%s' "Enter the one where to get account data: "
					read ServiceProfile
				fi
				ServiceProfileFile="/etc/openvpn/server/${ServiceProfile}.conf"
				if [ ! -f "$ServiceProfileFile" ] ; then
					printf '%s\n' "${sERROR}E: Service profile file \"${ServiceProfileFile}\" not found.${fRESET}" 1>&2
					LastStatus=95 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
			fi
		else
			printf '%s\n' "${sERROR}E: No VPN service configured. Please setup first with action:${fRESET}" 1>&2
			printf '%s\n' "   server create" 1>&2
			LastStatus=101 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		if [ "$Account_Name" = "" ] ; then
			if [ -d "/etc/openvpn/data-for-clients/${ServiceProfile}" ] ; then
				printf '%s\n' "Existing accounts with saved data at $ServiceProfile service:"
				ls -1 "/etc/openvpn/data-for-clients/${ServiceProfile}"/ | sed -e 's|^|\t|g'
			fi
			printf '%s' "Account Name to export: "
			read Account_Name
		fi
		if [ "$Account_Name" != "" ] ; then
			ClientDataDir="/etc/openvpn/data-for-clients/${ServiceProfile}/${Account_Name}"
			if [ "$(ls -1 "$ClientDataDir" 2>/dev/null | grep -ie '\.conf$')" = "" ] ; then
				printf '%s\n' "${sERROR}E: No access profile found at $ClientDataDir${fRESET}" 1>&2
				LastStatus=101 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
		else
			printf '%s\n' "${sERROR}E: Account name not specified to be exported.${fRESET}" 1>&2
			LastStatus=82 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		if [ "$ExportPackage" = "" ] ; then
			printf '%s\n' "New package path ${ParO}example: /var/backups/vpn-client/${Account_Name}.tar.gz ${ParC}:"
			read ExportPackage
		fi
		if [ "$ExportPackage" = "" ] ; then
			printf '%s\n' "${sERROR}E: Package path not specified to save an account export.${fRESET}" 1>&2
			LastStatus=82 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		PreviousDir="$(pwd)"
		cd "$ClientDataDir"
		if [ "$(printf '%s' "$ExportPackage" | grep -e '^/')" = "" ] ; then
			ExportPackage="${PreviousDir}/${ExportPackage}"
		fi
		EmpaquetarContenido "$ExportPackage" .
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		cd "$PreviousDir"
		if [ $StatusCode -eq 0 ] ; then
			printf '%s\n' "Exported:"
			du -Psxh "$ExportPackage"
		fi
	fi
	return $StatusCode
}

Server_account_export ()
{
	Client_account_export "$@"
	return $?
}

Client_account_import ()
# Configure a VPN link with server's copied data to this client
{
	local ImportPackage="$1"
	local Access_Name="$2"
	local PreviousDir=''
	local OriginProfileFile=''
	local OriginAccessName=''
	local FileName=''
	local FileExtension=''
	local CurFiles=''
	local CurFile=''
	local WaitForStart=5
	local RemainForStart=0
	local ProfileCopied=0
	local DevType='tun'
	local CurValue1=''
	local CurValue2=''
	local CurNr=0
	local DevName=''
	local CaFile=''
	local KeyFile=''
	local CertFile=''
	local AuthFile=''
	local LastStatus=0
	local StatusCode=0

	Client_prepare
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	if [ $StatusCode -eq 0 ] ; then
		if [ "$ImportPackage" = "" ] ; then
			printf '%s\n' "Package or directory with access data:"
			read ImportPackage
		fi
		if [ "$ImportPackage" = "" ] ; then
			printf '%s\n' "${sERROR}E: Account data path not specified to get access information.${fRESET}" 1>&2
			LastStatus=82 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		else
			if [ ! -e "$ImportPackage" ] ; then
				printf '%s\n' "${sERROR}E: Account data path not found: $ImportPackage${fRESET}" 1>&2
				LastStatus=82 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
		fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		if [ "$(printf '%s' "$ImportPackage" | grep -e '^/')" = "" ] ; then ImportPackage="$(pwd)/${ImportPackage}" ; fi
		if [ -f "$ImportPackage" ] && [ "$(printf '%s\n' "$ImportPackage" | grep -ie '\.conf$' -ie '\.ovpn$')" != "" ] ; then
			OriginProfileFile="$ImportPackage"
		else
			rm -fr "${DirTemp}/${ProgramName}.$$"
			if [ -d "$ImportPackage" ] ; then
				cp -a "$ImportPackage" "${DirTemp}/${ProgramName}.$$"
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			else
				PreviousDir="$(pwd)"
				MkdirPP "${DirTemp}/${ProgramName}.$$" :openvpn u=rwX,g=rX,o= g+s
				cd "${DirTemp}/${ProgramName}.$$"
				DesempaquetarContenido "$ImportPackage"
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				cd "$PreviousDir"
			fi
			if [ $StatusCode -eq 0 ] ; then
				OriginProfileFile="$(find "${DirTemp}/${ProgramName}.$$" | grep -ie '\.ovpn$' | head -n 1)"
				if [ "$OriginProfileFile" = "" ] ; then
					OriginProfileFile="$(find "${DirTemp}/${ProgramName}.$$" | grep -ie '\.conf$' | head -n 1)"
				fi
				if [ ! -f "$OriginProfileFile" ] ; then
					printf '%s\n' "${sERROR}E: Import data does not contain access profile.${fRESET}" 1>&2
					LastStatus=65 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
			fi
		fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		FileName="$(printf '%s\n' "$OriginProfileFile" | tr -s '/' '\n' | tail -n 1)"
		FileExtension="$(printf '%s\n' "$FileName" | grep -e '\.' | tr -s '.' '\n' | tail -n 1)"
		OriginAccessName="$(printf '%s\n' "$FileName" | sed -e "s|\.${FileExtension}$||")"
		if [ "$Access_Name" = "." ] ; then
			if [ "$OriginAccessName" != "" ] ; then
				Access_Name="$OriginAccessName"
			else
				Access_Name=''
			fi
		fi
		if [ "$Access_Name" = "" ] ; then
			if [ "$OriginAccessName" != "" ] ; then
				printf '%s\n' "Service proposed access name was: $OriginAccessName"
				printf '%s\n' "Enter new access name or press only ENTER to use proposed one:"
			else
				printf '%s' "Enter new access name: "
			fi
			read Access_Name
			if [ "$Access_Name" = "" ] ; then Access_Name="$OriginAccessName" ; fi
		fi
		if [ "$Access_Name" != "" ] ; then
			ProfileFile="/etc/openvpn/client/${Access_Name}.conf"
			if [ -f "$ProfileFile" ] ; then
				printf '%s\n' "${sERROR}E: Access profile already registered as $ProfileFile${fRESET}" 1>&2
#				printf '%s\n' "   To remove, you should previously disable&stop service ${OpenvpnClientName}@${Access_Name}" 1>&2
				printf '%s\n' "   You need to remove $ProfileFile before importing with same name" 1>&2
				LastStatus=103 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			else
				printf '%s\n' "Importing access data to client profile: $Access_Name"
			fi
		else
			printf '%s\n' "${sERROR}E: New access name not specified to create from an account import.${fRESET}" 1>&2
			LastStatus=82 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		if [ -f "$ImportPackage" ] && [ "$(printf '%s\n' "$ImportPackage" | grep -ie '\.conf$' -ie '\.ovpn$')" != "" ] ; then
			printf '%s\n' "Importing simple profile file"
		else
			PreviousDir="$(pwd)"
			cd "${DirTemp}/${ProgramName}.$$"
			if [ $StatusCode -eq 0 ] ; then
				chown -R root:openvpn .
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				chmod -R u=rwX,g=rX,o= .
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				find . -type d -exec chmod g+s {} +
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
			if [ $StatusCode -eq 0 ] ; then
				MkdirPP "/etc/openvpn/remotes-data/${Access_Name}" :openvpn u=rwX,g=rX,o= g+s
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
			if [ $StatusCode -eq 0 ] ; then
				CurFiles="$(ls -1 | grep -ie '\.key$' -ie '\.crt$' -ie '\.pem$')"
				IFS="$(printf '\n\b')" ; for CurFile in $CurFiles ; do unset IFS
					if [ -e "/etc/openvpn/remotes-data/${Access_Name}/${CurFile}" ] ; then
						printf '%s\n' "W: Overwriting /etc/openvpn/remotes-data/${Access_Name}/${CurFile}" 1>&2
					else
						printf '%s\n' "$CurFile"
					fi
					mv "$CurFile" "/etc/openvpn/remotes-data/${Access_Name}/${CurFile}"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					if [ $StatusCode -ne 0 ] ; then break ; fi
					if [ "$(cat "$OriginProfileFile" | cut -f 1 -d '#' | tr -s '\t' ' ' | sed -e 's| $||g' | grep -ie '^ca ' | head -n 1 | sed -e 's|.*/||g')" = "$CurFile" ] ; then
						CaFile="/etc/openvpn/remotes-data/${Access_Name}/${CurFile}"
					fi
					if [ "$(cat "$OriginProfileFile" | cut -f 1 -d '#' | tr -s '\t' ' ' | sed -e 's| $||g' | grep -ie '^key ' | head -n 1 | sed -e 's|.*/||g')" = "$CurFile" ] ; then
						KeyFile="/etc/openvpn/remotes-data/${Access_Name}/${CurFile}"
					fi
					if [ "$(cat "$OriginProfileFile" | cut -f 1 -d '#' | tr -s '\t' ' ' | sed -e 's| $||g' | grep -ie '^cert ' | head -n 1 | sed -e 's|.*/||g')" = "$CurFile" ] ; then
						CertFile="/etc/openvpn/remotes-data/${Access_Name}/${CurFile}"
					fi
				done
			fi
			if [ $StatusCode -eq 0 ] ; then
				CurFiles="$(ls -1 | grep -ie '\.auth')"
				CurFile="$(printf '%s' "$CurFiles" | head -n 1)"
				if [ "$CurFiles" = "$CurFile" ] ; then
					# Single file
					if [ -e "/etc/openvpn/remotes-data/${Access_Name}/${CurFile}" ] ; then
						printf '%s\n' "W: Overwriting /etc/openvpn/remotes-data/${Access_Name}/${CurFile}" 1>&2
					else
						printf '%s\n' "$CurFile"
					fi
					mv "$CurFile" "/etc/openvpn/remotes-data/${Access_Name}/${CurFile}"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					if [ "$(cat "$OriginProfileFile" | cut -f 1 -d '#' | tr -s '\t' ' ' | sed -e 's| $||g' | grep -ie '^auth-user-pass ' | head -n 1 | sed -e 's|.*/||g')" = "$CurFile" ] ; then
						AuthFile="/etc/openvpn/remotes-data/${Access_Name}/${CurFile}"
					fi
				else
					IFS="$(printf '\n\b')" ; for CurFile in $CurFiles ; do unset IFS
						if [ -e "/etc/openvpn/client/${CurFile}" ] ; then
							printf '%s\n' "W: Overwriting /etc/openvpn/client/${CurFile}" 1>&2
						else
							printf '%s\n' "$CurFile"
						fi
						mv "$CurFile" "/etc/openvpn/client/${CurFile}"
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						if [ $StatusCode -ne 0 ] ; then break ; fi
						if [ "$(cat "$OriginProfileFile" | cut -f 1 -d '#' | tr -s '\t' ' ' | sed -e 's| $||g' | grep -ie '^auth-user-pass ' | head -n 1 | sed -e 's|.*/||g')" = "$CurFile" ] ; then
							AuthFile="/etc/openvpn/client/${CurFile}"
						fi
					done
				fi
			fi
			cd "$PreviousDir"
		fi
		if [ $StatusCode -eq 0 ] ; then
			MkfilePP "$ProfileFile" root:openvpn u=rwX,g=rX,o= g+s
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			if [ $StatusCode -eq 0 ] ; then
				DevType="$(cat "$OriginProfileFile" | cut -f 1 -d '#' | tr -s '\t' ' ' | sed -e 's| $||g' | grep -e '^dev-type ' | cut -f 2 -d ' ')"
				CurValue1="$(cat /etc/openvpn/client/* 2>/dev/null | grep -e '^dev ')"
				CurValue2="$(ListaDispositivosRed)"
				CurNr=1
				DevName="vpn-${DevType}${CurNr}"
				while [ "$(printf '%s\n' "$CurValue1" | grep -e "^dev ${DevName}$")" != "" ] || [ "$(printf '%s' " $CurValue2 " | grep -e " $DevName ")" != "" ] ; do
					CurNr=$((CurNr + 1))
					DevName="vpn-${DevType}${CurNr}"
				done
				cat "$OriginProfileFile" | sed -e "s|^dev .*|dev ${DevName}|g" > "$ProfileFile"
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				if [ $StatusCode -eq 0 ] ; then
					ProfileCopied=1
					rm "$OriginProfileFile"
					if [ "$CaFile" != "" ] ; then
						sed -i -e "s|^ca .*|ca ${CaFile}|i" "$ProfileFile"
						sed -i -e "s|^ca\t.*|ca ${CaFile}|i" "$ProfileFile"
					fi
					if [ "$KeyFile" != "" ] ; then
						sed -i -e "s|^key .*|key ${KeyFile}|i" "$ProfileFile"
						sed -i -e "s|^key\t.*|key ${KeyFile}|i" "$ProfileFile"
					fi
					if [ "$CertFile" != "" ] ; then
						sed -i -e "s|^cert .*|cert ${CertFile}  # keep secret|i" "$ProfileFile"
						sed -i -e "s|^cert\t.*|cert ${CertFile}  # keep secret|i" "$ProfileFile"
					fi
					if [ "$AuthFile" != "" ] ; then
						sed -i -e "s|^auth-user-pass .*|auth-user-pass ${AuthFile}|i" "$ProfileFile"
						sed -i -e "s|^auth-user-pass\t.*|auth-user-pass ${AuthFile}|i" "$ProfileFile"
					fi
					sed -i -e "s|^log .*|log /var/log/openvpn/${Access_Name}.log|i" "$ProfileFile"
					sed -i -e "s|^log\t.*|log /var/log/openvpn/${Access_Name}.log|i" "$ProfileFile"
					sed -i -e "s|^log-append .*|log-append /var/log/openvpn/${Access_Name}.log|i" "$ProfileFile"
					sed -i -e "s|^log-append\t.*|log-append /var/log/openvpn/${Access_Name}.log|i" "$ProfileFile"
				fi
			fi
		fi
		if [ $StatusCode -eq 0 ] ; then
			if [ -d "${DirTemp}/${ProgramName}.$$" ] ; then
				cd "${DirTemp}/${ProgramName}.$$"
				CurFiles="$(find | grep -ve '^\.$' | sed -e 's|^\./||g')"
				cd "$PreviousDir"
				if [ "$CurFiles" != "" ] ; then
					printf '%s\n' "W: Unknown files not imported:" 1>&2
					printf '%s\n' "$CurFiles" 1>&2
				fi
			fi
			rm -fr "${DirTemp}/${ProgramName}.$$"
		fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		OpenvpnVersion="$(openvpn --version | grep -ie '^OpenVPN [0-9]' | cut -f 2 -d ' ')"
		if [ "$(ComparaVersions $OpenvpnVersion 2.4)" = "<" ] ; then
			# Not structured configuration at /etc/openvpn
			if [ -L "/etc/openvpn/${Access_Name}.conf" ] ; then rm "/etc/openvpn/${Access_Name}.conf" ; fi
			if [ -e "/etc/openvpn/${Access_Name}.conf" ] ; then mv "/etc/openvpn/${Access_Name}.conf" "/etc/openvpn/${Access_Name}.conf" ; fi
			ln -s "$ProfileFile" "/etc/openvpn/${Access_Name}.conf"
		fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		printf '%s\n' "Starting client VPN link"
		ServiceAction start $OpenvpnClientName "$Access_Name"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		RemainForStart=$WaitForStart
		ServiceAction status $OpenvpnClientName "$Access_Name" >/dev/null 2>&1
		LastStatus=$?
		while [ $LastStatus -eq 3 ] && [ $RemainForStart -gt 0 ] ; do
			printf '.' 1>&2
			sleep 1
			ServiceAction status $OpenvpnClientName "$Access_Name" >/dev/null 2>&1
			LastStatus=$?
			RemainForStart=$((RemainForStart - 1))
		done
		if [ $RemainForStart -ne $WaitForStart ] ; then
			printf '\n' 1>&2
		fi
		if [ $LastStatus -eq 0 ] ; then
			ServiceAction enable $OpenvpnClientName "$Access_Name"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		else
			printf '%s\n' "${sERROR}E: Service ${OpenvpnClientName}@${Access_Name} did not launch ${ParO}${StatusCode}${ParC}.${fRESET}" 1>&2
			LastStatus=108 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
	fi
	if [ $ProfileCopied -eq 1 ] ; then
		printf '\n'
		printf '%s\n' "${sINFO}I: Access profile can be customized at $ProfileFile${fRESET}"
	fi
	return $StatusCode
}

Client_account_link ()
# Manually configure a VPN link for this client
{
	local LastStatus=0
	local StatusCode=0

	printf '%s\n' "${sERROR}E: Action, parameter or feature not yet implemented${fRESET}" 1>&2
	LastStatus=63 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	return $StatusCode
}

Client_account_list ()
{
	local ClientAccessesList=''
	
	ClientAccessesList="$(ClientProfiles)"
	if [ "$ClientAccessesList" != "" ] ; then
		printf '%s\n' "${sHEAD1}REGISTERED VPN CLIENT ACCESSES:${fRESET}"
		printf '%s\n' "$ClientAccessesList" | sed -e "s|^|${sVALUE}|g" | sed -e "s|$|${fRESET}|g"
	else
		printf '%s\n' "${sINFO}I: No profiles registered to connect this host as VPN client.${fRESET}"
	fi
}

Client_list ()
{
	Client_account_list "$@"
	return $?
}

Client_account ()
{
	local CalledFunction="$1"
	if [ $# -gt 0 ] ; then shift ; fi
	local AvailableFormat_B=''
	local AvailableFormat_E=''
	local LastStatus=0
	local StatusCode=0

	if [ "$(cat "$MeCallFile" | grep -e "^Client_account_${CalledFunction} ()" -e "^Client_account_${CalledFunction}()")" != "" ] ; then
		Client_account_$CalledFunction "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	else
		if [ "$CalledFunction" != "" ] && [ "$CalledFunction" != "--help" ] ; then
			printf '%s\n' "${sERROR}E: Unknown function or action for \"account\"${fRESET}" 1>&2
			LastStatus=87 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			printf '\n'
		else
			LastStatus=254 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
		if [ "$(ClientProfiles)" = "" ] ; then
			AvailableFormat_B="${sDISABLED}"
			AvailableFormat_E="${fRESET}"
		fi
		printf '%s\n' "${sHEAD1}Actions about VPN client accesses:${fRESET}"
		printf '%s\n' ""
		printf '%s\n' "	client account create   ${ParO}Create a client access to this server${ParC}"
		printf '%s\n' "${AvailableFormat_B}	client account remove   ${ParO}Revoke a client access to this server${ParC}${AvailableFormat_E}"
		printf '%s\n' "${AvailableFormat_B}	client account export   ${ParO}Package data from this server to be copied to a client host${ParC}${AvailableFormat_E}"
		printf '%s\n' "	client account import   ${ParO}Configure a VPN link with server's copied data to this client${ParC}"
		printf '%s\n' "	client account link     ${ParO}Manually setup a VPN access for this client${ParC}"
		printf '%s\n' "${AvailableFormat_B}	client account enable   ${ParO}Make a connection from this client autostart on system boot${ParC}${AvailableFormat_E}"
		printf '%s\n' "${AvailableFormat_B}	client account disable  ${ParO}Make a connection from this client to be only manual${ParC}${AvailableFormat_E}"
		printf '%s\n' "	client accounts list    ${ParO}Show registered VPN accesses to remote servers${ParC}"
	fi
	return $StatusCode
}

Client_accounts ()
{
	Client_account "$@"
	return $?
}

Client ()
{
	local CalledFunction="$1"
	if [ $# -gt 0 ] ; then shift ; fi
	local AvailableFormat_B=''
	local AvailableFormat_E=''
	local LastStatus=0
	local StatusCode=0

	if [ "$(cat "$MeCallFile" | grep -e "^Client_account_${CalledFunction} ()" -e "^Client_account_${CalledFunction}()")" != "" ] ; then
#		Client_$CalledFunction "$@"
		Client_account_$CalledFunction "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	else
		if [ "$(cat "$MeCallFile" | grep -e "^Client_${CalledFunction} ()" -e "^Client_${CalledFunction}()")" != "" ] ; then
			Client_$CalledFunction "$@"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		else
			if [ "$CalledFunction" != "" ] && [ "$CalledFunction" != "--help" ] ; then
				printf '%s\n' "${sERROR}E: Unknown function or action for \"client\"${fRESET}" 1>&2
				LastStatus=87 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				printf '\n'
			else
				LastStatus=254 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
			if [ "$(ClientProfiles)" = "" ] ; then
				AvailableFormat_B="${sDISABLED}"
				AvailableFormat_E="${fRESET}"
			fi
			printf '%s\n' "${sHEAD1}Actions about Virtual Private Network accesses:${fRESET}"
			printf '%s\n' ""
			printf '%s\n' "	client account  ${ParO}actions to profile VPN accesses${ParC}"
			printf '%s\n' "${AvailableFormat_B}	client start    ${ParO}Connect this host to another server${ParC}${AvailableFormat_E}"
			printf '%s\n' "${AvailableFormat_B}	client stop     ${ParO}Shutdown connection for this client${ParC}${AvailableFormat_E}"
			printf '%s\n' "${AvailableFormat_B}	client restart  ${ParO}Stop+Start connection for this client${ParC}${AvailableFormat_E}"
			printf '%s\n' "	clients list    ${ParO}Show registered VPN accesses to remote servers${ParC}"
		fi
	fi
	return $StatusCode
}

Clients ()
{
	Client "$@"
	return $?
}

Status ()
{
	local FilterType="$1"  # Supported: empty (""), servers, accounts
	local ServiceProfiles=''
	local CurServed_File=''
	local CurServed_Name=''
	local CurServed_ClientAccounts=''
	local CurAccount=''
	local ClientAccessesList=''
	local CurConfigContent=''
	local CurPort=''
	local CurProtocol=''
	local CurIP=''
	local CurDev=''
	local CurDev_Formatted=''
	local CurCCD=''
	local CurPid=''
	local CurStatus=''
	local CurComm=''
	local CurLogfile=''
	local LastLogged=''
	local LastLogged_L1=''
	local LastLogged_L2=''
	local LastConnectionTime=''
	local LastConnectionIP=''
	local LastConnectionStatus=''
	local CurClientID=''
	local CurProfileFile=''
	local CurProfileFilePath=''
	local RouteRables=''
	local CurTableName=''
	local CurTable=''
	local CurImpliedTables=''
	local CurImpliedRuleSelectors=''
	local CurRuleString=''
	local CurLine=''
	local ServerManager_Host=''
	local ServerManager_Port=''
	local ServerManager_Password=''
	local ServerManager_Report=''
	local CurReceived=''
	local CurSent=''
	local CurStats=''
	local CurVirtualIP=''
	local iptablesCommand='iptables-legacy-save'
	local IpRegex=''
	local CurClientsIPs=''
	local CurClientIP=''
	local HostIPs=''
	local CurStatsSuffix=''
	local LastStatus=0
	local StatusCode=0

	HostIPs="$(ip address | grep -e 'inet .*/' | tr -s '\t' ' ' | sed -e 's|^ ||g' | cut -f 2 -d ' ')"
	cd /etc/openvpn/server
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	if [ $LastStatus -eq 0 ] && [ "$FilterType" != "accesses" ] ; then
		ServiceProfiles="$(ls -1 | grep -ie '\.conf$' -ie '\.ovpn$')"
		if [ "$ServiceProfiles" != "" ] ; then
			printf '%s\n' "${sHEAD1}CURRENT STATUS OF VPN SERVICES TO PROVIDE:${fRESET}"
			IFS="$(printf '\n\b')" ; for CurServed_File in $ServiceProfiles ; do unset IFS
				CurConfigContent="$(cat "$CurServed_File")"
				CurPort="$(printf '%s' "$CurConfigContent" | tr -s '\t' ' ' | grep -ie '^port .' | tail -n 1 | cut -f 2 -d ' ')"
				CurProtocol="$(printf '%s' "$CurConfigContent" | tr -s '\t' ' ' | grep -ie '^proto .' | tail -n 1 | cut -f 2 -d ' ' | cut -f 1 -d '-')"
				CurIP="$(printf '%s' "$CurConfigContent" | tr -s '\t' ' ' | grep -ie '^local .' | tail -n 1 | cut -f 2 -d ' ')"
				if [ "$CurIP" = "" ] ; then CurIP='0.0.0.0' ; fi
				CurDev="$(printf '%s' "$CurConfigContent" | tr -s '\t' ' ' | grep -ie '^dev .' | tail -n 1 | cut -f 2 -d ' ')"
				if $(ip address show dev "$CurDev" >/dev/null 2>&1) ; then
					CurDev_Formatted="${sINFO}${CurDev}${fRESET}"
				else
					CurDev_Formatted="${sDISABLED}${CurDev}${fRESET}"
				fi
				CurImpliedTables=''
				RouteRables="$(ip rule list | grep -e ' lookup ' | sed -e 's|.* lookup ||g' -e 's|  | |g' -e 's| $||g')"
				IFS="$(printf '\n\b')" ; for CurTableName in $RouteRables ; do unset IFS
					CurTable="$(ip route show table "$CurTableName" 2>/dev/null)"  # "default" table is usually empty or query produces error.
					if [ "$(printf '%s' "$CurTable" | grep -e "^default via.* dev $CurDev " -e "^default via.*  dev ${CurDev}$")" != "" ] ; then
						CurImpliedTables="$(printf '%s\n' "$CurImpliedTables" ; printf '%s\n' "$CurTableName")"
					fi
				done
				CurImpliedTables="$(printf '%s' "$CurImpliedTables" | grep -ve '^$' | sort -u)"
				CurCCD="$(printf '%s' "$CurConfigContent" | tr -s '\t' ' ' | grep -ie '^client-config-dir .' | tail -n 1 | cut -f 2- -d ' ')"
				if [ "$CurPort" != "" ] && [ "$CurProtocol" != "" ] ; then
					CurPid="$(ss -tulnp | tr -s '\t' ' ' | grep -ie "^${CurProtocol} .*:${CurPort} " | tr ',' '\n' | sed -e 's|.*pid=|pid=|gi' | grep -ie '^pid=' | cut -f 2 -d '=')"
					if ! Is_IntegerNr $CurPid ; then
						# Old iproute2 versions do not note "pid=" indicator
						CurPid="$(ss -tulnp | tr -s '\t' ' ' | grep -ie "^${CurProtocol} .*:${CurPort} " | sed -e 's|.* users:||g' | cut -f 2 -d ',')"
					fi
					if Is_IntegerNr $CurPid ; then
						CurComm="$(ps --no-headers -o comm $CurPid)"
						if $(ip address show dev "$CurDev" >/dev/null 2>&1) ; then
							CurDev_Formatted="${sPROGRESS}${CurDev}${fRESET}"
							CurStatus="${sPROGRESS}pid:${CurPid}/${CurComm}${fRESET}"
						else
							CurStatus="pid:${CurPid}/${CurComm}"
						fi
					else
						CurStatus="${sDISABLED}stopped${fRESET}"
					fi
				else
					CurPid='?'
					CurStatus='?'
				fi
				CurLogfile="$(printf '%s' "$CurConfigContent" | tr -s '\t' ' ' | grep -ie '^log-append .' | tail -n 1 | cut -f 2- -d ' ')"
#				CurServed_Name="$(printf '%s\n' "$ServiceProfiles" | sed -e 's|\.conf$||gi' | sed -e 's|\.ovpn$||gi')"
				FileName="$(printf '%s\n' "$CurServed_File" | tr -s '/' '\n' | tail -n 1)"
				FileExtension="$(printf '%s\n' "$FileName" | grep -e '\.' | tr -s '.' '\n' | tail -n 1)"
				CurServed_Name="$(printf '%s\n' "$FileName" | sed -e "s|\.${FileExtension}$||")"
				printf '%s\n' "	${sVALUE}${fUNDERL}$CurServed_Name${fRESET} ${CurDev_Formatted}${sINFO}[${CurIP}] ${CurProtocol}/${CurPort}${fRESET} ${CurStatus}"
				ServerManager_Report="$(printf '%s' "$CurConfigContent" | tr -s '\t' ' ' | grep -ie '^management .' | grep -ive ' unix ' | tail -n 1)"
				if [ "$ServerManager_Report" != "" ] ; then
					ServerManager_Host="$(printf '%s' "$ServerManager_Report" | cut -sf 2 -d ' ')"
					ServerManager_Port="$(printf '%s' "$ServerManager_Report" | cut -sf 3 -d ' ')"
					ServerManager_Password="$(printf '%s' "$ServerManager_Report" | cut -sf 4- -d ' ')"
					if [ -f "$ServerManager_Password" ] ; then
						ServerManager_Password="$(cat "$ServerManager_Password" | head -n 1)"
					else
						ServerManager_Password=''
					fi
					ServerManager_Report="$(RemoteFunctionTelnet 2 "$ServerManager_Host" "$ServerManager_Port" '' "$ServerManager_Password" 'status' 'OpenVPN Management Interface Version' 'END')"
				fi
				IFS="$(printf '\n\b')" ; for CurTableName in $CurImpliedTables ; do unset IFS
#					CurTable="$(ip route show table "$CurTableName" | grep -ve prohibit)"
					CurTable="$(ip route show table "$CurTableName" 2>/dev/null)"  # "default" table is usually empty or query produces error.
					CurImpliedRuleSelectors="$(ip rule list | grep -e " lookup $CurTableName " -e " lookup ${CurTableName}$" | cut -f 2- -d ':' | sed -e "s| lookup ${CurTableName}$||" -e "s| lookup $CurTableName | |")"
					IFS="$(printf '\n\b')" ; for CurRuleString in $CurImpliedRuleSelectors ; do unset IFS
						CurRuleString="$(echo TrimAndSingle $CurRuleString | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g')"
						IFS="$(printf '\n\b')" ; for CurLine in $CurTable ; do unset IFS
							printf '%s\n' "		IP RULE: $CurRuleString ${CurLine}"
						done
					done
				done
				if [ "$FilterType" != "servers" ] ; then
#					CurServed_ClientAccounts="$(ls -1 "/etc/openvpn/data-for-clients/${CurServed_Name}"/ 2>/dev/null)"  #do
					CurServed_ClientAccounts="$(ls -1 "${CurCCD}"/ "/etc/openvpn/data-for-clients/${CurServed_Name}"/ 2>/dev/null | grep -ve '/' -ve '^$' | sort -u)"
					if [ "$CurServed_ClientAccounts" != "" ] ; then
						printf '%s\n' "	\`-> Client accounts:"
						IFS="$(printf '\n\b')" ; for CurAccount in $CurServed_ClientAccounts ; do unset IFS
							LastConnectionTime=''
							LastConnectionIP=''
							LastConnectionStatus=''
							if [ "$CurLogfile" != "" ] ; then
#								LastLogged="$(sed -e '/Service ${CurServed_Name}:.* account ${CurAccount}$/q' "$CurLogfile" 2>/dev/null | tail -n 2)"
#								LastLogged="$(awk "1;/Service ${CurServed_Name}:.* account ${CurAccount}$/{exit}" "$CurLogfile" 2>/dev/null | tail -n 2)"
								LastLogged="$(tac "$CurLogfile" | grep -e "Service ${CurServed_Name}:.* account ${CurAccount}$" --after-context=$(wc -l < "$CurLogfile") | head -n 2 | tac)"
								if [ "$LastLogged" = "" ] ; then
									LastLogged="$(tac "$CurLogfile" | grep -e "Service .*/${CurServed_Name}:.* account ${CurAccount}$" -e "\[${CurAccount}\] Peer Connection Initiated" --after-context=$(wc -l < "$CurLogfile") | head -n 2 | tac)"
								fi
								if [ "$(printf '%s' "$LastLogged" | grep -e "$CurAccount")" = "" ] ; then LastLogged='' ; fi  # AWK selects similar lines if no exact match.
								LastLogged_L1="$(printf '%s' "$LastLogged" | head -n 1)"
								LastLogged_L1="$(echo TrimAndSingle $LastLogged_L1 | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g')"
								LastLogged_L2="$(printf '%s' "$LastLogged" | tail -n 1)"
								LastLogged_L2="$(echo TrimAndSingle $LastLogged_L2 | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g')"
								LastConnectionStatus="$(printf '%s' "$LastLogged" | grep -e "uthentication .* account ${CurAccount}$" | sed -e "s| account ${CurAccount}$||g" | tr ' ' '\n' | tail -n 2 | head -n 1)"
								if [ "$LastConnectionStatus" != "" ] ; then
									if [ "$(printf '%s' "$LastConnectionStatus" | grep -ie '^succ')" != "" ] ; then
										if [ "$(printf '%s' "$ServerManager_Report" | grep -e "^CLIENT_LIST,")" != "" ] ; then
											LastConnectionStatus="Last:${LastConnectionStatus}"
										else
											# Only highlight if no connections could be detected.
											LastConnectionStatus="Last:${sGOOD}${LastConnectionStatus}${fRESET}"
										fi
									else
										LastConnectionStatus="Last:${sWARN}${LastConnectionStatus}${fRESET}"
									fi
								fi
								LastConnectionTime="$(printf '%s' "$LastLogged_L1" | cut -f 1 -d '.' | grep -e '.........')"
								if Is_IntegerNr $LastConnectionTime ; then
									LastConnectionTime="$(date -d "@${LastConnectionTime}" '+%F %T')"
								else
									LastConnectionTime="$(printf '%s' "$LastLogged_L2" | sed -e 's| [0-9]*\.[0-9]*\.[0-9]*\.[0-9]*.*||g')"
									LastConnectionTime="$(date -d "$LastConnectionTime" '+%F %T' 2>/dev/null)"
									if [ "$LastConnectionTime" = "" ] ; then LastConnectionTime='                   ' ; fi
								fi
								LastConnectionIP="$(printf '%s' "$LastLogged_L1" | tr -s ':/ ' '\n' | grep -e '^[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*$')"
								if [ "$LastConnectionIP" = "" ] ; then
									LastConnectionIP="$(printf '%s' "$LastLogged_L2" | tr -s ':/ ' '\n' | grep -e '^[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*$')"
								fi
								if [ "$LastConnectionIP" != "" ] ; then
									LastConnectionIP="Last:${fUNDERL}${LastConnectionIP}${fRESET}"
								fi
							fi
							LastLogged="$(printf '%s' "$ServerManager_Report" | grep -e "^CLIENT_LIST,${CurAccount},")"
							CurStats=''
							CurWanIP=''
							if [ "$LastLogged" != "" ] ; then
								LastConnectionTime="$(printf '%s' "$LastLogged" | cut -f 9 -d ',')"
								if Is_IntegerNr $LastConnectionTime ; then
									CurReceived="$(printf '%s' "$LastLogged" | cut -f 6 -d ',')"
									CurSent="$(printf '%s' "$LastLogged" | cut -f 7 -d ',')"
									CurStats=$(date +%s)
									CurStats=$((CurStats - LastConnectionTime))
									CurStatsSuffix='sec'
									if [ $CurStats -ge 120 ] ; then
										CurStatsSuffix='min'
										CurStats=$((CurStats/60))
										if [ $CurStats -ge 120 ] ; then
											CurStatsSuffix='hour'
											CurStats=$((CurStats/60))
											if [ $CurStats -ge 48 ] ; then
												CurStatsSuffix='day'
												CurStats=$((CurStats/24))
											fi
										fi
									fi
									if [ $CurStats -le 0 ] ; then CurStats=1 ; fi
									CurReceived=$((CurReceived+CurSent))
									CurStats=$((CurReceived/CurStats*8))
									CurStats=" $(NumeroResumit $CurStats '0A1 ')bits/${CurStatsSuffix}"
									LastConnectionTime="${sPROGRESS}$(date -d "@${LastConnectionTime}" '+%F %T')${fRESET}"
								else
									LastConnectionTime='                   '
								fi
								LastConnectionIP=''
								CurClientsIPs="$(printf '%s' "$LastLogged" | cut -f 3 -d ',' | cut -f 1 -d ':')"
								for CurClientIP in $CurClientsIPs ; do
									if [ "$LastConnectionIP" != "" ] ; then LastConnectionIP="${LastConnectionIP}," ; fi
									LastConnectionIP="${LastConnectionIP}${fUNDERL}${CurClientIP}${fRESET}"
								done
								CurVirtualIP="$(printf '%s' "$LastLogged" | cut -f 4 -d ',' | cut -f 1 -d ':')"
								if ! Is_Executable "$iptablesCommand" ; then iptablesCommand=iptables-save ; fi
								if Is_Executable "$iptablesCommand" && [ "$CurVirtualIP" != "" ] ; then
									IpRegex="$(printf '%s\n' "$CurVirtualIP" | sed -e 's|\.|\\.|g')"
									CurWanIP="$($iptablesCommand | grep -e "-s ${IpRegex}/.* -j SNAT " | tail -n 1 | sed -e 's| --|\n--|g' | grep -e '^--to-source ' | cut -f 2 -d ' ')"
									if [ "$CurWanIP" != "" ] ; then
										IpRegex="$(printf '%s\n' "$CurWanIP" | sed -e 's|\.|\\.|g')"
										if [ "$(printf '%s' "$HostIPs" | grep -e "^${IpRegex}/" -e "^${IpRegex}$")" != "" ] ; then
											CurWanIP="->${sPROGRESS}${CurWanIP}${fRESET}"
										else
											CurWanIP="->${sDISABLED}${CurWanIP}${fRESET}"
										fi
									else
										CurWanIP="${sWARN}->${fRESET}"
									fi
								else
									CurWanIP=''
								fi
								LastConnectionIP="${LastConnectionIP}->${CurVirtualIP}"
								LastConnectionStatus="${sGOOD}connected${fRESET}"
							fi
							printf '%s\n' "		${sVALUE}$CurAccount${fRESET}	$LastConnectionTime [${LastConnectionIP}]${CurWanIP}	${LastConnectionStatus}${CurStats}"
						done
					else
						printf '%s\n' "	\`-> ${ParO}no client accounts${ParC}"
					fi
				fi
			done
		fi
	fi
	if [ $LastStatus -eq 0 ] && [ "$FilterType" != "accounts" ] ; then
		ClientAccessesList="$(ClientProfiles)"
		if [ "$ClientAccessesList" != "" ] ; then
			if [ "$ServiceProfiles" != "" ] ; then printf '\n' ; fi
			printf '%s\n' "${sHEAD1}CURRENT STATUS OF VPN CLIENT ACCESSES:${fRESET}"
#			printf '%s\n' "$ClientAccessesList" | sed -e "s|^|${sVALUE}|g" | sed -e "s|$|${fRESET}|g" | sed -e 's|^|\t|g'
			IFS="$(printf '\n\b')" ; for CurClientID in $ClientAccessesList ; do unset IFS
				printf '\n'
				CurProfileFilePath="$(find /etc/openvpn/client | grep -ie "^/etc/openvpn/client/${CurClientID}\.conf$" -ie "^/etc/openvpn/client/${CurClientID}\.ovpn$" | head -n 1)"
				CurImpliedTables=''
				if [ "$CurProfileFilePath" != "" ] ; then
#					CurProfileFile="$(printf '%s\n' "$CurProfileFilePath" | tr -s '/' '\n' | tail -n 1)"	# Problems with a path begun with "-" in old GNU basename versions
					CurConfigContent="$(cat "$CurProfileFilePath")"
					CurPort="$(printf '%s' "$CurConfigContent" | tr -s '\t' ' ' | grep -ie '^port .' | tail -n 1 | cut -f 2 -d ' ')"
					CurProtocol="$(printf '%s' "$CurConfigContent" | tr -s '\t' ' ' | grep -ie '^proto .' | tail -n 1 | cut -f 2 -d ' ' | cut -f 1 -d '-')"
					CurIP="$(printf '%s' "$CurConfigContent" | tr -s '\t' ' ' | grep -ie '^remote .' | tail -n 1 | cut -f 2 -d ' ')"
					CurDev="$(printf '%s' "$CurConfigContent" | tr -s '\t' ' ' | grep -ie '^dev .' | tail -n 1 | cut -f 2 -d ' ')"
					if $(ip address show dev "$CurDev" >/dev/null 2>&1) ; then
						CurDev_Formatted="${sINFO}${CurDev}${fRESET}"
					else
						CurDev_Formatted="${sDISABLED}${CurDev}${fRESET}"
					fi
					CurPid="$(ps -A -o pid,comm,cmd | grep -ve 'grep' | grep -e "[0-9]* openvpn .* --config ${CurClientID}\.conf" -e "[0-9]* openvpn .* --config ${CurClientID}\.ovpn" | sed -e 's|  | |g' -e 's|^ ||g' | cut -f 1 -d ' ')"
					if Is_IntegerNr $CurPid ; then
						CurComm="$(ps --no-headers -o comm $CurPid)"
						if $(ip address show dev "$CurDev" >/dev/null 2>&1) ; then
							CurDev_Formatted="${sPROGRESS}${CurDev}${fRESET}"
							CurStatus="${sPROGRESS}pid:${CurPid}/${CurComm}${fRESET}"
						else
							CurStatus="pid:${CurPid}/${CurComm}"
						fi
					else
						CurStatus="${sDISABLED}stopped${fRESET}"
					fi
					RouteRables="$(ip rule list | grep -e ' lookup ' | sed -e 's|.* lookup ||g' -e 's|  | |g' -e 's| $||g')"
					IFS="$(printf '\n\b')" ; for CurTableName in $RouteRables ; do unset IFS
						CurTable="$(ip route show table "$CurTableName" 2>/dev/null)"  # "default" table is usually empty or query produces error.
						if [ "$(printf '%s' "$CurTable" | grep -e "^default via.* dev $CurDev " -e "^default via.*  dev ${CurDev}$")" != "" ] ; then
							CurImpliedTables="$(printf '%s\n' "$CurImpliedTables" ; printf '%s\n' "$CurTableName")"
						fi
					done
					CurImpliedTables="$(printf '%s' "$CurImpliedTables" | grep -ve '^$' | sort -u)"
					CurLogfile="$(printf '%s' "$CurConfigContent" | tr -s '\t' ' ' | grep -ie '^log-append .' | tail -n 1 | cut -f 2- -d ' ')"
				fi
#				printf '%s\n' "	${sVALUE}${CurClientID}${fRESET}"
				printf '%s\n' "	${sVALUE}${CurClientID}${fRESET}	${CurDev_Formatted}${sINFO}[${CurIP}] ${CurProtocol}/${CurPort}${fRESET} ${CurStatus}"
				IFS="$(printf '\n\b')" ; for CurTableName in $CurImpliedTables ; do unset IFS
#					CurTable="$(ip route show table "$CurTableName" | grep -ve prohibit)"
					CurTable="$(ip route show table "$CurTableName")"
					CurImpliedRuleSelectors="$(ip rule list | grep -e " lookup $CurTableName " -e " lookup ${CurTableName}$" | cut -f 2- -d ':' | sed -e "s| lookup ${CurTableName}$||" -e "s| lookup $CurTableName | |")"
					IFS="$(printf '\n\b')" ; for CurRuleString in $CurImpliedRuleSelectors ; do unset IFS
						CurRuleString="$(echo TrimAndSingle $CurRuleString | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g')"
						IFS="$(printf '\n\b')" ; for CurLine in $CurTable ; do unset IFS
							printf '%s\n' "		IP RULE: $CurRuleString ${CurLine}"
						done
					done
				done
			done
		fi
	fi
	if [ $LastStatus -eq 0 ] ; then
		if [ "$ServiceProfiles" = "" ] && [ "$ClientAccessesList" = "" ] ; then
			if [ "$FilterType" = "" ] ; then
				printf '%s\n' "${sINFO}I: No VPN profiles configured.${fRESET}"
			else
				printf '%s\n' "${sINFO}I: No VPN profiles configured ${ParO}${FilterType}${ParC}.${fRESET}"
			fi
		fi
	fi
	return $StatusCode
}

SetupSudoer ()
{
	local DurruterExecutable=''
	local AccountGroups=''
	local LastStatus=0
	local StatusCode=0

	if [ $StatusCode -eq 0 ] ; then
		if ! Is_Executable sudo || ! Is_Executable visudo ; then
			printf '%s\n' "E: Required software missing: sudo/visudo" 1>&2
			LastStatus=53 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
	fi
	if [ $StatusCode -eq 0 ] && [ ! -f /etc/sudoers ] ; then
		printf '%s\n' "E: File not found: /etc/sudoers" 1>&2
		LastStatus=53 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	DurruterExecutable="$(command -v durruter 2>/dev/null)"
	if [ ! -x "$DurruterExecutable" ] ; then DurruterExecutable="/sbin/durruter" ; fi
	if [ ! -x "$DurruterExecutable" ] ; then DurruterExecutable="/bin/durruter" ; fi
	if [ ! -x "$DurruterExecutable" ] ; then DurruterExecutable="/usr/sbin/durruter" ; fi
	if [ ! -x "$DurruterExecutable" ] ; then DurruterExecutable="/usr/bin/durruter" ; fi
	if [ ! -x "$DurruterExecutable" ] ; then DurruterExecutable="" ; fi
	
	if [ $StatusCode -eq 0 ] ; then
		visudo -c
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		if [ $LastStatus -ne 0 ] ; then
			printf '%s\n' "${sERROR}Fix sudoers files before setup-sudoer for $ProgramName" 1>&2
		fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		if [ -d /etc/sudoers.d ] ; then
			printf '%s\n' "openvpn ALL = (root) NOPASSWD: $DurruterExecutable" | tee /etc/sudoers.d/openvpn-durruter
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			chown root:root /etc/sudoers.d/openvpn-durruter
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			chmod 0440 /etc/sudoers.d/openvpn-durruter
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			if [ $StatusCode -eq 0 ] ; then
				visudo -c >/dev/null
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				if [ $LastStatus -ne 0 ] ; then
					printf '%s\n' "Removing /etc/sudoers.d/openvpn-durruter"
					rm -f /etc/sudoers.d/openvpn-durruter
				fi
			else
				printf '%s\n' "Removing /etc/sudoers.d/openvpn-durruter"
				rm -f /etc/sudoers.d/openvpn-durruter
			fi
		else
			if [ "$(cat /etc/sudoers | grep -e "^openvpn .* ${DurruterExecutable}$")" = "" ] ; then
				cp -a /etc/sudoers /tmp/sudoers.sesele
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				if [ $StatusCode -eq 0 ] ; then
					printf '\n%s\n' "openvpn ALL = (root) NOPASSWD: $DurruterExecutable" | tee -a /etc/sudoers
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					if [ $StatusCode -eq 0 ] ; then
						visudo -c >/dev/null
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						if [ $LastStatus -ne 0 ] ; then
							if [ -f /tmp/sudoers.sesele ] ; then
								printf '%s\n' "Restoring /etc/sudoers"
								cat /tmp/sudoers.sesele > /etc/sudoers
							fi
						fi
					else
						if [ -f /tmp/sudoers.sesele ] ; then
							printf '%s\n' "Restoring /etc/sudoers"
							cat /tmp/sudoers.sesele > /etc/sudoers
						fi
					fi
				fi
				rm -f /tmp/sudoers.sesele
			else
				printf '%s\n' "I: There is already a line for openvpn to $DurruterExecutable"
				printf '%s\n' "   Not rewriting /etc/sudoers"
			fi
		fi
	fi
	return $StatusCode
}

Is_ServedRunning ()
# Syntax as a function: Is_ServedRunning $Name
# Description: Returns (exitcode 0) TRUE if specified VPN-Server profile has a PID running. Not checking if connection succeed.
# Use example (without brackets []):
#	if Is_ServedRunning MyService ; then echo "Online." ; fi
{
	local ServedName="$1"
	local CurFile=''
	local CurConfigContent=''
	local CurProtocol=''
	local CurPort=''
	local CurPid=''
	local TrueCode=254  # 254=FALSE

	CurFile="$(ServiceProfileFile "$ServedName")"
	CurConfigContent="$(cat "$CurFile" 2>/dev/null)"
	CurProtocol="$(printf '%s' "$CurConfigContent" | tr -s '\t' ' ' | grep -ie '^proto .' | tail -n 1 | cut -f 2 -d ' ' | cut -f 1 -d '-')"
	CurPort="$(printf '%s' "$CurConfigContent" | tr -s '\t' ' ' | grep -ie '^port .' | tail -n 1 | cut -f 2 -d ' ')"
	CurPid="$(ss -tulnp | tr -s '\t' ' ' | grep -ie "^${CurProtocol} .*:${CurPort} " | tr ',' '\n' | sed -e 's|.*pid=|pid=|gi' | grep -ie '^pid=' | cut -f 2 -d '=')"
	if ! Is_IntegerNr $CurPid ; then
		# Old iproute2 versions do not note "pid=" indicator
		CurPid="$(ss -tulnp | tr -s '\t' ' ' | grep -ie "^${CurProtocol} .*:${CurPort} " | sed -e 's|.* users:||g' | cut -f 2 -d ',')"
	fi
	if Is_IntegerNr $CurPid ; then TrueCode=0 ; fi
	return $TrueCode
}

Is_ClientAccessRunning ()
# Syntax as a function: Is_ClientAccessRunning $Name
# Description: Returns (exitcode 0) TRUE if specified VPN-Client profile has a PID running. Not checking if connection succeed.
# Use example (without brackets []):
#	if Is_ClientAccessRunning MyClient ; then echo "Online." ; fi
{
	local ClientName="$1"
	local CurPid=''
	local TrueCode=254  # 254=FALSE
	
	CurPid="$(ps -A -o pid,comm,cmd | grep -ve 'grep' | grep -e "[0-9]* openvpn .* --config ${ClientName}\.conf" -e "[0-9]* openvpn .* --config ${ClientName}\.ovpn" | sed -e 's|  | |g' -e 's|^ ||g' | cut -f 1 -d ' ')"
	if Is_IntegerNr $CurPid ; then TrueCode=0 ; fi
	return $TrueCode
}

Is_OpenvpnEnabled ()
# Code taken from SystemProgramIsEnabled() from init_script
# Returns (exitcode) true if the main service is fully enabled to start on system boot. Otherwise false.
# Syntax (without brackets []):
#	if SystemProgramIsEnabled ; then printf '%s\n' "Yes." ; fi
{
	local RcBase=''
	local InRcLocal=0
	local ValueBoolean=1
	local ServiceName='openvpn'
	local ProgramExecutablePath='openvpn'
	local SystemvEnablementData=''
	
	if [ "$(cat "$RcLocal" 2>/dev/null | cut -f 1 -d '#' | tr -s '\t' ' ' | tr -s ' ' | sed -e 's|^ ||g' | sed -e '/^exit/q' | grep -E "^(${ProgramExecutablePath}|${ServiceName}) start")" != "" ] ; then InRcLocal=1 ; fi
	if [ "$(cat /etc/rc.d/rc.local 2>/dev/null | cut -f 1 -d '#' | tr -s '\t' ' ' | tr -s ' ' | sed -e 's|^ ||g' | sed -e '/^exit/q' | grep -E "^(${ProgramExecutablePath}|${ServiceName}) start")" != "" ] ; then InRcLocal=1 ; fi
	if [ "$(cat /etc/rc.local 2>/dev/null | cut -f 1 -d '#' | tr -s '\t' ' ' | tr -s ' ' | sed -e 's|^ ||g' | sed -e '/^exit/q' | grep -E "^(${ProgramExecutablePath}|${ServiceName}) start")" != "" ] ; then InRcLocal=1 ; fi
	SystemvEnablementData="$(cat "/etc/default/${ServiceName}" 2>/dev/null | tr -s '\t' ' ' | tr -s ' ' | sed -e 's|^ ||g' | grep -ie '^ENABLED' -ie '^RUN=')"
	if [ "$(printf '%s\n' "$SystemvEnablementData" | grep -ie '^ENABLED=')" != "" ] ; then
		SystemvEnablementData="$(printf '%s\n' "$SystemvEnablementData" | grep -ie '^ENABLED=' | tail -n 1)"
	else
		SystemvEnablementData="$(printf '%s\n' "$SystemvEnablementData" | grep -ie '^RUN=' | tail -n 1)"
	fi
	if [ $InRcLocal -eq 1 ] ; then
		# Init system independent way ~ hard way to invoke program
		ValueBoolean=0
		if [ "$SystemvEnablementData" != "" ] ; then
			# Manual filter present
			if [ "$(printf '%s\n' "$SystemvEnablementData" | grep -iE '=(1|y)')" = "" ] ; then
				# Manual filter says something but "enabled"
				ValueBoolean=1
			fi
		fi
	else
		if [ "$SystemvEnablementData" != "" ] ; then
			# Manual filter present
			if [ "$(printf '%s\n' "$SystemvEnablementData" | grep -iE '=(1|y)')" != "" ] ; then
				# Manual filter says "enabled"
				ValueBoolean=0
			fi
		else
			# No manual filter; enabled depends only on init system
			ValueBoolean=0
		fi
		if [ $ValueBoolean -eq 0 ] ; then
			ValueBoolean=1
			case "$InitSoftware" in
				"systemd" )
					systemctl --quiet --system is-enabled "${ServiceName}.service" 2>/dev/null
					LastStatus=$?
					if [ $LastStatus -eq 0 ] ; then ValueBoolean=0 ; fi
					if [ $ValueBoolean -eq 1 ] && [ "$(command -v /sbin/insserv 2>/dev/null)$(command -v insserv 2>/dev/null)$(command -v /usr/sbin/update-rc.d 2>/dev/null)$(command -v update-rc.d 2>/dev/null)$(command -v chkconfig 2>/dev/null)" != "" ] && [ "$(command -v /sbin/runlevel 2>/dev/null)$(command -v runlevel 2>/dev/null)" != "" ] ; then
						# It seems there is a SystemV compatibility layer; must check for upgraded environments from SystemV to Systemd
						if [ -d /etc/rc1.d ] ; then RcBase=/etc ; fi
						if [ -d /etc/rc.d/rc1.d ] ; then RcBase=/etc/rc.d ; fi
						if [ -d "$RcBase" ] ; then
							for CurrentRc in 1 2 3 4 5 ; do
								if [ $ValueBoolean -ne 0 ] && [ -d "${RcBase}/rc${CurrentRc}.d" ] ; then
									IFS="$(printf '\n\b')" ; for CurrentLink in $(ls -1 "${RcBase}/rc${CurrentRc}.d"/ 2>/dev/null | grep -ie '^S') ; do unset IFS	#"
										if [ "$(ReadlinkF "${RcBase}/rc${CurrentRc}.d/${CurrentLink}" | grep -e "/${ServiceName}$")" != "" ] ; then ValueBoolean=0 ; fi	#"
									done
								fi
							done
						fi
					fi
					;;
				"upstart" )
					# Upstart considers it enabled when service profile file is present
					if [ "$InitProfilesDir" != "" ] ; then
						if [ -f "${InitProfilesDir}/${ServiceName}.conf" ] ; then ValueBoolean=0 ; fi
						if [ "$InitProfilesDir_0" != "" ] && [ -f "${InitProfilesDir_0}/${ServiceName}.conf" ] ; then ValueBoolean=0 ; fi
					fi
					if [ $ValueBoolean -eq 1 ] && [ "$(command -v /sbin/insserv 2>/dev/null)$(command -v insserv 2>/dev/null)$(command -v /usr/sbin/update-rc.d 2>/dev/null)$(command -v update-rc.d 2>/dev/null)$(command -v chkconfig 2>/dev/null)" != "" ] && [ "$(command -v /sbin/runlevel 2>/dev/null)$(command -v runlevel 2>/dev/null)" != "" ] ; then
						# It seems there is a SystemV compatibility layer; must check for upgraded environments from SystemV to Upspart
						if [ -d /etc/rc1.d ] ; then RcBase=/etc ; fi
						if [ -d /etc/rc.d/rc1.d ] ; then RcBase=/etc/rc.d ; fi
						if [ -d "$RcBase" ] ; then
							for CurrentRc in 1 2 3 4 5 ; do
								if [ $ValueBoolean -ne 0 ] && [ -d "${RcBase}/rc${CurrentRc}.d" ] ; then
									IFS="$(printf '\n\b')" ; for CurrentLink in $(ls -1 "${RcBase}/rc${CurrentRc}.d"/ 2>/dev/null | grep -ie '^S') ; do unset IFS	#"
										if [ "$(ReadlinkF "${RcBase}/rc${CurrentRc}.d/${CurrentLink}" | grep -e "/${ServiceName}$")" != "" ] ; then ValueBoolean=0 ; fi	#"
									done
								fi
							done
						fi
					fi
					;;
				"systemv" )
					if [ "$ServiceEnabler" = "chkconfig" ] ; then
						if [ "$(chkconfig --list "$ServiceName" | grep -iE '(1|2|3|4|5):on')" ] ; then ValueBoolean=0 ; fi
					else
						RcBase=""
						if [ -d /etc/rc1.d ] ; then RcBase=/etc ; fi
						if [ -d /etc/rc.d/rc1.d ] ; then RcBase=/etc/rc.d ; fi
						if [ -d "$RcBase" ] ; then
							for CurrentRc in 1 2 3 4 5 ; do
								if [ $ValueBoolean -ne 0 ] && [ -d "${RcBase}/rc${CurrentRc}.d" ] ; then
									IFS="$(printf '\n\b')" ; for CurrentLink in $(ls -1 "${RcBase}/rc${CurrentRc}.d"/ 2>/dev/null | grep -ie '^S') ; do unset IFS	#"
										if [ "$(ReadlinkF "${RcBase}/rc${CurrentRc}.d/${CurrentLink}" | grep -e "/${ServiceName}$")" != "" ] ; then ValueBoolean=0 ; fi	#"
									done
								fi
							done
						else
							if [ -x "/etc/init.d/${ServiceName}" ] ; then ValueBoolean=0 ; fi
						fi
					fi
					;;
			esac
		fi
	fi
	return $ValueBoolean
}

Is_ClientAccessEnabled ()
# Syntax as a function: Is_ClientAccessEnabled $Name
# Description: Returns (exitcode 0) TRUE if specified VPN-Client profile is enabled to be loaded on service boot.
# Use example (without brackets []):
#	if Is_ClientAccessEnabled MyClient ; then echo "Online." ; fi
{
	local ClientName="$1"
	local CurPid=''
	local TrueCode=254  # 254=FALSE
	
	if [ ! -x "/bin/systemctl" ] && [ -x "/usr/sbin/service" ] ; then
		Is_OpenvpnEnabled
		TrueCode=$?
	else
		systemctl is-enabled "${OpenvpnClientName}@${ClientName}" >/dev/null
		TrueCode=$?
		if [ $TrueCode -ne 0 ] ; then TrueCode=254 ; fi
	fi
	return $TrueCode
}

ReloadVPN_now ()
{
	local ServedList=''
	local ClientAccessesList=''
	local CurServed=''
	local CurAccess=''
	local LastStatus=0
	local StatusCode=0

	ServedList="$(ServiceProfiles)"
	IFS="$(printf '\n\b')" ; for CurServed in $ServedList ; do unset IFS
		if Is_ServedRunning "$CurServed" ; then
			printf '%s\n' "I: VPN process for served \"${CurServed}\" is already running."
		else
			Server_ServiceAction start "$CurServed"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			if [ $StatusCode -eq 0 ] && Is_IntegerNr $ReloadCheckAfter ; then
				printf '%s\n' "Waiting $ReloadCheckAfter seconds to check if \"${CurServed}\" VPN service keeps running..."
				sleep $ReloadCheckAfter
				if Is_ServedRunning "$CurServed" ; then
					LogProgram 2 "I: \"${CurServed}\" VPN service started now."
					if [ "$StatusNotifyTo" != "" ] ; then
						MailNotify "gesvipen@$(hostname -f)" "$StatusNotifyTo" "VPN service started: $CurServed" "VPN server profile was found dead, and new start seems to keep runing."
					fi
				else
					LogProgram 1 "E: \"${CurServed}\" VPN service fails to run."
					LastStatus=110 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					if [ "$StatusNotifyTo" != "" ] ; then
						MailNotify "gesvipen@$(hostname -f)" "$StatusNotifyTo" "VPN service dead: $CurServed" "VPN server profile was found not running, and new start failed to keep it running."
					fi
				fi
			else
				LogProgram 1 "E: \"${CurServed}\" VPN service fails to run."
				LastStatus=110 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				if [ "$StatusNotifyTo" != "" ] ; then
					MailNotify "gesvipen@$(hostname -f)" "$StatusNotifyTo" "VPN service dead: $CurServed" "VPN server profile was found not running, and new start failed to keep it running."
				fi
			fi
		fi
	done
	ClientAccessesList="$(ClientProfiles)"
	IFS="$(printf '\n\b')" ; for CurAccess in $ClientAccessesList ; do unset IFS
		if Is_ClientAccessRunning "$CurAccess" ; then
			printf '%s\n' "I: VPN process for access \"${CurAccess}\" is already running."
		else
			if Is_ClientAccessEnabled "$CurAccess" ; then
				Client_account_ServiceAction start "$CurAccess"
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				if [ $StatusCode -eq 0 ] && Is_IntegerNr $ReloadCheckAfter ; then
					printf '%s\n' "Waiting $ReloadCheckAfter seconds to check if \"${CurAccess}\" VPN access keeps running..."
					sleep $ReloadCheckAfter
					if Is_ClientAccessRunning "$CurAccess" ; then
						LogProgram 2 "I: \"${CurAccess}\" VPN access started now."
						if [ "$StatusNotifyTo" != "" ] ; then
							MailNotify "gesvipen@$(hostname -f)" "$StatusNotifyTo" "VPN access started: $CurAccess" "VPN client profile was found dead, and new start seems to keep runing."
						fi
					else
						LogProgram 1 "E: \"${CurAccess}\" VPN access fails to run."
						LastStatus=110 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						if [ "$StatusNotifyTo" != "" ] ; then
							MailNotify "gesvipen@$(hostname -f)" "$StatusNotifyTo" "VPN access dead: $CurAccess" "VPN client profile was found not running, and new start failed to keep it running."
						fi
					fi
				else
					LogProgram 1 "E: \"${CurAccess}\" VPN access fails to run."
					LastStatus=110 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					if [ "$StatusNotifyTo" != "" ] ; then
						MailNotify "gesvipen@$(hostname -f)" "$StatusNotifyTo" "VPN access dead: $CurAccess" "VPN client profile was found not running, and new start failed to keep it running."
					fi
				fi
			fi
		fi
	done
	return $StatusCode
}

ReloadVPN ()
{
	local Action="$1"
	local ScriptFiles=''
	local CurScriptFile=''
	local RegisterCronCall=0
	local LastStatus=0
	local StatusCode=0

	case "$Action" in
		"now" )
				ReloadVPN_now
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				;;
		"hourly" )
				RegisterCronCall=1
				;;
		"daily" )
				RegisterCronCall=1
				;;
		"weekly" )
				RegisterCronCall=1
				;;
		"monthly" )
				RegisterCronCall=1
				;;
		"manual" )
				ScriptFiles="$(ls -1 "/etc/cron.hourly/${ProgramName}-reload" "/etc/cron.daily/${ProgramName}-reload" "/etc/cron.weekly/${ProgramName}-reload" "/etc/cron.monthly/${ProgramName}-reload" 2>/dev/null)"
				if [ "$ScriptFiles" != "" ] ; then
					IFS="$(printf '\n\b')" ; for CurScriptFile in $ScriptFiles ; do unset IFS
						printf '%s\n' "Removing $(printf '%s' "$CurScriptFile" | cut -f 2 -d '.' | cut -f 1 -d '/') reload call."
						rm "$CurScriptFile"
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					done
				else
					printf '%s\n' "I: No Cron call was registered to reload VPNs."
				fi
				;;
		* )
				printf '%s\n' "Specific acctions for reloading links:"
				printf '%s\n' ""
				printf '%s\n' "$ProgramName reload now      ${ParO}Start dead served/client VPNs${ParC}"
				printf '%s\n' "$ProgramName reload hourly   ${ParO}Register a Cron call for one reload per hour${ParC}"
				printf '%s\n' "$ProgramName reload daily    ${ParO}Register a Cron call for one reload per day${ParC}"
				printf '%s\n' "$ProgramName reload weekly   ${ParO}Register a Cron call for one reload per week${ParC}"
				printf '%s\n' "$ProgramName reload monthly  ${ParO}Register a Cron call for one reload per month${ParC}"
				printf '%s\n' "$ProgramName reload manual   ${ParO}Unregister Cron reload calls${ParC}"
				LastStatus=86 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				;;
	esac
	if [ $RegisterCronCall -eq 1 ] ; then
		CurScriptFile="/etc/cron.${Action}/${ProgramName}-reload"
		printf '%s\n' '#!/bin/sh' > "$CurScriptFile"
		printf '%s\n' "\"${MeCallFile}\" reload now" >> "$CurScriptFile"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		chmod +x "$CurScriptFile"
		if [ $StatusCode -eq 0 ] ; then
			printf '%s\n' "I: $Action reload scheduled with Cron service."
			if [ "$StatusNotifyTo" = "" ] ; then
				printf '%s\n' "You can configure StatusNotifyTo parameter at $CurrentConfigFile to e-mail when a VPN is found stopped."
			fi
		fi
	fi
	return $StatusCode
}

#[/gesvipen]


##### TEMPLATE FUNCTIONS TO CUSTOMIZE #####

Install_precp_Pre ()
# Example: Install_precp_Pre /cdrom/program
# Will be run after uninstalling previous version (all before installing new program files).
# Useful for old programs migration/uninstall before doing anything about this one.
# WARNING: Package manager cannot invoke this action before this script file is installed. Recommended to copy actions to preinst script or redo them later.
{
	local SoftwareDir="$1"
	if [ $# -gt 0 ] ; then shift ; fi
	local LastStatus=0
	local StatusCode=0

	return $StatusCode
}

Install_precp_Post ()
# Example: Install_precp_Post /cdrom/program
# Will be run after uninstalling previous version and before copying new program files.
# WARNING: Package manager cannot invoke this action before this script file is installed. Recommended to copy actions to preinst script or redo them later.
{
	local SoftwareDir="$1"
	if [ $# -gt 0 ] ; then shift ; fi
	local LastStatus=0
	local StatusCode=0

	return $StatusCode
}

Install_postcp_More ()
# Example: Install_postcp_More /cdrom/service
# Will be run once program files are already installed.
{
	local SoftwareDir="$1"
	if [ $# -gt 0 ] ; then shift ; fi
	local LastStatus=0
	local StatusCode=0

	DurruterExecutable="$(command -v durruter 2>/dev/null)"
	if [ ! -x "$DurruterExecutable" ] ; then DurruterExecutable="/sbin/durruter" ; fi
	if [ ! -x "$DurruterExecutable" ] ; then DurruterExecutable="/bin/durruter" ; fi
	if [ ! -x "$DurruterExecutable" ] ; then DurruterExecutable="/usr/sbin/durruter" ; fi
	if [ ! -x "$DurruterExecutable" ] ; then DurruterExecutable="/usr/bin/durruter" ; fi
	if [ ! -x "$DurruterExecutable" ] ; then DurruterExecutable="" ; fi
	if [ "$DurruterExecutable" != "" ] && [ ! -f /etc/sudoers.d/openvpn-durruter ] && [ "$(cat /etc/sudoers 2>/dev/null | grep -e "^openvpn .* ${DurruterExecutable}$")" = "" ] ; then
		printf '%s\n' "W: You should add necessary \"sudo\" permissions to openvpn for $DurruterExecutable" 1>&2
		printf '%s\n' "   ${ParO}run \"$ProgramName setup-sudoer\" for helper${ParC}" 1>&2
	fi
	return $StatusCode
}

Uninstall_predel_More ()
# Will be run after stopping service and before program files removal.
{
	sleep 0
}

Uninstall_Postdeletion ()
# Will be run after program files removal.
{
	sleep 0
}

Uninstall_purge_MorePre ()
# Will be run after Uninstall_Postdeletion and just before removing config files/dirs and logs.
{
	local LastStatus=0
	local StatusCode=0

	return $StatusCode
}

Uninstall_purge_MorePost ()
# Will be run just after removing config files/dirs and logs.
{
	local LastStatus=0
	local StatusCode=0

	return $StatusCode
}

Configuration_Saved_Pre ()
# Preliminar configurations at beginning of Configuration(). Useful to migrate old configurations and data before assuming them for production.
{
	local ReadOnly=0
	if [ "$1" = "ro" ] ; then ReadOnly=1 ; shift ; fi
#[gesvipen]
	if [ ! -x "/bin/systemctl" ] && [ -x "/usr/sbin/service" ] ; then
		OpenvpnServerName='openvpn'
		OpenvpnClientName='openvpn'
	else
		OpenvpnServerName='openvpn-server'
		OpenvpnClientName='openvpn-client'
	fi
#[/gesvipen]
}

Configuration_Saved_Post ()
{
	local ReadOnly=0
	if [ "$1" = "ro" ] ; then ReadOnly=1 ; shift ; fi
#[gesvipen]
	if [ $ReadOnly -eq 0 ] && [ "$(cat "$CurrentConfigFile" | grep -ie 'tapdev')" = "" ] ; then
		printf '%s\n' "# Notes:" >> "$CurrentConfigFile"
		printf '%s\n' "#	- Bridges specified here are overriden by specified at profile.conf call" >> "$CurrentConfigFile"
		printf '%s\n' "#	- Blank values override wider scope values. Example: tapdev_vpn0_bridge= means don't bridge although tapdev_defaultbridge is specified." >> "$CurrentConfigFile"
		printf '%s\n' "" >> "$CurrentConfigFile"
		printf '%s\n' "# If TAP devices should be attached to specified bridge by default" >> "$CurrentConfigFile"
		printf '%s\n' "tapdev_defaultbridge=" >> "$CurrentConfigFile"
		printf '%s\n' "" >> "$CurrentConfigFile"
		printf '%s\n' "# If named TAP device should be attached to specified bridge" >> "$CurrentConfigFile"
		printf '%s\n' "#tapdev_vpn0_bridge=" >> "$CurrentConfigFile"
		printf '%s\n' "#${ParO}any variable-dislike character parses to underscore${ParC} Example for vpn-tun0" >> "$CurrentConfigFile"
		printf '%s\n' "#tapdev_vpn_tun0_bridge=" >> "$CurrentConfigFile"
		printf '%s\n' "" >> "$CurrentConfigFile"
		printf '%s\n' "# If TAP device with named local IP should be attached to specified bridge ${ParO}overrides NIC name${ParC}" >> "$CurrentConfigFile"
		printf '%s\n' "#tapip_192_168_1_250_bridge=" >> "$CurrentConfigFile"
	fi
	ServiceKeysWithPassword="$(GetSetLocalConfig $ReadOnly ServiceKeysWithPassword '' 0 '\n# ServiceKeysWithPassword: If VPN server certificate keys will be encrypted with pass phrase ${ParO}1${ParC} or this is only for CA ${ParO}0${ParC}')"
	ValidDays="$(IniVarValue /etc/sesele/system.conf ValidDays '' 24855 =)"
	ValidDays="$(GetSetLocalConfig $ReadOnly ValidDays '' "$ValidDays" '\n# ValidDays: Number of days to expiring date, for new EASYRSA certificates.')"
	if ! Is_IntegerNr "$ValidDays" ; then ValidDays=24855 ; fi
	if [ $ValidDays -lt 0 ] ; then
		ValidDays=24855
		printf '%s\n' "W: ValidDays fixed to $ValidDays." 1>&2
	fi
	if [ $ValidDays -eq 0 ] || [ $ValidDays -gt 24855 ] ; then
		printf '%s\n' "W: Strange ValidDays value for new certificates: $ValidDays" 1>&2
	fi
	ReloadCheckAfter="$(GetSetLocalConfig $ReadOnly ReloadCheckAfter '' 5 '\n# ReloadCheckAfter: How many seconds to wait for checking a VPN reload keeps process running.')"
	StatusNotifyTo="$(GetSetLocalConfig $ReadOnly StatusNotifyTo '' '""' '\n# StatusNotifyTo: E-mail addresses to notify on VPN process not running.')"
#[/gesvipen]
}

ProgramHelp ()
{
	local SudoPrefix=''
	local SudoSuffix=''
	local InstallerActions=''
	local BaseName=''
	
	BaseName="$(printf '%s\n' "$MeExecutable" | tr -s '/' '\n' | tail -n 1)"
	printf '%s\n' "$(ScriptHeaderValue Description) ${ParO}${ProgramName}${ParC} $(ScriptHeaderValue Version)"
	ScriptHeaderValue Homepage
	printf '%s\n' ""
	if [ $(MeInstalled) -eq 1 ] || [ "$InstalledExpected" = "0" ] ; then
		if [ "$RootRequired" != "0" ] && [ $(id -u) -ne 0 ] ; then
			if [ "$(command -v sudo 2>/dev/null)" != "" ] ; then
				SudoPrefix="sudo "
			else
				SudoPrefix='su -c "'
				SudoSuffix='"'
			fi
		fi
		if [ "$ProgramInstaller" = "1" ] ; then
			if [ $(MeInstalled) -eq 1 ] ; then
				InstallerActions="|uninstall|purge"
			else
				InstallerActions="|install"
			fi
		fi
#[gesvipen]
#		printf '%s\n' "Usage: ${SudoPrefix}${ProgramName} {${InstallerActions}}${SudoSuffix}"
		printf '%s\n' "Usage: ${SudoPrefix}${ProgramName} {server|client|status${InstallerActions}}${SudoSuffix}"
		printf '%s\n' ""
		printf '%s\n' "	server   Actions for VPN service"
		printf '%s\n' "	client   Actions about VPN accesses"
#		printf '%s\n' "	serve    Create a VPN access to this server"
#		printf '%s\n' "	unserve  Remove a VPN access to this server"
#		printf '%s\n' "	link     Create a connection to VPN service"
#		printf '%s\n' "	unlink   Remove a connection to VPN service"
		printf '%s\n' "	status   Show configured accesses and/or VPN connections"
#		printf '%s\n' "	export   Backup configured access or VPN connection"
#		printf '%s\n' "	import   Restore configured access or VPN connection"
#[/gesvipen]
	else
		if [ $(id -u) -ne 0 ] ; then
			if [ "$(command -v sudo 2>/dev/null)" != "" ] ; then
				SudoPrefix="sudo "
			else
				SudoPrefix='su -c "'
				SudoSuffix='"'
			fi
		fi
		if [ "$ProgramInstaller" = "1" ] ; then
			printf '%s\n' "To install as a system program:"
			printf '%s\n' "${SudoPrefix}${BaseName} install${SudoSuffix}"
			printf '%s\n' ""
			printf '%s\n' "To remove the program:"
			printf '%s\n' "${SudoPrefix}${ProgramName} uninstall${SudoSuffix}"
		else
			printf '%s\n' "${sWARN}W: Program not installed. Use your package manager to install ${ProgramName}.${fRESET}"
		fi
	fi
}


##### MAIN SCRIPT #####

LastStatus=0 ; StatusCode=0
if [ $(id -u) -eq 0 ] && [ "$USER" != "root" ] && [ "$USER" != "" ] && [ "$(env | grep -ie LXC -ie '^container=')" != "" ] ; then
	# Patch to not legate some LXC unprivileged variables on lxc-attach/calls
	export USER=root ; export HOME=/root ; export LOGNAME=root
	if [ -f /etc/default/locale ] ; then
		IFS="$(printf "\n\b")" ; for CurVar in $(cat /etc/default/locale | cut -f 1 -d '#' | grep -e '=') ; do unset IFS
			eval export $CurVar
		done
	fi
fi
MeCallFile="$0"
PreviousDir="$(pwd)"
MeDir="$(Dirname "$MeCallFile")"
cd "$MeDir"
MeDir="$(pwd)"
cd "$PreviousDir"
MeCallFile="${MeDir}/$(printf '%s\n' "$MeCallFile" | tr -s '/' '\n' | tail -n 1)"
if [ "$LogLevel" = "" ] ; then LogLevel=3 ; fi	# A nested call can export LogLevel
LogProgram 4 '$' "$0" "$@"
#cd / # avoid blocking mount points from being unmounted - It's a problem to deal with user specified relative paths

Action="$1"
if [ $# -gt 0 ] ; then shift ; fi
Action="$(Lowercase "$Action")"
case "$Action" in
	"remove" ) Action="uninstall" ; ActionMode="remove" ;;
	"purge" ) Action="uninstall" ; ActionMode="purge" ;;
	"-h" ) Action="--help" ;;
	"-V" ) Action="--version" ;;
#[gesvipen]
	"service" ) Action="server" ;;
#[/gesvipen]
esac
case "$Action" in
	"install" )
		Configuration ro "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		if [ $StatusCode -eq 0 ] && [ "$PackageManager_Call" = "" ] ; then
			if [ "$ProgramInstaller" != "1" ] ; then
				LastStatus=93 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				ProgramHelp 1>&2
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			else
				LackDependencies="$(DependenciasFaltan "$DependsOnSoftware")"	#"
				if [ "$LackDependencies" != "" ] ; then
					printf '%s\n' "${sERROR}E: Following software must be installed before this dependent program:" 1>&2
					printf '%s\n' "   ${LackDependencies}${fRESET}" 1>&2
					LastStatus=53 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
				LackDependencies="$(DependenciasFaltan "$RecommendedSoftware")"	#"
				if [ "$LackDependencies" != "" ] ; then
					printf '%s\n' "${sWARN}W: Following software is also recommended for this program:" 1>&2
					printf '%s\n' "   ${LackDependencies}${fRESET}" 1>&2
				fi
			fi
		fi
		if [ $StatusCode -eq 0 ] && [ $(id -g) -ne 0 ] ; then
			printf '%s\n' "${sERROR}E: Install actions need to be run with superuser ${ParO}root${ParC} permissions.${fRESET}" 1>&2
			LastStatus=45 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
		if [ $StatusCode -eq 0 ] ; then
			LogProgram 3 "Installing $ProgramName $(ScriptHeaderValue Version)"
			if [ "$PackageManager_Call" = "" ] || [ "$PackageManager_Call" = "precp" ] || [ "$PackageManager_Call" = "whole" ] ; then
				# WARNING: Package manager cannot invoke this action before this script file is installed. Recommended to copy actions to preinst script.
				Install_precp "$@"
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
		fi
		if [ $StatusCode -eq 0 ] ; then
			if [ "$PackageManager_Call" = "" ] || [ "$PackageManager_Call" = "cp" ] || [ "$PackageManager_Call" = "whole" ] ; then
#				printf '%s\n' "Installing $ProgramName"
				Install_cp "$MeExecutable" "$@"
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
		fi
		if [ $StatusCode -eq 0 ] ; then
			if [ "$PackageManager_Call" = "" ] || [ "$PackageManager_Call" = "postcp" ] || [ "$PackageManager_Call" = "whole" ] ; then
				Install_postcp "$@"
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				if [ $StatusCode -eq 0 ] && [ "$PackageManager_Call" = "" ] ; then
					printf '%s\n' ""
					printf '%s\n' "For more options, run: ${ProgramName} --help"
				fi
			fi
		fi
		;;
	"uninstall" )
		Configuration ro "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		if [ "$ActionMode" = "remove" ] ; then
			if [ "$ProgramInstaller" = "1" ] || [ "$PackageManager_Call" != "" ] ; then
				printf '%s\n' "${sWARN}W: To uninstall $ProgramName you must use the action \"uninstall\"${fRESET}" 1>&2
				LastStatus=80 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
			if [ $StatusCode -eq 0 ] ; then
				LastStatus=90 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				ProgramHelp 1>&2
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
		fi
		if [ $StatusCode -eq 0 ] && [ "$PackageManager_Call" = "" ] ; then
			if [ "$ProgramInstaller" != "1" ] ; then
				LastStatus=93 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				ProgramHelp 1>&2
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
		fi
		if [ $StatusCode -eq 0 ] && [ $(id -g) -ne 0 ] ; then
			printf '%s\n' "${sERROR}E: Uninstall actions need to be run with superuser ${ParO}root${ParC} permissions.${fRESET}" 1>&2
			LastStatus=45 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
		if [ $StatusCode -eq 0 ] ; then
			LogProgram 3 "Uninstalling $ProgramName $(ScriptHeaderValue Version)"
			if [ "$PackageManager_Call" = "" ] || [ "$PackageManager_Call" = "predel" ] || [ "$PackageManager_Call" = "whole" ] ; then
				Uninstall_predel
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				if [ $StatusCode -eq 0 ] && [ "$PackageManager_Call" = "predel" ] ; then
					cp "$MeExecutable" "/var/tmp/${ProgramName}.uninstall.tmp"
					cp "$MeExecutable" "${DirTempX}/${ProgramName}.uninstall.tmp"	# Compatibility with old package script: postrm
					chmod u=rx,g=r,o= "/var/tmp/${ProgramName}.uninstall.tmp"
					chmod u=rx,g=r,o= "${DirTempX}/${ProgramName}.uninstall.tmp"
				fi
			fi
		fi
		if [ $StatusCode -eq 0 ] ; then
			if [ "$PackageManager_Call" = "" ] || [ "$PackageManager_Call" = "del" ] || [ "$PackageManager_Call" = "whole" ] ; then
				Uninstall_del
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
		fi
		if [ $StatusCode -eq 0 ] ; then
			if [ "$PackageManager_Call" = "" ] || [ "$PackageManager_Call" = "postdel" ] || [ "$PackageManager_Call" = "whole" ] ; then
				Uninstall_postdel
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
		fi
		if [ $StatusCode -eq 0 ] && [ "$ActionMode" = "purge" ] ; then
			if [ "$PackageManager_Call" = "" ] || [ "$PackageManager_Call" = "purge" ] ; then
				Uninstall_purge
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
		fi
		;;
	"debug" )
		Configuration "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		export LogLevel=4
		"$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		;;
	"--version" )
		Configuration ro "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		printf '%s\n' "${ProgramName} $(ScriptHeaderValue Version)"
		ScriptHeaderValue Copyright
		ScriptHeaderValue Homepage
		;;
	"--help" )
		Configuration ro "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		ProgramHelp
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		;;
#[gesvipen]
	"server" )
		Configuration "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		Server "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		;;
	"servers" )
		Configuration "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		Servers "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		;;
	"client" )
		Configuration "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		Client "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		;;
	"clients" )
		Configuration "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		Clients "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		;;
	"status" )
		Configuration "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		Status "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		;;
	"gw-updown" )
		Configuration "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		Event_GatewayUpDown "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		;;
	"auth-client" )
		Configuration "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		Event_AuthClient "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		;;
	"setup-sudoer" )
		Configuration "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		SetupSudoer "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		;;
	"reload" )
		Configuration "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		ReloadVPN "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		;;
#[/gesvipen]
	"" )
		printf '%s\n' "${sERROR}E: No action specified.${fRESET}" 1>&2
		LastStatus=79 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		Configuration ro "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		ProgramHelp 1>&2
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		;;
	* )
		printf '%s\n' "${sERROR}E: Syntax error; Unknown action ${Action}${fRESET}" 1>&2
		LastStatus=90 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		Configuration ro "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		ProgramHelp 1>&2
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		;;
esac

cd "$PreviousDir" 2>/dev/null
if [ "$NozeroStatusToFile" != "" ] && [ ! -f "$NozeroStatusToFile" ] && [ $StatusCode -ne 0 ] ; then echo $StatusCode > "$NozeroStatusToFile" ; fi
exit $StatusCode
