#!/bin/sh

: <<SCRIPTHEADER
Description: Helper to write Asterisk PJSIP and extensions from simple tab lists
Version: 1.1.0
Copyright: GNU GPL (2024-2026) 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/asterisk-tools/
# Software releases can be downloaded from: https://...##

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

# Program development ToDo:
#	- Config & data exporter, to allow host migration or restoration.
#	- Public lines should not be able to call to local extensions as an internal call.
#	- Configurable local prefix and local length
#	- Support client.tpl exceptions, like "client_john.tpl" to be applied for users "john" and "john-555001" unlike client.tpl for the rest.
#	- Support scenario without asterisk-oncall
#	- Get inbound caller name from addressbook, to put it in CALLERID(name) before Dial()
#		Explore CLI command "dialplan set chanvar" to Dialplan asks for a value, and asterisk-oncall returns it by setting a variable.
#	- Support prepaid accounting to restrict outbound calls
#	- Action "create trunk" for a wizard
#	- Action "create user" for a wizard
#	- Catch errors (test with codec lack)
#	- Support multidomain for clients: sip.example.net sip.example.com
#	- Asterisk first-time setup

# Notes:
#	- Movistar-ES trunk (LAN router)
#		LinPhone:
#			Dirección SIP -> sip:(numero telefono)@telefonica.net
#			Dirección de servidor SIP (proxy) -> <sip:10.31.255.134:5070;transport=udp>
#			Contraseña: (numero de teléfono)


# ProgramName: Brief and compact code name that needs to be unique in the software world. Will be used for filenames and some directories.
ProgramName="asterisk-simple"
# 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 asterisk"
RecommendedSoftware="asterisk-oncall"
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
# DataIsPublic: If Configuration and logs can be read by others (1) or not (0)
DataIsPublic=0
# LogRotation: Logrotate frequency to setup (see man logrotate). Empty to not configure rotation of log files.
LogRotation="size 1M"


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

ParO='(' ; ParC=')' ; Tab="$(printf '\t')"
if [ "$TERM" != "" ] ; then
	perl -e "exit${ParO}-p STDOUT ? 1 : 0${ParC};"
	RedirectedStdout=$?
fi
if [ "$TERM" != "" ] && [ $RedirectedStdout -eq 0 ] ; then
	# TO DO: Replace all tput calls by printf. Many documentation tell how to escape through "echo -e"
	# https://stackoverflow.com/questions/2924697/how-does-one-output-bold-text-in-bash
	# https://gist.github.com/izabera/9903f9d942e2667ef2cb
	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)"  # Darker
	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 250)"
	cfGRAY="$(tput setaf 8)"
	cfCGREEN="$(tput setaf 10)"
	cfCYELLOW="$(tput setaf 11)"  # Darker
	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)"
	fBOLD_="$(printf '\033[22m')"  # not bold	https://stackoverflow.com/questions/15579739/in-an-xterm-can-i-turn-off-bold-or-underline-without-resetting-the-current-colo
	fREVERSEC="$(tput rev)"
	fLOW="$(tput dim)"
	fLOW_="$(printf '\033[22m')"  # not dark
	fITALIC="$(tput sitm)"
	fITALIC_="$(printf '\033[23m')"  # not italic
	fUNDERL="$(tput smul)"
	fUNDERL_="$(printf '\033[24m')"  # not underline
	fUNDERLx="$(tput rmul)"  # not underline
#	fNOSTANDOUT="$(tput rmso)"  # No bold, no lines  # Not working in GNU/Linux
#	fNOSTANDOUT="$(echo -e '\033[0m' | sed -e 's|-e||')"  # No bold, no lines, no italic, etc. # Dash echo is buggy with escaped sequences.
#	fNOSTANDOUT="$(/bin/echo -e '\033[0m')"  # Workaround to use Bash if installed
	fNOSTANDOUT="$(printf '\033[0m')"  # No bold, no lines, no italic, etc. # Dash echo is buggy with escaped sequences.
	fBELL="$(tput bel)"
	fRESET="$(tput sgr0)"
	
	sPROMPT="${fBELL}${cfWHITE}${cbDGRAY}${fBOLD}"
	sHEAD0="${cfCYAN}${cbBLACK}${fBOLD}${fUNDERL}"
	sHEAD1="${cfCYAN}${cbDGRAY}${fBOLD}"
	sWARN="${cfYELLOW}${cbDGRAY}${fBOLD}"
	sERROR="${fBELL}${cfRED}${cbBLACK}${fBOLD}"
	sINFO="${cfWHITE}${cbDGRAY}"
	sPROGRESS="${cfCGREEN}${cbDGRAY}"
	sVALUE="${cfVIOLET}${cbDGRAY}${fBOLD}"
	sGOOD="${cfWHITE}${cbBLUEGREEN}${fBOLD}"
	sGOODEM="$sGOOD"
	sGOODNORMAL="${cbBLACK}${cfGREEN}${fBOLD}"
	sDISABLED="${fLOW}"
else
	cbDGRAY=''
	cbRED=''
	cbGREEN=''
	cbCBROWN=''
	cbVIOLET=''
	cbBLUEGREEN=''
	cbCGRAY=''
	cbGRAY=''
	cbCGREEN=''
	cbCYELLOW=''
	cbYELLOW=''
	cbCBLUE=''
	cbCVIOLET=''
	cbCYAN=''
	cbWHITE=''
	cbBLACK=''
	cbBLUE=''
	cbPINK=''
	
	cfDGRAY=''
	cfRED=''
	cfGREEN=''
	cfCBROWN=''
	cfVIOLET=''
	cfBLUEGREEN=''
	cfCGRAY=''
	cfGRAY=''
	cfCGREEN=''
	cfCYELLOW=''
	cfYELLOW=''
	cfCBLUE=''
	cfCVIOLET=''
	cfCYAN=''
	cfWHITE=''
	cfBLACK=''
	cfBLUE=''
	cfPINK=''
	
	fBOLD=''
	fREVERSEC=''
	fLOW=''
	fUNDERL=''
	fUNDERLx=''
	fNOSTANDOUT=''
	fRESET=''
	
	sPROMPT=''
	sHEAD0=''
	sHEAD1=''
	sWARN=''
	sERROR=''
	sINFO=''
	sPROGRESS=''
	sVALUE=''
	sGOOD=''
	sGOODEM=''
	sGOODNORMAL=''
	sDISABLED=''
fi

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

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

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
}

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 unset IFS
				NrElement=$((NrElement + 1))
				if [ $NrElement -gt 1 ] ; then
					if [ $NrElement -gt 2 ] ; then
						if [ "$Value" != "/" ] ; then Value="${Value}/" ; fi
					fi
					Value="${Value}${PreviousElement}"
				else
					if [ "$CurrentElement" = "" ] ; then Value="/" ; fi
				fi
				PreviousElement="$CurrentElement"
			done
			unset IFS
			if [ $NrElement -eq 1 ] && [ "$Value" = "" ] ; then Value="." ; fi
		else
			Value="/"
		fi
	else
		Value="."
	fi
	if [ "$Value" != "" ] ; then printf '%s\n' "$Value" ; fi
}

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

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

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

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

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

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

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

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

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

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

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

LogProgram ()
# Syntax as a sentence: LogProgram $ThisMessageLevel $Message
# Description: Writes message to configured log file, depending on configured LogLevel vs ThisMessageLevel for message.
# Expected parameters:
#	$1	Level number of the message:
#		0=NothingToSay 1=Error 2=Warning 3=NormalInfo 4=DebugInfo
#		(same numbers in negative sign to not write date-time stamp)
#	(rest)	String for stdout/stderr/log
#		Escaped \n are replaced by line breaks.
# Notes:
#	- TTY warning/error/debug formats are already put
#	- stderr is already used for warning/error/debug messages
#	- I:/W:/E: prefixes are NOT added automatically.
# Depends on functions: Is_IntegerNr Dirname LogToFile
# Depends on software packages: perl-base sed
# Supports environment variables: LogLevel MainLog MainControllerLog INIT_SCRIPT_InitCall MaxLogLines DataIsPublic LogTime
{
	local ThisMessageLevel="$1"
	if [ $# -gt 0 ] ; then shift ; fi
	local CallType=''
	local LogFile=''
	local DatetimeStamp=0
	local AskedText=''
	local CurString=''
	local ProgramLogLevel=3
	local RedirectedStdout=''
	local LoggingLog=''
	local LastStatus=0
	local StatusCode=0
	
	perl -e "exit${ParO}-p STDOUT ? 1 : 0${ParC};"  #'
	RedirectedStdout=$?
	if [ $RedirectedStdout -ne 0 ] ; then
		sWARN=''
		fRESET=''
		sERROR=''
		sGOOD=''
	fi
	if [ $ThisMessageLevel -ge 0 ] ; then
		DatetimeStamp=1
	fi
	ThisMessageLevel="$(printf '%s\n' "$ThisMessageLevel" | sed -e 's|^-||g')"
	if Is_IntegerNr "$LogLevel" ; then ProgramLogLevel=$LogLevel ; fi
	if [ "$*" != "" ] && [ $ProgramLogLevel -ge $ThisMessageLevel ] && [ $ThisMessageLevel -gt 0 ] ; then
		unset IFS ; 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 [ $ThisMessageLevel -ge 4 ] ; then
			printf '%s\n' "${sINFO}${AskedText}${fRESET}" 1>&2
		else
			if [ $ThisMessageLevel -eq 3 ] ; then
				printf '%s\n' "$AskedText"
			else
				if [ $ThisMessageLevel -eq 2 ] ; then
					printf '%s\n' "${sWARN}${AskedText}${fRESET}" 1>&2
				else
					if [ $ThisMessageLevel -eq 1 ] ; then
						printf '%s\n' "${sERROR}${AskedText}${fRESET}" 1>&2
					else
						printf '%s\n' "$AskedText" 1>&2
					fi
				fi
			fi
		fi
		LogFile="$MainLog"
		if [ "$LogFile" = "" ] && [ "$MainControllerLog" != "" ] ; then
			LogFile="$MainControllerLog"
			if [ "$INIT_SCRIPT_InitCall" != "1" ] ; then
				CallType=" ${ParO} Manual ${ParC} "
			fi
		fi
		if [ "$LogFile" != "" ] ; then
			if [ "$DataIsPublic" != "" ] && [ ! -f "$LogFile" ] ; then
				if [ ! -d "$(Dirname "$LogFile")" ] ; then
					mkdir -p "$(Dirname "$LogFile")"
					if [ "$DataIsPublic" = "1" ] ; then
						chmod a+rX "$(Dirname "$LogFile")"
					else
						chmod g-w,o= "$(Dirname "$LogFile")"
					fi
				fi
				touch "$LogFile"
				if [ "$DataIsPublic" = "1" ] ; then
					chmod a+r "$LogFile"
				else
					chmod g-w,o= "$LogFile"
				fi
			fi
			LoggingLog="$(LogToFile "$LogFile" "$DatetimeStamp" "$MaxLogLines" "${CallType}${AskedText}")"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			if [ $LastStatus -ne 0 ] ; then
				printf '%s\n' "${sERROR}${LoggingLog}${fRESET}" 1>&2
			fi
		fi
	fi
	return $StatusCode
}

ExecuteString ()
# Description: Writes specified parameters to a shell script, and runs the script.
# Notes:
#	- Defined functions are not copied to temporay script, so don't make it call them
#	- 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
}


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

BreakingControls ()
# THIS FUNCTION IS DEVELOPED AT PROJECT: init_script
# THIS FUNCTION IS COPIED TO PROJECTS: script.sh
{
	local ReservedWords="reload,create,delete,list,details,uninstall,purge,start,stop,restart,status,enable,disable"
	
	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
	if [ "$(printf '%s\n' ",${ReservedWords}," | grep -ie ",${ServiceName},")" != "" ] ; then
		printf '%s\n' "${sERROR}E: ServiceName cannot match the reserved word \"${ServiceName}\"${fRESET}" 1>&2
		exit 92
	fi
	if [ "$(printf '%s\n' ",${ReservedWords}," | grep -ie ",${ProgramName},")" != "" ] ; then
		printf '%s\n' "${sERROR}E: ProgramName cannot match the reserved word \"${ProgramName}\"${fRESET}" 1>&2
		exit 92
	fi
	if [ "$(printf '%s\n' ",${ReservedWords}," | grep -ie ",${ChildSingularName},")" != "" ] ; then
		printf '%s\n' "${sERROR}E: ChildSingularName cannot match the reserved word \"${ChildSingularName}\"${fRESET}" 1>&2
		exit 92
	fi
	if [ "$(printf '%s\n' ",${ReservedWords}," | grep -ie ",${ChildsPluralName},")" != "" ] ; then
		printf '%s\n' "${sERROR}E: ChildsPluralName cannot match the reserved word \"${ChildsPluralName}\"${fRESET}" 1>&2
		exit 92
	fi
}

MkdirAndOrPublic ()
# Syntax as a sentence: MkdirAndOrPublic $NewPath [$OwningForNewElements] [$PermissionsForNewElements] [$MorePermissions]
# THIS FUNCTION IS DEVELOPED AT PROJECT: init_script
# THIS FUNCTION IS COPIED TO PROJECTS: script.sh
# Description: Similar job as "mkdir -p" but is allows owner setting and permissions and different behaviour by $DataIsPublic variable
# Expected parameters:
#	$1	Directory path to create as necessary
#	$2	(optional) User, :Group or User:Group specification as allowed by chown.
#	$3	(optional) Permissions specification as allowed by chmod. Environment variable $DataIsPublic will override permissions to others can read or not
#	$4	(optional) Additional permissions specification to set (useful for g+s not being applied by MkdirAndOrPublic)
# Notes:
#	- Owners/permissions are only set when creating subdirectories; not when they already exist.
#	- Purpose on not using MkdirPP() is to avoid its large dependencies.
# Depends on functions: (none)
# Depends on software packages: (none)
{
	local NewPath="$1"
	local OwningForNewElements="$2"
	local PermissionsForNewElements="$3"
	local MorePermissions="$4"
	local CurrentElement=''
	local PreviousDir=''
	local LastStatus=0
	local StatusCode=0
	
	PreviousDir="$(pwd)"
	IFS='/' ; for CurrentElement in $NewPath ; do unset IFS
#	IFS="$(printf "/")" ; for CurrentElement in $NewPath ; do unset IFS
		if [ $StatusCode -eq 0 ] ; then
			if [ "$CurrentElement" = "" ] ; then
				cd /
			else
				if [ -e "$CurrentElement" ] ; then
					cd "$CurrentElement"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				else
					mkdir "$CurrentElement"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					if [ $StatusCode -eq 0 ] ; then
						if [ "$OwningForNewElements" != "" ] && [ "$OwningForNewElements" != "." ] ; then
							chown "$OwningForNewElements" "$CurrentElement"
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						fi
						if [ "$PermissionsForNewElements" != "" ] && [ "$PermissionsForNewElements" != "." ] ; then
							chmod "$PermissionsForNewElements" "$CurrentElement"
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						fi
						if [ "$MorePermissions" != "" ] && [ "$MorePermissions" != "." ] ; then
							chmod "$MorePermissions" "$CurrentElement"
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						fi
						if [ "$DataIsPublic" = "1" ] ; then
							chmod a+rX "$CurrentElement"
						else
							chmod g-w,o= "$CurrentElement"
						fi
						cd "$CurrentElement"
					fi
				fi
			fi
		fi
	done
	cd "$PreviousDir"
	if [ "$NozeroStatusToFile" != "" ] && [ ! -f "$NozeroStatusToFile" ] && [ $StatusCode -ne 0 ] ; then echo $StatusCode > "$NozeroStatusToFile" ; fi
	return $StatusCode
}

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 MinAgeLine=''
	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
	if [ $StatusCode -eq 0 ] && [ "$LogRotation" != "" ] && [ -d /etc/logrotate.d ] && [ $(id -u) -eq 0 ] ; then
		if [ "$LogRotation" != "hourly" ] ; then MinAgeLine='minage 1' ; fi
		printf '%s\n' "\"${MainLog}\" {
	compress
	notifempty
	nocreate
	rotate 5
	$LogRotation
	$MinAgeLine
	missingok
}" > "/etc/logrotate.d/${ProgramName}"
	fi
	return $StatusCode
}

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

Uninstall_del ()
{
	local MeVersion=''
	MeVersion="$(ScriptHeaderValue Version)"
#	DeleteIfNotMe "$ProgramExecutablePath"
#	DeleteIfNotMe "/bin/${ProgramName}"
#	DeleteIfNotMe "/sbin/${ProgramName}"
#	DeleteIfNotMe "/usr/bin/${ProgramName}"
#	DeleteIfNotMe "/usr/sbin/${ProgramName}"
#	DeleteIfNotMe "/usr/local/bin/${ProgramName}"
#	DeleteIfNotMe "/usr/local/sbin/${ProgramName}"
	rm -f "/bin/${ProgramName}"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	rm -f "/sbin/${ProgramName}"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	rm -f "/usr/bin/${ProgramName}"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	rm -f "/usr/sbin/${ProgramName}"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	rm -f "/usr/local/bin/${ProgramName}"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	rm -f "/usr/local/sbin/${ProgramName}"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	rm -f "/bin/${ProgramName}.sh"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	rm -f "/sbin/${ProgramName}.sh"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	rm -f "/usr/bin/${ProgramName}.sh"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	rm -f "/usr/sbin/${ProgramName}.sh"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	rm -f "/usr/local/bin/${ProgramName}.sh"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	rm -f "/usr/local/sbin/${ProgramName}.sh"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	rm -f "$ProgramExecutablePath"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	if [ $StatusCode -eq 0 ] ; then
		printf '%s\n' "$ProgramName $MeVersion : Program files ${sINFO}removed${fRESET}."
	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}."*
	rm -f "/etc/logrotate.d/${ProgramName}"
	Uninstall_purge_MorePost "$@"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	if [ $LastStatus -eq 0 ] ; then
		printf '%s\n' "Configurations and logs cleaned."
	fi
	return $StatusCode
}

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

# Function taken from script.sh template.

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

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

	if [ "$1" = "ro" ] ; then ReadOnly=1 ; shift ; fi
	MeExecutable="$(ReadlinkF "$MeCallFile")"
	if [ ! -d "$DirTemp" ] ; then DirTemp="/run/shm" ; fi
	if [ ! -d "$DirTemp" ] ; then DirTemp="/tmp" ; fi
	if [ ! -d "$DirTempX" ] ; then DirTempX="/tmp" ; fi
	BreakingControls
	if [ "$1" = "pmc" ] ; then
		PackageManager_Call="$2"
		PackageManager_Mode="$3"
		PackageManager_ForVersion="$4"
	else
		PackageManager_Call=''
	fi
	if [ "$RootRequired" = "0" ] ; then
		ProgramExecutablePath="/bin/${ProgramName}"
	else
		ProgramExecutablePath="/sbin/${ProgramName}"
	fi
#	if [ "$EssentialAtBoot" != "1" ] ; then  # FHS /usr Merge
		ProgramExecutablePath="/usr${ProgramExecutablePath}"
#	fi
	Configuration_Saved_Pre "$@"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	if [ ! -d "$SystemConfigDir" ] && [ $ReadOnly -eq 0 ] && [ -w "$(Dirname "$SystemConfigDir")" ] ; then
#[asterisk-simple]
#		MkdirAndOrPublic "$SystemConfigDir" '' u=rwX,go=rX
		MkdirAndOrPublic "$SystemConfigDir" ':asterisk' u=rwX,go=rX g+s
#[/asterisk-simple]
	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 [ ! -d "$UserDir" ] ; then
		# Sometimes Systemd does not set HOME variable
		UserDir="$(cat /etc/passwd | grep -e "^$(id -un):" | cut -f 6 -d ':')"
		if [ ! -d "$UserDir" ] && [ "$HOME" != "" ] ; then
			UserDir="$HOME"
		fi
	fi
	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
		MkdirAndOrPublic "$UserConfigDir"
	fi
	if [ -f "${UserConfigDir}/default.conf" ] ; then
		# Files structure upgrade
		if [ $ReadOnly -eq 0 ] ; then
			mv "${UserConfigDir}/default.conf" "$UserConfigFile"
		else
			UserConfigFile="${UserConfigDir}/default.conf"
		fi
	fi
	if [ $ReadOnly -eq 0 ] && [ -w "$(Dirname "$SystemConfigFile")" ] ; then
		if [ ! -f "$SystemConfigFile" ] ; then
			printf '%s\n' "# $(ScriptHeaderValue Description)" >> "$SystemConfigFile"
			if [ "$RootRequired" = "0" ] ; then
				printf '%s\n' "# $ProgramName system-wide and user-default working parameters" >> "$SystemConfigFile"
			else
				printf '%s\n' "# $ProgramName working parameters" >> "$SystemConfigFile"
			fi
			printf '%s\n' "" >> "$SystemConfigFile"
		fi
	fi
	CurrentConfigDir="$SystemConfigDir"
	CurrentConfigFile="$SystemConfigFile"
	if [ $(id -u) -ne 0 ] && [ "$RootRequired" = "0" ] ; then
		CurrentConfigDir="$UserConfigDir"
		CurrentConfigFile="$UserConfigFile"
		if [ $ReadOnly -eq 0 ] && [ -w "$(Dirname "$UserConfigFile")" ] ; then
			if [ ! -f "$UserConfigFile" ] ; then
				printf '%s\n' "# $(ScriptHeaderValue Description)" >> "$UserConfigFile"
				printf '%s\n' "# $ProgramName user working parameters. This overrides configurations over $SystemConfigFile" >> "$UserConfigFile"
				printf '%s\n' "" >> "$UserConfigFile"
			fi
		fi
	fi
	MainLog="/var/log/${ProgramName}.log"
	if [ $(id -u) -ne 0 ] ; then
		MainLog="${UserConfigDir}/main.log"
	fi
	if [ "$LogLevel" != "4" ] ; then	# Debugging probably a nested call (exported LogLevel)
		LoadVarsValues "$UserConfigFile" 'LogLevel MaxLogLines' 2 "$SystemConfigFile"
		LastStatus=$?
		if [ $LastStatus -eq 104 ] ; then
			LogLevel="$(GetSetLocalConfig "$ReadOnly" LogLevel '' 3 '# LogLevel: 0=Nothing 1=Errors 2=Warnings+E 3=Info+W+E 4=Debug')"
			MaxLogLines="$(GetSetLocalConfig "$ReadOnly" MaxLogLines '' 0 '\n# MaxLogLines: 0=Unlimited')"
		else
			if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
		if ! Is_IntegerNr "$LogLevel" ; then LogLevel=3 ; fi
	fi
	if [ $ReadOnly -eq 0 ] ; then
		Configuration_Saved_Post "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	else
		Configuration_Saved_Post ro "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	return $StatusCode
}


##### PROGRAM FUNCTIONS from @-funcions x.x.x #####

#[asterisk-simple]
ReplaceVariables ()
# Syntax as a sentence: ReplaceVariables "$VariablesDefinition" "$Template"
# Description: Makes template variables substitution with variables content. Result is returned to stdout.
# Expected parameters:
#	$1	File (or content) where variables and values are defined in a table:
#		Each line is: VariableName + TAB + Value
#	$2	File (or content) with cases to be searched and replaced.
# Notes:
#	- Variables names can contain anything filtrable by sed. I.e: MyVariable {MyVariable} $MyVariable
#	  BUT vertical bar character (|)
#	- Variables names are case-sensitive
#	- No quotes are needed for values
#	- Consider envsubst: cat MyFile.txt | envsubst
# Depends on functions: (none)
# Depends on software packages: sed
{
	local VariablesDefinition="$1"
	local Template="$2"
	local CurVar=''
	local CurVarName=''
	local CurVarValue=''
	local Tab=''
	local Value=''
	
	if [ -f "$VariablesDefinition" ] ; then
		VariablesDefinition="$(cat "$VariablesDefinition")"
	fi
	if [ -f "$Template" ] ; then
		Template="$(cat "$Template")"
	fi
	Value="$Template"
	if [ "$Value" != "" ] ; then
		Tab="$(printf '\t')"
		IFS="$(printf '\n\b')" ; for CurVar in $VariablesDefinition ; do unset IFS
			CurVarName="$(printf '%s\n' "$CurVar" | cut -f 1 -d "$Tab")"
			CurVarValue="$(printf '%s\n' "$CurVar" | cut -sf 2- -d "$Tab")"
			Value="$(printf '%s\n' "$Value" | sed -e "s|${CurVarName}|${CurVarValue}|g")"
		done
		if [ "$Value" != "" ] ; then
			printf '%s\n' "$Value"
		fi
	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] [BindAddress] [TimeoutS] [TriesNr])
# Description: Returns (stdout) retrieved content from internet
# Expected parameters:
#	$1	URI to get the page from
#	$2	(optional or empty) Set the list of DNS servers to be used instead of the system default.  The list of IP addresses should be separated with commas.
#	$3	(optional or empty) Local IP address to bind to when making client TCP/IP connections.
#		This option can be useful if your machine is bound to multiple IPs.
#	$4	(optional or empty) Seconds timeout. Default is 15.
#	$5	(optional) Tries number. Default is 2.

# Notes:
#	- Logging and error messages are returned to stderr
# Depends on functions: Is_Executable Is_IntegerNr
# Depends on software packages: wget|curl
{
	local Url="$1"
	local OverrideDNS="$2"
	local BindAddress="$3"
	local TimeoutS="$4"
	local TriesNr="$5"
	local StatusCode=0
	local WgetBind=''
	local CurlBind=''
	local RetriesNr=0
	
	if [ "$BindAddress" != "" ] ; then
		WgetBind="--bind-address=$BindAddress"
		CurlBind="--interface $BindAddress"
	fi
	if ! Is_IntegerNr $TimeoutS ; then TimeoutS=15 ; fi
	if ! Is_IntegerNr $TriesNr ; then TriesNr=2 ; fi
	if [ $TriesNr -le 0 ] ; then TriesNr=1 ; fi
	RetriesNr=$((TriesNr - 1))
	if Is_Executable wget ; then
		if [ "$(wget --help 2>/dev/null | grep -e '--no-check-certificate')" != "" ] ; then
			if [ "$OverrideDNS" != "" ] ; then
				printf '%s\n' "\$ wget $WgetBind --no-check-certificate \"--dns-servers=${OverrideDNS}\" -T $TimeoutS -t $TriesNr -O - \"$Url\"" 1>&2
				wget $WgetBind --no-check-certificate "--dns-servers=${OverrideDNS}" -T $TimeoutS -t $TriesNr -O - "$Url"
				StatusCode=$?
				if [ $StatusCode -eq 2 ] || [ $StatusCode -eq 7 ] || [ $StatusCode -eq 8 ] ; then
					# 2: unrecognized option '--dns-servers=... (because not libcares build)
					# 8: Server issued an error response (such as 403 Forbidden for this UA) -> Must try with curl
					if Is_Executable curl ; then
						printf '%s\n' "\$ curl $CurlBind --dns-servers \"$OverrideDNS\" --connect-timeout $TimeoutS --retry $RetriesNr -k -L \"$Url\"" 1>&2
						curl $CurlBind --dns-servers "$OverrideDNS" --connect-timeout $TimeoutS --retry $RetriesNr -k -L "$Url"
						StatusCode=$?
					else
						printf '%s\n' "\$ wget $WgetBind --no-check-certificate -T $TimeoutS -t $TriesNr -O - \"$Url\"" 1>&2
						wget $WgetBind --no-check-certificate -T $TimeoutS -t $TriesNr -O - "$Url"
						StatusCode=$?
					fi
				else
					if [ $StatusCode -eq 4 ] ; then
						# GnuTLS: A TLS fatal alert has been received (wget using old GnuTLS when connecting to TLS-SNI services) - curl seems to not fail on this.
						if Is_Executable curl ; then
							printf '%s\n' "\$ curl $CurlBind --dns-servers \"$OverrideDNS\" --connect-timeout $TimeoutS --retry $RetriesNr -k -L \"$Url\"" 1>&2
							curl $CurlBind --dns-servers "$OverrideDNS" --connect-timeout $TimeoutS --retry $RetriesNr -k -L "$Url"
							StatusCode=$?
						fi
					fi
				fi
			else
				printf '%s\n' "\$ wget --no-check-certificate -T $TimeoutS -t $TriesNr -O - \"$Url\"" 1>&2
				wget $WgetBind --no-check-certificate -T $TimeoutS -t $TriesNr -O - "$Url"
				StatusCode=$?
				if [ $StatusCode -eq 4 ] ; then
					# GnuTLS: A TLS fatal alert has been received (wget using old GnuTLS when connecting to TLS-SNI services) - curl seems to not fail on this.
					if Is_Executable curl ; then
						printf '%s\n' "\$ curl $CurlBind --connect-timeout $TimeoutS --retry $RetriesNr -k -L \"$Url\"" 1>&2
						curl $CurlBind --connect-timeout $TimeoutS --retry $RetriesNr -k -L "$Url"
						StatusCode=$?
					fi
				fi
			fi
		else
			if [ "$OverrideDNS" != "" ] ; then
				printf '%s\n' "\$ wget $WgetBind \"--dns-servers=${OverrideDNS}\" -T $TimeoutS -t $TriesNr -O - \"$Url\"" 1>&2
				wget $WgetBind "--dns-servers=${OverrideDNS}" -T $TimeoutS -t $TriesNr -O - "$Url"
				StatusCode=$?
				if [ $StatusCode -eq 2 ] ; then
					# 2: unrecognized option '--dns-servers=... (because not libcares build)
					# 8: Server issued an error response (such as 403 Forbidden for this UA) -> Must try with curl
					if Is_Executable curl ; then
						printf '%s\n' "\$ curl $CurlBind --dns-servers \"$OverrideDNS\" --connect-timeout $TimeoutS --retry $RetriesNr -k -L \"$Url\"" 1>&2
						curl $CurlBind --dns-servers "$OverrideDNS" --connect-timeout $TimeoutS --retry $RetriesNr -k -L "$Url"
						StatusCode=$?
					else
						printf '%s\n' "\$ wget $WgetBind -T $TimeoutS -t $TriesNr -O - \"$Url\"" 1>&2
						wget $WgetBind -T $TimeoutS -t $TriesNr -O - "$Url"
						StatusCode=$?
					fi
				else
					if [ $StatusCode -eq 4 ] ; then
						# GnuTLS: A TLS fatal alert has been received (wget using old GnuTLS when connecting to TLS-SNI services) - curl seems to not fail on this.
						if Is_Executable curl ; then
							printf '%s\n' "\$ curl $CurlBind --dns-servers \"$OverrideDNS\" --connect-timeout $TimeoutS --retry $RetriesNr -k -L \"$Url\"" 1>&2
							curl $CurlBind --dns-servers "$OverrideDNS" --connect-timeout $TimeoutS --retry $RetriesNr -k -L "$Url"
							StatusCode=$?
						fi
					fi
				fi
			else
				printf '%s\n' "\$ wget $WgetBind -T $TimeoutS -t $TriesNr -O - \"$Url\"" 1>&2
				wget $WgetBind -T $TimeoutS -t $TriesNr -O - "$Url"
				StatusCode=$?
				if [ $StatusCode -eq 4 ] ; then
					# GnuTLS: A TLS fatal alert has been received (wget using old GnuTLS when connecting to TLS-SNI services) - curl seems to not fail on this.
					if Is_Executable curl ; then
						printf '%s\n' "\$ curl $CurlBind --connect-timeout $TimeoutS --retry $RetriesNr -k -L \"$Url\"" 1>&2
						curl $CurlBind --connect-timeout $TimeoutS --retry $RetriesNr -k -L "$Url"
						StatusCode=$?
					fi
				fi
			fi
		fi
	else
		if Is_Executable curl ; then
			# Warning: curl interprets some symbols as [] {}
			if [ "$OverrideDNS" != "" ] ; then
				printf '%s\n' "\$ curl $CurlBind --dns-servers \"$OverrideDNS\" --connect-timeout $TimeoutS --retry $RetriesNr -k -L \"$Url\"" 1>&2
				curl $CurlBind --dns-servers "$OverrideDNS" --connect-timeout $TimeoutS --retry $RetriesNr -k -L "$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 $CurlBind --connect-timeout $TimeoutS --retry $RetriesNr -k -L \"$Url\"" 1>&2
					curl $CurlBind --connect-timeout $TimeoutS --retry $RetriesNr -k -L "$Url"
					StatusCode=$?
				fi
			else
				printf '%s\n' "\$ curl $CurlBind --connect-timeout $TimeoutS --retry $RetriesNr -k -L \"$Url\"" 1>&2
				curl $CurlBind --connect-timeout $TimeoutS --retry $RetriesNr -k -L "$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
}

WorkingInternetDNS ()
# Syntax as a function: "$(WorkingInternetDNS [AskedResultsNr [Proposals]])"
# Description: Returns IPv4 address of a working public domain name service. Space-separated list of hosts.
# Expected parameters:
#	$1	(optional or empty) Number of desired addresses if possible. Default is 1.
#	$2	(optional) IPv4 addresses to prioritize if they work (space separated).
# Notes:
#	- If does not resolve, returns empty "" string
# TO DO:
#	- Query opennic.org to get recommended servers
#	- Allow proposing first hosts candidates: Direct IP addresses or an URL where to get candidates from.
#	- Allow restrict type, such as only OpenNIC or only-proposed
# Depends on functions: Is_IntegerNr Is_Executable EsIP
# Depends on software packages: host:dig:dnsget:nslookup/bind9-host|dnsutils|udns-utils
{
	local AskedResultsNr=$1
	local Proposals="$2"
	local OpenNicServers='51.77.149.139 152.53.15.127 194.36.144.87 94.247.43.254 130.61.69.123 195.10.195.195 65.21.1.106 37.252.191.197 172.233.66.93'
	local DominationServers='9.9.9.9 1.1.1.1 8.8.8.8 8.8.4.4'
	local PossibleServers="$OpenNicServers $DominationServers"
	local CurServer=''
	local CurPointedIP=''
	local Netname='www.w3.org'
	local CurValue=''
	local ValuesNr=0
	local Value=''
	
	if ! Is_IntegerNr "$AskedResultsNr" ; then AskedResultsNr=1 ; fi
	if [ "$Proposals" != "" ] && [ "$Proposals" != "." ] ; then
		PossibleServers="$Proposals $PossibleServers"
	fi
	unset IFS ; for CurServer in $PossibleServers ; do
		if [ $ValuesNr -lt $AskedResultsNr ] ; then
			CurPointedIP=''
			if [ "$(EsIP "$CurPointedIP")" != "1" ] && Is_Executable host ; then
				CurPointedIP="$(host -W 3 "$Netname" "$CurServer" | grep -ie 'address ..*\...*\...*\...*' | sed -e 's|.*address ||g')"
				CurPointedIP="$(printf '%s' "$CurPointedIP" | tail -n 1)"
			fi
			if [ "$(EsIP "$CurPointedIP")" != "1" ] && Is_Executable dig ; then
				CurPointedIP="$(dig +short +timeout=3 +tries=1 "@${CurServer}" "$Netname")"
				CurPointedIP="$(printf '%s' "$CurPointedIP" | tail -n 1)"
			fi
			if [ "$(EsIP "$CurPointedIP")" != "1" ] && Is_Executable dnsget ; then
				CurPointedIP="$(dnsget -q -n "$CurServer" "$Netname" | grep -e '..*\...*\...*\...*')"
				CurPointedIP="$(printf '%s' "$CurPointedIP" | tail -n 1)"
			fi
			if [ "$(EsIP "$CurPointedIP")" != "1" ] && Is_Executable nslookup ; then
				CurPointedIP="$(nslookup -retry=1 -timeout=3 "$Netname" "$CurServer" | grep -ive 'NXDOMAIN' -ive "${CurServer}#" | tr -s '\t' ' ' | grep -ie 'Address:')"
				CurPointedIP="$(printf '%s\n' "$CurPointedIP" | cut -f 2 -d ':' | sed -e 's|^ ||g' | sed -e 's| $||g')"
				CurPointedIP="$(printf '%s' "$CurPointedIP" | tail -n 1)"
			fi
			if [ "$(EsIP "$CurPointedIP")" = "1" ] ; then
				if [ "$Value" != "" ] ; then Value="$Value " ; fi
				Value="${Value}${CurServer}"
				ValuesNr=$((ValuesNr + 1))
			fi
		fi
	done
	if [ "$Value" != "" ] ; then printf '%s\n' "$Value" ; fi
}

PublicIP ()
# Syntax as function: $(PublicIP [BindAddress] [TimeoutS])
# Description: Returns (stdout) the public IP address in internet, querying it to well-known websites.
# Expected parameters:
#	$1	(optional or empty) Local IP address to bind to when making client TCP/IP connections.
#		This option can be useful if your machine is bound to multiple IPs.
#	$2	(optional) Seconds timeout for each try. There can be as many tries as servers configured.
# Notes:
#	- If cannot get the address or isn't valid, doesn't return anything.
# To Do:
#	- Implement queries to public adapted DNS services. Example:
#		dig +short myip.opendns.com @1.1.1.1
# Depends on functions: EsIP HttpGetContent WorkingInternetDNS
# Depends on software packages: grep, sed
{
	local BindAddress="$1"
	local TimeoutS="$2"
	local Server1="http://www.formyip.com/"
	local Label1="Your IP is "
	local Server2="http://checkip.dyndns.org/"
	local Label2="IP Address: "
	local Server3="https://grn.cat/ip/"  # GRN
	local Label3=""
	local Server4="https://ifconfig.me/ip/"  # Google
	local Label4=""
	local LabelWords=0
	local Value=''
	local DNS=''
	local LastStatus=0
	local StatusCode=0
	
	DNS="$(WorkingInternetDNS)"
	if [ "$Value" = "" ] ; then
		Value="$(HttpGetContent $Server1 "$DNS" "$BindAddress" "$TimeoutS" 1 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 "$DNS" "$BindAddress" "$TimeoutS" 1 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 "$DNS" "$BindAddress" "$TimeoutS" 1 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 "$DNS" "$BindAddress" "$TimeoutS" 1 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
}

#[/asterisk-simple]


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

#[asterisk-simple]
AsteriskVersion ()
{
	local Value=''
	Value="$(asterisk -V 2>/dev/null | tr -s '\t ' '\n' | grep -e '^[0-9].*\..' | head -n 1 | cut -f 1 -d '.')"
	if ! Is_IntegerNr "$Value" ; then
		Value="$(/usr/sbin/asterisk -V 2>/dev/null | tr -s '\t ' '\n' | grep -e '^[0-9].*\..' | head -n 1 | cut -f 1 -d '.')"
	fi
	if ! Is_IntegerNr "$Value" ; then
		Value="$(/sbin/asterisk -V 2>/dev/null | tr -s '\t ' '\n' | grep -e '^[0-9].*\..' | head -n 1 | cut -f 1 -d '.')"
	fi
	if Is_IntegerNr "$Value" ; then
		printf '%s\n' "$Value"
	fi
}

SortedCodecs ()
{
	local ReferentCodecs='opus speex g722 alaw g729 g726 ulaw'
	local QualitySortFirst='opus speex32 speex16 g722'
	local SupportedCodecs=''
	local CurCodec=''
	local CurCodec2=''
	local CurCodec3=''
	local CurTranslated_YES=1
	local ReferentSupported=''
	local ReferentTranslated=''
	local CurTranslation=''
	local CurTranslated=''
	local CurUntranslated=''
	local AdditionalTranslated=''
	local Prioritized=''
	local NotPrioritized=''
	local Value=''
	
	SupportedCodecs="$(asterisk -rx 'core show translation' | tr -s '\t' ' ' | grep -e '[0-9] [0-9].*[0-9] [0-9]' | grep -e ' -' | sed -e 's|^ ||g' | cut -f 1 -d ' ' | grep -ive 'test')"
	for CurCodec in $ReferentCodecs ; do
		if [ "$(printf '%s' "$SupportedCodecs" | grep -ie "^${CurCodec}")" != "" ] ; then
			ReferentSupported="$ReferentSupported $CurCodec"
		fi
	done
#	Value="$SupportedCodecs"
	for CurCodec in $ReferentSupported ; do
		CurTranslated_YES=1
		CurTranslation="$(asterisk -rx "core show translation paths $CurCodec" | tr -s '\t' ' ' | grep -ive 'test' | grep -ie ' To .*:.*->')"
		CurTranslated="$(printf '%s' "$CurTranslation" | grep -ive ':[0-9].* To .*:.*No Translation Path' | sed -e 's|.* To ||gi' | cut -f 1 -d ':' | grep -ive "^${CurCodec}")"
		CurUntranslated="$(printf '%s' "$CurTranslation" | grep -ie ':[0-9].* To .*:.*No Translation Path' | sed -e 's|.* To ||gi' | cut -f 1 -d ':' | grep -ive "^${CurCodec}")"
		for CurCodec2 in $ReferentSupported ; do
			if [ "$(printf '%s' "$CurUntranslated" | grep -e "^${CurCodec2}")" != "" ] ; then
				CurTranslated_YES=0
			fi
		done
		if [ $CurTranslated_YES -eq 1 ] ; then
			ReferentTranslated="$ReferentTranslated $CurCodec"
			for CurCodec2 in $CurUntranslated ; do
				AdditionalTranslated="$(printf '%s' "$AdditionalTranslated" | grep -ve "^${CurCodec2}")"
			done
			AdditionalTranslated="$(printf '%s\n' "$AdditionalTranslated" ; printf '%s\n' "$CurTranslated" | grep -ive "^${CurCodec}$")"
		fi
	done
	if [ "$ReferentTranslated" = "" ] ; then
		for CurCodec in $ReferentSupported ; do
			CurTranslation="$(asterisk -rx "core show translation paths $CurCodec" | tr -s '\t' ' ' | grep -ive 'test' | grep -ie ' To .*:.*->')"
			CurTranslated="$(printf '%s' "$CurTranslation" | grep -ive ':[0-9].* To .*:.*No Translation Path' | sed -e 's|.* To ||gi' | cut -f 1 -d ':')"
			CurUntranslated="$(printf '%s' "$CurTranslation" | grep -ie ':[0-9].* To .*:.*No Translation Path' | sed -e 's|.* To ||gi' | cut -f 1 -d ':')"
			for CurCodec2 in $CurUntranslated ; do
				AdditionalTranslated="$(printf '%s' "$AdditionalTranslated" | grep -ve "^${CurCodec2}")"
			done
			if [ "$(printf '%s' "$AdditionalTranslated" | grep -e "^${CurCodec}$")" != "" ] ; then
				AdditionalTranslated="$(printf '%s\n' "$AdditionalTranslated" ; printf '%s\n' "$CurTranslated" | grep -ive "^${CurCodec}$")"
			else
				AdditionalTranslated="$(printf '%s\n' "$AdditionalTranslated" ; printf '%s\n' "$CurTranslated")"
			fi
		done
	fi
	if [ "$ReferentTranslated" != "" ] ; then
		for CurCodec in $ReferentTranslated $AdditionalTranslated ; do
			if [ "$(echo " $Value " | grep -ie " $CurCodec " )" = "" ] ; then
				Value="$Value $CurCodec"
			fi
		done
	else
		for CurCodec in $AdditionalTranslated ; do
			if [ "$(echo " $Value " | grep -ie " $CurCodec " )" = "" ] ; then
				Value="$Value $CurCodec"
			fi
		done
	fi
	NotPrioritized="$Value"
	for CurCodec in $QualitySortFirst ; do
		if [ "$(echo $SupportedCodecs | tr ' ' '\n' | grep -ie "^${CurCodec}$")" != "" ] ; then
			for CurCodec2 in $NotPrioritized ; do
				CurCodec3="$(echo $QualitySortFirst | tr ' ' '\n' | grep -ie "^$CurCodec2")"
				if [ "$CurCodec3" != "" ] ; then
					Prioritized="$Prioritized $CurCodec3"
					NotPrioritized="$(echo $NotPrioritized | sed -e "s| ${CurCodec2} | |g" -e "s|^${CurCodec2} ||g" -e "s| ${CurCodec2}$||g" -e "s|${CurCodec2}||g")"
				fi
			done
		fi
	done
	Value="$Prioritized $NotPrioritized"
	if [ "$Value" != "" ] ; then
		echo $Value | tr ' ' ','
	fi
}

ReloadService ()
{
	local LastStatus=0
	local StatusCode=0
	
	echo "Applying changes to Asterisk service:"
	asterisk -rx "pjsip reload" | sed -e 's|^|\t|g'
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	asterisk -rx "dialplan reload" | sed -e 's|^|\t|g'
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	asterisk -rx "voicemail reload" | sed -e 's|^|\t|g'
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	echo "(Service errors/warns/notices maybe are not shown)"
	return $StatusCode
}

PublicNrClients ()
# Descripcion:
#	Returns (stdout) a list of accounts that have given public number authorized, one per line. All data is got and built from users.tab
{
	local PUBLICNR="$1"
	local RequiredOption="$2"
	local Value=''
	
	IFS="$(printf '\n\b')" ; for CurUser in $(cat "${CurrentConfigDir}/users.tab" | grep -ve '^#' -ve '^;' -ve '^$') ; do unset IFS
		CLIUSERID="$(printf '%s' "$CurUser" | cut -sf 1)"
		CLIUSERNUMBERS="$(printf '%s' "$CurUser" | cut -sf 4)"
		for CLIUSERNUMBERS_CUR in $(echo "$CLIUSERNUMBERS" | tr -s ',' ' ') ; do
			CLIUSERNUMBERS_CUR_NR="$(printf '%s' "$CLIUSERNUMBERS_CUR" | cut -f 1 -d '/')"
			CLIUSERNUMBERS_CUR_OPTS="$(printf '%s' "$CLIUSERNUMBERS_CUR" | cut -sf 2 -d '/')"
			if [ "$CLIUSERNUMBERS_CUR_NR" = "$PUBLICNR" ] && [ "$(printf '%s' "$CLIUSERNUMBERS_CUR_OPTS" | grep -ie "$RequiredOption")" != "" ] ; then
				S_CLIUSERNUMBERS_CUR=''
				if [ "$CLIUSERNUMBERS_CUR_NR" != "" ] ; then S_CLIUSERNUMBERS_CUR="${UserLineSeparator}${CLIUSERNUMBERS_CUR_NR}" ; fi
				SipUsername="${CLIUSERID}${S_CLIUSERNUMBERS_CUR}"
				printf '%s\n' "$SipUsername"
			fi
		done
	done
}

RebuildConfigs ()
{
	local PostAction="$1"
	local DeploymentDir='/etc/asterisk/asterisk-simple'
	local ReloadAsterisk=1
	local ExtensionsDone=''
	local CurKey=''
	local AccountsDone=''
	local CurContacts=''
	local LanguageSpec=''
	local CombinedMonitorTracks='dual'
	local VoiceMail_Context='default'
	local VoiceMail_Extension=''
	local VoiceMail_ID=''
	local TheSortedCodecs=''
	local ErrorsNr=0
	local WarningsNr=0
	local LastStatus=0
	local StatusCode=0
	
	if [ "$InboundMonitorTracks" = "mix" ] || [ "$OutboundMonitorTracks" = "mix" ] ; then
		CombinedMonitorTracks='mix'
	fi
	if [ ! -f /etc/asterisk/pjsip.conf ] ; then
		LogProgram 1 "${sERROR}E: Asterisk configuration files not found.${fRESET}" 1>&2
		LastStatus=53 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		ErrorsNr=$((ErrorsNr + 1))
	fi
	if [ $StatusCode -eq 0 ] ; then
		TheSortedCodecs="$(SortedCodecs)"
		if [ ! -d "$DeploymentDir" ] ; then
			cp -a /etc/asterisk /tmp/asterisk-simple.$$
			rm -fr /tmp/asterisk-simple.$$/*
			mv /tmp/asterisk-simple.$$ "$DeploymentDir"
		fi
		if [ -f "${DeploymentDir}/itsp-s.conf" ] ; then
			echo "Rebuilding ${DeploymentDir}/itsp-s.conf"
			mv "${DeploymentDir}/itsp-s.conf" "${DeploymentDir}/itsp-s.conf.bak"
		else
			echo "Building ${DeploymentDir}/itsp-s.conf"
		fi
		cp -a /etc/asterisk/pjsip.conf "${DeploymentDir}/itsp-s.conf"
		echo "; =========== Warning: FILE AUTOMATICALLY REWRITTEN BY $ProgramName ==========" > "${DeploymentDir}/itsp-s.conf"
		echo "" >> "${DeploymentDir}/itsp-s.conf"
		CurTrunk_LineNr=0
		IFS="$(printf '\n\b')" ; for CurTrunk in $(cat "${CurrentConfigDir}/trunks.tab" | grep -ve '^#' -ve '^;' -ve '^$') ; do unset IFS
			CurTrunk_LineNr=$((CurTrunk_LineNr + 1))
			PUBLICNR="$(printf '%s' "$CurTrunk" | cut -sf 1)"
			PUBLICNAME="$(printf '%s' "$CurTrunk" | cut -sf 2)"
			ITSP="$(printf '%s' "$CurTrunk" | cut -sf 3)"
			PRVURI="$(printf '%s' "$CurTrunk" | cut -sf 4)"
			PRVURI_USR="$(printf '%s' "$PRVURI" | grep -e '@' | cut -sf 1 -d '@')"
			PRVURI_HOST="$(printf '%s' "$PRVURI" | cut -sf 2 -d '@')"
			PRVPASSWORD="$(printf '%s' "$CurTrunk" | cut -sf 5)"
			INBOUNDMAILTO="$(printf '%s' "$CurTrunk" | cut -sf 6)"
			INBOUNDMAILTO_ADDR="$(printf '%s' "$INBOUNDMAILTO" | cut -f 1 -d '/')"
			INBOUNDMAILTO_LANG="$(printf '%s' "$INBOUNDMAILTO" | cut -sf 2 -d '/')"
			INBOUNDMAILTO_OPT="$(printf '%s' "$INBOUNDMAILTO" | cut -sf 3 -d '/')"
			if [ "$INBOUNDMAILTO_LANG" != "" ] ; then
				LanguageSpec="language = $INBOUNDMAILTO_LANG"
			else
				LanguageSpec=";language = en"
			fi
			printf "\tTrunk registration to $ITSP for number $PUBLICNR\n"
			if [ "$PUBLICNR" != "" ] && [ "$(printf '%s' "$PUBLICNR" | grep -e '^#' -e '^;')" = "" ] ; then
				if [ "$ITSP" != "" ] && [ "$(printf '%s' "$PRVURI" | grep -e '.@.')" != "" ] ; then
					if [ ! -f "${CurrentConfigDir}/trunk_${ITSP}.tpl" ] ; then
						# https://docs.asterisk.org/Configuration/Channel-Drivers/SIP/Configuring-res_pjsip/PJSIP-Configuration-Sections-and-Relationships/
						echo "${sINFO}Creating example template for provider sections registration+auth+aor+endpoint+identify: ${sWARN}${CurrentConfigDir}/trunk_${ITSP}.tpl${fRESET}"
						cp -a /etc/asterisk/pjsip.conf "${CurrentConfigDir}/trunk_${ITSP}.tpl"
						echo ';========== CONNECTION TO {ITSP} FOR LINE {PUBLICNR} ==========

[reg-trk-{PUBLICNR}]
type = registration
auth_rejection_permanent = yes
server_uri = sip:{PRVURI_HOST}
client_uri = sip:{PRVURI}
contact_user = {PRVURI_USR}
line = yes
endpoint = {PRVURI_USR}
outbound_auth = auth-trk-{PUBLICNR}
;auth = auth-trk-{PUBLICNR}
transport = transport-udp
support_path = yes

[auth-trk-{PUBLICNR}]
type = auth
auth_type = userpass
username = {PRVURI_USR}
password = {PRVPASSWORD}

; AOR and Endpoint names are required to match match SIP-specified username
[{PRVURI_USR}]
type = aor
;contact = sip:{PRVURI}
;{CONTACTS}
max_contacts = 20

; AOR and Endpoint names are required to match match SIP-specified username
[{PRVURI_USR}]
type = endpoint
identify_by = auth_username
context = plan-trunk-inbound
disallow = all' > "${CurrentConfigDir}/trunk_${ITSP}.tpl"
						echo "allow = {SortedCodecs}" > "${CurrentConfigDir}/trunk_${ITSP}.tpl"
						echo 'outbound_auth = auth-trk-{PUBLICNR}
auth = auth-trk-{PUBLICNR}
aors = {PRVURI_USR}
;callerid = {PUBLICNAME} <{PUBLICNR}>
callerid_tag = {PUBLICNAME}
from_user = {PRVURI_USR}
;from_domain = {SipFqdn}
from_domain = {PRVURI_HOST}
trust_id_outbound = yes
trust_id_inbound = yes
direct_media = no
disable_direct_media_on_nat = yes
dtmf_mode = auto
force_rport = yes
;redirect_method = uri_pjsip
rtp_symmetric = yes
send_pai = no 
send_rpid = yes
;media_encryption = yes
media_encryption_optimistic = yes
tone_zone = es
{LanguageSpec}
rtp_keepalive = 30
rtp_timeout = 30
rtp_timeout_hold = 60
;geoloc_incoming_call_profile = location-profile-neutral

;[id-trk-{PUBLICNR}]
;type = identify
;endpoint = {PRVURI_USR}
;match = {PRVURI_HOST}


' > "${CurrentConfigDir}/trunk_${ITSP}.tpl"
						ReloadAsterisk=0
					fi
					printf "{SipFqdn}\t${SipFqdn}\n{PUBLICNR}\t${PUBLICNR}\n{PUBLICNAME}\t${PUBLICNAME}\n{ITSP}\t${ITSP}\n{PRVURI}\t${PRVURI}\n{PRVURI_USR}\t${PRVURI_USR}\n{PRVURI_HOST}\t${PRVURI_HOST}\n{PRVPASSWORD}\t${PRVPASSWORD}\n{INBOUNDMAILTO}\t${INBOUNDMAILTO}\n{LanguageSpec}\t${LanguageSpec}\n{UserLineSeparator}\t${UserLineSeparator}\n{SortedCodecs}\t${TheSortedCodecs}\n" > /tmp/asterisk-simple.$$.vars
					ReplaceVariables /tmp/asterisk-simple.$$.vars "${CurrentConfigDir}/trunk_${ITSP}.tpl" >> "${DeploymentDir}/itsp-s.conf"
					rm /tmp/asterisk-simple.$$.vars
				else
					printf '%s\n' "${sERROR}E: Bad data at ${CurrentConfigDir}/trunks.tab line ${CurTrunk_LineNr}${fRESET}" 1>&2
					ErrorsNr=$((ErrorsNr + 1))
				fi
			fi
		done
		
		
		CurUser_LineNr=0
		if [ -f "${DeploymentDir}/clients.conf" ] ; then
			echo "Rebuilding ${DeploymentDir}/clients.conf"
			mv "${DeploymentDir}/clients.conf" "${DeploymentDir}/clients.conf.bak"
		else
			echo "Building ${DeploymentDir}/clients.conf"
		fi
		cp -a /etc/asterisk/pjsip.conf "${DeploymentDir}/clients.conf"
		echo "; =========== Warning: FILE AUTOMATICALLY REWRITTEN BY $ProgramName ==========" > "${DeploymentDir}/clients.conf"
		echo "" >> "${DeploymentDir}/clients.conf"
		AccountsDone=''
		IFS="$(printf '\n\b')" ; for CurUser in $(cat "${CurrentConfigDir}/users.tab" | grep -ve '^#' -ve '^;' -ve '^$') ; do unset IFS
			CurUser_LineNr=$((CurUser_LineNr + 1))
			CLIUSERID="$(printf '%s' "$CurUser" | cut -sf 1)"
			CLIUSERPASS="$(printf '%s' "$CurUser" | cut -sf 2)"
			CLIUSERDESCRIPTION="$(printf '%s' "$CurUser" | cut -sf 3)"
			CLIUSERNUMBERS="$(printf '%s' "$CurUser" | cut -sf 4)"
			CLIUSERMAILTO="$(printf '%s' "$CurUser" | cut -sf 5)"
			CLIUSERMAILTO_ADDR="$(printf '%s' "$CLIUSERMAILTO" | cut -f 1 -d '/')"
			CLIUSERMAILTO_LANG="$(printf '%s' "$CLIUSERMAILTO" | cut -sf 2 -d '/')"
			CLIUSERMAILTO_OPT="$(printf '%s' "$CLIUSERMAILTO" | cut -sf 3 -d '/')"
			if [ "$CLIUSERMAILTO_LANG" != "" ] ; then
				LanguageSpec="language = $CLIUSERMAILTO_LANG"
			else
				LanguageSpec=";language = en"
			fi
			SipUsername="$CLIUSERID"
			md5_cred="$(printf '%s' "${SipUsername}:${RealmForClients}:${CLIUSERPASS}" | md5sum | cut -f 1 -d ' ')"
			if [ "$(printf '%s\n' "$AccountsDone" | grep -e "^${SipUsername}$")" = "" ] ; then
				if [ "$CLIUSERMAILTO_LANG" != "" ] ; then
					printf '\t%s\n' "Internal-only account for user ${CLIUSERID} with language $CLIUSERMAILTO_LANG: $CLIUSERID"
				else
					printf '\t%s\n' "Internal-only account for user ${CLIUSERID} ${sINFO}(no language set)${fRESET}: $CLIUSERID"
				fi
				printf "{SipFqdn}\t${SipFqdn}\n{CLIUSERID}\t${CLIUSERID}\n{CLIUSERPASS}\t${CLIUSERPASS}\n{CLIUSERDESCRIPTION}\t${CLIUSERDESCRIPTION}\n{CLIUSERNUMBERS}\t${CLIUSERNUMBERS}\n{CLIUSERMAILTO}\t${CLIUSERMAILTO_ADDR}\n{CLIUSERMAILTO_ADDR}\t${CLIUSERMAILTO_ADDR}\n{CLIUSERNUMBERS_CUR}\t\n{CLIUSERNUMBERS_CUR_NR}\t\n{CLIUSERNUMBERS_CUR_OPTS}\t\n{S_CLIUSERNUMBERS_CUR}\t\n{LanguageSpec}\t${LanguageSpec}\n{UserLineSeparator}\t${UserLineSeparator}\n{SipUsername}\t${SipUsername}\n{SortedCodecs}\t${TheSortedCodecs}\n{md5_cred}\t${md5_cred}\n" > /tmp/asterisk-simple.$$.vars
				ReplaceVariables /tmp/asterisk-simple.$$.vars "${CurrentConfigDir}/client.tpl" >> "${DeploymentDir}/clients.conf"
				rm /tmp/asterisk-simple.$$.vars
				AccountsDone="$(printf '%s\n' "$AccountsDone" ; printf '%s\n' "$SipUsername")"
			else
				printf '\t%s\n' "${sINFO}Avoiding to repeat account \"${SipUsername}\" because of more public lines/mails${fRESET}"
			fi
			for CLIUSERNUMBERS_CUR in $(echo "$CLIUSERNUMBERS" | tr -s ',' ' ') ; do
				CLIUSERNUMBERS_CUR_NR="$(printf '%s' "$CLIUSERNUMBERS_CUR" | cut -f 1 -d '/')"
				CLIUSERNUMBERS_CUR_OPTS="$(printf '%s' "$CLIUSERNUMBERS_CUR" | cut -sf 2 -d '/')"
				S_CLIUSERNUMBERS_CUR=''
				if [ "$CLIUSERNUMBERS_CUR_NR" != "" ] ; then S_CLIUSERNUMBERS_CUR="${UserLineSeparator}${CLIUSERNUMBERS_CUR_NR}" ; fi
				SipUsername="${CLIUSERID}${S_CLIUSERNUMBERS_CUR}"
				md5_cred="$(printf '%s' "${SipUsername}:${RealmForClients}:${CLIUSERPASS}" | md5sum | cut -f 1 -d ' ')"
				if [ "$(printf '%s\n' "$AccountsDone" | grep -e "^${SipUsername}$")" = "" ] ; then
					printf '\t%s\n' "Client account for user $CLIUSERID to place $CLIUSERNUMBERS_CUR_NR calls: ${CLIUSERID}${S_CLIUSERNUMBERS_CUR}"
					printf "{SipFqdn}\t${SipFqdn}\n{CLIUSERID}\t${CLIUSERID}\n{CLIUSERPASS}\t${CLIUSERPASS}\n{CLIUSERDESCRIPTION}\t${CLIUSERDESCRIPTION}\n{CLIUSERNUMBERS}\t${CLIUSERNUMBERS}\n{CLIUSERMAILTO}\t${CLIUSERMAILTO_ADDR}\n{CLIUSERMAILTO_ADDR}\t${CLIUSERMAILTO_ADDR}\n{CLIUSERNUMBERS_CUR}\t${CLIUSERNUMBERS_CUR}\n{CLIUSERNUMBERS_CUR_NR}\t${CLIUSERNUMBERS_CUR_NR}\n{CLIUSERNUMBERS_CUR_OPTS}\t${CLIUSERNUMBERS_CUR_OPTS}\n{S_CLIUSERNUMBERS_CUR}\t${S_CLIUSERNUMBERS_CUR}\n{LanguageSpec}\t${LanguageSpec}\n{UserLineSeparator}\t${UserLineSeparator}\n{SipUsername}\t${SipUsername}\n{SortedCodecs}\t${TheSortedCodecs}\n{md5_cred}\t${md5_cred}\n" > /tmp/asterisk-simple.$$.vars
					ReplaceVariables /tmp/asterisk-simple.$$.vars "${CurrentConfigDir}/client.tpl" >> "${DeploymentDir}/clients.conf"
					rm /tmp/asterisk-simple.$$.vars
					AccountsDone="$(printf '%s\n' "$AccountsDone" ; printf '%s\n' "$SipUsername")"
				else
					printf '\t%s\n' "${sINFO}Avoiding to repeat login account \"${SipUsername}\" because of trunk line ${CLIUSERNUMBERS_CUR_NR}${fRESET}"
				fi
			done
		done
		
		if [ ! -f "${DeploymentDir}/head.conf" ] ; then
			echo "${sINFO}Creating example content for beginning system+global+transport sections: ${sWARN}${DeploymentDir}/head.conf${fRESET}"
			DefaultNIC="$(ip r l | grep -ie '^default ' | head -n 1 | sed -e 's|.* dev ||g' | cut -f 1 -d ' ')"
			DefaultIP="$(ip r l | grep -e "..*\..*\...*\..* dev $DefaultNIC .* link src " | sed -e 's|.* src ||g' | cut -f 1 -d ' ')"
			DefaultNet="$(ip r l | grep -e "..*\..*\...*\..* dev $DefaultNIC .* link src " | cut -f 1 -d ' ')"
			ThePublicIP=$(PublicIP)
			if [ "$(printf '%s' "$DefaultNet" | grep -e '/')" != "" ] ; then
				AddLocalNet="local_net = $DefaultNet"
			fi
			cp -a /etc/asterisk/pjsip.conf "${DeploymentDir}/head.conf"
			echo "[system]
disable_tcp_switch = yes


[global]
; SYNOPSIS: Options that apply globally to all SIP communications
type = global
debug = no
endpoint_identifier_order = username,ip,anonymous
;use_callerid_contact = no

;[location-profile-neutral]
;type = profile


[transport-udp]
; LOG WARNING: Transport '...' is not fully reloadable, not reloading: protocol, bind, TLS, TCP, ToS, or CoS options.
; Means only that 'pjsip reload' avoids disrupt in-progress calls. Set allow_reload=yes to allow it anyway, without having to restart service.
type = transport
protocol = udp
bind = 0.0.0.0
$AddLocalNet
local_net = 127.0.0.0/8
external_media_address = $ThePublicIP
external_signaling_address = $ThePublicIP

[transport-tls]
; W: Transport '...' is not fully reloadable, not reloading: protocol, bind, TLS, TCP, ToS, or CoS options.
; Means only that 'pjsip reload' avoids disrupt in-progress calls. Set allow_reload=yes to allow it anyway, without having to restart service.
type = transport
protocol = tls" > "${DeploymentDir}/head.conf"
			if [ "$(echo QUIT | openssl s_client -showcerts -connect localhost:5061 2>&1 | grep -ive 'Connect' | grep -ie 'ssl.*error' -ie 'tls.*error')" = "" ] ; then
				echo ';Enable TLSv1_2 method if you see any routines-tls_setup_handshake-internal error logged' >> "${DeploymentDir}/head.conf"
				echo ';method = TLSv1_2' >> "${DeploymentDir}/head.conf"
			else
				echo 'method = TLSv1_2' >> "${DeploymentDir}/head.conf"
			fi
			echo "bind = 0.0.0.0
ca_list_file = /etc/sesele/local-certs/${SipFqdn}/fullchain.pem
cert_file = /etc/sesele/local-certs/${SipFqdn}/fullchain.pem
priv_key_file = /etc/sesele/local-certs/${SipFqdn}/privkey.pem" >> "${DeploymentDir}/head.conf"
		else
			echo "Rewriting ${DeploymentDir}/head.conf"
			cp -a "${DeploymentDir}/head.conf" "${DeploymentDir}/head.conf.bak"
		fi
		
		
		echo "Rewriting /etc/asterisk/pjsip.conf"
		if [ ! -f /etc/asterisk/pjsip.conf.manual ] ; then cp -a /etc/asterisk/pjsip.conf /etc/asterisk/pjsip.conf.manual ; fi
		echo "; Changes to this files and included can be applied with one of methods:
;       CLI> pjsip reload
;       $ sudo asterisk -rx 'pjsip reload'

; The log NOTICE: Type 'system' is not reloadable, maintaining previous values
; Means only that [system] parameters change require service restart

#include ${DeploymentDir}/head.conf
#include ${DeploymentDir}/itsp-s.conf
#include ${DeploymentDir}/clients.conf" > /etc/asterisk/pjsip.conf


		echo "Rebuilding /etc/asterisk/extensions.conf"
		mv /etc/asterisk/extensions.conf /etc/asterisk/extensions.conf.bak
		cp -a /etc/asterisk/extensions.conf.bak /etc/asterisk/extensions.conf
		if [ ! -f /etc/asterisk/extensions.conf.manual ] ; then cp -a /etc/asterisk/extensions.conf.bak /etc/asterisk/extensions.conf.manual ; fi
		# https://docs.asterisk.org/Configuration/Dialplan/Pattern-Matching/
		echo "; =========== Warning: FILE AUTOMATICALLY REWRITTEN BY $ProgramName ==========" > /etc/asterisk/extensions.conf
		echo "" >> /etc/asterisk/extensions.conf
		echo '; APPLY CHANGES with Asterisk CLI> dialplan reload

; https://docs.asterisk.org/Configuration/Dialplan/Variables/Channel-Variables/Asterisk-Standard-Channel-Variables/

[general]
static = yes
writeprotect = yes
autofallthrough = yes
clearglobalvars = yes

[globals]
; Global variables
MONITOR_EXEC = true
' >> /etc/asterisk/extensions.conf

		echo '[get-concerned-for-mail]
exten => _..,1,Set(ConcernedForMail=.)' >> /etc/asterisk/extensions.conf
		IFS="$(printf '\n\b')" ; for CurTrunk in $(cat "${CurrentConfigDir}/trunks.tab" | grep -ve '^#' -ve '^;' -ve '^$') ; do unset IFS
			PUBLICNR="$(printf '%s' "$CurTrunk" | cut -sf 1)"
			PUBLICNAME="$(printf '%s' "$CurTrunk" | cut -sf 2)"
			printf '\t%s\n' "Voice/mail for target \"${PUBLICNR}\" concern same public number: $PUBLICNR"
			echo "same => n,Execif(\$[\"\${EXTEN}\" = \"${PUBLICNR}\"]?Set(ConcernedForMail=${PUBLICNR}))" >> /etc/asterisk/extensions.conf
		done
#		ExtensionsDone=''
		IFS="$(printf '\n\b')" ; for CurUser in $(cat "${CurrentConfigDir}/users.tab" | grep -ve '^#' -ve '^;' -ve '^$') ; do unset IFS
			CLIUSERID="$(printf '%s' "$CurUser" | cut -sf 1)"
			CLIUSERDESCRIPTION="$(printf '%s' "$CurUser" | cut -sf 3)"
			CLIUSERNUMBERS="$(printf '%s' "$CurUser" | cut -sf 4)"
			printf '\t%s\n' "Voice/mail for target \"${CLIUSERID}\" concern same user account: $CLIUSERID"
			echo "same => n,Execif(\$[\"\${EXTEN}\" = \"${CLIUSERID}\"]?Set(ConcernedForMail=${CLIUSERID}))" >> /etc/asterisk/extensions.conf
			for CLIUSERNUMBERS_CUR in $(echo "$CLIUSERNUMBERS" | tr -s ',' ' ') ; do
				CLIUSERNUMBERS_CUR_NR="$(printf '%s' "$CLIUSERNUMBERS_CUR" | cut -f 1 -d '/')"
				CLIUSERNUMBERS_CUR_OPTS="$(printf '%s' "$CLIUSERNUMBERS_CUR" | cut -sf 2 -d '/')"
				S_CLIUSERNUMBERS_CUR="${UserLineSeparator}${CLIUSERNUMBERS_CUR_NR}"
				SipUsername="${CLIUSERID}${S_CLIUSERNUMBERS_CUR}"
				# Concerned lines assume concerned users. This implies e-mail notifications + voicemail boxes (user-55501 concerns only 55501 for any message)
				printf '\t%s\n' "Internal voice/mail for target \"${SipUsername}\" concern line of public number: $CLIUSERNUMBERS_CUR_NR"
				echo "same => n,Execif(\$[\"\${EXTEN}\" = \"${SipUsername}\"]?Set(ConcernedForMail=${CLIUSERNUMBERS_CUR_NR}))" >> /etc/asterisk/extensions.conf
#				printf '\t%s\n' "Internal voice/mail for target \"${SipUsername}\" concern user account: $CLIUSERID"
#				echo "same => n,Execif(\$[\"\${EXTEN}\" = \"${SipUsername}\"]?Set(ConcernedForMail=${CLIUSERID}))" >> /etc/asterisk/extensions.conf
			done
		done
		echo 'same => n,Return(${ConcernedForMail})
' >> /etc/asterisk/extensions.conf

		echo '[interruptible-dial]
exten => s,1,Dial(${ARG1},${ARG2},${ARG3})
same => n,Return(${DIALSTATUS})
exten => a,1,Return(${DIALSTATUS})
exten => h,1,Return(${DIALSTATUS})
exten => i,1,Return(${DIALSTATUS})
exten => t,1,Return(${DIALSTATUS})
exten => T,1,Return(${DIALSTATUS})
' >> /etc/asterisk/extensions.conf
		
		echo '[nice-dial]
; This fixes some DIALSTATUS case
exten => s,1,Set(TimeBegin=${EPOCH})
same => n,Set(TimePassed=0)
same => n,Set(Timeout=${ARG2})
same => n,Gosub(interruptible-dial,s,1,(${ARG1},${Timeout},${ARG3}))
same => n,Set(DIALSTATUS=${GOSUB_RETVAL})
same => n,Set(DialDuration=$[${EPOCH} - ${TimeBegin}])
same => n,Execif($[$[${DialDuration} > 10] | [${DialDuration} >= ${Timeout}]]?Set(TimePassed=1))
same => n,Execif($[$["${DIALSTATUS}" = "CHANUNAVAIL"] & ["${TimePassed}" = "1"]]?Set(DIALSTATUS=NOANSWER))
same => n,Return(${DIALSTATUS})
' >> /etc/asterisk/extensions.conf
		
		echo '[interruptible-voicemail]
;Para la administración del buzón: VoiceMailMain(box@context)
exten => s,1,VoiceMail(${ARG1})
same => n,Return(${VMSTATUS})
exten => a,1,Return(${VMSTATUS})
exten => h,1,Return(${VMSTATUS})
exten => i,1,Return(${VMSTATUS})
exten => t,1,Return(${VMSTATUS})
exten => T,1,Return(${VMSTATUS})
' >> /etc/asterisk/extensions.conf
		
		echo '[interruptible-voicemail-options]
;Para la administración del buzón: VoiceMailMain(box@context)
exten => s,1,VoiceMail(${ARG1},${ARG2})
same => n,Return(${VMSTATUS})
exten => a,1,Return(${VMSTATUS})
exten => h,1,Return(${VMSTATUS})
exten => i,1,Return(${VMSTATUS})
exten => t,1,Return(${VMSTATUS})
exten => T,1,Return(${VMSTATUS})
' >> /etc/asterisk/extensions.conf
		
		echo '[decide-voicemail]
exten => _..,1,Set(GoVoicemail=0)
same => n,Set(DIALSTATUS=${ARG1})
same => n,Set(DialTo=${ARG2})' >> /etc/asterisk/extensions.conf
		for CurKey in $(echo $DIALSTATUS_GoToVoicemail | tr ',' ' ') ; do
			echo "same => n,Execif(\$[\"\${DIALSTATUS}\" = \"${CurKey}\"]?Set(GoVoicemail=1))" >> /etc/asterisk/extensions.conf
		done
		echo 'same => n,Execif($["${DialTo}" = ""]?Set(GoVoicemail=1))' >> /etc/asterisk/extensions.conf
		echo "same => n,Set(VoiceMail_Context=${VoiceMail_Context})" >> /etc/asterisk/extensions.conf
		echo ';same => n,Set(VoiceMail_ID=${EXTEN}@${VoiceMail_Context})
same => n,Set(VoiceMail_ID=)
same => n,Gosub(get-concerned-for-mail,${EXTEN},1)
same => n,Set(VoiceMail_Extension=${GOSUB_RETVAL})
same => n,Execif($["${VoiceMail_Extension}" = "."]?Set(VoiceMail_Extension=))
same => n,Set(ConcernedVoicemailEnabled=0)' >> /etc/asterisk/extensions.conf
		IFS="$(printf '\n\b')" ; for CurTrunk in $(cat "${CurrentConfigDir}/trunks.tab" | grep -ve '^#' -ve '^;' -ve '^$') ; do unset IFS
			PUBLICNR="$(printf '%s' "$CurTrunk" | cut -sf 1)"
			INBOUNDMAILTO="$(printf '%s' "$CurTrunk" | cut -sf 6)"
			INBOUNDMAILTO_ADDR="$(printf '%s' "$INBOUNDMAILTO" | cut -f 1 -d '/')"
			INBOUNDMAILTO_LANG="$(printf '%s' "$INBOUNDMAILTO" | cut -sf 2 -d '/')"
			INBOUNDMAILTO_OPT="$(printf '%s' "$INBOUNDMAILTO" | cut -sf 3 -d '/')"
			if [ "$(printf '%s' "$INBOUNDMAILTO_OPT" | grep -ie 'v')" ] ; then
				echo "same => n,Execif($[\"\${VoiceMail_Extension}\" = \"${PUBLICNR}\"]?Set(ConcernedVoicemailEnabled=1))" >> /etc/asterisk/extensions.conf
			fi
		done
		IFS="$(printf '\n\b')" ; for CurUser in $(cat "${CurrentConfigDir}/users.tab" | grep -ve '^#' -ve '^;' -ve '^$') ; do unset IFS
			CLIUSERID="$(printf '%s' "$CurUser" | cut -sf 1)"
			CLIUSERMAILTO="$(printf '%s' "$CurUser" | cut -sf 5)"
			CLIUSERMAILTO_ADDR="$(printf '%s' "$CLIUSERMAILTO" | cut -f 1 -d '/')"
			CLIUSERMAILTO_LANG="$(printf '%s' "$CLIUSERMAILTO" | cut -sf 2 -d '/')"
			CLIUSERMAILTO_OPT="$(printf '%s' "$CLIUSERMAILTO" | cut -sf 3 -d '/')"
			if [ "$(printf '%s' "$CLIUSERMAILTO_OPT" | grep -ie 'v')" ] ; then
				echo "same => n,Execif($[\"\${VoiceMail_Extension}\" = \"${CLIUSERID}\"]?Set(ConcernedVoicemailEnabled=1))" >> /etc/asterisk/extensions.conf
			fi
		done
		echo 'same => n,Execif($["${VoiceMail_Extension}" != ""]?Set(VoiceMail_ID=${VoiceMail_Extension}@${VoiceMail_Context}))
;same => n,Execif($["${ConcernedVoicemailEnabled}" != "1"]?Set(GoVoicemail=0))
same => n,Execif($["${GoVoicemail}" = "1"]?System(asterisk-oncall link-monitor-voicemail ${UNIQUEID} ${VoiceMail_ID}@ogg))' >> /etc/asterisk/extensions.conf
		ASTSPOOLDIR="$(cat /etc/asterisk/asterisk.conf | grep -e '^astspooldir =.*/' -e '^astspooldir=.*/' | head -n 1 | cut -f 2- -d '=' | cut -f 2- -d '>' | sed -e 's|.* /|/|g')"
		if [ ! -d "$ASTSPOOLDIR" ] ; then ASTSPOOLDIR='/var/spool/asterisk' ; fi
		echo "same => n,Set(CustomGreeting=\${STAT(e,${ASTSPOOLDIR}/voicemail/${VoiceMail_Context}/\${VoiceMail_Extension}/unavail.wav16)})" >> /etc/asterisk/extensions.conf
		if [ ! -d /usr/share/asterisk/sounds/es-no ] ; then
			# Create a language clone with default voicemail greeting disabled
			# Otherwise, any box-custom "unavail" voice is followed by "vm-intro" too.
			SoundsTemplateLanguage="$(ls -1 /usr/share/asterisk/sounds | grep -e '^..$' | tail -n 1)"
			if [ "$SoundsTemplateLanguage" = "" ] ; then
				SoundsTemplateLanguage="$(ls -1 /usr/share/asterisk/sounds | grep -e '^.._' | tail -n 1)"
			fi
			rm -fr /var/lib/asterisk/sounds/local_es-NO
			cp -a "/usr/share/asterisk/sounds/$SoundsTemplateLanguage" /var/lib/asterisk/sounds/local_es-NO
			for CurFile in $(ls -1 /var/lib/asterisk/sounds/local_es-NO/vm-intro*) ; do
				cat /dev/null > "$CurFile"
			done
			sudo rm -f /usr/share/asterisk/sounds/es_NO
			sudo ln -s /var/lib/asterisk/sounds/local_es-NO /usr/share/asterisk/sounds/es_NO
			sudo rm -f /usr/share/asterisk/sounds/es-no
			sudo ln -s /var/lib/asterisk/sounds/local_es-NO /usr/share/asterisk/sounds/es-no
			sudo rm -f /etc/alternatives/asterisk-prompt-es-no
			sudo ln -s /var/lib/asterisk/sounds/local_es-NO /etc/alternatives/asterisk-prompt-es-no
			sudo rm -f /etc/alternatives/asterisk-prompt-es-no
			sudo ln -s /var/lib/asterisk/sounds/local_es-NO /etc/alternatives/asterisk-prompt-es-no
		fi
		echo 'same => n,Execif($[$["${GoVoicemail}" = "1"] & $["${CustomGreeting}" = "1"]]?Set(PreLanguage=${CHANNEL(language)}))
same => n,Execif($[$["${GoVoicemail}" = "1"] & $["${CustomGreeting}" = "1"]]?Set(CHANNEL(language)=es-no))
same => n,Set(GOSUB_RETVAL=)
same => n,Execif($[$["${GoVoicemail}" = "1"] & $["${CustomGreeting}" = "1"] & $["${ConcernedVoicemailEnabled}" = "1"]]?Gosub(interruptible-voicemail-options,s,1,(${VoiceMail_ID},u)))
same => n,Execif($[$["${GoVoicemail}" = "1"] & $["${CustomGreeting}" = "1"]]?Set(CHANNEL(language)=${PreLanguage}))
same => n,Execif($[$["${GoVoicemail}" = "1"] & $["${CustomGreeting}" = "0"] & $["${ConcernedVoicemailEnabled}" = "1"]]?Gosub(interruptible-voicemail,s,1,(${VoiceMail_ID})))
same => n,Set(VMSTATUS=${GOSUB_RETVAL})
same => n,Execif($[$["${GoVoicemail}" = "1"] & $["${ConcernedVoicemailEnabled}" != "1"] & $["${DIALSTATUS}" = "CHANUNAVAIL"]]?Playback(beep&nbdy-avail-to-take-call))
same => n,Execif($[$["${GoVoicemail}" = "1"] & $["${ConcernedVoicemailEnabled}" != "1"] & $["${DIALSTATUS}" = "CONGESTION"]]?Playback(beep&is-curntly-busy))
same => n,Execif($[$["${GoVoicemail}" = "1"] & $["${ConcernedVoicemailEnabled}" != "1"] & $["${DIALSTATUS}" = "NOANSWER"]]?Playback(beep&number-not-answering))
same => n,Execif($[$["${GoVoicemail}" = "1"] & $["${ConcernedVoicemailEnabled}" != "1"] & $["${DIALSTATUS}" = "BUSY"]]?Playback(beep&the-party-you-are-calling&is-curntly-busy))
same => n,Execif($[$["${GoVoicemail}" = "1"] & $["${ConcernedVoicemailEnabled}" != "1"] & $["${DIALSTATUS}" = "CANCEL"]]?Playback(beep&number-not-answering))
same => n,Execif($[$["${GoVoicemail}" = "1"] & $["${ConcernedVoicemailEnabled}" != "1"] & $["${DIALSTATUS}" = "DONTCALL"]]?Playback(beep&pls-try-call-later))
same => n,Execif($[$["${GoVoicemail}" = "1"] & $["${ConcernedVoicemailEnabled}" != "1"] & $["${DIALSTATUS}" = "TORTURE"]]?Playback(beep&privacy-stop-calling-not-welcome))
same => n,Execif($[$["${GoVoicemail}" = "1"] & $["${ConcernedVoicemailEnabled}" != "1"] & $["${DIALSTATUS}" = "INVALIDARGS"]]?Playback(beep&option-is-invalid))
same => n,Execif($["${GoVoicemail}" = "1"]?System(asterisk-oncall update-monitor ${UNIQUEID} VoiceMail_Context=${VoiceMail_Context} VoiceMail_Extension=${VoiceMail_Extension} VMSTATUS=${VMSTATUS}))
same => n,Return(${VMSTATUS})' >> /etc/asterisk/extensions.conf
		# https://docs.asterisk.org/Configuration/Dialplan/Special-Dialplan-Extensions/
		echo 'exten => a,1,Return(a)
exten => h,1,Return(h)
exten => i,1,Return(AST_CAUSE_INVALID_NUMBER_FORMAT)
exten => t,1,Return(AST_CAUSE_RECOVERY_ON_TIMER_EXPIRE)
exten => T,1,Return(AST_CAUSE_RECOVERY_ON_TIMER_EXPIRE)
' >> /etc/asterisk/extensions.conf
		# DEVICE_STATE() returns one of: UNKNOWN | NOT_INUSE | INUSE | BUSY | INVALID | UNAVAILABLE | RINGING | RINGINUSE | ONHOLD
		printf '%s\n' '[add-callable-endpoint]
exten => _..,1,Set(Endpoint=${ARG1})
same => n,Set(DialTo=${ARG2})
same => n,Set(CurState=${DEVICE_STATE(PJSIP/${Endpoint})})
;same => n,System(asterisk-oncall update-monitor ${UNIQUEID} DEVICE_STATE/${Endpoint}=${CurState})
same => n,Set(CurToAdd=)
same => n,Execif($["${CurState}" = "UNKNOWN"]?Set(CurToAdd=${Endpoint}))
same => n,Execif($["${CurState}" = "NOT_INUSE"]?Set(CurToAdd=${Endpoint}))
same => n,Execif($["${CurState}" = "INUSE"]?Set(CurToAdd=${Endpoint}))
same => n,Execif($["${CurState}" = "RINGING"]?Set(CurToAdd=${Endpoint}))
same => n,Execif($["${CurState}" = "RINGINUSE"]?Set(CurToAdd=${Endpoint}))
same => n,Execif($["${CurState}" = "ONHOLD"]?Set(CurToAdd=${Endpoint}))
same => n,Execif($[$["${DialTo}" != ""] & $["${CurToAdd}" != ""]]?Set(DialTo=${DialTo}&))
same => n,Execif($["${CurToAdd}" != ""]?Set(DialTo=${DialTo}PJSIP/${CurToAdd}))
same => n,Return(${DialTo})
' >> /etc/asterisk/extensions.conf
		# Monitor file formats: CLI> core show file formats
		printf '%s\n' '[plan-inbound-monitor-dial-vm]
exten => _..,1,Set(DialTo=${ARG1})
same => n,Set(CallerNumber=${CALLERID(num)})
same => n,Set(ConcernedOrigin=${CallerNumber})
same => n,Set(ConcernedDestination=${ARG2})
same => n,Set(MonitorType=${ARG3})
same => n,Execif($[$["${CallerNumber}" != "${FILTER(+0123456789,${CallerNumber})}"] | $["${CallerNumber}" = ""]]?Set(CallerNumber=${CALLERID(name)}))
same => n,Execif($[$["${CallerNumber}" != "${FILTER(+0123456789,${CallerNumber})}"] & $["${CALLERID(num)}" != ""]]?Set(CallerNumber=${CALLERID(num)}))
same => n,Execif($[$["${CALLERID(num)}" = "Anonymous"] | ["${CALLERID(name)}" = "Anonymous"] | $["${CALLERID(num)}" = ""]]?Set(CallerNumber=Numero_oculto))
same => n,Execif($[$["${CALLERID(num)}" != "${FILTER(+0123456789,${CALLERID(num)})}"] & $["${CALLERID(name)}" = "${FILTER(+0123456789,${CallerNumber})}"]]?Set(CALLERID(name)=))
same => n,Execif($["${CallerNumber}" = "${FILTER(+0123456789,${CallerNumber})}"]?Set(CALLERID(num)=${CallerNumber}))
same => n,Execif($["${CALLERID(name)}" = ""]?Set(CALLERID(name)="${CallerNumber}"))
same => n,Verbose(Entra llamada ${UNIQUEID} de ${CALLERID(name)} <${CALLERID(num)}> para ${EXTEN} y se inicia monitorizacion)
same => n,Set(PreferredLanguage=${CHANNEL(language)})
same => n,Execif($["${PreferredLanguage}" = ""]?Set(PreferredLanguage=.))
same => n,Set(MonitorFilename=${STRFTIME(,,%Y%m%d-%H%M%S)}_${CallerNumber}_${EXTEN})
same => n,System(asterisk-oncall monitor-call ${UNIQUEID} M ${CALLERID(num)} ${EXTEN} ${ConcernedOrigin} ${ConcernedDestination} ${PreferredLanguage} ${MonitorFilename})
same => n,Execif($["${MonitorType}" = "dual"]?Monitor(wav,${MonitorFilename},m))
same => n,Execif($["${MonitorType}" = "mix"]?MixMonitor("${MonitorFilename}.ogg",i(MixMonitorID)))
;same => n,Progress()
;same => n,Execif($["${DialTo}" != ""]?Answer())
same => n,Set(GOSUB_RETVAL=)
same => n,Execif($["${DialTo}" != ""]?Gosub(nice-dial,s,1,(${DialTo},30,r)))
same => n,Set(DIALSTATUS=${GOSUB_RETVAL})
same => n,NoOp(Al pasar la llamada ${UNIQUEID} a los contactos de ${DialTo} se obtuvo el estado ${DIALSTATUS})
same => n,System(asterisk-oncall update-monitor ${UNIQUEID} DIALSTATUS=${DIALSTATUS})
same => n,Gosub(decide-voicemail,${EXTEN},1,(${DIALSTATUS},${DialTo}))
same => n,Set(VMSTATUS=${GOSUB_RETVAL})
;same => n,NoOp(Se cierra la monitorizacion de la llamada ${UNIQUEID}. decide-voicemail devuelve {VMSTATUS})' >> /etc/asterisk/extensions.conf
		# As of Asterisk 16, playback files specification can include absolute path but not extension (Asterisk must be able to choose best format for this call)
		printf '%s\n' "same => n,Execif(\$[\$[\"\${DialTo}\" = \"\"] & \$[\"\${VMSTATUS}\" = \"\"]]?Playback(${PreSilence_2}vm-nobodyavail))" >> /etc/asterisk/extensions.conf
		printf '%s\n' 'same => n,Execif($[$["${DialTo}" = ""] & $["${VMSTATUS}" = ""]]?System(asterisk-oncall update-monitor ${UNIQUEID} CallConclusion=u))
same => n,Execif($[$["${DialTo}" != ""] & $["${DIALSTATUS}" = "CHANUNAVAIL"] & $["${VMSTATUS}" = ""]]?System(asterisk-oncall update-monitor ${UNIQUEID} CallConclusion=r))
same => n,Execif($["${MonitorType}" = "dual"]?StopMonitor())
same => n,Execif($["${MonitorType}" = "mix"]?StopMixMonitor(${MixMonitorID}))
;same => n,Execif($["${MonitorType}" = "mix"]?StopMixMonitor())
same => n,Return(${DIALSTATUS})
exten => a,1,Return(a)
exten => h,1,Return(h)
exten => i,1,Return(AST_CAUSE_INVALID_NUMBER_FORMAT)
exten => t,1,Return(AST_CAUSE_RECOVERY_ON_TIMER_EXPIRE)
exten => T,1,Return(AST_CAUSE_RECOVERY_ON_TIMER_EXPIRE)
' >> /etc/asterisk/extensions.conf
		
		printf '%s\n' '[plan-inbound-route-extensions]' >> /etc/asterisk/extensions.conf
		ExtensionsDone=''
		IFS="$(printf '\n\b')" ; for CurUser in $(cat "${CurrentConfigDir}/users.tab" | grep -ve '^#' -ve '^;' -ve '^$') ; do unset IFS
			CLIUSERID="$(printf '%s' "$CurUser" | cut -sf 1)"
			CLIUSERPASS="$(printf '%s' "$CurUser" | cut -sf 2)"
			CLIUSERNUMBERS="$(printf '%s' "$CurUser" | cut -sf 4)"
			for CLIUSERNUMBERS_CUR in $(echo "$CLIUSERNUMBERS" | tr -s ',' ' ') ; do
				CLIUSERNUMBERS_CUR_NR="$(printf '%s' "$CLIUSERNUMBERS_CUR" | cut -f 1 -d '/')"
				CLIUSERNUMBERS_CUR_OPTS="$(printf '%s' "$CLIUSERNUMBERS_CUR" | cut -sf 2 -d '/')"
				if [ "$(printf '%s' "/${ExtensionsDone}/" | grep -e "/${CLIUSERNUMBERS_CUR_NR}/")" = "" ] ; then
					DialTo=''
					CurPublicNrClients="$(PublicNrClients "$CLIUSERNUMBERS_CUR_NR" i)"
					if [ "$CurPublicNrClients" != "" ] ; then
						echo "exten => ${CLIUSERNUMBERS_CUR_NR},1,Set(DialTo=)" >> /etc/asterisk/extensions.conf
						IFS="$(printf '\n\b')" ; for CurDialAccount in $CurPublicNrClients ; do unset IFS
							if [ "$DialTo" != "" ] ; then DialTo="${DialTo}&" ; fi
							DialTo="${DialTo}PJSIP/${CurDialAccount}"
							echo "same => n,Gosub(add-callable-endpoint,\${EXTEN},1,(${CurDialAccount},\${DialTo}))" >> /etc/asterisk/extensions.conf
							echo 'same => n,Set(DialTo=${GOSUB_RETVAL})' >> /etc/asterisk/extensions.conf
						done
						printf '\t%s\n' "Inbound calls to $CLIUSERNUMBERS_CUR_NR will dial accounts: $(printf '%s' "$DialTo" | sed -e 's|&|, |g' -e 's|PJSIP/||g')"
#						echo "exten => ${CLIUSERNUMBERS_CUR_NR},1,Gosub(plan-inbound-monitor-dial-vm,\${EXTEN},1,(${DialTo},${CLIUSERNUMBERS_CUR_NR},${InboundMonitorTracks}))" >> /etc/asterisk/extensions.conf
						echo "same => n,Gosub(plan-inbound-monitor-dial-vm,\${EXTEN},1,(\${DialTo},${CLIUSERNUMBERS_CUR_NR},${InboundMonitorTracks}))" >> /etc/asterisk/extensions.conf
						echo 'same => n,System(asterisk-oncall update-monitor ${UNIQUEID} END)' >> /etc/asterisk/extensions.conf
						echo 'same => n,Return()' >> /etc/asterisk/extensions.conf
					else
						LogProgram 2 "W: No authorized accounts found to attend inbound calls to public number $CLIUSERNUMBERS_CUR_NR at ${CurrentConfigDir}/users.tab"
						WarningsNr=$((WarningsNr + 1))
					fi
					ExtensionsDone="${ExtensionsDone}/${CLIUSERNUMBERS_CUR_NR}"
				fi
			done
		done
		printf '%s\n' "" >> /etc/asterisk/extensions.conf

		# Some ITSP dive login name as caller dialed number.
		printf '%s\n' '[plan-trunk-inbound]
exten => _..,1,Set(WantedExtension=${EXTEN})' >> /etc/asterisk/extensions.conf
		IFS="$(printf '\n\b')" ; for CurTrunk in $(cat "${CurrentConfigDir}/trunks.tab" | grep -ve '^#' -ve '^;' -ve '^$') ; do unset IFS
			PUBLICNR="$(printf '%s' "$CurTrunk" | cut -sf 1)"
			PRVURI="$(printf '%s' "$CurTrunk" | cut -sf 4)"
			PRVURI_USR="$(printf '%s' "$PRVURI" | grep -e '@' | cut -sf 1 -d '@')"
			printf '%s\n' "same => n,Execif(\$[\"\${EXTEN}\" = \"${PRVURI_USR}\"]?Set(WantedExtension=${PUBLICNR}))" >> /etc/asterisk/extensions.conf
		done
		echo 'same => n,Gosub(plan-inbound-route-extensions,${WantedExtension},1)
same => n,Hangup()
' >> /etc/asterisk/extensions.conf

		printf '%s\n' '[plan-calling-internal]
exten => _..,1,Verbose(Cursa llamada ${UNIQUEID} desde ${CALLERID(num)} a la extension interna ${EXTEN} y se inicia monitorizacion)
same => n,Set(MonitorType=${ARG1})
same => n,Set(PreferredLanguage=${CHANNEL(language)})
same => n,Execif($["${PreferredLanguage}" = ""]?Set(PreferredLanguage=.))
same => n,Set(MonitorFilename=${STRFTIME(,,%Y%m%d-%H%M%S)}_${CALLERID(num)}_${EXTEN})
same => n,Gosub(get-concerned-for-mail,${CALLERID(num)},1)
same => n,Set(ConcernedOrigin=${GOSUB_RETVAL})
same => n,Gosub(get-concerned-for-mail,${EXTEN},1)
same => n,Set(ConcernedDestination=${GOSUB_RETVAL})
same => n,Set(PreferredLanguage=${CHANNEL(language)})
same => n,Execif($["${PreferredLanguage}" = ""]?Set(PreferredLanguage=.))
same => n,System(asterisk-oncall monitor-call ${UNIQUEID} M ${CALLERID(num)} ${EXTEN} ${ConcernedOrigin} ${ConcernedDestination} ${PreferredLanguage} ${MonitorFilename})
same => n,Execif($["${MonitorType}" = "dual"]?Monitor(wav,${MonitorFilename},m))
same => n,Execif($["${MonitorType}" = "mix"]?MixMonitor("${MonitorFilename}.ogg",i(MixMonitorID)))
same => n,Set(DialTo=PJSIP/${EXTEN})
;same => n,Progress()
;same => n,Answer()
same => n,Set(GOSUB_RETVAL=)
same => n,Gosub(nice-dial,s,1,(${DialTo},30,r))
same => n,Set(DIALSTATUS=${GOSUB_RETVAL})
same => n,NoOp(Al pasar la llamada ${UNIQUEID} a la extensión ${EXTEN} se obtuvo el estado ${DIALSTATUS})
same => n,System(asterisk-oncall update-monitor ${UNIQUEID} DIALSTATUS=${DIALSTATUS})
same => n,Gosub(decide-voicemail,${EXTEN},1,(${DIALSTATUS},PJSIP/${EXTEN}))
same => n,Set(VMSTATUS=${GOSUB_RETVAL})
;same => n,NoOp(Se cierra la monitorizacion de la llamada ${UNIQUEID}. decide-voicemail devuelve {VMSTATUS})' >> /etc/asterisk/extensions.conf
		# As of Asterisk 16, playback files specification can include absolute path but not extension (Asterisk must be able to choose best format for this call)
		printf '%s\n' "same => n,Execif(\$[\$[\"\${DialTo}\" = \"\"] & \$[\"\${VMSTATUS}\" = \"\"]]?Playback(${PreSilence_2}vm-nobodyavail))" >> /etc/asterisk/extensions.conf
		printf '%s\n' 'same => n,Execif($[$["${DialTo}" = ""] & $["${VMSTATUS}" = ""]]?System(asterisk-oncall update-monitor ${UNIQUEID} CallConclusion=u))
same => n,Execif($[$["${DialTo}" != ""] & $["${DIALSTATUS}" = "CHANUNAVAIL"] & $["${VMSTATUS}" = ""]]?System(asterisk-oncall update-monitor ${UNIQUEID} CallConclusion=r))
same => n,Execif($["${MonitorType}" = "dual"]?StopMonitor())
same => n,Execif($["${MonitorType}" = "mix"]?StopMixMonitor(${MixMonitorID}))
;same => n,Execif($["${MonitorType}" = "mix"]?StopMixMonitor())
same => n,Return(${DIALSTATUS})
exten => a,1,Return(a)
exten => h,1,Return(h)
exten => i,1,Return(AST_CAUSE_INVALID_NUMBER_FORMAT)
exten => t,1,Return(AST_CAUSE_RECOVERY_ON_TIMER_EXPIRE)
exten => T,1,Return(AST_CAUSE_RECOVERY_ON_TIMER_EXPIRE)

[plan-calling-subaccount]
exten => _..,1,Verbose(Desde ${CALLERID(num)} (interna) se intenta llamar a la subcuenta interna ${EXTEN}, que solo es para llamadas con el exterior.)
same => n,System(asterisk-oncall update-monitor ${UNIQUEID} CallPlan=R CallConclusion=r)' >> /etc/asterisk/extensions.conf
		printf '%s\n' "same => n,Playback(${PreSilence_2}cannot-complete-as-dialed&to-enter-a-diff-number&hangup-try-again)" >> /etc/asterisk/extensions.conf
		printf '%s\n' 'same => n,Return(CHANUNAVAIL)
exten => a,1,Return(CHANUNAVAIL)
exten => h,1,Return(CHANUNAVAIL)
exten => i,1,Return(CHANUNAVAIL)
exten => t,1,Return(CHANUNAVAIL)
exten => T,1,Return(CHANUNAVAIL)

[plan-calling-outbound]
;exten => _[0-9+]XXXXXXXX!,1,Verbose(Sale llamada ${UNIQUEID} desde ${CALLERID(num)} al numero exterior ${EXTEN} y se inicia monitorizacion)
exten => _..,1,Verbose(Sale llamada ${UNIQUEID} desde ${CALLERID(num)} al numero exterior ${EXTEN} y se inicia monitorizacion)
same => n,Set(PjsipDialWith=${ARG1})
same => n,Set(MonitorType=${ARG2})
same => n,Set(PUBLICNR=${ARG3})
same => n,Set(PreferredLanguage=${CHANNEL(language)})
same => n,Execif($["${PreferredLanguage}" = ""]?Set(PreferredLanguage=.))
same => n,Set(MonitorFilename=${STRFTIME(,,%Y%m%d-%H%M%S)}_${CALLERID(num)}_${EXTEN})
;same => n,System(asterisk-oncall monitor-call ${UNIQUEID} M ${CALLERID(num)} ${EXTEN} ${CALLERID(num)} ${PreferredLanguage} ${MonitorFilename})
;same => n,Gosub(get-concerned-for-mail,${CALLERID(num)},1)
same => n,Gosub(get-concerned-for-mail,${PUBLICNR},1)
same => n,System(asterisk-oncall monitor-call ${UNIQUEID} M ${CALLERID(num)} ${EXTEN} ${GOSUB_RETVAL} ${EXTEN} ${PreferredLanguage} ${MonitorFilename})
same => n,Execif($["${MonitorType}" = "dual"]?Monitor(wav,${MonitorFilename},m))
same => n,Execif($["${MonitorType}" = "mix"]?MixMonitor("${MonitorFilename}.ogg",i(MixMonitorID)))
;same => n,Set(CALLERID(name-valid)=no)
;same => n,Progress()
;same => n,Answer()
;same => n,Ringing()
same => n,Set(GOSUB_RETVAL=)
same => n,Gosub(nice-dial,s,1,(PJSIP/${PjsipDialWith},30,r))
same => n,Set(DIALSTATUS=${GOSUB_RETVAL})
same => n,NoOp(Al salir en llamada ${UNIQUEID} al numero ${EXTEN} se obtuvo el estado ${DIALSTATUS})
same => n,System(asterisk-oncall update-monitor ${UNIQUEID} DIALSTATUS=${DIALSTATUS})' >> /etc/asterisk/extensions.conf
		printf '%s\n' "same => n,Execif(\$[\"\${DIALSTATUS}\" = \"CHANUNAVAIL\"]?Playback(${PreSilence_2}cannot-complete-as-dialed))" >> /etc/asterisk/extensions.conf
		printf '%s\n' ';same => n,NoOp(Se cierra la monitorizacion de la llamada ${UNIQUEID})
same => n,Execif($["${MonitorType}" = "dual"]?StopMonitor())
same => n,Execif($["${MonitorType}" = "mix"]?StopMixMonitor(${MixMonitorID}))
;same => n,Execif($["${MonitorType}" = "mix"]?StopMixMonitor())
same => n,Return(${DIALSTATUS})
exten => a,1,Return(a)
exten => h,1,Return(h)
exten => i,1,Return(AST_CAUSE_INVALID_NUMBER_FORMAT)
exten => t,1,Return(AST_CAUSE_RECOVERY_ON_TIMER_EXPIRE)
exten => T,1,Return(AST_CAUSE_RECOVERY_ON_TIMER_EXPIRE)

[decide-outbound]
;exten => _[0-9+]XXXXXXXX!,1,NoOp(Seleccionando salida de la llamada ${UNIQUEID} por parte del llamante ${CALLERID(num)} para el destino ${EXTEN})
exten => _..,1,NoOp(Seleccionando salida de la llamada ${UNIQUEID} por parte del llamante ${CALLERID(num)} para el destino ${EXTEN})
same => n,Set(MonitorType=${ARG1})
same => n,Set(PjsipDialWith=)
same => n,Set(ConcernedUser=)
same => n,Set(FixedDialWith=${EXTEN})
; Replace +123456789 by 00123456789 for international calls
same => n,Execif($["${CUT(FixedDialWith,+,1)}" = ""]?Set(FixedDialWith=00${CUT(FixedDialWith,+,2-)})
same => n,Set(UserHasSomeOutbound=0)' >> /etc/asterisk/extensions.conf
		AccountsDone=''
		IFS="$(printf '\n\b')" ; for CurUser in $(cat "${CurrentConfigDir}/users.tab" | grep -ve '^#' -ve '^;' -ve '^$') ; do unset IFS
			CLIUSERID="$(printf '%s' "$CurUser" | cut -sf 1)"
			CLIUSERNUMBERS="$(printf '%s' "$CurUser" | cut -sf 4)"
			echo "same => n,Execif(\$[\"\${CALLERID(num)}\" = \"${CLIUSERID}\"]?Set(ConcernedUser=${CLIUSERID}))" >> /etc/asterisk/extensions.conf
			CurUser_HasSomeOutbound=0
			for CLIUSERNUMBERS_CUR in $(echo "$CLIUSERNUMBERS" | tr -s ',' ' ') ; do
				CLIUSERNUMBERS_CUR_NR="$(printf '%s' "$CLIUSERNUMBERS_CUR" | cut -f 1 -d '/')"
				CLIUSERNUMBERS_CUR_OPTS="$(printf '%s' "$CLIUSERNUMBERS_CUR" | cut -sf 2 -d '/')"
				S_CLIUSERNUMBERS_CUR="${UserLineSeparator}${CLIUSERNUMBERS_CUR_NR}"
				SipUsername="${CLIUSERID}${S_CLIUSERNUMBERS_CUR}"
				if [ "$(printf '%s\n' "$AccountsDone" | grep -e "^${SipUsername}$")" = "" ] ; then
					echo "same => n,Execif(\$[\"\${CALLERID(num)}\" = \"${SipUsername}\"]?Set(ConcernedUser=${CLIUSERID}))" >> /etc/asterisk/extensions.conf
					if [ "$(printf '%s' "$CLIUSERNUMBERS_CUR_OPTS" | grep -ie 'o')" != "" ] ; then
						CurUser_HasSomeOutbound=1
						CurTrunk="$(cat "${CurrentConfigDir}/trunks.tab" | grep -ve '^#' -ve '^;' -ve '^$' | grep -e "^${CLIUSERNUMBERS_CUR_NR}$(printf '\t')")"
						CurCommented=''
						if [ "$CurTrunk" = "" ] ; then
							LogProgram 2 "W: No trunk specification found for $CLIUSERID public number $CLIUSERNUMBERS_CUR_NR at ${CurrentConfigDir}/trunks.tab"
							WarningsNr=$((WarningsNr + 1))
							echo "same => n,Execif(\$[\"\${CALLERID(num)}\" = \"${SipUsername}\"]?Set(PjsipDialWith=)" >> /etc/asterisk/extensions.conf
						else
							PUBLICNR="$(printf '%s' "$CurTrunk" | cut -sf 1)"
							PUBLICNAME="$(printf '%s' "$CurTrunk" | cut -sf 2)"
							ITSP="$(printf '%s' "$CurTrunk" | cut -sf 3)"
							PRVURI="$(printf '%s' "$CurTrunk" | cut -sf 4)"
							PRVURI_USR="$(printf '%s' "$PRVURI" | grep -e '@' | cut -sf 1 -d '@')"
							PRVURI_HOST="$(printf '%s' "$PRVURI" | cut -sf 2 -d '@')"
							printf '\t%s\n' "Outbound calls from \"${SipUsername}\" will use trunk of public number: $CLIUSERNUMBERS_CUR_NR"
							echo "same => n,Execif(\$[\"\${CALLERID(num)}\" = \"${SipUsername}\"]?Set(PUBLICNR=${PUBLICNR}))" >> /etc/asterisk/extensions.conf
#							echo "same => n,Execif(\$[\"\${CALLERID(num)}\" = \"${CLIUSERID}${S_CLIUSERNUMBERS_CUR}\"]?Dial(PJSIP/${PRVURI_USR}/sip:\${FixedDialWith}@${PRVURI_HOST},30,g))" >> /etc/asterisk/extensions.conf
							echo "same => n,Execif(\$[\"\${CALLERID(num)}\" = \"${SipUsername}\"]?Set(PjsipDialWith=${PRVURI_USR}/sip:\${FixedDialWith}@${PRVURI_HOST}))" >> /etc/asterisk/extensions.conf
#							echo "same => n,Execif(\$[\"\${CALLERID(num)}\" = \"${SipUsername}\"]?Set(MonitorType=${OutboundMonitorTracks}))" >> /etc/asterisk/extensions.conf
						fi
						AccountsDone="$(printf '%s\n' "$AccountsDone" ; printf '%s\n' "$SipUsername")"
					fi
				else
					printf '\t%s\n' "${sINFO}Avoiding to repeat outbound for account \"${SipUsername}\" because of user ${CurUser}${fRESET}"
				fi
			done
			if [ $CurUser_HasSomeOutbound -eq 1 ] ; then
				echo "same => n,Execif(\$[\"\${ConcernedUser}\" = \"${CLIUSERID}\"]?Set(UserHasSomeOutbound=1))" >> /etc/asterisk/extensions.conf
			fi
		done
		echo 'same => n,Execif($["${PjsipDialWith}" != ""]?Gosub(plan-calling-outbound,${EXTEN},1,(${PjsipDialWith},${MonitorType},${PUBLICNR})))
same => n,Set(DIALSTATUS=${GOSUB_RETVAL})' >> /etc/asterisk/extensions.conf
		# As of Asterisk 16, playback files specification can include absolute path but not extension (Asterisk must be able to choose best format for this call)
#		echo "same => n,Execif(\$[\"\${PjsipDialWith}\" = \"\"]?Playback(all-outgoing-lines-unavailable))" >> /etc/asterisk/extensions.conf
		echo "same => n,Execif(\$[\$[\"\${PjsipDialWith}\" = \"\"] & \$[\"\${UserHasSomeOutbound}\" = \"1\"]]?Playback(${PreSilence_2}with&this&your-account&not-auth-pstn))" >> /etc/asterisk/extensions.conf
		echo "same => n,Execif(\$[\$[\"\${PjsipDialWith}\" = \"\"] & \$[\"\${UserHasSomeOutbound}\" != \"1\"]]?Playback(${PreSilence_2}with&this&your-account&all-outgoing-lines-unavailable))" >> /etc/asterisk/extensions.conf
		# https://docs.asterisk.org/Configuration/Miscellaneous/Hangup-Cause-Mappings/
		echo "same => n,System(asterisk-oncall update-monitor \${UNIQUEID} MonitorType=\${MonitorType})" >> /etc/asterisk/extensions.conf
		echo 'same => n,Return(${DIALSTATUS})
exten => a,1,Return(a)
exten => h,1,Return(h)
exten => i,1,Return(AST_CAUSE_INVALID_NUMBER_FORMAT)
exten => t,1,Return(AST_CAUSE_RECOVERY_ON_TIMER_EXPIRE)
exten => T,1,Return(AST_CAUSE_RECOVERY_ON_TIMER_EXPIRE)

[plan-calling]
exten => _..,1,Set(FixedExten=${EXTEN})
same => n,Set(FirstRouteType=outbound)' >> /etc/asterisk/extensions.conf
		IFS="$(printf '\n\b')" ; for CurUser in $(cat "${CurrentConfigDir}/users.tab" | grep -ve '^#' -ve '^;' -ve '^$') ; do unset IFS
			CLIUSERID="$(printf '%s' "$CurUser" | cut -sf 1)"
			CLIUSERNUMBERS="$(printf '%s' "$CurUser" | cut -sf 4)"
			echo "same => n,Execif(\$[\"\${FixedExten}\" = \"${CLIUSERID}\"]?Set(FirstRouteType=internal-user))" >> /etc/asterisk/extensions.conf
			for CLIUSERNUMBERS_CUR in $(echo "$CLIUSERNUMBERS" | tr -s ',' ' ') ; do
				CLIUSERNUMBERS_CUR_NR="$(printf '%s' "$CLIUSERNUMBERS_CUR" | cut -f 1 -d '/')"
				CLIUSERNUMBERS_CUR_OPTS="$(printf '%s' "$CLIUSERNUMBERS_CUR" | cut -sf 2 -d '/')"
				S_CLIUSERNUMBERS_CUR="${UserLineSeparator}${CLIUSERNUMBERS_CUR_NR}"
				SipUsername="${CLIUSERID}${S_CLIUSERNUMBERS_CUR}"
				# Some softphone, when user types a destination with no letter (such as 001-555001), it converts it to numeric-only string
				NumericOnlyExtension="$(printf '%s' "$SipUsername" | sed -e 's|[^0123456789]||g')"
				echo "same => n,Execif(\$[\"\${FixedExten}\" = \"${NumericOnlyExtension}\"]?Set(FixedExten=${SipUsername}))" >> /etc/asterisk/extensions.conf
				# This exposes to risk of matching an internal subextension with a derired public number
				echo "same => n,Execif(\$[\"\${FixedExten}\" = \"${SipUsername}\"]?Set(FirstRouteType=internal-user-subaccount))" >> /etc/asterisk/extensions.conf
			done
		done
#		echo "exten => _[0-9+]XXXXXXXX!,1,Gosub(decide-outbound,\${EXTEN},1(${OutboundMonitorTracks}))" >> /etc/asterisk/extensions.conf
		echo "same => n,Execif(\$[\"\${FirstRouteType}\" = \"outbound\"]?Gosub(decide-outbound,\${FixedExten},1,(${OutboundMonitorTracks})))" >> /etc/asterisk/extensions.conf
		echo "same => n,Execif(\$[\"\${FirstRouteType}\" = \"internal-user\"]?Gosub(plan-calling-internal,\${FixedExten},1,(${OutboundMonitorTracks})))" >> /etc/asterisk/extensions.conf
		echo "same => n,Execif(\$[\"\${FirstRouteType}\" = \"internal-user-subaccount\"]?Gosub(plan-calling-subaccount,\${FixedExten},1,(${OutboundMonitorTracks})))" >> /etc/asterisk/extensions.conf
		echo 'same => n,System(asterisk-oncall update-monitor ${UNIQUEID} END)
same => n,Hangup()' >> /etc/asterisk/extensions.conf
#		echo "exten => _..,1,Gosub(plan-calling-internal,\${EXTEN},1,(${CombinedMonitorTracks}))" >> /etc/asterisk/extensions.conf
#		echo 'same => n,System(asterisk-oncall update-monitor ${UNIQUEID} END)
#same => n,Hangup()
#exten => a,1,System(asterisk-oncall update-monitor ${UNIQUEID} END)
#same => n,Hangup()' >> /etc/asterisk/extensions.conf
		echo 'exten => h,1,System(asterisk-oncall update-monitor ${UNIQUEID} END)
same => n,Hangup()
exten => i,1,System(asterisk-oncall update-monitor ${UNIQUEID} END)
same => n,Hangup(AST_CAUSE_INVALID_NUMBER_FORMAT)
exten => t,1,System(asterisk-oncall update-monitor ${UNIQUEID} END)
same => n,Hangup(AST_CAUSE_RECOVERY_ON_TIMER_EXPIRE)
exten => T,1,System(asterisk-oncall update-monitor ${UNIQUEID} END)
same => n,Hangup(AST_CAUSE_RECOVERY_ON_TIMER_EXPIRE)' >> /etc/asterisk/extensions.conf

		echo "Rebuilding /etc/asterisk/voicemail.conf"
		mv /etc/asterisk/voicemail.conf /etc/asterisk/voicemail.conf.bak
		cp -a /etc/asterisk/voicemail.conf.bak /etc/asterisk/voicemail.conf
		if [ ! -f /etc/asterisk/voicemail.conf.manual ] ; then cp -a /etc/asterisk/voicemail.conf.bak /etc/asterisk/voicemail.conf.manual ; fi
		echo "; =========== Warning: FILE AUTOMATICALLY REWRITTEN BY $ProgramName ==========" > /etc/asterisk/voicemail.conf
		echo "" >> /etc/asterisk/voicemail.conf
		echo '; APPLY CHANGES with Asterisk CLI> voicemail reload

[general]
; Only emailsubject and emailbody seem to parse variables.

; Available formats depend on /usr/lib/asterisk/modules/format_*.so
; Opus preferred for modern players, but Maemo has no player for it.
;format=gsm|wav|spx|ogg|mp3
format = ogg

;externnotify appended parameters on VoiceMailMain() execution end: <username@domain> <callerid-name> <callerid-number>
;externnotify appended parameters on VoiceMail() execution end: <context> <extension> <new-voicemails> <old-voicemails> <urgent-voicemails>
externnotify=asterisk-oncall update-monitor-voicemail
' >> /etc/asterisk/voicemail.conf
		echo "serveremail = asterisk@$(hostname -f)" >> /etc/asterisk/voicemail.conf
		printf '%s\n' 'fromstring=PBX
;Set maxmsg to 0 if 
;maxmsg = 100
maxsilence = 5
;silencethreshold=128
;maxlogins=3
;emailsubject=Mensaje de voz de ${CallerNumber}
;emailbody=OUTBOUND-TRUNKMSD=${OUTBOUND-TRUNKMSD}\nOUTBOUND-TRUNK=${OUTBOUND-TRUNK}\nIAXINFO-AEL=${IAXINFO-AEL}\nCONSOLE-AEL=${CONSOLE-AEL}\nTRUNKMSD=${TRUNKMSD}\nTRUNK=${TRUNK}\nIAXINFO=${IAXINFO}\nCONSOLE=${C>
;emailbody=https://docs.asterisk.org/Configuration/Dialplan/Variables/Channel-Variables/Asterisk-Standard-Channel-Variables/VoiceMail-Channel-Variables/\n\nCalledNumber=${CalledNumber}\nCallerNumber=${CallerNum>
;emailbody=Han llamado del ${CallerNumberNice} grabando un mensaje de duracion ${VM_DUR} (m:s)\nLa llamada fue recogida por la linea ${CalledNumber} (${CalledName}).\n\n${VM_DATE}

;charset=UTF-8' >> /etc/asterisk/voicemail.conf
		if [ "$DefaultLocale" != "" ] ; then
			echo "locale=${DefaultLocale}" >> /etc/asterisk/voicemail.conf
		fi
		echo ';emaildateformat=%A, %-d de %B de %Y a las %T

;sendvoicemail=yes ; Allow the user to compose and send a voicemail while inside
' >> /etc/asterisk/voicemail.conf
		echo "[${VoiceMail_Context}]" >> /etc/asterisk/voicemail.conf
		echo '; Es la especificación de correo-e aquí que implica el envío automático por parte de Asterisk.
;<vm-extension> = <password>,<EmailName>,<EmailAddress>,<EmailAddress_ForBrief>,<options>
;100 = 1234,My line,john@example.net,,delete=yes
' >> /etc/asterisk/voicemail.conf
		ASTSPOOLDIR="$(cat /etc/asterisk/asterisk.conf | grep -e '^astspooldir =.*/' -e '^astspooldir=.*/' | head -n 1 | cut -f 2- -d '=' | cut -f 2- -d '>' | sed -e 's|.* /|/|g')"
		if [ ! -d "$ASTSPOOLDIR" ] ; then ASTSPOOLDIR='/var/spool/asterisk' ; fi
		TrunksMailboxesNr=0
		IFS="$(printf '\n\b')" ; for CurTrunk in $(cat "${CurrentConfigDir}/trunks.tab" | grep -ve '^#' -ve '^;' -ve '^$') ; do unset IFS
			PUBLICNR="$(printf '%s' "$CurTrunk" | cut -sf 1)"
			PUBLICNAME="$(printf '%s' "$CurTrunk" | cut -sf 2)"
			PRVURI="$(printf '%s' "$CurTrunk" | cut -sf 4)"
			PRVURI_USR="$(printf '%s' "$PRVURI" | grep -e '@' | cut -sf 1 -d '@')"
			PRVURI_HOST="$(printf '%s' "$PRVURI" | cut -sf 2 -d '@')"
			PRVPASSWORD="$(printf '%s' "$CurTrunk" | cut -sf 5)"
			INBOUNDMAILTO="$(printf '%s' "$CurTrunk" | cut -sf 6)"
			INBOUNDMAILTO_ADDR="$(printf '%s' "$INBOUNDMAILTO" | cut -f 1 -d '/')"
			INBOUNDMAILTO_LANG="$(printf '%s' "$INBOUNDMAILTO" | cut -sf 2 -d '/')"
			INBOUNDMAILTO_OPT="$(printf '%s' "$INBOUNDMAILTO" | cut -sf 3 -d '/')"
			if [ "$(printf '%s' "$INBOUNDMAILTO_OPT" | grep -ie 'v')" ] ; then
				TrunksMailboxesNr=$((TrunksMailboxesNr + 1))
				if [ "$(ls "${ASTSPOOLDIR}/voicemail/${VoiceMail_Context}/${PUBLICNR}"/unavail.* 2>/dev/null)" != "" ] ; then
					printf '\t%s\n' "Voice mailbox ${PUBLICNR}@${VoiceMail_Context} for external line/endpoint ${sVALUE}${PRVURI_USR}${fRESET} -> $INBOUNDMAILTO_ADDR ${ParO}with custom greeting already: unavail${ParC}"
				else
					printf '\t%s\n' "Voice mailbox ${PUBLICNR}@${VoiceMail_Context} for external line/endpoint ${sVALUE}${PRVURI_USR}${fRESET} -> $INBOUNDMAILTO_ADDR ${ParO}${sINFO}using DEFAULT greeting${fRESET}: vm-intro${ParC}"
					printf '\t\t%s\n' "${ParO}a custom greeting could be as ${ASTSPOOLDIR}/voicemail/${VoiceMail_Context}/${PUBLICNR}/unavail.wav16${ParC}"
					if [ ! -d "${ASTSPOOLDIR}/voicemail/${VoiceMail_Context}/${PUBLICNR}" ] ; then
						printf '\t\t%s\n' "${ParO}make a first-time use of this voicemail so Asterisk creates path${ParC}"
					fi
				fi
				INBOUNDMAILTO_ADDR=''  # Disable Asterisk own voicemail sending
				echo "${PUBLICNR} = $(printf '%s' "$PRVPASSWORD" | tr ',' '.'),$(printf '%s' "$PUBLICNAME" | tr ',' '.'),$(printf '%s' "$INBOUNDMAILTO_ADDR" | tr -s ',' '|'),,delete=no" >> /etc/asterisk/voicemail.conf
			fi
		done
		if [ $TrunksMailboxesNr -eq 0 ] ; then
			printf '\t%s\n' "${sINFO}I: No mailbox enabled for any external line${fRESET}"
		fi
		AccountsDone=''
		UsersMailboxesNr=0
		IFS="$(printf '\n\b')" ; for CurUser in $(cat "${CurrentConfigDir}/users.tab" | grep -ve '^#' -ve '^;' -ve '^$') ; do unset IFS
			CLIUSERID="$(printf '%s' "$CurUser" | cut -sf 1)"
			if [ "$(printf '%s\n' "$AccountsDone" | grep -e "^${CLIUSERID}$")" = "" ] ; then
				UsersMailboxesNr=$((UsersMailboxesNr + 1))
				CLIUSERPASS="$(printf '%s' "$CurUser" | cut -sf 2)"
				CLIUSERDESCRIPTION="$(printf '%s' "$CurUser" | cut -sf 3)"
				CLIUSERMAILTO="$(printf '%s' "$CurUser" | cut -sf 5)"
				CLIUSERMAILTO_ADDR="$(printf '%s' "$CLIUSERMAILTO" | cut -f 1 -d '/')"
				CLIUSERMAILTO_LANG="$(printf '%s' "$CLIUSERMAILTO" | cut -sf 2 -d '/')"
				CLIUSERMAILTO_OPT="$(printf '%s' "$CLIUSERMAILTO" | cut -sf 3 -d '/')"
				if [ "$(printf '%s' "$CLIUSERMAILTO_OPT" | grep -ie 'v')" ] ; then
					if [ "$(ls "${ASTSPOOLDIR}/voicemail/${VoiceMail_Context}/${CLIUSERID}"/unavail.* 2>/dev/null)" != "" ] ; then
						printf '\t%s\n' "Voice mailbox ${CLIUSERID}@${VoiceMail_Context} for internal user/endpoint ${sVALUE}${CLIUSERID}${fRESET} -> $CLIUSERMAILTO_ADDR ${ParO}with custom greeting already: unavail${ParC}"
					else
						printf '\t%s\n' "Voice mailbox ${CLIUSERID}@${VoiceMail_Context} for internal user/endpoint ${sVALUE}${CLIUSERID}${fRESET} -> $CLIUSERMAILTO_ADDR ${ParO}${sINFO}using DEFAULT greeting${fRESET}: vm-intro${ParC}"
						printf '\t\t%s\n' "${ParO}a custom greeting could be as ${ASTSPOOLDIR}/voicemail/${VoiceMail_Context}/${CLIUSERID}/unavail.wav16${ParC}"
						if [ ! -d "${ASTSPOOLDIR}/voicemail/${VoiceMail_Context}/${CLIUSERID}" ] ; then
							printf '\t\t%s\n' "${ParO}make a first-time use of this voicemail so Asterisk creates path${ParC}"
						fi
					fi
					CLIUSERMAILTO_ADDR=''  # Disable Asterisk own voicemail sending
					echo "${CLIUSERID} = $(printf '%s' "$CLIUSERPASS" | tr ',' '.'),$(printf '%s' "$CLIUSERDESCRIPTION" | tr ',' '.'),$(printf '%s' "$CLIUSERMAILTO_ADDR" | tr -s ',' '|'),,delete=no" >> /etc/asterisk/voicemail.conf
				fi
				AccountsDone="$(printf '%s\n' "$AccountsDone" ; printf '%s\n' "$CLIUSERID")"
			else
				printf '\t%s\n' "${sINFO}Avoiding to repeat voicemail for account \"${CLIUSERID}\" because of more public lines/mails${fRESET}"
			fi
		done
		if [ $UsersMailboxesNr -eq 0 ] ; then
			printf '\t%s\n' "${sINFO}I: No mailbox enabled for any external line${fRESET}"
		fi

		if [ $ReloadAsterisk -eq 1 ] ; then
			ReloadService
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			if [ $LastStatus -ne 0 ] ; then ErrorsNr=$((ErrorsNr + 1)) ; fi
			if [ "$PostAction" = "status" ] ; then
				AsteriskStatus
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				if [ $LastStatus -ne 0 ] ; then ErrorsNr=$((ErrorsNr + 1)) ; fi
			fi
		else
			printf '%s\n' "${sWARN}W: Not calling Asterisk to reload pjsip because there are example files to customize.${fRESET}" 1>&2
			WarningsNr=$((WarningsNr + 1))
		fi
	fi
	if [ $WarningsNr -gt 0 ] ; then
		printf '%s\n' "$WarningsNr WARNINGS REPORTED. You better clean those issues so service is consistent with your expectations." 1>&2
	fi
	if [ $ErrorsNr -gt 0 ] ; then
		printf '%s\n' "$ErrorsNr ERRORS REPORTED. You should fix those problems so service works as expected." 1>&2
	fi
	if [ $((WarningsNr + ErrorsNr)) -eq 0 ] ; then
		printf '%s\n' "${sINFO}I: pjsip/dialplan/voicemail reload is otfen not enough. You may need to restart Asterisk service.${fRESET}" 1>&2
	fi
	return $StatusCode
}

AsteriskStatus ()
{
	echo "${sHEAD1}Service and execution${fRESET}"
	asterisk -rx "core show version"
	LastStatus=$?
	if [ $LastStatus -ne 0 ] ; then return $LastStatus ; fi
	asterisk -rx "core show uptime"
	asterisk -rx "core waitfullybooted"
	echo ""
	echo "${sHEAD1}Bridges:${fRESET}"
	asterisk -rx "bridge show all" | sed -e 's|^|\t|g'
	echo "${sHEAD1}Active conference bridges:${fRESET}"
	asterisk -rx "confbridge list" | sed -e 's|^|\t|g'
	echo "${sHEAD1}Calls:${fRESET}"
	asterisk -rx "core show calls" | sed -e 's|^|\t|g'
	echo "${sHEAD1}Channels:${fRESET}"
	asterisk -rx "core show channels verbose" | sed -e 's|^|\t|g'
	echo "${sHEAD1}Local channels:${fRESET}"
	asterisk -rx "local show channels" | sed -e 's|^|\t|g'
	echo "${sHEAD1}PJSIP channels:${fRESET}"
	asterisk -rx "pjsip list channels" | sed -e 's|^|\t|g'
	echo "${sHEAD1}PJSIP registrations:${fRESET}"
	asterisk -rx "pjsip list registrations" | sed -e 's|^|\t|g'
	echo "${sHEAD1}PJSIP endpoints:${fRESET}"
	asterisk -rx "pjsip list endpoints" | sed -e 's|^|\t|g'
#	echo "${sHEAD1}PJSIP channels detail:${fRESET}"
#	asterisk -rx "pjsip show channels" | sed -e 's|^|\t|g'
	echo "${sHEAD1}Voicemail boxes:${fRESET} ${ParO}storage at /var/spool/asterisk/voicemail ${ParC}"
	asterisk -rx "voicemail show users" | sed -e 's|^|\t|g'
}

#[/asterisk-simple]


##### 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.
{
	sleep 0
}

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

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

	return $StatusCode
}

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

	return $StatusCode
}

Configuration_Saved_Pre ()
# Preliminar configurations at beginning of Configuration(). Useful to migrate old configurations and data before assuming them for production.
{
	local ReadOnly=0
	if [ "$1" = "ro" ] ; then ReadOnly=1 ; shift ; fi
}

Configuration_Saved_Post ()
{
	local ReadOnly=0
	if [ "$1" = "ro" ] ; then ReadOnly=1 ; shift ; fi
#[asterisk-simple]
	local LastStatus=0
	local StatusCode=0

	SipFqdn="$(GetSetLocalConfig "$ReadOnly" SipFqdn '' "sip.example.net" '\n# SipFqdn: Domain name for AOR contacts')"
	RealmForClients="$(GetSetLocalConfig "$ReadOnly" AsteriskRealm '' '"asterisk"' '\n')"
	UserLineSeparator="$(GetSetLocalConfig "$ReadOnly" UserLineSeparator '' '"-"' '\n# UserLineSeparator: Separator character between username and external number, to create accounts login string')"
	DIALSTATUS_GoToVoicemail="$(GetSetLocalConfig "$ReadOnly" DIALSTATUS_GoToVoicemail '' '"CHANUNAVAIL,CONGESTION,NOANSWER,BUSY,INVALIDARGS"' '\n# DIALSTATUS_GoToVoicemail: Result statuses of Dial() action that decide to run VoiceMail(). Allowed keys (separated by commas): CHANUNAVAIL,CONGESTION,NOANSWER,BUSY,ANSWER,CANCEL,DONTCALL,TORTURE,INVALIDARGS')"
	InboundMonitorTracks="$(GetSetLocalConfig "$ReadOnly" InboundMonitorTracks '' "dual" '\n# InboundMonitorTracks: Use old Monitor action (dual) or maintained MixMonitor action (mix) when recording calls from ITSP')"
	OutboundMonitorTracks="$(GetSetLocalConfig "$ReadOnly" OutboundMonitorTracks '' "mix" '\n# OutboundMonitorTracks: Use old Monitor action (dual) or maintained MixMonitor action (mix) when recording calls from local SIP accounts (also to internal destinations)')"
	DefaultLocale="$LANG"
	DefaultLocale_Lang="$(printf '%s' "$DefaultLocale" | cut -f 1 -d '.' | cut -f 1 -d '_')"
	if [ "$DefaultLocale_Lang" = "" ] || [ "$(ls -1 "/usr/share/asterisk/sounds" | grep -e "^${DefaultLocale_Lang}$" -e "^${DefaultLocale_Lang}_")" = "" ] ; then
		DefaultLocale="$(cat /etc/default/locale 2>/dev/null | grep -e '^LANG=' | cut -f 2- -d '=' | cut -f 2 -d '"')"
		DefaultLocale_Lang="$(printf '%s' "$DefaultLocale" | cut -f 1 -d '.' | cut -f 1 -d '_')"
		if [ "$DefaultLocale_Lang" = "" ] || [ "$(ls -1 "/usr/share/asterisk/sounds" | grep -e "^${DefaultLocale_Lang}$" -e "^${DefaultLocale_Lang}_")" = "" ] ; then
			DefaultLocale=''
		fi
	fi
	DefaultLocale="$(GetSetLocalConfig "$ReadOnly" DefaultLocale '' "$DefaultLocale" '\n# DefaultLocale: Fallback language specification for unassisted Voicemail')"	#do
	if [ $ReadOnly -eq 0 ] ; then
		if [ ! -f "${CurrentConfigDir}/trunks.tab" ] ; then
			echo "${sINFO}Creating example file for trunks registrations: ${sWARN}${CurrentConfigDir}/trunks.tab${fRESET}"
			cp -a /etc/asterisk/pjsip.conf "${CurrentConfigDir}/trunks.tab"
			cat /dev/null > "${CurrentConfigDir}/trunks.tab"
			printf '# PUBLICNR\tPUBLICNAME\tITSP\tPRVURI\tPRVPASSWORD\tINBOUNDMAILTO\n' >> "${CurrentConfigDir}/trunks.tab"
			printf '# Note: E-mail syntax is address/langguage/options. Options are "v" to enable voicemail\n' >> "${CurrentConfigDir}/trunks.tab"
			printf '# To apply changes: asterisk-simple rebuild\n' >> "${CurrentConfigDir}/trunks.tab"
			printf '555000001\tMy Company\tMyProvider\tmeuser@sip.myprovider.com\t1234\tinfo@example.com\n' >> "${CurrentConfigDir}/trunks.tab"
			printf '555000401\tSecond Company\tAnotherProvider\tuser2@sip.myprovider.com\t1234\tcontact@example.com//v\n' >> "${CurrentConfigDir}/trunks.tab"
			printf '555000601\tMy home\tMyProvider\twe@sip.myprovider.net\t1234\twe@example.net/es/v\n' >> "${CurrentConfigDir}/trunks.tab"
		fi
		if [ ! -f "${CurrentConfigDir}/users.tab" ] ; then
			echo "${sINFO}Creating example file for client users who register here: ${sWARN}${CurrentConfigDir}/users.tab${fRESET}"
			cp -a /etc/asterisk/pjsip.conf "${CurrentConfigDir}/users.tab"
			cat /dev/null > "${CurrentConfigDir}/users.tab"
			RandomPassword="$(dd if=/dev/urandom count=1 2> /dev/null | md5sum | tr -s '\t' ' ' | cut -f 1 -d ' ' | cut -c 1-10)"
			printf '# CLIUSERID\tCLIUSERPASS\tCLIUSERDESCRIPTION\tCLIUSERNUMBERS\tCLIUSERMAILTO\n' >> "${CurrentConfigDir}/users.tab"
			printf '# Note1: Password can not contain commas, due to Voicemail specifications\n' >> "${CurrentConfigDir}/users.tab"
			printf '# Note2: CliUserNumbers syntax is PublicNr/options. Options are "i" to attend inbound, "o" to allow outbound calls and "io" for both\n' >> "${CurrentConfigDir}/users.tab"
			printf '# Note3: E-mail syntax is address/language/options. Options are "v" to enable voicemail\n' >> "${CurrentConfigDir}/users.tab"
			printf '# To apply changes: asterisk-simple rebuild\n' >> "${CurrentConfigDir}/users.tab"
			printf "john\t${RandomPassword}\tJohn Doe\t555000001/io,555000002/io\tjohn@example.net\n" >> "${CurrentConfigDir}/users.tab"
			printf "maria\t${RandomPassword}\tMari\t555000001/i,555000002/i\tmaria@example.net/es\n" >> "${CurrentConfigDir}/users.tab"
			printf "brigitte\t${RandomPassword}\tBrigitte Alons\t555000001/oi,555000002/o\tba@example.net//v\n" >> "${CurrentConfigDir}/users.tab"
		fi
#		if [ ! -f "${CurrentConfigDir}/devices.tab" ] ; then
#			echo "Creating example file for remote devices used by clients to connect here: ${CurrentConfigDir}/devices.tab"
#			cp -a /etc/asterisk/pjsip.conf "${CurrentConfigDir}/devices.tab"
#			printf '# DEVICEID\tDEVUSER\tDEVDESCRIPTION\n' > "${CurrentConfigDir}/devices.tab"
#			printf '1\tjohn\tHome PC\n' >> "${CurrentConfigDir}/devices.tab"
#			printf '2\tjohn\tCellular phone\n' >> "${CurrentConfigDir}/devices.tab"
#		fi
		if [ ! -f "${CurrentConfigDir}/client.tpl" ] ; then
			# https://docs.asterisk.org/Configuration/Channel-Drivers/SIP/Configuring-res_pjsip/PJSIP-Configuration-Sections-and-Relationships/
			echo "Creating example template for client sections identify+auth+aor+endpoint: ${sINFO}${CurrentConfigDir}/client.tpl${fRESET}"
			cp -a /etc/asterisk/pjsip.conf "${CurrentConfigDir}/client.tpl"
			cat /dev/null > "${CurrentConfigDir}/client.tpl"
			echo "
; ==================== Client account ====================

[auth-cli-{CLIUSERID}{S_CLIUSERNUMBERS_CUR}]
type = auth" >> "${CurrentConfigDir}/client.tpl"
#			echo "auth_type = userpass
#username = {SipUsername}
#password = {CLIUSERPASS}" >> "${CurrentConfigDir}/client.tpl"
			echo "auth_type = md5
username = {SipUsername}
md5_cred = {md5_cred}" >> "${CurrentConfigDir}/client.tpl"
			echo "
[{SipUsername}]
type = aor
;contact = sip:{SipUsername}@{SipFqdn}
max_contacts = 10

;[end-cli-{CLIUSERID}{S_CLIUSERNUMBERS_CUR}]
[{SipUsername}]
type = endpoint
auth = auth-cli-{CLIUSERID}{S_CLIUSERNUMBERS_CUR}
aors = {SipUsername}
from_domain = {SipFqdn}
;identify_by = auth_username
identify_by = username
transport = transport-udp
context = plan-calling
disallow = all
allow = {SortedCodecs}
tone_zone = es
{LanguageSpec}
direct_media = no
disable_direct_media_on_nat = yes
rtp_symmetric=yes
rewrite_contact=yes
force_rport = yes
dtmf_mode = auto
rtp_keepalive = 30
rtp_timeout = 30
rtp_timeout_hold = 60
;geoloc_incoming_call_profile = location-profile-neutral

" >> "${CurrentConfigDir}/client.tpl"
		fi
		if Is_Executable sox ; then
			MkdirAndOrPublic "${CurrentConfigDir}/sounds" ':asterisk' u=rwX,go=rX g+s
			printf '%s\n' "Generating sound effects as necessary"
			if [ ! -f "${CurrentConfigDir}/sounds/silence-1.wav16" ] ; then
				sox -n -t raw -b 16 -r 16000 "${CurrentConfigDir}/sounds/silence-1.wav16" synth 1 sine 0
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				if [ $LastStatus -eq 0 ] ; then PreSilence_1="${CurrentConfigDir}/sounds/silence-1&" ; fi
			fi
			if [ ! -f "${CurrentConfigDir}/sounds/silence-2.wav16" ] ; then
				sox -n -t raw -b 16 -r 16000 "${CurrentConfigDir}/sounds/silence-2.wav16" synth 1 sine 0
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				if [ $LastStatus -eq 0 ] ; then PreSilence_2="${CurrentConfigDir}/sounds/silence-2&" ; fi
			fi
			if [ ! -f "${CurrentConfigDir}/sounds/silence-3.wav16" ] ; then
				sox -n -t raw -b 16 -r 16000 "${CurrentConfigDir}/sounds/silence-3.wav16" synth 1 sine 0
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				if [ $LastStatus -eq 0 ] ; then PreSilence_3="${CurrentConfigDir}/sounds/silence-3&" ; fi
			fi
			if [ ! -f "${CurrentConfigDir}/sounds/silence-4.wav16" ] ; then
				sox -n -t raw -b 16 -r 16000 "${CurrentConfigDir}/sounds/silence-4.wav16" synth 1 sine 0
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				if [ $LastStatus -eq 0 ] ; then PreSilence_4="${CurrentConfigDir}/sounds/silence-4&" ; fi
			fi
		fi
		if [ $StatusCode -eq 0 ] ; then
			if [ -f "${CurrentConfigDir}/sounds/silence-1.wav16" ] ; then PreSilence_1="${CurrentConfigDir}/sounds/silence-1&" ; fi
			if [ -f "${CurrentConfigDir}/sounds/silence-2.wav16" ] ; then PreSilence_2="${CurrentConfigDir}/sounds/silence-2&" ; fi
			if [ -f "${CurrentConfigDir}/sounds/silence-3.wav16" ] ; then PreSilence_3="${CurrentConfigDir}/sounds/silence-3&" ; fi
			if [ -f "${CurrentConfigDir}/sounds/silence-4.wav16" ] ; then PreSilence_4="${CurrentConfigDir}/sounds/silence-4&" ; fi
		fi
	fi
	return $StatusCode
#[asterisk-simple]
}

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

Action="$1"
if [ $# -gt 0 ] ; then shift ; fi
Action="$(Lowercase "$Action")"
case "$Action" in
	"remove" ) Action="uninstall" ; ActionMode="remove" ;;
	"purge" ) Action="uninstall" ; ActionMode="purge" ;;
	"-h" ) Action="--help" ;;
	"-V" ) Action="--version" ;;
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
		;;
#[asterisk-simple]
	"rebuild" )
		Configuration "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		RebuildConfigs "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		;;
	"status" )
		Configuration "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		AsteriskStatus "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		;;
#[/asterisk-simple]
	"" )
		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
