#!/bin/sh

: <<SCRIPTHEADER
Description: Chrooted SFTP accesses helper
Version: 1.3.4
Copyright: GNU GPL (2010-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 projectes_publics/remcage/remcage
# Software releases can be downloaded from: https://...##

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

# Program development ToDo:
#	- remcage list ; Que no cuente los datos a menos que se pida con: remcage report
#	- Ftpd_enable() : Perfil para fail2ban
#	- Al actualizar de remcage.sh realmente antiguo, deberia tomar la precaucion de mover /etc/sftp-roots.d a /etc/remcage/sftp-roots.d si existe.
#	- Acción «details» = «show» para una cuenta
#	- Acción list: Reemplazar columnas PROPIETARIO & PERM.RAIZ por P.PERMISOS en donde sólo señalar si son o no correctos.
#	- Acción status: Columna U.ENTRADA con fecha-hora de la última sesión.
#	- Detectar que el directorio del nuevo acceso ya tiene «permisos adecuados» y entonces no preguntar sino informar.
#	- Cuando se pregunta «Quiere adaptar el propietario del directorio y contenidos» hay que ofrecer 3 opciones:
#		Directorio + contenidos
#		Lo justo para este acceso
#		No
#	- Otros nombres posibles para el proyecto: sefu
#	- eliminar(): Si id(usuario) != id(sftpshared) entonces buscar elementos "owned by" y pasarlos a sftpshared o root
#	- Integrar la funcionalidad de adduser-sftponly.sh
#	- Preveer la gestión completa desde gestionarlamp.sh , incluyendo pasar contraseña
#	- Importación/exportación de datos, para copias de seguridad y migraciones;
#	  exportar creando un script con las instrucciones de creación de la cuenta con
#	  contraseña ya crifrada.
#	- Envio de informe de resultado por correo-e (--mailinfo para todo --mailerror para sólo en caso de error)
#	- Parámetro --rotate para la copia de seguridad
#	- Desactivación, eliminación y papelera.
#	- Documentar la creacion del usuario compartido:
#	  adduser --force-badname --comment ',' --no-create-home --ingroup sftponly sftpshared
#	- Función de crear subdirectorio, para que detecte a qué acceso corresponde y lo cree con propietario&permisos coherentes.
#	- Acción "fixperms" para revisarle los permisos al destino de una cuenta.
#	- Permitir especificar contraseña para "create", e incluso que se autogenere contraseña con apg/md5


# ProgramName: Brief and compact code name that needs to be unique in the software world. Will be used for filenames and some directories.
ProgramName="remcage"
# 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"
RecommendedSoftware=""
SuggestedSoftware=""
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
# EssentialAtBoot: Is this program necessary for a rescue boot before mounting /usr? (1=Yes 0=No). This determines program FHS location (1: /bin /sbin 0: /usr/bin /usr/sbin)
EssentialAtBoot=0


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

if [ "$TERM" != "" ] ; then
	perl -e 'exit(-p STDOUT ? 1 : 0);'
	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}"
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=''
fi

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)
{
	local TestValue="$1"
	local TrueCode=254  # 254=FALSE
	local LastStatus=0
	
	TestValue="$(expr "$TestValue" : '[ ]*\(.*[^ ]\)[ ]*$')"	# Trim spaces
	if [ "$TestValue" = "" ] ; then TestValue='.' ; fi
	[ "$TestValue" -eq "$TestValue" ] > /dev/null 2>&1
	LastStatus=$?
	if [ $LastStatus -eq 0 ] ; then	TrueCode=0 ; fi
	return $TrueCode
}

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
}

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
}

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: (none)
# Depends on software packages: grep, tr|awk|sed
{
	local OriginalString="$1"
	
	if [ "$OriginalString" != "" ] ; then
		# Busybox tr has no upper/lower conversion
		if [ "$(command -v tr 2>/dev/null)" != "" ] && [ "$(tr --help 2>&1 | grep -e ':lower:')" != "" ] ; then
			printf '%s\n' "$OriginalString" | tr '[:upper:]' '[:lower:]'
		else
			if [ "$(command -v awk 2>/dev/null)" != "" ] ; then
				printf '%s\n' "$OriginalString" | awk '{print tolower($0)}'
			else
				if [ "$(command -v sed 2>/dev/null)" != "" ] && [ "$(sed --help 2>&1 | grep -e 'GNU')" != "" ] ; then
					printf '%s\n' "$OriginalString" | sed -re 's/([[:upper:]]?)/\L\1/g'
				fi
			fi
		fi
	fi
}

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:
#	- 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: (none)
# 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 [ "$(command -v "$EjecutableActual" 2>/dev/null)" != "" ] ; 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
}

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 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
	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
	return $StatusCode
}

LoadVarsValues ()
# Syntax as a sentence: LoadVarsValues $FileOrContent $VariablesNames $VarsRequired $DefaultsFileOrContent
# Descripcion: Runs 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.
#	$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)
#	$3	(optional or empty) Variable names to filter (space separated). If not specified, all file variables will be loaded.
#	$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).
#	- Variables are loaded with case insensitive filter, but are loaded as origin case names.
# Depends on functions: (none)
# Depends on software packages: grep, sed
{
	local FileOrContent="$1"
	local VariablesNames="$2"
	local VarsRequired="$3"
	local DefaultsFileOrContent="$4"
	local CurVarName=''
	local VarsFilter=''
	local LoadedContent=''
	local LastStatus=0
	local StatusCode=0
	
	if [ "$VariablesNames" != "" ] ; then
		for CurVarName in $VariablesNames ; do
			VarsFilter="$VarsFilter -ie ^${CurVarName}="
		done
	else
		VarsFilter=" -ie ^[a-z].*="
	fi
	if [ "$DefaultsFileOrContent" != "" ] ; then
		if [ -f "$DefaultsFileOrContent" ] ; then
			DefaultsFileOrContent="$(cat "$DefaultsFileOrContent")"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
		LoadedContent="$(printf '%s\n' "$DefaultsFileOrContent" | sed -e 's|^[ \t]*||g' | grep $VarsFilter)"
		if [ "$LoadedContent" != "" ] ; then
			eval $LoadedContent
		fi
	fi
	if [ "$FileOrContent" != "" ] ; then
		if [ -f "$FileOrContent" ] ; then
			FileOrContent="$(cat "$FileOrContent")"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
		LoadedContent="$(printf '%s\n' "$FileOrContent" | sed -e 's|^[ \t]*||g' | grep $VarsFilter)"
		if [ "$LoadedContent" != "" ] ; then
			eval $LoadedContent
			if [ "$VarsRequired" = "2" ] && [ "$VariablesNames" != "" ] ; then
				for CurVarName in $VariablesNames ; do
					if [ "$(printf '%s' "$LoadedContent" | grep -ie "^${CurVarName}=")" = "" ] ; then
						LastStatus=104 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					fi
				done
			fi
		else
			if [ "$VarsRequired" = "1" ] || [ "$VarsRequired" = "2" ] ; then
				LastStatus=104 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
		fi
	fi
	return $StatusCode
}

LogProgram ()
# Syntax as a sentence: LogProgram $ThisLevel $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: Dirname
# Depends on software packages: (none)
# Depends on environment variables: LogLevel MainLog
{
	local ThisLevel="$1"
	if [ $# -gt 0 ] ; then shift ; fi
	local DatetimeStamp=''
	local AskedText=''
	local CurString=''
	
	if [ $ThisLevel -ge 0 ] ; then
		DatetimeStamp="$(date '+%Y-%m-%dT%T%z')"
	fi
	ThisLevel="$(printf '%s\n' "$ThisLevel" | sed -e 's|^-||g')"
	if [ "$*" != "" ] && [ $LogLevel -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
		if [ "$MainLog" != "" ] ; then
			mkdir -p "$(Dirname "$MainLog")"
			printf '%s\n' "$DatetimeStamp $AskedText" >> "$MainLog"
		fi
	fi
}


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

BreakingControls ()
{
	if [ $(id -g) -ne 0 ] && [ "$RootRequired" != "0" ] ; then
		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 ()
{
#	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' "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
		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
	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 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')"
		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 x.x.x #####

#[remcage]
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
}

GroupMembers ()
# Syntax as function: $(GroupMembers "$GroupName")
# Description: Returns (stdout) a list of user names that are members of specified group
# Notes: If it's not called by root, files /etc/group and /etc/passwd must be readable.
# Depends on functions: (none)
# Depends on software packages: grep, sed
{
	local GroupName="$1"
	local Gid=''
	local Value=''
	
	if [ "$GroupName" != "" ] ; then
		Gid="$(cat /etc/group 2>/dev/null | grep -e "^${GroupName}:" | cut -f 3 -d ':')"
		Value="$(cat /etc/group 2>/dev/null | grep -e "^${GroupName}:" | cut -f 4 -d ':' | tr -s ',' ' ')"
		Value="$(printf '%s\n' "$Value" ; cat /etc/passwd | cut -f 1,4 -d ':' | grep -e ":${Gid}$" | cut -f 1 -d ':')"
		Value="$(echo TrimAndSingle $Value | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g')"
		Value="$(printf '%s\n' "$Value" | tr -s ' ' '\n' | awk '!seen[$0]++')"
	fi
	Value="$(echo TrimAndSingle $Value | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g')"
	if [ "$Value" != "" ] ; then printf '%s\n' "$Value" ; fi
}

ListadoEncabezados ()
# Sintaxis como instrucción: ListadoEncabezados "$ListId" "$KeyType" $Column1Label $Column2Label $Column3Label $ColumnLabel...
# Descripción:
#	Almacena los encabezados para las columnas de datos para ListadoMostrar
# Parámetros esperados:
#	$1	Identificador exclusivo para este listado, por el que crear los datos temporales. Se recomienda algo como Nombre.$$
#	$2	Tipo ordenación de la clave: "0" o "A" según si es numérico o alfanumérico respectivamente.
#	$3...	Etiqueta de encabezado. Algo como "_NOMBRE" implica alineación a la derecha.
# PENDIENTE: Poder especificar que se recorten columnas a una longitud maxima.
# Notes:
#	- Tab characters in data are replaced by spaces
# Depends on functions: (none)
# Depends on software packages: grep sed
{
	local ListId="$1"
	if [ $# -gt 0 ] ; then shift ; fi
	local KeyType="$1"
	if [ $# -gt 0 ] ; then shift ; fi
	local DataPath=''
	local CurLabel=''
	local CurColumnNr=0
	local CurColumnLength=0
	local DataRow=''
	local CurColAlign=
	local ColsAligns=''
	local SomeHeader=0
	local LastStatus=0
	local StatusCode=0
	
	if [ ! -d "$DirTemp" ] ; then DirTemp="/run/shm" ; fi
	if [ ! -d "$DirTemp" ] ; then DirTemp="/tmp" ; fi
	DataPath="${DirTemp}/list.$(printf '%s' "$ListId" | sed -e 's|.*/||g')"
	if [ -f "${DataPath}/key.typ" ] || [ -f "${DataPath}/heading.txt" ] ; then
		printf '%s\n' "${sWARN}W: Removing previous list with identifier: ${ListId}${fRESET}" 1>&2
	fi
	rm -fr "$DataPath"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	mkdir -p "$DataPath"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	chmod u=rwX,g=rX,o= "$DataPath"
	printf '%s' "$KeyType" > "${DataPath}/key.typ"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	for CurLabel in "$@" ; do
		CurColumnNr=$((CurColumnNr + 1))
		CurLabel="$(printf '%s' "$CurLabel" | tr '\t' ' ')"
		CurColAlign="$(printf '%c' "$CurLabel")"
		if [ "$CurColAlign" = "_" ] ; then
			CurLabel="$(printf '%s' "$CurLabel" | cut -c 2-)"
		else
			CurColAlign='/'
		fi
		ColsAligns="$ColsAligns	$CurColAlign"
		DataRow="$DataRow	$CurLabel"
		if [ "$CurLabel" != "" ] ; then SomeHeader=1 ; fi
		CurColumnLength=${#CurLabel}
		printf '%3s\n' "$CurColumnLength" | tr ' ' '0' >> "${DataPath}/${CurColumnNr}.len"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	done
	printf '%s' "$ColsAligns" | cut -f 2- > "${DataPath}/aligns.tab"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	if [ $SomeHeader -eq 1 ] ; then
		printf '%s' "$DataRow" | cut -f 2- > "${DataPath}/heading.txt"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	else
		cat /dev/null > "${DataPath}/heading.txt"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ "$NozeroStatusToFile" != "" ] && [ ! -f "$NozeroStatusToFile" ] && [ $StatusCode -ne 0 ] ; then echo $StatusCode > "$NozeroStatusToFile" ; fi
	return $StatusCode
}

ListadoNuevoRegistro ()
# Sintaxis como instrucción: ListadoNuevoRegistro "$ListId" "$OrderKey" $Column1Data $Column2Data $Column3Data $Data...
# Descripción:
#	Almacena los datos especificados para ser mostrados por la función ListadoMostrar
# Parámetros esperados:
#	$1	Identificador exclusivo para este listado, por el que se almacenan los datos temporales.
#	$2	Dato a usar para ordenar los registros alfabéticamente. Para no reordenar, especificar cadena en blanco ""
#	$3...	Campos a mostrar en el listado
# Notes:
#	- Tab characters in data are replaced by spaces
# Depends on functions: (none)
# Depends on software packages: sed
{
	local ListId="$1"
	if [ $# -gt 0 ] ; then shift ; fi
	local OrderKey="$1"
	if [ $# -gt 0 ] ; then shift ; fi
	local DataPath=''
	local CurColumnData=''
	local CurColumnNr=0
	local ColumnsLengthsFiles=''
	local CurColumnLengthsFile=''
	local DataRow=''
	local LastStatus=0
	local StatusCode=0
	
	if [ ! -d "$DirTemp" ] ; then DirTemp="/run/shm" ; fi
	if [ ! -d "$DirTemp" ] ; then DirTemp="/tmp" ; fi
	DataPath="${DirTemp}/list.$(printf '%s' "$ListId" | sed -e 's|.*/||g')"
	if [ -f "${DataPath}/key.typ" ] && [ -f "${DataPath}/heading.txt" ] ; then
		KeyType="$(cat "${DataPath}/key.typ")"
		OrderKey="$(printf '%s' "$OrderKey" | tr '\t' ' ')"
		DataRow="$OrderKey"
		ColumnsLengthsFiles="$(ls -1 "${DataPath}"/*.len)"
		IFS="$(printf '\n\b')" ; for CurColumnLengthsFile in $ColumnsLengthsFiles ; do unset IFS
			CurColumnNr=$((CurColumnNr + 1))
			CurColumnData="$(printf '%s' "$1" | tr '\t' ' ')"
			DataRow="$DataRow	$CurColumnData"
			printf '%3s\n' "${#CurColumnData}" | tr ' ' '0' >> "${DataPath}/${CurColumnNr}.len"
			if [ $# -gt 0 ] ; then shift ; fi
		done
		# This is for more data than headed:
		for CurColumnData in "$@" ; do
			CurColumnNr=$((CurColumnNr + 1))
			CurColumnData="$(printf '%s' "$CurColumnData" | tr '\t' ' ')"
			DataRow="$DataRow	$CurColumnData"
			printf '%4s\n' "${#CurColumnData}" | tr ' ' '0' >> "${DataPath}/${CurColumnNr}.len"
		done
		printf '%s\n' "$DataRow" >> "${DataPath}/data.txt"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	else
		printf '%s\n' "${sERROR}E: List data not found with identifier: ${ListId}${fRESET}" 1>&2
		LastStatus=101 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ "$NozeroStatusToFile" != "" ] && [ ! -f "$NozeroStatusToFile" ] && [ $StatusCode -ne 0 ] ; then echo $StatusCode > "$NozeroStatusToFile" ; fi
	return $StatusCode
}

ListadoMostrar ()
# Sintaxis como instrucción: ListadoMostrar "$ListId" "$ColumnSeparator" "$SortOrientation"
# Descripción:
#	Muestra los registros preparados anteriormente en forma de listado de columnas rectas, a base
#	de la inserción de espacios.
# Parámetros esperados:
#	$1	Identificador exclusivo para este listado, por el que hay los datos preparados.
#	$2	Cadena de separación AÑADIDA entre columnas, como por ejemplo un espacio " " o una línea "|"
#	$3	"a" para ordenar la clave en sentido 0-9 A-Z , o "r" para ordenarlas en sentido 9-0 Z-A
# Notas:
#	- Una vez usada la función, se borran los datos temporales y se pierde la información del listado.
#	- TAB can not be used as ColumnSeparator
# Depends on functions: Is_Executable
# Depends on software packages: sed
# Recommends other software: column/bsdmainutils
{
	local ListId="$1"
	local ColumnSeparator="$2"
	local SortOrientation="$3"
	local DataPath=''
	local KeyType=''
	local Heading=''
	local CurHeader=''
	local SortParms=''
	local SortedRecords=''
	local CurRecord=''
	local ColumnsNr=0
	local CurColumnData=''
	local FirstRecord=0
	local ColsLengths=''
	local CurColLength=0
	local HeaderSeparator=''
	local ColsAligns=''
	local CurLine=''
	local HeaderLine=''
	local LastStatus=0
	local StatusCode=0
	
	if [ ! -d "$DirTemp" ] ; then DirTemp="/run/shm" ; fi
	if [ ! -d "$DirTemp" ] ; then DirTemp="/tmp" ; fi
	DataPath="${DirTemp}/list.$(printf '%s' "$ListId" | sed -e 's|.*/||g')"
	if [ -f "${DataPath}/key.typ" ] && [ -f "${DataPath}/heading.txt" ] ; then
		KeyType="$(cat "${DataPath}/key.typ")"
		ColsAligns="$(cat "${DataPath}/aligns.tab")"
		Heading="$(cat "${DataPath}/heading.txt")"
		HeaderSeparator="$(printf '%s' "$ColumnSeparator" | sed -e 's|.| |g')"
		FirstRecord=1
		ColumnsNr="$(ls -1 "${DataPath}"/*.len | wc -l)"
		if [ -f "${DataPath}/data.txt" ] ; then
			if [ "$KeyType" = "0" ] ; then
				SortParms="$SortParms -n"
			fi
			if [ "$SortOrientation" = "r" ] ||  [ "$SortOrientation" = "R" ] ; then
				SortParms="$SortParms -r"
			fi
			SortedRecords="$(cat "${DataPath}/data.txt" | sort $SortParms | cut -f 2-)"
			IFS="$(printf '\n\b')" ; for CurRecord in $SortedRecords ; do unset IFS
				CurColumnNr=0
				CurLine=''
				while [ $CurColumnNr -lt $ColumnsNr ]; do
					CurColumnNr=$((CurColumnNr + 1))
					CurColumnData="$(printf '%s' "$CurRecord" | cut -sf $CurColumnNr)"
					if [ $FirstRecord -eq 1 ]; then
						CurColLength="$(cat "${DataPath}/${CurColumnNr}.len" | sort | tail -n 1)"
						ColsLengths="$ColsLengths	$CurColLength"
					else
						CurColLength="$(printf '%s' "$ColsLengths" | cut -sf $CurColumnNr)"
					fi
					if [ $CurColumnNr -gt 1 ] ; then
						CurLine="$(printf '%s' "${CurLine}$(printf '\t')${ColumnSeparator}")"
						if [ $FirstRecord -eq 1 ] && [ "$Heading" != "" ]; then
							HeaderLine="$(printf '%s' "${HeaderLine}$(printf '\t')${HeaderSeparator}")"
						fi
					fi
					if [ "$(printf '%s' "$ColsAligns" | cut -sf $CurColumnNr)" = "_" ] ; then
						CurLine="${CurLine}$(printf "%${CurColLength}s" "$CurColumnData")"
						if [ $FirstRecord -eq 1 ] && [ "$Heading" != "" ]; then
							CurHeader="$(printf "%s" "$Heading" | cut -sf $CurColumnNr)"
							HeaderLine="${HeaderLine}$(printf "%${CurColLength}s" "$CurHeader")"
						fi
					else
						CurLine="${CurLine}$(printf "%-${CurColLength}s" "$CurColumnData")"
						if [ $FirstRecord -eq 1 ] && [ "$Heading" != "" ]; then
							CurHeader="$(printf "%s" "$Heading" | cut -sf $CurColumnNr)"
							HeaderLine="${HeaderLine}$(printf "%-${CurColLength}s" "$CurHeader")"
						fi
					fi
				done
				if [ $FirstRecord -eq 1 ]; then
					ColsLengths="$(printf '%s' "$ColsLengths" | cut -sf 2-)"
					FirstRecord=0
					if [ "$Heading" != "" ]; then
						printf '%s\n' "$HeaderLine" >> "${DataPath}/columns.tab"
					fi
				fi
				printf '%s\n' "$CurLine" >> "${DataPath}/columns.tab"
			done
		fi
		if [ $FirstRecord -eq 1 ] && [ "$Heading" != "" ]; then
			CurColumnNr=0
			CurLine=''
			while [ $CurColumnNr -lt $ColumnsNr ]; do
				CurColumnNr=$((CurColumnNr + 1))
				CurColumnData="$(printf '%s' "$Heading" | cut -sf $CurColumnNr)"
				if [ $CurColumnNr -gt 1 ] ; then CurLine="$(printf '%s' "${CurLine}$(printf '\t')${HeaderSeparator}")" ; fi
				CurLine="${CurLine}${CurColumnData}"
			done
			printf '%s\n' "$CurLine" >> "${DataPath}/columns.tab"
		fi
		if [ -f "${DataPath}/columns.tab" ] ; then
			if Is_Executable column; then
				cat "${DataPath}/columns.tab" | column -t -s "$(printf '\t')"
			else
				cat "${DataPath}/columns.tab" | sed -e 's|\t||g'
			fi
		fi
	else
		printf '%s\n' "${sERROR}E: List data not found with identifier: ${ListId}${fRESET}" 1>&2
		LastStatus=101 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	rm -fr "$DataPath"
	if [ "$NozeroStatusToFile" != "" ] && [ ! -f "$NozeroStatusToFile" ] && [ $StatusCode -ne 0 ] ; then echo $StatusCode > "$NozeroStatusToFile" ; fi
	return $StatusCode
}

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
}

FilesDirTree ()
# Returns (stdout) the list of each directory path to be re/created for specified files paths
# Example: for "/etc/ssl/newcerts/02.pem" and "/etc/ssl/newcerts/01.pem" returns "/etc" "/etc/ssl" "/etc/ssl/newcerts" (in 3 lines)
# Expected parameters:
#	$1	List of files (one path per line)
# Depends on functions: Dirname
# Depends on software packages: sed
{
	local FilesList="$1"
	local CurrentFile=''
	local CurrentPath=''
	
	FilesList="$(printf '%s\n' "$FilesList" | sed -e 's|//|/|g')"
	IFS="$(printf '\n\b')" ; for CurrentFile in $FilesList ; do unset IFS
		CurrentPath="$(Dirname "$CurrentFile")"
		while [ "$CurrentPath" != '.' ] && [ "$CurrentPath" != '/' ] ; do
			if [ "$Value" = "" ] ; then
				Value="$CurrentPath"
			else
				Value="$(printf '%s\n' "$Value" ; printf '%s\n' "$CurrentPath")"
			fi
			CurrentPath="$(Dirname "$CurrentPath")"
		done
	done
	Value="$(printf '%s\n' "$Value" | sort -u)"
	if [ "$Value" != "" ] ; then
		printf '%s\n' "$Value"
	fi
}

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"
}

HttpGetContent ()
# Syntax as function: $(HttpGetContent "$Url" "$DNS")
# Description: Returns (stdout) retrieved text from internet
# Expected parameters:
#	$1	URI to get the page from
#	$2	(optional) Set the list of DNS servers to be used instead of the system default.  The list of IP addresses should be separated with commas.
# Notes:
#	- Logging and error messages are returned to stderr
# Depends on functions: Is_Executable
# Depends on software packages: wget|curl
{
	local Url="$1"
	local OverrideDNS="$2"
	local StatusCode=0
	
	if Is_Executable wget ; then
		if [ "$(wget --help 2>/dev/null | grep -e '--no-check-certificate')" != "" ] ; then
			if [ "$OverrideDNS" != "" ] ; then
				printf '%s\n' "\$ wget --no-check-certificate \"--dns-servers=${OverrideDNS}\" -T 15 -t 2 -O - \"$Url\"" 1>&2
				wget --no-check-certificate "--dns-servers=${OverrideDNS}" -T 15 -t 2 -O - "$Url"
				StatusCode=$?
				if [ $StatusCode -eq 2 ] ; then
					# unrecognized option '--dns-servers=... (because not libcares build)
					if Is_Executable curl ; then
						printf '%s\n' "\$ curl --dns-servers \"$OverrideDNS\" --connect-timeout 15 --retry 2 -k \"$Url\"" 1>&2
						curl --dns-servers "$OverrideDNS" --connect-timeout 15 --retry 2 -k "$Url"
						StatusCode=$?
					else
						printf '%s\n' "\$ wget --no-check-certificate -T 15 -t 2 -O - \"$Url\"" 1>&2
						wget --no-check-certificate -T 15 -t 2 -O - "$Url"
						StatusCode=$?
					fi
				fi
			else
				printf '%s\n' "\$ wget --no-check-certificate -T 15 -t 2 -O - \"$Url\"" 1>&2
				wget --no-check-certificate -T 15 -t 2 -O - "$Url"
				StatusCode=$?
			fi
		else
			if [ "$OverrideDNS" != "" ] ; then
				printf '%s\n' "\$ wget \"--dns-servers=${OverrideDNS}\" -T 15 -t 2 -O - \"$Url\"" 1>&2
				wget "--dns-servers=${OverrideDNS}" -T 15 -t 2 -O - "$Url"
				StatusCode=$?
				if [ $StatusCode -eq 2 ] ; then
					# unrecognized option '--dns-servers=... (because not libcares build)
					if Is_Executable curl ; then
						printf '%s\n' "\$ curl --dns-servers \"$OverrideDNS\" --connect-timeout 15 --retry 2 -k \"$Url\"" 1>&2
						curl --dns-servers "$OverrideDNS" --connect-timeout 15 --retry 2 -k "$Url"
						StatusCode=$?
					else
						printf '%s\n' "\$ wget -T 15 -t 2 -O - \"$Url\"" 1>&2
						wget -T 15 -t 2 -O - "$Url"
						StatusCode=$?
					fi
				fi
			else
				printf '%s\n' "\$ wget -T 15 -t 2 -O - \"$Url\"" 1>&2
				wget -T 15 -t 2 -O - "$Url"
				StatusCode=$?
			fi
		fi
	else
		if Is_Executable curl ; then
			# Warning: curl interprets some symbols as [] {}
			if [ "$OverrideDNS" != "" ] ; then
				printf '%s\n' "\$ curl --dns-servers \"$OverrideDNS\" --connect-timeout 15 --retry 2 -k \"$Url\"" 1>&2
				curl --dns-servers "$OverrideDNS" --connect-timeout 15 --retry 2 -k "$Url"
				StatusCode=$?
				if [ $StatusCode -eq 4 ] ; then
					# A requested feature, protocol or option was not found built-in in this libcurl due to a build-time decision.
					printf '%s\n' "\$ curl --connect-timeout 15 --retry 2 -k \"$Url\"" 1>&2
					curl --connect-timeout 15 --retry 2 -k "$Url"
					StatusCode=$?
				fi
			else
				printf '%s\n' "\$ curl --connect-timeout 15 --retry 2 -k \"$Url\"" 1>&2
				curl --connect-timeout 15 --retry 2 -k "$Url"
				StatusCode=$?
			fi
		else
			printf '%s\n' "${sERROR}E: Neither wget nor curl program are available to obtain content from internet.${fRESET}" 1>&2
			StatusCode=52
		fi
	fi
	if [ "$NozeroStatusToFile" != "" ] && [ ! -f "$NozeroStatusToFile" ] && [ $StatusCode -ne 0 ] ; then echo $StatusCode > "$NozeroStatusToFile" ; fi
	return $StatusCode
}

PublicIP ()
# Syntax as function: $(PublicIP)
# Description: Returns (stdout) the public IP address in internet, querying it to well-known websites.
# Expected parameters: none.
# To Do:
#	- Use HttpGetContent() instead of wget
# Notes:
#	- If cannot get the address or isn't valid, doesn't return anything.
# Depends on functions: EsIP HttpGetContent
# Depends on software packages: grep, sed
{
	local Server1="http://www.formyip.com/"
	local Label1="Your IP is "
	local Server2="http://checkip.dyndns.org/"
	local Label2="IP Address: "
	local Server3="http://grn.es/ip/"  # GRN
	local Label3=""
	local Server4="https://ifconfig.me/ip/"  # Google
	local Label4=""
	local LabelWords=0
	local Value=''
	local LastStatus=0
	local StatusCode=0
	
	if [ "$Value" = "" ] ; then
		Value="$(HttpGetContent $Server1 2>/dev/null)"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		Value="$(printf '%s' "$Value" | grep -ie "$Label1")"	# GREP RESULTS 1 IF NO MATCH
		if [ "$Value" != "" ] ; then
			Value="$(printf '%s' "$Value" | tr -s ' ' '_' | tr -s "<>/\"=" " " | tr -s " " "\n" | tr -s '_' ' ')"
			if [ "$Label1" != "" ] ; then
				Value="$(printf '%s' "$Value" | sed -e "s/.*$Label1/$Label1/g" | grep -ie "$Label1" | tr -s " " "\n")"
				LabelWords=$(WordsNumber () { printf '%s' $#; }; WordsNumber $Label1)
				Value="$(printf '%s' "$Value" | head -n $((LabelWords + 1)) | tail -n 1)"
			fi
		fi
		if [ "$(EsIP "$Value")" != "1" ] ; then Value="" ; fi
	fi
	if [ "$Value" = "" ] ; then
		Value="$(HttpGetContent $Server2 2>/dev/null)"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		Value="$(printf '%s' "$Value" | grep -ie "$Label2")"	# GREP RESULTS 1 IF NO MATCH
		if [ "$Value" != "" ] ; then
			Value="$(printf '%s' "$Value" | tr -s ' ' '_' | tr -s "<>/\"=" " " | tr -s " " "\n" | tr -s '_' ' ')"
			if [ "$Label2" != "" ] ; then
				Value="$(printf '%s' "$Value" | sed -e "s/.*$Label2/$Label2/g" | grep -ie "$Label2" | tr -s " " "\n")"
				LabelWords=$(WordsNumber () { printf '%s' $#; }; WordsNumber $Label2)
				Value="$(printf '%s' "$Value" | head -n $((LabelWords + 1)) | tail -n 1)"
			fi
		fi
		if [ "$(EsIP "$Value")" != "1" ] ; then Value="" ; fi
	fi
	if [ "$Value" = "" ] ; then
		Value="$(HttpGetContent $Server3 2>/dev/null)"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		Value="$(printf '%s' "$Value" | grep -ie "$Label3")"	# GREP RESULTS 1 IF NO MATCH
		if [ "$Value" != "" ] ; then
			Value="$(printf '%s' "$Value" | tr -s ' ' '_' | tr -s "<>/\"=" " " | tr -s " " "\n" | tr -s '_' ' ')"
			if [ "$Label3" != "" ] ; then
				Value="$(printf '%s' "$Value" | sed -e "s/.*$Label3/$Label3/g" | grep -ie "$Label3" | tr -s " " "\n")"
				LabelWords=$(WordsNumber () { printf '%s' $#; }; WordsNumber $Label3)
				Value="$(printf '%s' "$Value" | head -n $((LabelWords + 1)) | tail -n 1)"
			fi
		fi
		if [ "$(EsIP "$Value")" != "1" ] ; then Value="" ; fi
	fi
	if [ "$Value" = "" ] ; then
		Value="$(HttpGetContent $Server4 2>/dev/null)"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		Value="$(printf '%s' "$Value" | grep -ie "$Label4")"	# GREP RESULTS 1 IF NO MATCH
		if [ "$Value" != "" ] ; then
			Value="$(printf '%s' "$Value" | tr -s ' ' '_' | tr -s "<>/\"=" " " | tr -s " " "\n" | tr -s '_' ' ')"
			if [ "$Label4" != "" ] ; then
				Value="$(printf '%s' "$Value" | sed -e "s/.*$Label4/$Label4/g" | grep -ie "$Label4" | tr -s " " "\n")"
				LabelWords=$(WordsNumber () { printf '%s' $#; }; WordsNumber $Label4)
				Value="$(printf '%s' "$Value" | head -n $((LabelWords + 1)) | tail -n 1)"
			fi
		fi
		if [ "$(EsIP "$Value")" != "1" ] ; then Value="" ; fi
	fi
	if [ "$Value" != "" ] ; then
		printf '%s\n' "$Value"
		StatusCode=0
	fi
	if [ "$NozeroStatusToFile" != "" ] && [ ! -f "$NozeroStatusToFile" ] && [ $StatusCode -ne 0 ] ; then echo $StatusCode > "$NozeroStatusToFile" ; fi
	return $StatusCode
}

#[/remcage]


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

#[remcage]
AccountLinkPath ()
# Returns the path that links (or is) chrooted home directory
# Example: "$(AccountLinkPath remote-john)" == "/etc/remcage/sftp-roots.d/remote-john"
{
	local AccountName="$1"
	local AccountHome=''
	local Value=''
	
	AccountHome="$(cat /etc/passwd 2>/dev/null | grep -e "^${AccountName}:" | cut -f 6 -d ':')"
	Value="$(printf '%s\n' "$ChrootDirectory" | sed -e "s|%u|${AccountName}|g" | sed -e "s|%h|${AccountHome}|g")"
	if [ "$Value" != "" ] ; then printf '%s\n' "$Value" ; fi
}

ver ()
{
	local DirTemp=""
	local Cuentas=""
	local CurrentAccount=""
	local CurrentDestination=""
	local CurrentPermissions=""
	local CurrentOwners=""
	local CurrentContent=""
	local AccountsDone=""
	local CurrentLinkPath
	local VarTmp=""
	
	printf '%s\n' "Grupo de usuarios de acceso enjaulado: $USERS_GN"
	printf '%s\n' "Cuenta para permisos compartidos: $CommonUser"
	printf '%s\n' ""
	DirTemp="$(mktemp)"
	rm "$DirTemp"
	mkdir "$DirTemp"
	chmod u=rwX,g=rX,o= "$DirTemp"
	ListadoEncabezados "$DirTemp" A CUENTA DESTINO PROPIETARIO PERM.RAIZ _CONTENIDO
	Cuentas="$(GroupMembers $USERS_GN | tr -s ' ' '\n')"
	if [ "$Cuentas" = "" ] && [ -d "$sftp_links_path" ] ; then
		Cuentas="$(ls -1A "${sftp_links_path}"/)"
	fi
	Cuentas="$(printf '%s\n' "$Cuentas" ; getent group "$USERS_GN" | cut -f 4 -d ':' | tr -s ',' '\n')"
	IFS="$(printf "\n\b")" ; for CurrentAccount in $Cuentas ; do unset IFS
		if [ "$(printf '%s\n' "$AccountsDone" | grep -e "$CurrentAccount")" = "" ] ; then
			printf '.' 1>&2
			AccountsDone="$(printf '%s\n' "$AccountsDone" ; printf '%s\n' "$CurrentAccount")"
			CurrentLinkPath="$(AccountLinkPath "$CurrentAccount")"
			if [ -e "$CurrentLinkPath" ] || [ -L "$CurrentLinkPath" ] ; then
				CurrentDestination="$(readlink "$CurrentLinkPath")"
				if [ -e "$CurrentDestination" ] ; then
					CurrentOwners="$(stat --format=%U:%G "$CurrentDestination")"
					CurrentPermissions="$(stat --format=%A "$CurrentDestination")"
					CurrentContent="$(echo aaa$(du -sh "$CurrentDestination") | sed -e 's|^aaa||g' | sed -e 's|^ ||g' | cut -f 1 -d ' ')"
				else
					CurrentOwners="${ParO}!${ParC}"
					CurrentPermissions="${ParO}!${ParC}"
					CurrentContent="${ParO}ND!${ParC}"
				fi
			fi
			if [ "$CurrentDestination" = "" ] ; then
				VarTmp="$(cat /etc/shadow | grep -e "^${CurrentAccount}:" | cut -f 2 -d ':')"
				if [ ${#VarTmp} -gt 1 ] ; then
					if [ "$(printf '%s\n' "$VarTmp" | cut -c 1)" != "!" ] && [ "$(printf '%s\n' "$VarTmp" | cut -c 1)" != "*" ] ; then
						VarTmp='ValidPassword'
					else
						VarTmp="Password=$(printf '%s\n' "$VarTmp" | cut -c 1)***"
					fi
				else
					VarTmp="Password=$(printf '%s\n' "$VarTmp" | cut -c 1-2)"
				fi
				CurrentDestination="$(cat /etc/passwd | grep -e "^${CurrentAccount}:" | cut -f 7 -d ':')"
				CurrentDestination="[LoginShell=${CurrentDestination} ${VarTmp}]"
			fi
			ListadoNuevoRegistro "$DirTemp" "$CurrentDestination" "$CurrentAccount" "$CurrentDestination" "$CurrentOwners" "$CurrentPermissions" "$CurrentContent "
			CurrentDestination=""
			CurrentOwners=""
			CurrentPermissions=""
			CurrentContent=""
		fi
	done
	printf '\n' 1>&2
	ListadoMostrar "$DirTemp" "  " r
	rm -r "$DirTemp"
}

List ()
{
	ver "$@"
	return $?
}

crear ()
{
	local Destinacio="$1"
	local Username="$2"
	local Resposta=""
	local RutasCrear=""
	local RutaActual=""
	local StatusCode=0
	if [ "$Destinacio" != "" ] ; then
		if [ "$Username" = "" ] ; then
			printf '%s\n' "Creacion de un miembro del grupo \"$USERS_GN\"."
			printf '%s\n' "Escribe el nombre para la nueva cuenta de usuario:"
			read Username
		fi
		if [ "$Username" != "" ] ; then
			# Comprovació de caràcters basada en el "NAME_REGEX" de /etc/adduser.conf
			# (també permetem "." entremig)
#			if [ "$(printf '%s\n' "$Username" | grep -e "^[a-z][-a-z0-9_.#+]*[a-z0-9]\$")" != "" ] ; then
			if [ "$(printf '%s\n' "$Username" | grep -e "^[a-z][-a-z0-9_.]*[a-z0-9]\$")" != "" ] ; then
				if [ ${#Username} -gt 32 ] ; then
					printf '%s\n' "${sWARN}W: Asked username is longer than 32 characters standard.${fRESET}"
				fi
				if [ "$LinkToShared" = "yes" ] || [ "$LinkToShared" = "1" ] ; then
					CommonUser_ID="$(id -u "$CommonUser" 2>/dev/null)"
					if [ "$CommonUser_ID" != "" ] ; then
						if [ "$USERGROUPS" = "no" ] ; then
							useradd --badname --non-unique --uid $CommonUser_ID --gid $USERS_GN $Username
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
							if [ $LastStatus -ne 0 ] ; then
								printf '%s\n' "Command was: ${sVALUE}useradd --badname --non-unique --uid $CommonUser_ID --gid $USERS_GN $Username${fRESET}" 1>&2
							fi
							if [ $StatusCode -eq 0 ] ; then
								usermod -s /bin/false $Username
								LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
							fi
						else
							useradd --badname --non-unique --uid $CommonUser_ID $Username
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
							if [ $LastStatus -ne 0 ] ; then
								printf '%s\n' "Command was: ${sVALUE}useradd --badname --non-unique --uid $CommonUser_ID $Username${fRESET}" 1>&2
							fi
							if [ $StatusCode -eq 0 ] ; then
								usermod --append --groups $USERS_GN $Username
								LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
							fi
							if [ $StatusCode -eq 0 ] ; then
								usermod -s /bin/false $Username
								LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
							fi
						fi
						if [ $StatusCode -eq 0 ] ; then
							passwd $Username
						fi
					else
						printf '%s\n' "E: No se encuentra el usuario comun \"${CommonUser}\"."
						printf '%s\n' "   You can follow addgoup and adduser guide at http://wiki.gilug.org/index.php/How_to_mount_SFTP_accesses"
						printf '%s\n' ""
						printf '%s\n' "Nota1: La configuracion de esta herramienta se guarda en $SystemConfigFile"
						printf '%s\n' "Nota2: Metodo breve para crear la cuenta:"
						GecosParm='--comment'
						if [ "$(adduser --help | grep -e '--comment')" = "" ] ; then GecosParm='--gecos' ; fi
						printf '%s\n' "sudo adduser --force-badname  --disabled-password $GecosParm ',' --no-create-home --ingroup $USERS_GN $CommonUser"
						printf '%s\n' "sudo usermod --groups $USERS_GN $CommonUser"
						printf '%s\n' "sudo usermod -s /bin/false $CommonUser"
						printf '%s\n' "sudo passwd -l $CommonUser"
						LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					fi
				else
					GecosParm='--comment'
					if [ "$(adduser --help | grep -e '--comment')" = "" ] ; then GecosParm='--gecos' ; fi
					if [ "$USERGROUPS" = "no" ] ; then
						adduser --force-badname $GecosParm ',' --no-create-home --ingroup $USERS_GN $Username | grep -ve "/home/${Username}"
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						if [ "$ADD_EXTRA_GROUPS" = "0" ] ; then
							# Set to a single group and remove from $EXTRA_GROUPS
							usermod --groups $USERS_GN $Username
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						fi
						usermod -s /bin/false $Username
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
#						if [ $StatusCode -eq 0 ] && [ -d /var/lib/AccountsService/users ] ; then
#							# To not show user account in display manager list
#							SetIniVarValue "/var/lib/AccountsService/users/${Username}" SystemAccount "User" true = ''
#						fi
					else
						adduser --force-badname $GecosParm ',' --no-create-home $Username | grep -ve "/home/${Username}"
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						if [ "$ADD_EXTRA_GROUPS" = "0" ] && [ "$(cat /etc/group | grep -e "^${Username}:")" != "" ] ; then
							# Set to a single group and remove from $EXTRA_GROUPS
							usermod --groups $Username $Username
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						fi
						usermod --append --groups $USERS_GN $Username
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						usermod -s /bin/false $Username
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					fi
				fi
				if [ $StatusCode -eq 0 ] && [ "$ADD_EXTRA_GROUPS" != "0" ] ; then
					if [ "$EXTRA_GROUPS" = "" ] ; then
						EXTRA_GROUPS="$(ValorVariableFichero "/etc/adduser.conf" "EXTRA_GROUPS" "")"
					fi
					if [ "$EXTRA_GROUPS" != "" ] ; then
						usermod --append --groups $(printf '%s\n' "$EXTRA_GROUPS" | tr -s " " ",") $Username
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					fi
				fi
				if id "$Username" >/dev/null 2>&1 && [ -d /var/lib/AccountsService/users ] ; then
					# To not be listed by session managers
					printf '%s\n' "Setting $Username as a non-GUI account"
					SetIniVarValue "/var/lib/AccountsService/users/${Username}" SystemAccount User "true" = ''
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
				if [ $StatusCode -eq 0 ] && [ ! -d "$Destinacio" ] ; then
					RutasCrear="$(FilesDirTree "${Destinacio}/abc.txt")"
					IFS="$(printf "\n\b")" ; for RutaActual in $RutasCrear ; do unset IFS
						if [ ! -d "$RutaActual" ] ; then
							mkdir -p "$RutaActual"
							chown "${Username}:${USERS_GN}" "$RutaActual"
							chmod u=rwX,g=rX,o= "$RutaActual"
						fi
					done
				fi
				if [ $StatusCode -eq 0 ] ; then
					CurrentLinkPath="$(AccountLinkPath "$Username")"
					if [ "$sftp_links_path" != "" ] ; then
						mkdir -p "$sftp_links_path"
					fi
					rm -f "$CurrentLinkPath"
					if [ "$Destinacio" != "$CurrentLinkPath" ] ; then
						ln -s "$Destinacio" "$CurrentLinkPath"
					fi
					usermod --home "$CurrentLinkPath" $Username
					chown root "$Destinacio"
					chmod go-w "$Destinacio"
					printf '%s\n' "Para acceder al directorio y poder modificar, debe tener los permisos adecuados."
					printf '%s\n' "¿Quiere adaptar el propietario del directorio y contenidos?"
					printf '%s\n' "$Destinacio"
					printf '%s\n' "[S/n]"
					Resposta="$(RespostaLletra)"
					if [ "$Resposta" != "n" ] && [ "$Resposta" != "N" ] ; then
						chown -R "$Username" "$Destinacio"
						chown root:$(id -gn "$Username") "$Destinacio"
						chmod ug+rX "$Destinacio"
						chmod go-w "$Destinacio"
						if [ "$USERGROUPS" = "no" ] ; then
							chmod g+s "$Destinacio"
						fi
					fi
					printf '%s\n' "Note:    To allow ${Username} writing files, use writable subdirectories, not the chrooted one."
					printf '%s\n' "Example: mkdir \"${Destinacio}/documents\""
					printf '%s\n' "         chown ${Username}:${USERS_GN} \"${Destinacio}/documents\""
					printf '%s\n' "         chmod u+rwX,g+srwX \"${Destinacio}/documents\""
					printf '%s\n' "Done."
				fi
			else
				printf '%s\n' "E: Invalid user name."
				printf '%s\n' "   Specify a username that begins with a letter and ends with letter or number."
				printf '%s\n' "   There can be dots . and hyphens -_ between."
				printf '%s\n' "   ${ParO}letters must be lower case and no international ones such as Ç or Ñ${ParC}"
				StatusCode=1
			fi
		else
			printf '%s\n' "No account name: Canceled by user" 1>&2
			# Not returning error exitcode to allow ignore account creation by a master process wizard (such as ctctl).
#			LastStatus=130 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
	else
		ProgramHelp "$@"
	fi
	return $StatusCode
}

Create ()
{
	crear "$@"
	return $?
}

eliminar ()
{
	local Username="$1"
	local Destino=""
	local StatusCode=0
	if [ "$Username" = "" ] ; then
		printf '%s\n' "Eliminacion de un miembro del grupo \"$USERS_GN\"."
		printf '%s\n' "Escriba el nombre de la cuenta a eliminar:"
		read Username
	fi
	if [ "$Username" != "" ] ; then
		id -gn "$Username" > /dev/null
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		if [ $StatusCode -eq 0 ] ; then
			if [ "$(id -gn "$Username")" = "$USERS_GN" ] ; then
				if [ "$Username" != "$CommonUser" ] ; then
					CurrentLinkPath="$(AccountLinkPath "$Username")"
					if [ -e "$CurrentLinkPath" ] ; then
						Destino="$(readlink "$CurrentLinkPath")"
						if [ -e "$Destino" ] ; then
							printf '%s\n' "Con esta cuenta se accedia a: $Destino"
						else
							printf '%s\n' "La cuenta enlazaba a una ruta inexistente: $Destino"
						fi
						rm -f "$CurrentLinkPath"
					else
						printf '%s\n' "Esta cuenta no tenia acceso a ninguna ruta."
					fi
					userdel "$Username"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					if [ $LastStatus -eq 0 ] ; then
						rm -f "/var/lib/AccountsService/users/${Username}"
					fi
					if [ $StatusCode -eq 0 ] ; then
						printf '%s\n' "Hecho."
					fi
				else
					printf '%s\n' "E: La cuenta $Username es compartida por los distintos accesos."
					StatusCode=1
				fi
			else
				printf '%s\n' "E: La cuenta $Username no es del grupo $USERS_GN"
				StatusCode=1
			fi
		fi
	else
		printf '%s\n' "E: Cuenta de usuario no especificada."
		StatusCode=1
	fi
	return $StatusCode
}

Delete ()
{
	eliminar "$@"
	return $?
}

Status ()
{
	local OpensshdLogfile="/var/log/auth.log"
	local List=""
	local CurrentLine=""
	local CurrentRealUser=""
	local CurrentTime=""
	local CurrentVirtualUser=""
	local CurrentRoot=""
	local CurrentContent=""
	local CurrentUsersHome=""
	local DirTemp=""
	local CurrentLinkPath=""
	
	printf '%s\n' "Grupo de usuarios de acceso enjaulado: $USERS_GN"
	printf '%s\n' "Cuenta para permisos compartidos: $CommonUser"
	printf '%s\n' ""
	DirTemp="$(mktemp)"
	rm "$DirTemp"
	mkdir "$DirTemp"
	chmod u=rwX,g=rX,o= "$DirTemp"
	ListadoEncabezados "$DirTemp" A PID U.EFECTIVO CUENTA IP.CLIENTE _INICIO RAIZ CONTENIDO
#	List="$(ps -A -o ppid,pid,uid,stime,cmd | grep -e 'sshd:.*@notty$' | grep -ve ' grep ')"
	List="$(ps -A -o ppid,pid,uid,stime,cmd | grep -E 'sshd:.*@notty($| )' | grep -ve ' grep ')"
	IFS="$(printf "\n\b")" ; for CurrentLine in $List ; do unset IFS
		CurrentPPid="$(echo aaa$CurrentLine | sed -e 's|^aaa||g' | sed -e 's|^ ||g' | cut -f 1 -d ' ')"
		CurrentClientIP="$(cat "$OpensshdLogfile" | grep -e "\[${CurrentPPid}\]" | grep -e ' from ' | sed -re 's/( from | port )/\n/g' | tail --lines=2 | head --lines=1)"
		CurrentPid="$(echo aaa$CurrentLine | sed -e 's|^aaa||g' | sed -e 's|^ ||g' | cut -f 2 -d ' ')"
		CurrentRealUser="$(echo aaa$CurrentLine | sed -e 's|^aaa||g' | sed -e 's|^ ||g' | cut -f 3 -d ' ')"
		CurrentRealUser="$(cat /etc/passwd | grep -e ".*:.*:${CurrentRealUser}:.*:.*:.*:" | cut -f 1 -d ':' | head --lines=1)"
		CurrentTime="$(echo aaa$CurrentLine | sed -e 's|^aaa||g' | sed -e 's|^ ||g' | cut -f 4 -d ' ')"
		CurrentVirtualUser="$(echo aaa$CurrentLine | sed -e 's|^aaa||g' | sed -e 's|^ ||g' | sed -e 's/ /\n/g' | tail --lines=1 | cut -f 1 -d '@')"
		CurrentUsersHome="$(cat /etc/passwd | grep -e ".*:.*:${CurrentRealUser}:.*:.*:.*:" | cut -f 6 -d ':')"
		if [ "$(printf '%s\n' " $(groups "$CurrentVirtualUser") " | grep -e " $USERS_GN ")" != "" ] ; then
			CurrentLinkPath="$(AccountLinkPath "$CurrentVirtualUser")"
			CurrentRoot="$(readlink "$CurrentLinkPath")"
		else
			CurrentRoot="/"
		fi
		CurrentContent=""
		if [ -d "$CurrentRoot" ] ; then
			if [ "$CurrentRoot" != "/" ] ; then
				CurrentContent="$(echo aaa$(du -sh "$CurrentRoot") | sed -e 's|^aaa||g' | sed -e 's|^ ||g' | cut -f 1 -d ' ')"
			else
				CurrentContent="${ParO}todo${ParC}"
			fi
		else
			CurrentContent="${ParO}?${ParC}"
		fi
		ListadoNuevoRegistro "$DirTemp" "$CurrentTime" "$CurrentPid" "$CurrentRealUser" "$CurrentVirtualUser" "$CurrentClientIP" "$CurrentTime" "$CurrentRoot" "$CurrentContent"
	done
	ListadoMostrar "$DirTemp" "  " a
	rm -r "$DirTemp"
}

Log ()
{
	FilterAccount="$1"
	local OpensshdLogfile="/var/log/auth.log"
	local LastStatus=0
	local StatusCode=0

	if [ -f "$OpensshdLogfile" ] ; then
		LogFiles="$(ls -1 /var/log/ 2>/dev/null | grep -e '^auth\..*\.gz$' | cut -f 3 -d '.' | sort -gr)"
		if [ "$FilterAccount" != "" ] ; then
			for CurFile in $LogFiles ; do
				gunzip -c /var/log/auth.log.${CurFile}.gz 2>/dev/null | grep -e ' password for .* from .* ssh2' -e 'sshd:session' | grep -ie "user ${FilterAccount}$" -ie "user ${FilterAccount} " -ie "for ${FilterAccount} from"
			done
			for CurFile in 9 8 7 6 5 4 3 2 1 ; do
				cat /var/log/auth.log.$CurFile 2>/dev/null | grep -e ' password for .* from .* ssh2' -e 'sshd:session' | grep -ie "user ${FilterAccount}$" -ie "user ${FilterAccount} " -ie "for ${FilterAccount} from"
			done
			cat /var/log/auth.log 2>/dev/null | grep -e ' password for .* from .* ssh2' -e 'sshd:session' | grep -ie "user ${FilterAccount}$" -ie "user ${FilterAccount} " -ie "for ${FilterAccount} from"
		else
			for CurFile in $LogFiles ; do
				gunzip -c /var/log/auth.log.${CurFile}.gz 2>/dev/null | grep -e ' password for .* from .* ssh2' -e 'sshd:session'
			done
			for CurFile in 9 8 7 6 5 4 3 2 1 ; do
				cat /var/log/auth.log.$CurFile 2>/dev/null | grep -e ' password for .* from .* ssh2' -e 'sshd:session'
			done
			cat /var/log/auth.log 2>/dev/null | grep -e ' password for .* from .* ssh2' -e 'sshd:session'
		fi
	else
		printf '%s\n' "E: File not found: $OpensshdLogfile" 1>&2
		LastStatus=60 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	return $StatusCode
}

Ftpd_enable ()
{
	local ThePublicIP=''
	local VpsPort20=''
	local LastStatus=0
	local StatusCode=0

	if [ $StatusCode -eq 0 ] ; then
#		apt install vsftpd
#		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		LackDependencies="$(DependenciasFaltan vsftpd)"
		if [ "$LackDependencies" != "" ] ; then
			printf '%s\n' "${sERROR}E: Following software must be installed before this setup:" 1>&2
			printf '%s\n' "   ${LackDependencies}${fRESET}" 1>&2
			LastStatus=53 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		ThePublicIP="$(PublicIP)"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		if [ "$ThePublicIP" = "" ] ; then
			printf '%s\n' "${sERROR}E: Could not detect public IP to configure passive mode.${fRESET}" 1>&2
			LastStatus=105 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		VpsPort20="$(cat /etc/ssh/sshd_config /etc/ssh/sshd_config.d/* 2>/dev/null | tr -s '\t' ' ' | grep -ie '^Port ' | cut -f 2 -d ' ' | sort -n | tail -n 1)"
		if Is_IntegerNr $VpsPort20 ; then
			VpsPort20=$((VpsPort20 - 2))
		else
			VpsPort20=20
		fi
		printf '%s' "Enter passive data TCP port to configure ${ParO}such as ${VpsPort20}${ParC}: "
		read VpsPort20
		if Is_IntegerNr "$VpsPort20" ; then
			if [ $VpsPort20 -lt 1 ] || [ $VpsPort20 -gt 65535 ] ; then
				printf '%s\n' "${sERROR}E: TCP port number must be in range 1-65535.${fRESET}" 1>&2
				LastStatus=88 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
		else
			printf '%s\n' "${sERROR}E: Integer number is required.${fRESET}" 1>&2
			LastStatus=84 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
	fi
	if [ $StatusCode -eq 0 ] && [ ! -f /etc/pam.d/vsftpd.distrib ] ; then
		dpkg-divert --local --rename --add /etc/pam.d/vsftpd
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		cp -a /etc/pam.d/vsftpd.distrib /etc/pam.d/vsftpd
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ $StatusCode -eq 0 ] && [ ! -f /etc/vsftpd.conf.distrib ] ; then
		dpkg-divert --local --rename --add /etc/vsftpd.conf
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		cp -a /etc/vsftpd.conf.distrib /etc/vsftpd.conf
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		printf '%s\n' "Configuring /etc/pam.d/vsftpd to not require shell access and require be in $USERS_GN group."
		sed -i 's|^\(auth.*required.*pam_shells.so\)|#\1|g' /etc/pam.d/vsftpd
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		if [ "$(cat /etc/pam.d/vsftpd | tr -s '\t' ' ' | grep -ie 'auth required pam_succeed_if.so')" = "" ] ; then
			printf "auth\trequired\tpam_succeed_if.so user ingroup ${USERS_GN}\n" >> /etc/pam.d/vsftpd
		fi
		
#		sed -i 's|^#listen=.*|# vsftpd has a bug to face IPv6 on passive mode.\nlisten=YES|g' /etc/vsftpd.conf
#		sed -i 's|^listen_ipv6=.*|# vsftpd has a bug to face IPv6 on passive mode.\nlisten_ipv6=NO|g' /etc/vsftpd.conf
#		sed -i 's|^#write_enable=.*|write_enable=YES|g' /etc/vsftpd.conf
#		sed -i 's|^#local_umask=.*|local_umask=007|g' /etc/vsftpd.conf
#		sed -i 's|^#ftpd_banner=.*|ftpd_banner=Welcome to FTP service.|g' /etc/vsftpd.conf
#		sed -i 's|^#chroot_local_user=.*|chroot_local_user=YES|g' /etc/vsftpd.conf
#		echo "hide_ids=YES" | tee -a /etc/vsftpd.conf
#		echo "pasv_enable=YES" | tee -a /etc/vsftpd.conf
#		echo "pasv_min_port=$VpsPort20" | tee -a /etc/vsftpd.conf
#		echo "pasv_max_port=$VpsPort20" | tee -a /etc/vsftpd.conf
#		echo "pasv_address=${ThePublicIP}" | tee -a /etc/vsftpd.conf
		printf '%s\n' "Configuring /etc/vsftpd.conf for a passive mode service."
		printf '%s\n' "# Configuration generated by ${ProgramName} $(ScriptHeaderValue Version)
# read the vsftpd.conf.5 manual page to get a full idea of vsftpd's
# capabilities.

# vsftpd has a bug to face IPv6 on passive mode.
listen=YES
listen_ipv6=NO

anonymous_enable=NO
# allow local users to log in
local_enable=YES

# enable any form of FTP write command.
write_enable=YES

local_umask=007

# Activate directory messages - messages given to remote users when they
# go into a certain directory.
dirmessage_enable=YES

# display directory listings with the time in  your local time zone instead of GMT.
use_localtime=YES

ftpd_banner=Welcome to FTP service.
chroot_local_user=YES

# This option should be the name of a directory which is empty.  Also, the
# directory should not be writable by the ftp user. This directory is used
# as a secure chroot() jail at times vsftpd does not require filesystem
# access.
secure_chroot_dir=/var/run/vsftpd/empty

pam_service_name=vsftpd

# user and group information in directory listings to be displayed as \"ftp\"
hide_ids=YES

# all FTP requests and responses be logged. Useful for debugging.
log_ftp_protocol=NO

pasv_enable=YES
pasv_min_port=$VpsPort20
pasv_max_port=$VpsPort20

# override the IP address that vsftpd will advertise in response to the PASV command.
# By default, LAN address is advertised and this may not work for Internet.
pasv_address=$ThePublicIP" > /etc/vsftpd.conf
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ $StatusCode -eq 0 ] && [ $(MeInstalled) -eq 1 ] ; then
		printf '%s\n' '#!/bin/sh' > /etc/cron.daily/remcage-ftp-publicip
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		printf '%s\n' "# Update pasv_address at /etc/vsftpd.conf
$MeCallFile ftp publicip
exit \$?" >> /etc/cron.daily/remcage-ftp-publicip
		chmod ug+rx /etc/cron.daily/remcage-ftp-publicip
	fi
	if [ $StatusCode -eq 0 ] ; then
		systemctl enable vsftpd
		systemctl restart vsftpd
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	return $StatusCode
}

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

	if [ $StatusCode -eq 0 ] ; then
		rm -f /etc/cron.daily/remcage-ftp-publicip
	fi
	if [ $StatusCode -eq 0 ] && [ -f /etc/pam.d/vsftpd.distrib ] ; then
		rm -f /etc/pam.d/vsftpd
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		dpkg-divert --local --rename --remove /etc/pam.d/vsftpd
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ $StatusCode -eq 0 ] && [ -f /etc/vsftpd.conf.distrib ] ; then
		rm -f /etc/vsftpd.conf
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		dpkg-divert --local --rename --remove /etc/vsftpd.conf
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ $StatusCode -eq 0 ] && Is_Executable vsftpd ; then
		systemctl disable vsftpd
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		systemctl stop vsftpd
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		if [ $StatusCode -eq 0 ] ; then
			printf '%s\n' "${sINFO}I: You should now remove vsftpd software package.${fRESET}" 1>&2
		fi
	fi
	return $StatusCode
}

Ftpd_publicip ()
{
	local OldPublicIP=''
	local CurPublicIP=''
	local LastStatus=0
	local StatusCode=0

	if [ "$(cat /etc/vsftpd.conf 2>/dev/null | grep -ie '^pasv_address')" != "" ] ; then
		OldPublicIP="$(IniVarValue /etc/vsftpd.conf pasv_address '' "" =)"
		CurPublicIP="$(PublicIP)"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		if [ $StatusCode -eq 0 ] ; then
			if [ "$CurPublicIP" != "" ] ; then
				if [ "$CurPublicIP" != "$OldPublicIP" ] ; then
					printf '%s\n' "Old public IP was $OldPublicIP - Replacing by current one: $CurPublicIP"
					SetIniVarValue /etc/vsftpd.conf pasv_address '' "$CurPublicIP" =
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				else
					printf '%s\n' "Public IP $OldPublicIP has not changed."
				fi
			else
				printf '%s\n' "${sERROR}E: Could not detect public IP to update passive mode.${fRESET}" 1>&2
				LastStatus=105 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
		fi
	else
		printf '%s\n' "${sERROR}E: pasv_address key not found in /etc/vsftpd.conf${fRESET}" 1>&2
		LastStatus=78 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	return $StatusCode
}

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

	if [ "$(cat "$MeCallFile" | grep -e "^Ftpd_${CalledFunction} ()" -e "^Ftpd_${CalledFunction}()")" != "" ] ; then
		Ftpd_$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 \"ftp\"${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
		printf '%s\n' "${sHEAD1}Actions about VSFTPD:${fRESET}"
		printf '%s\n' ""
		printf '%s\n' "	ftp enable    ${ParO}Setup VSFTPD to serve same SFTP accounts${ParC}"
		printf '%s\n' "	ftp disable   ${ParO}Remove only VSFTPD setup${ParC}"
	fi
	return $StatusCode
}

#[/remcage]


##### 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

	return $StatusCode
}

Uninstall_predel_More ()
# Will be run after stopping service and before program files removal.
{
	DisableFtp='y'
	local LastStatus=0
	local StatusCode=0

	if Is_Executable vsftpd ; then
		printf '%s' "Remove VSFTPD configuration? [y/N]"
		DisableFtp="$(RespostaLletra "$DisableFtp")"
	fi
	if [ "$DisableFtp" = "y" ] ; then
		Ftpd_disable
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	return $StatusCode
}

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

#[remcage]
	if [ "$USERS_GN" != "" ] ; then
		DeleteAccounts="$(GroupMembers $USERS_GN)"
		if [ "$sftp_links_path" != "" ] ; then
			IFS="$(printf "\n\b")" ; for CurAccount in $DeleteAccounts ; do unset IFS
				CurrentLinkPath="$(AccountLinkPath "$CurAccount")"
				if [ "$(printf '%s\n' "$CurrentLinkPath" | grep -e "^${sftp_links_path}/.")" ] ; then
					printf '%s\n' "Deleting access: $CurName"
					userdel "$CurName"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					rm -f "$CurrentLinkPath"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					if [ $LastStatus -eq 0 ] ; then
						rm -f "/var/lib/AccountsService/users/$CurName"
					fi
				fi
			done
		fi
		if [ "$DeleteAccounts" != "" ] && [ "$(GroupMembers $USERS_GN)" = "" ] ; then
			groupdel "$USERS_GN"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
	fi
#[/remcage]
	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
}

Configuration_Saved_Post ()
{
	local ReadOnly=0
	if [ "$1" = "ro" ] ; then ReadOnly=1 ; shift ; fi
#[remcage]
	local LastStatus=0
	local StatusCode=0
	
	ChrootDirectory=""
	sftp_links_path=""
	if [ "$(cat /etc/ssh/sshd_config /etc/ssh/sshd_config.d/*.conf 2>/dev/null | tr -s '\t' ' ' | sed -e 's|^ ||g' | grep -ie '^Subsystem sftp internal-sftp')" != "" ] ; then
		ChrootDirectory="$(cat /etc/ssh/sshd_config /etc/ssh/sshd_config.d/*.conf 2>/dev/null | tr -s '\t' ' ' | sed -e 's|^ ||g' | sed -ne '/^Subsystem sftp internal-sftp/,//p' | sed -e '/^$/q' | cut -f 1 -d '#')"
		ChrootDirectory="$(printf '%s\n' "$ChrootDirectory" | grep -ie '^ChrootDirectory .' | cut -f 2- -d ' ')"
	fi
	if [ "$ChrootDirectory" = "" ] ; then
		ChrootDirectory="${SystemConfigDir}/sftp-roots.d/%u"
	fi
	sftp_links_path="$(printf '%s\n' "$ChrootDirectory" | cut -f 1 -d '%' | sed -e 's|/$||g')"
	USERS_GN=""
	if [ "$(cat /etc/ssh/sshd_config /etc/ssh/sshd_config.d/*.conf 2>/dev/null | tr -s '\t' ' ' | sed -e 's|^ ||g' | grep -ie '^Subsystem sftp internal-sftp')" != "" ] ; then
		USERS_GN="$(cat /etc/ssh/sshd_config /etc/ssh/sshd_config.d/*.conf 2>/dev/null | tr -s '\t' ' ' | sed -e 's|^ ||g' | sed -ne '/^Subsystem sftp internal-sftp/,//p' | sed -e '/^$/q' | cut -f 1 -d '#')"
		USERS_GN="$(printf '%s\n' "$USERS_GN" | grep -ie '^Match Group .' | cut -f 3- -d ' ')"
	fi
	if [ "$USERS_GN" = "" ] ; then
		USERS_GN="sftponly"
	fi
	if [ $ReadOnly -eq 0 ] ; then
		if [ -f /etc/gestionarsftp.conf ] && [ "$SystemConfigFile" != "/etc/gestionarsftp.conf" ] ; then
			if [ -f "$SystemConfigFile" ] ; then
				printf '%s\n' "" >> "$SystemConfigFile"
				printf '%s\n' "# Migrated settings from old gestionarsftp.conf" >> "$SystemConfigFile"
				cat /etc/gestionarsftp.conf >> "$SystemConfigFile"
				mv /etc/gestionarsftp.conf /etc/gestionarsftp.conf.bak
			else
				mkdir -p "$(dirname "$SystemConfigFile")"
				mv /etc/gestionarsftp.conf "$SystemConfigFile"
			fi
		fi
		if [ -f /etc/remcage.conf ] && [ "$SystemConfigFile" != "/etc/remcage.conf" ] ; then
			if [ -f "$SystemConfigFile" ] ; then
				printf '%s\n' "" >> "$SystemConfigFile"
				printf '%s\n' "# Migrated settings from old remcage.conf" >> "$SystemConfigFile"
				cat /etc/remcage.conf >> "$SystemConfigFile"
				mv /etc/remcage.conf /etc/remcage.conf.bak
			else
				mkdir -p "$(dirname "$SystemConfigFile")"
				mv /etc/remcage.conf "$SystemConfigFile"
			fi
		fi
#		USERS_GN="$(GetOrSetIniVarValue "$SystemConfigFile" USERS_GN '' "sftponly" = '' "# USERS_GN: Common group to assign to new user accounts")"
		if [ "$USERS_GN" != "" ] && [ "$(cat /etc/group | grep -e "^${USERS_GN}:")" = "" ] ; then
			groupadd "$USERS_GN"
		fi
		USERGROUPS="$(GetOrSetIniVarValue "$SystemConfigFile" USERGROUPS '' "no" = '' "# USERGROUPS: If own main group is to be created for each new account ${ParO}yes${ParC} or is to be assigned to \$USERS_GN ${ParO}no${ParC} or to apply system policy ${ParO}default${ParC}")"	#in
		EXTRA_GROUPS="$(GetOrSetIniVarValue "$SystemConfigFile" EXTRA_GROUPS '' '""' = '' "# EXTRA_GROUPS: Space-separated list of group names to be additionally assigned to new accounts. To apply system policy, leave empty \"\"")"
		ADD_EXTRA_GROUPS="$(GetOrSetIniVarValue "$SystemConfigFile" ADD_EXTRA_GROUPS '' 0 = '' "# ADD_EXTRA_GROUPS: If extra groups are to be added ${ParO}1${ParC} or new accounts are to be members of a single group ${ParO}0${ParC}")"
		CommonUser="$(GetOrSetIniVarValue "$SystemConfigFile" CommonUser '' "sftpshared" = '' "# CommonUser: A common username for same UID to accounts that share resorces as owners")"	#in
		if [ "$CommonUser" != "" ] && [ "$(cat /etc/group | grep -e "^${USERS_GN}:")" != "" ] ; then
			id "$CommonUser" > /dev/null 2>&1
			LastStatus=$?
			if [ $LastStatus -ne 0 ] ; then
				Gecos="Created by $(printf '%s\n' "$ProgramName" | tr -s ':' '.')"
				GecosParm='--comment'
				if [ "$(adduser --help | grep -e '--comment')" = "" ] ; then GecosParm='--gecos' ; fi
				adduser --force-badname --disabled-password $GecosParm "$Gecos" --no-create-home --ingroup "$USERS_GN" "$CommonUser" | grep -ve "/home/${CommonUser}"
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				if [ "$ADD_EXTRA_GROUPS" = "0" ] ; then
					# Set to a single group and remove from $EXTRA_GROUPS
					usermod --groups "$USERS_GN" "$CommonUser"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
				usermod -s /bin/false "$CommonUser"
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
		fi
		LinkToShared="$(GetOrSetIniVarValue "$SystemConfigFile" LinkToShared '' 1 = '' "# LinkToShared: If new accounts are to work as CommonUser ${ParO}1${ParC} or each one with own identifier ${ParO}0${ParC}")"
#		sftp_links_path="$(GetOrSetIniVarValue "$SystemConfigFile" sftp_links_path '' "\"${SystemConfigDir}/sftp-roots.d\"" = '' "# sftp_links_path: Directory where to create symbolic links to chroot path for each user account")"	#in
		if [ "$sftp_links_path" != "" ] && [ ! -d "$sftp_links_path" ] ; then
			mkdir -p "$sftp_links_path"
			if [ "$USERS_GN" != "" ] ; then
				chown root:${USERS_GN} "$sftp_links_path"
			else
				chown root:root "$sftp_links_path"
			fi
			chmod u=rwX,g=rX,o= "$sftp_links_path"
		fi
		if [ "$(cat /etc/group | grep -e "^${USERS_GN}:")" = "" ] ; then
			printf '%s\n' "E: Group not found: $USERS_GN"
			printf '%s\n' "   You can follow groupadd guide at http://wiki.gilug.org/index.php/How_to_mount_SFTP_accesses"
			printf '%s\n' "Note: $ProgramName configuration is at: $SystemConfigFile"
			LastStatus=102 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
		if [ $StatusCode -eq 0 ] ; then
			if [ -f /etc/pam.d/sshd ] ; then
				if [ "$(cat /etc/pam.d/sshd 2>/dev/null | cut -f 1 -d '#' | grep -ie 'session.*optional.*pam_umask.so.*umask=')" = "" ] ; then
#					printf '%s\n' 'W: /etc/pam.d/sshd seems not configured with session pam_umask.'
#					printf '%s\n' "   You can follow pam.d guide at http://wiki.gilug.org/index.php/How_to_mount_SFTP_accesses"
					printf '%s\n' "Configuring PAM session umask"
					printf '%s\n' "" >> /etc/pam.d/sshd
					printf '%s\n' "# Default umask mask for SSH/SFTP sessions" >> /etc/pam.d/sshd
					printf '%s\n' "# Shell sessions: Settings in /etc/profile or ~/.bashrc or ~/.profile will replace this" >> /etc/pam.d/sshd
					printf '%s\n' "session optional        pam_umask.so umask=0002" >> /etc/pam.d/sshd
				fi
			else
				printf '%s\n' "E: openssh-server file not found: /etc/pam.d/sshd" 1>&2
				LastStatus=95 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
		fi
		if [ $StatusCode -eq 0 ] ; then
			if [ -f /etc/ssh/sshd_config ] ; then
				if [ "$(cat /etc/ssh/sshd_config /etc/ssh/sshd_config.d/*.conf 2>/dev/null | tr -s '\t' ' ' | sed -e 's|^ ||g' | grep -ie '^Subsystem sftp internal-sftp')" = "" ] ; then
#					printf '%s\n' 'W: /etc/ssh/sshd_config seems not configured with Subsystem internal-sftp.'
#					printf '%s\n' "   You can follow sshd_config guide at http://wiki.gilug.org/index.php/How_to_mount_SFTP_accesses"
#					printf '%s\n' "   ${ParO}but with Group $USERS_GN ${ParC}"
#					printf '%s\n' "   ${ParO}but with ChrootDirectory ${sftp_links_path}/%u ${ParC}"
					printf '%s\n' "Configuring sftp subsystem for group $USERS_GN"
					sed -ie 's|\(^Subsystem.*sftp.*\)|#\1|gi' /etc/ssh/sshd_config
					if [ -d /etc/ssh/sshd_config.d ] ; then
						cp -a /etc/ssh/sshd_config /etc/ssh/sshd_config.d/sftp.conf
						printf '%s\n' "" > /etc/ssh/sshd_config.d/sftp.conf
						printf '%s\n' "Subsystem	sftp	internal-sftp" >> /etc/ssh/sshd_config.d/sftp.conf
						printf '%s\n' "Match Group $USERS_GN" >> /etc/ssh/sshd_config.d/sftp.conf
						printf '%s\n' "	# Variables for ChrootDirectory: %h ${ParO}\$HOME${ParC} %u ${ParO}\$USERNAME${ParC}" >> /etc/ssh/sshd_config.d/sftp.conf
						printf '%s\n' "	ChrootDirectory $ChrootDirectory" >> /etc/ssh/sshd_config.d/sftp.conf
						printf '%s\n' "	ForceCommand internal-sftp -u 0002" >> /etc/ssh/sshd_config.d/sftp.conf
						printf '%s\n' "	AllowTcpForwarding no" >> /etc/ssh/sshd_config.d/sftp.conf
						printf '%s\n' "	X11Forwarding no" >> /etc/ssh/sshd_config.d/sftp.conf
					else
						printf '%s\n' "" >> /etc/ssh/sshd_config
						printf '%s\n' "Subsystem	sftp	internal-sftp" >> /etc/ssh/sshd_config
						printf '%s\n' "Match Group $USERS_GN" >> /etc/ssh/sshd_config
						printf '%s\n' "	# Variables for ChrootDirectory: %h ${ParO}\$HOME${ParC} %u ${ParO}\$USERNAME${ParC}" >> /etc/ssh/sshd_config
						printf '%s\n' "	ChrootDirectory $ChrootDirectory" >> /etc/ssh/sshd_config
						printf '%s\n' "	ForceCommand internal-sftp -u 0002" >> /etc/ssh/sshd_config
						printf '%s\n' "	AllowTcpForwarding no" >> /etc/ssh/sshd_config
						printf '%s\n' "	X11Forwarding no" >> /etc/ssh/sshd_config
					fi
					if [ "$(ps -A -o comm | grep -e 'ssh')" != "" ] ; then
						printf '%s\n' "${sINFO}I: Please, restart SSH service to changes take effect.${fRESET}" 1>&2
					fi
				fi
			else
				printf '%s\n' "E: sshd server configuration file not found: /etc/ssh/sshd_config" 1>&2
				LastStatus=95 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
		fi
	else
		if [ -f /etc/gestionarsftp.conf ] && [ "$SystemConfigFile" != "/etc/gestionarsftp.conf" ] ; then
			SystemConfigFile=/etc/gestionarsftp.conf
		fi
		if [ -f /etc/remcage.conf ] && [ "$SystemConfigFile" != "/etc/remcage.conf" ] ; then
			SystemConfigFile=/etc/remcage.conf
		fi
#		USERS_GN="$(IniVarValue "$SystemConfigFile" USERS_GN '' "sftponly" = '')"
		USERGROUPS="$(IniVarValue "$SystemConfigFile" USERGROUPS '' "no" = '')"
		EXTRA_GROUPS="$(IniVarValue "$SystemConfigFile" EXTRA_GROUPS '' "" = '')"
		ADD_EXTRA_GROUPS="$(IniVarValue "$SystemConfigFile" ADD_EXTRA_GROUPS '' 0 = '')"
		CommonUser="$(IniVarValue "$SystemConfigFile" CommonUser '' "sftpshared" = '')"
		LinkToShared="$(IniVarValue "$SystemConfigFile" LinkToShared '' 1 = '')"
#		sftp_links_path="$(IniVarValue "$SystemConfigFile" sftp_links_path '' "$sftp_links_path" = '')"
	fi
	if [ $StatusCode -eq 0 ] && [ "$USERGROUPS" = "default" ] ; then
		USERGROUPS="$(IniVarValue "/etc/adduser.conf" USERGROUPS '' "default" = '')"
	fi
	if [ $StatusCode -eq 0 ] && [ "$ADD_EXTRA_GROUPS" = "default" ] ; then
		ADD_EXTRA_GROUPS="$(IniVarValue "/etc/adduser.conf" ADD_EXTRA_GROUPS '' 0 = '')"
	fi
	if [ "$LinkToShared" = "yes" ] ; then LinkToShared=1 ; fi
	if [ "$LinkToShared" = "no" ] ; then LinkToShared=0 ; fi
	return $StatusCode
#[/remcage]
}

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
#[remcage]
#		printf '%s\n' "Usage: ${SudoPrefix}${ProgramName} {${InstallerActions}}${SudoSuffix}"
		printf '%s\n' "Usage: ${SudoPrefix}${ProgramName} {list|status|log|create|delete${InstallerActions}}${SudoSuffix}"
		printf '%s\n' ""
		printf '%s\n' "Syntaxes:"
		printf '%s\n' ""
		printf '%s\n' "$ProgramName list                   ${ParO}Show registered accesses${ParC}"
		printf '%s\n' "$ProgramName status                 ${ParO}List active sessions${ParC}"
		printf '%s\n' "$ProgramName log                    ${ParO}Show last SSH sessions record${ParC}"
		printf '%s\n' "$ProgramName create /path/to/chroot ${ParO}Add a new SFTP account${ParC}"
		printf '%s\n' "$ProgramName delete \"username\"  ${ParO}Remove an SFTP account${ParC}"
		printf '%s\n' "$ProgramName ftp enable             ${ParO}Setup VSFTPD to serve same SFTP accounts${ParC}"
		printf '%s\n' "$ProgramName ftp disable            ${ParO}Remove only VSFTPD${ParC}"
		printf '%s\n' ""
		printf '%s\n' "W: ChrootDirectory: pathname of a directory to chroot to, after authentication. All components of the pathname must be root-owned directories that are not writable by any other user or group."
#[/remcage]
		printf '%s\n' ""
	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
ParO='(' ; ParC=')' ; Tab="$(printf '\t')"
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 2 -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" ;;
#[remcage]
	"ver" ) Action="list" ;;
	"estado" ) Action="status" ;;
	"history" ) Action="log" ;;
	"record" ) Action="log" ;;
	"crear" ) Action="create" ;;
	"eliminar" ) Action="delete" ;;
#[/remcage]
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
		;;
#[remcage]
	"list" )
		Configuration "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		List "$@"
		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
		;;
	"log" )
		Configuration "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		Log "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		;;
	"create" )
		Configuration "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		Create "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		;;
	"delete" )
		Configuration "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		Delete "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		;;
	"ftp" )
		Configuration "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		Ftpd "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		;;
#[/remcage]
	"" )
		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
