#!/bin/bash

# Free implementation of nxserver components
# Copyright (c) 2004 by Fabian Franz.
#           (c) 2008-23 by Dmitry Borisov <i@dimbor.ru>
# License: GNU GPL, version 2

shopt -s extglob

SHARED_CONFS="/usr/share/freenx-server"
. $SHARED_CONFS/nxfuncs

[ "$1" = "--admin" ] && { # simple wrapper to start nxserver in admin mode
	if stringinstring " nxadmin " " $(groups) "; then
		/usr/bin/sudo -p "" /bin/bash -c '/usr/bin/nxserver --admin' 2>&1
		exit $?
	else
		echo "NX> 2004 admin mode start failed"
		exit 1
	fi
}

#
# -----------------------------------------------------------------------------
# Various helper functions
# -----------------------------------------------------------------------------
#
nxlog() {
	[ "$NX_LOG_LEVEL" != "0" ] || return
	echo "$(date "+%T.%3N"): ${@/password=+([^&])&/password=*&}" >> "$nxuser_logfile"
}

cp_conv() {
# arg: <string>
# Used config vars: $COMMAND_ICONV $WIN_CP_CONVERT_CHAIN
# successively convert string charset
	#local lp="$FUNCNAME ($$/$BASHPID):"; #debug
	#nxlog "$lp starting with args \"$@\"" #debug
	local res=${1//+/ } cp_pair cp_from cp_to;
	[ -n "$COMMAND_ICONV" ] || { echo "$res"; return 1; }
	for cp_pair in $WIN_CP_CONVERT_CHAIN ; do
		cp_from=$(cutfn "$cp_pair" 0 '>'); [ -n "$cp_from" ] || cp_from="latin1"
		cp_to=$(cutfn "$cp_pair" 1 '>'); [ -n "$cp_to" ] || cp_to="UTF-8"
		res=$(echo "$res" | $COMMAND_ICONV -f $cp_from -t $cp_to)
		#nxlog "$lp converting $cp_from > $cp_to == \"$res\"" #debug
	done
	#nxlog "$lp return res='$res'" #debug
	echo "$res"
}

# =================== sqlite3 stuff =====================

declare -g sqcols_usess="session_id, status, display, type, client,\
 agent_pid, cookie, tail_pid, userip, acc_ip, mmport, cupsport, smbport"

declare -g sqcols_usvcs="svc, type, status, port, share, username, pass,\
 data, comp, addr"
#svc: internal service name
#type: smb-share | smb-prn | ipp-prn | media-pa | ...
#status: starting|on|stopping|off
#port: tunneled/remote port
#share: incomming share name
#data: options depended of service type eg mount dir...
#comp: remote computer name
#addr: ip address is 127.0.0.1 typically, another if remote connection used

init_usessions_db() {
	local usess_cols="session_id TEXT PRIMARY KEY, status TEXT, display TEXT,\
 type TEXT, client TEXT, agent_pid INT, cookie TEXT, tail_pid INT,\
 userip TEXT, acc_ip TEXT, mmport INT, cupsport INT, smbport INT"
	local svcs_cols="svc TEXT PRIMARY KEY, type TEXT, status TEXT, port INT,\
 share TEXT, comp TEXT, addr TEXT, username TEXT, pass TEXT, data TEXT"
	local qstr="CREATE TABLE IF NOT EXISTS usessions.usess($usess_cols);"
	qstr+="CREATE TABLE IF NOT EXISTS usessions.usvcs($svcs_cols);"
	q_dbe "$qstr"
}

# ------------------ user session (usess) control --------
usess_add() { q_row_ins "usess" "$1" "$2"; }
#usess_add() args: <col1,col2...> <val1&val2...>

usess_set() { q_rows_upd "usess" "session_id='$1'" "$2" "$3"; }
#usess_set() args: <session_id> <col1,col2...> <val1&val2...>

usess_get() { q_vals_str_get "usess" "session_id='$1'" "$2" "$3"; }
#usess_get() args: <session_id> <col1,col2...> [values_delim='&']

usess_close() {
#args: <session_id> <status>
	if [ "$SESSION_LOG_CLEAN" = "1" ]; then
		q_dbe "DELETE FROM usess WHERE session_id='$1';"
	else
		q_rows_upd "usess" "session_id='$1'" "status" "$2"
	fi
}

# -------------- user services (uservices) control --------
#usvcs_add() args: <col1,col2...> <val1&val2...>
usvcs_add() {	q_row_ins "usvcs" "$1" "$2"; }

#usvcs_set() args: <service> <col1,col2...> <val1&val2...>
usvcs_set() { q_rows_upd "usvcs" "svc='$1'" "$2" "$3"; }

#usvcs_get() args: <service> <col1,col2...> [values_delim='&']
usvcs_get() { q_vals_str_get "usvcs" "svc='$1'" "$2" "$3"; }

#
# -----------------------------------------------------------------------------
# Node functions module
# -----------------------------------------------------------------------------
#

sess_lport_name() {
#arg: <svc_type>
	case $1 in
	smb-share|smb-prn) echo "smbport";;
	ipp-prn)	echo "cupsport";;
	media-pa)	echo "mmport";;
	esac
}

norm_dir() {
#args: <share_dir> [parent_dir]
# exclude potential parts to exec and set dir path from given parent_dir
	local r=${1//\`/}; r=${r//\$[(\{]*[)\}]/$2}; r=${r/\.\.\//$2\/};
	r=${r/\.\//$2\/}; r=${r/\~\//$2\/}
	[[ "${r:0:1}" =~ [[:alnum:]] ]] && r="$2/$r"
	echo "$r"
}

uservice_mounted() {
#args: <type> <service/sharename/mountpoint> [port]
	local rc=0 txt="" pattern  port=""
	local patt_addr="127.0.0.1"; [ -n "$4" ] && patt_addr=$4
	case $1 in
	smb-share)
		# output of mount cmd not contains mount.cifs port option value
		# because we need to port arg ($3)
		txt=$(env LC_ALL=C $COMMAND_MOUNT_LIST 2>/dev/null)
		if stringinstring "//" "$2"; then pattern="($2)"
		elif ! stringinstring "/" "$2"; then pattern="//$patt_addr/($2)"
		else pattern="($2)"
		fi
		[ -n "$(rematchfn "$pattern" "$txt")" ] || return 1
		if [ -n "$3" ]; then
			 port_is_listening $3 || rc=1
		fi
	;;
	smb-prn|ipp-prn)
		txt=$(env LC_ALL=C $COMMAND_LPSTAT -v 2>/dev/null)
		pattern=".*$2.*//$patt_addr:("
		[ -n "$3" ] && pattern+="$3)" || pattern+="$num_pattern)"
		port=$(rematchfn "$pattern" "$txt"); rc=$?
		[ -n "$port" ] || return $rc
		port_is_listening "$port" || rc=1
	;;
	media-pa)
		case $2 in
		pa) # tunneled pa
			#$COMMAND_PA --check || return 1
			txt=$(env LC_ALL=C $COMMAND_PACTL list short 2>/dev/null)
			pattern="server=$patt_addr:("
			[ -n "$3" ] && pattern+="$3)" || pattern+="[0-9]+)"
			port=$(rematchfn "$pattern" "$txt"); rc=$?
			#nxlog "$1 port=$port" #debug
			[ -n "$port" ] || return $rc
			port_is_listening "$port" || rc=1
		;;
		esac
	;;
	esac
	return $rc
}

uservice_configure() {
#args:
# smb-share <svc> <port> <username> <password> <dir> <computername>
# 		*-prn <svc> <port> <username> <password> <opts> <computername> <share>
#				opts="model=;public=;defaultprinter="
# media-pa	<svc> <port> <""> <""> <opts>
	local lp="$FUNCNAME ($$/$BASHPID):";
	local cmdstr optstr comp rc=0 txt errstr uri
	case $1 in
	smb-share)
		# create/check mountpoint
		mkdir -p "$6" &>/dev/null
		[ -d "$6" ] || { nxlog "$lp unable to create dir='$6'"; return 1; }
		local egroup=$(id -gn "$USER") tmpopts=${SMB_MOUNT_OPTIONS//,/&}
		local dir_mode=$(getparam "$tmpopts" dir_mode)
		dir_mode=${dir_mode:(-3)}; [ -n "$dir_mode" ] || dir_mode="700"
		[ "$(stat -c %a "$6")" != "$dir_mode" ] && chown "0$dir_mode" "$6" &>/dev/null
		# mount options string
		optstr="uid=$USER,gid=$egroup,ip=127.0.0.1,port=$3,username=$4"
		[ -n "$5" ] && optstr+=",password=$5"
		[ -n "$SMB_MOUNT_OPTIONS" ] && optstr+=",$SMB_MOUNT_OPTIONS"
		cmdstr="$COMMAND_SUDO $COMMAND_SMBMOUNT $2 $6 -o $optstr"
		echo "$cmdstr"
	;;
	smb-prn|ipp-prn)
		local model=$(getparam "$6" "model" "" ';')
		[ "$model" = "NULL" ] && model=""
		local ppdn=${2#$USER}; ppdn=${ppdn:1}; ppdn=${ppdn%#[0-9]*}
		local ppdfn="$NX_PPD_DIR/$ppdn.ppd"
		[ -r "$ppdfn" ] || { # ppd is not found, search for driver in CUPS
			txt=$($COMMAND_LPINFO -m 2>/dev/null)
			local str drv=""
			[ -n "$model" ] && {
				while read str; do
					[[ "$str" =~ "$model" ]] && { drv="${str%% *}"; break; }
				done <<< "$txt"
			}
			[ -n "$drv" ] || {
				while read str; do
					[[ "$str" =~ "$ppdn" ]] && { drv="${str%% *}"; break; }
				done <<< "$txt"
			}
			[ -n "$drv" ] || {
				nxlog "$lp '$svc'; CUPS driver for service is not found";
				return 1; }
			$COMMAND_PPDCAT cat "$drv" > "$ppdfn" || {
				nxlog "$lp Can't save $ppdfn"; return 1; }
		}
		[ "${1:0:3}" = "smb" ] && \
			uri="nxsmb://$4:$5@127.0.0.1:$3/cifs/$8" || \
			uri="ipp://$4:$5@127.0.0.1:$3/printers/$8"
		cmdstr="$COMMAND_SUDO $COMMAND_LPADMIN -p $svc -P $ppdfn -v $uri -E"
		echo "$cmdstr"
	;;
	media-pa)
		case $2 in
		pa) # tunneled pa
			local uri="127.0.0.1:$3"
			# get sink and source from remote pa
			local rmods=$(env LC_ALL=C $COMMAND_PACTL -s $uri list short 2>/dev/null)
			[ -n "$rmods" ] || {
				nxlog "$lp '$svc'; can't get module list from remote PA ($uri)";
				return 1; }
			local rsink=$(rematchfn "(ts_receiver)" "$rmods") #"
			local rsource=$(rematchfn "(ts_sender.monitor)" "$rmods") #"
			[ -n "$rsink" -a -n "$rsource" ] || {
				local rinfo=$(env LC_ALL=C $COMMAND_PACTL -s $uri info 2>/dev/null)
				[ -n "$rsink" ] || \
					rsink=$(rematchfn "Default Sink:[[:blank:]]+(.+)" "$rinfo") #"
				[ -n "$rsource" ] || \
					rsource=$(rematchfn "Default Source:[[:blank:]]+(.+)" "$rinfo") #"
			}
			echo "$rsink $rsource"
		;;
		esac
	;;
	esac
	return $rc
}

uservice_mount() {
#args:
# smb-share <svc> <port> <username> <password> <dir> <computername>
# *-prn <svc> <port> <username> <password> <opts> <computername> <share>
# media-pa <svc> <port> "" "" <mode> <computername>
	local lp="$FUNCNAME ($$/$BASHPID):" rc=0 cmdstr errstr
	local i ok="" step=0.25 timeo=28 #7sec
	for (( i=0; i<=timeo; i++ )); do
		port_is_listening $3 && { ok="1"; break; }
		sleep $step"s"
	done
	[ -n "$ok" ] || \
		 { nxlog "$lp '$svc'; port $3 is not listen after $((timeo/4))s";
		  return 1; }

	cmdstr=$(uservice_configure "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8") || return 1
	case $1 in
	smb-share)
		#nxlog "$lp share cmdstr='$cmdstr'" #debug
		errstr=$($cmdstr 2>&1) || \
			{ nxlog "$lp $2 ($3) share mount failed: $errstr"
				rmdir "$6" &>/dev/null; return 1; }
		nxlog "$lp $2 ($3) share mounted"
	;;
	smb-prn|ipp-prn)
		#nxlog "$lp add printer cmdstr='$cmdstr'" #debug
		errstr=$($cmdstr 2>&1) || \
			{ nxlog "$lp $2 ($3) printer installing failed: $errstr"; return 1; }
		# post-configure
		local public=$(getparam "$6" "public" "" ';')
		[ "$public" != "1" ] && {
		errstr=$($COMMAND_SUDO $COMMAND_LPADMIN -p $svc -u allow:$USER,guest,root 2>&1) || \
			{ nxlog "$lp $2 ($3) printer set permission failed: $errstr"; }
		}
		local defp=$(getparam "$6" "defaultprinter" "" ';')
		[ "$defp" = "1" ] && {
		errstr=$($COMMAND_SUDO $COMMAND_LPADMIN -d $svc 2>&1) || \
			{ nxlog "$lp $2 ($3) printer set to default failed: $errstr"; }
		}
		nxlog "$lp $2 ($3) printer installed"
	;;
	media-pa)
		if  ! $COMMAND_PA --check &>/dev/null; then
			$COMMAND_PA --start --exit-idle-time=-1 &>/dev/null || {
			#--log-target=file:$nx_dir/pa-$3.log --log-level=4 || { #debug
				nxlog "$lp '$svc' can't start local pulseaudio server";
				return 1; }
			# automatic null-sink will be disabled
			# unload unnecessary local modules here too
			local rmmods="module-always-sink module-rescue-streams module-systemd-login \
module-device-restore module-stream-restore module-card-restore \
module-default-device-restore module-switch-on-port-available \
module-udev-detect module-suspend-on-idle module-console-kit"
			local txt=$($COMMAND_PACTL list short 2>/dev/null)
			local midpat="([0-9]+)[[:blank:]]+" mid rmod
			for rmod in $rmmods; do
				mid=$(rematchfn "$midpat$rmod" "$txt") && {
					$COMMAND_PACTL unload-module $mid &>/dev/null
					#nxlog "$lp ! $mid" #debug
				}
			done
		else nxlog "$lp '$svc' local pulseaudio server already started" #debug
		fi
		case $2 in
		pa) # tunneled pa
			local rsink=$(cutfn "$cmdstr" 0) rsource=$(cutfn "$cmdstr" 1)
			local mname="module-tunnel-sink" opts="server=127.0.0.1:$3" oo args ok2=""
			oo=$(cutfn "$6" 1 '-'); [ -n "$oo" ] && opts+=" rate=$oo"
			oo=$(cutfn "$6" 2 '-'); [ -n "$oo" ] && opts+=" channels=$oo"
			[ "$oo" = "1" ] && opts+=" channel_map=mono"
			if [ "$rsink" != "(null)" ]; then
				args="sink_name=tcl_out sink=$rsink $opts"
				errstr=$($COMMAND_PACTL load-module $mname $args 2>&1)
				[ $? -eq 0 ] && ok="tcl_out" || \
					nxlog "$FUNCNAME ($$): $2 ($3) can't load $mname $args; '$errstr'"
				[ -n "$ok" ] && $COMMAND_PACTL set-default-sink "tcl_out" &>/dev/null
			else nxlog "$lp $2 ($3) can't load $name with rsink=$rsink"
			fi
			mname="module-tunnel-source"
			if [ "$rsource" != "(null)" ]; then
				args="source_name=tcl_in source=$rsource $opts"
				errstr=$($COMMAND_PACTL load-module $mname $args 2>&1)
				[ $? -eq 0 ] && ok2="tcl_in" || \
					nxlog "$FUNCNAME ($$): $2 ($3) can't load $mname $args; '$errstr'"
				[ -n "$ok2" ] && $COMMAND_PACTL set-default-source "tcl_in" &>/dev/null
			else nxlog "$lp $2 ($3) can't load $name with rsource=$rsource"
			fi
			if [ -n "$ok" -o  -n "$ok2" ]; then 
				nxlog "$lp $2 ($3) tunnel modules loaded: $ok $ok2"
			else rc=1
			fi
		;;
		esac
	;;
	esac
	return $rc
}

uservice_umount() {
#args: <type> <svc/mountpoint> [data] [port]
	local lp="$FUNCNAME ($$/$BASHPID):" errstr=""
	local i ok="" step="0.5" ct=4
	case $1 in
	smb-share)
		local mdir=$3 res ffl=""
		if stringinstring "//" "$2"; then res="$2" # svc
		elif ! stringinstring "/" "$2"; then res="//127.0.0.1/$2" # sharename
		else mdir="$2" res="$2" # dir
		fi
		#[ -z "$mdir" ] && { # get mount dir directly
		#	local txt=$(LC_ALL=C mount 2>/dev/null)
		#	pattern="$res""[[:blank:]]+on[[:blank:]]+([^[:blank:]]+)"
		#	mdir=$(remathfn "$pattern" "$txt")
		#	nxlog "$lp share $res: given empty mount dir, loaded mdir='$mdir' "
		#}
		for (( i=1; i<=ct; i++ )); do
			(( i>=ct/2 )) && ffl="-f"
			errstr+=$($COMMAND_SUDO $COMMAND_SMBUMOUNT $ffl "$res" 2>&1) && \
				{ ok="1"; break; }
			sleep $step"s"
		done
		[ -n "$ok" ] || \
			 { nxlog "$lp $res share umount failed: $errstr"; return 1; }
		[ -n "$mdir" ] && [ -d "$mdir" ] && rmdir "$mdir" &>/dev/null
		nxlog "$lp $res share umounted"
	;;
	smb-prn|ipp-prn)
		for (( i=1; i<=ct; i++ )); do
			errstr+=$($COMMAND_SUDO $COMMAND_LPADMIN -x "$2" 2>&1) && \
				{ ok="1"; break; }
			sleep $step"s"
		done
		[ -n "$ok" ] || \
			 { nxlog "$lp $2 printer deleting failed: $errstr"; return 1; }
		nxlog "$lp $2 printer deleted"
	;;
	media-pa)
		case $2 in
		pa) # tunneled pa
			local midpat="([0-9]+)[[:blank:]]+module-" uri="127.0.0.1"
			[ -n "$4" ] && uri+=":$4"
			txt=$(env LC_ALL=C $COMMAND_PACTL list short 2>/dev/null)
			[ -n "$txt" ] || {
				nxlog "$lp '$svc'; local PA already stopped";	return 0; }
			local mid es mids=$(rematchfn "$midpat.+server=$uri" "$txt" all);
			#nxlog "$lp '$2'; rmids: "$mids; #debug
			for mid in $mids; do
				es=$($COMMAND_PACTL unload-module $mid 2>&1)
				[ $? -ne 0 ] && errstr+="\n$es"
			done
			[ -n "$errstr" ] && {
				nxlog "$lp '$2'; unload some local PA modules failed: $errstr";
			}
			nxlog "$lp $2 remote tunnel disconnected"
			#nxlog "$lp $2 remote tunnel: $($COMMAND_PACTL list short 2>/dev/null)" #debug
		;;
		esac
		[ -n "$3" ] && {
			$COMMAND_PA --kill || {
				nxlog "$lp '$2'; unable to kill local PA";	return 0; }
		}
	;;
	esac
	return 0
}

uservice_start() {
#args:
# <svc> [port] [type] [sharename] [username] [password] [data] [comp]
# [addr=127.0.0.1]
# if type is empty try to operate params from usess db
#	if port not empty try to start on him
# Used config vars: COMMAND_HIDE COMMAND_UNHIDE
	local lp="$FUNCNAME ($$/$BASHPID):" errstr="" qs svcport
	local st startfl="" checkfl="" updvars="" updvals="" hpass
	local svc="$1"  port="$2" type="$3" share="$4"
	local username="$5" pass="$6" data="$7" comp="$8"
	local addr="$9"; [ -z "$addr" ] && addr="127.0.0.1";
	[ "$type" = "smb-share" -a -n "$data" ] && data=$(norm_dir "$data" $HOME)

	local i ok="" step="0.25" timeo="28" #4sec
	[ -n "$type" -a -z "$port" ] && {
		# starting no restarting - we wait for session listening port just in case
		local lport_name=$(sess_lport_name $type)
		local wstr="session_id='$session_id' AND $lport_name>0"
		for (( i=0; i<=timeo; i++ )); do
			port=$(q_vals_str_get "usess" "$wstr" "$lport_name") && break
			sleep $step"s"
		done
		[ -n "$port" ] || {
			nxlog "$lp $svc session $lport_name no declared after $((timeo/4)) s";
			return 1; }
	}

	# waiting for suitable service status: on/off/""
	ok="" step="0.25" timeo="28" #7sec
	for (( i=0; i<=timeo; i++ )); do
		st=$(usvcs_get $svc "status") || { ok="1"; break; }
		stringinstring "$st" "starting|stopping" || { ok="1"; break; }
		sleep $step"s"
	done
	[ -n "$ok" ] || {
		nxlog "$lp service $svc ($port) still set in \"$st\" state after $((timeo/4)) s";
		# FIXME!
		[ "$st" = "stopping" ] || return 1;
		nxlog "$lp service $svc ($port) set state 'off' ultimately";
		usvcs_set $svc "status" "off"; st="off"
	}

	[ -z "$type" -a -z "$st" ] && {
		nxlog "$lp '$svc': params for service are not found in usess db";
		return 1; }
	[ -z "$type" ] && { #load params from usess db
		qs="$(usvcs_get $svc "type,port,share,username,pass,data,comp,addr")" || {
			nxlog "$lp '$svc': can't get service params from usess db"; return 1; }
		type=$(cutfn "$qs" 0 '&');
		[ -z "$port" ] && port=$(cutfn "$qs" 1 '&');
		share=$(cutfn "$qs" 2 '&'); username=$(cutfn "$qs" 3 '&');
		pass=$(cutfn "$qs" 4 '&'); pass=$(echo "$pass" | $COMMAND_UNHIDE);
		data=$(cutfn "$qs" 5 '&'); comp=$(cutfn "$qs" 6 '&');
		addr=$(cutfn "$qs" 7 '&');
		#nxlog "$lp service $svc ($st) load qs='$qs'" #debug
	}

	if [ "$st" = "on" ]; then
		if uservice_mounted $type $svc; then
			nxlog "$lp service $svc is allready mounted, skipping"; return 0
		else
			nxlog "$lp $svc service status is \"$st\", but it's not mounted. Try to start again";
			startfl=1; [ -n "$type" ] && checkfl=1;
			st="starting"; usvcs_set $svc "status" $st
		fi
	else
		if uservice_mounted $type $svc; then
			usvcs_set $svc "status" "stopping"
			nxlog "$lp $svc service status is \"$st\", but it's mounted. Try to stop";
			uservice_umount $type $svc $data || {	return 1; }
		fi
		if [ "$st" = "off" ]; then
			startfl=1; [ -n "$type" ] && checkfl=1;
			st="starting"; usvcs_set $svc "status" $st
		else # svc is not found in usess table
			hpass=$(echo $pass | $COMMAND_HIDE)
			usvcs_add "svc,type,status,port,share,comp,addr,username,pass,data" \
				"$svc&$type&starting&$port&$share&$comp&$addr&$username&$hpass&$data"
			startfl=1;
		fi
	fi

	[ -n "$startfl" ] && {
		[ -n "$checkfl" ] && {
			local s_share s_username s_pass s_data s_comp s_addr
			qs="$(usvcs_get $svc "share,username,pass,data,comp,addr")" || {
				nxlog "$lp can't get service $svc params from usess _db"; return 1; }
			s_share=$(cutfn "$qs" 0 '&'); s_username=$(cutfn "$qs" 1 '&');
			s_pass=$(cutfn "$qs" 2 '&'); s_pass=$(echo $s_pass | $COMMAND_UNHIDE);
			s_data=$(cutfn "$qs" 3 '&'); s_comp=$(cutfn "$qs" 4 '&');
			s_addr=$(cutfn "$qs" 5 '&');
			[ "$share" != "$s_share" ] && {
				nxlog "$lp $svc share strings are different '$s_share' > '$share'"
				updvars+=",share"; updvals+="&$share"; }
			[ "$username" != "$s_username" ] && {
				nxlog "$lp $svc username strings are different '$s_username' > '$username'"
				updvars+=",username"; updvals+="&$username"; }
			[ "$pass" != "$s_pass" ] && {
				nxlog "$lp $svc password strings are different"
				hpass=$(echo $pass | $COMMAND_HIDE)
				updvars+=",pass"; updvals+="&$hpass"; }
			[ "$data" != "$s_data" ] && {
				nxlog "$lp $svc share strings are different '$s_data' > '$data'"
				updvars+=",data"; updvals+="&$data"; }
			[ "$comp" != "$s_comp" ] && {
				nxlog "$lp $svc comp strings are different '$s_comp' > '$comp'"
				updvars+=",comp"; updvals+="&$comp"; }
			[ "$addr" != "$s_addr" ] && {
				nxlog "$lp $svc addr strings are different '$s_addr' > '$addr'"
				updvars+=",addr"; updvals+="&$addr"; }

		}

		#nxlog "$lp _$st _$type _$svc _$port _$username _$pass _$data _$comp" _$share" #debug
		if uservice_mount $type $svc $port "$(echo -e "${username//\%/\\x}")" \
			 "$pass" "$data" "$(echo -e "${comp//\%/\\x}")" "$share";
		then
			usvcs_set $svc "status,port""$updvars" "on&"$port$updvals
			return 0
		else usvcs_set $svc "status,port" "off&0"
		fi
	}
	return 1
}

uservice_stop() {
#arg: svc [type] [norestart]
# Used gvars: session_id
	local lp="$FUNCNAME ($$/$BASHPID):"
	local i ok="" st type=$2 lport_name lport="" data="" hardstop=$3
	# waiting for suitable service status: on
	local step=0.25 timeo=28 #7sec
	for (( i=0; i<=timeo; i++ )); do
		st=$(usvcs_get $svc "status")
		[ "$st" = "on" ] && { ok="1"; break; }
		sleep $step"s"
	done
	[ -n "$ok" ] || {
		nxlog "$lp service $svc still set in \"$st\" state after $((timeo/4))s waitng";
		return 1; }
	[ -z "$type" ] && type=$(usvcs_get $svc "type")
	usvcs_set $svc "status" "stopping"
	[ -n "$hardstop" ] || { # get suitable session first
		lport_name=$(sess_lport_name $type)
		local wstr="status='Running' AND session_id!='$session_id' AND $lport_name>0"
		#nxlog "$lp $svc wstr='$wstr'" #debug
		lport=$(q_vals_str_get "usess" "$wstr" "$lport_name") || {
			hardstop=1
			nxlog "$lp '$svc': other suitable listening port is not found" #; wstr='$wstr'" #debug
		}
	}
	[ -n "$hardstop" ] && { # if unable to restart we must known data
		data=$(usvcs_get $svc "data"); }
	#nxlog "$lp '$svc': hs=$hardstop; data=$data" #debug
	uservice_umount $type $svc "$data" "$port" || return 1
	[ -n "$hardstop" ] && {
		if [ "$SESSION_LOG_CLEAN" = "1" ]; then
			q_dbe "DELETE FROM usvcs WHERE svc='$1';"
		else usvcs_set $1 "status,port" "off&0"
		fi
		return 0;
	}
	usvcs_set $svc "status,port" "off&0"
	uservice_start $svc $lport #|| return 1
	return 0
}

node_stop_services() {
# Used gvars: session_id
	#local lp="$FUNCNAME ($$/$BASHPID):"
	local lports=",$(usess_get $session_id "smbport,cupsport,mmport" ',')"
	lports=${lports//,0/}; lports=${lports:1}
	[ -n "$lports" ] || return # no services in session
	local svc svcs
	svcs=$(q_vals_strs_get "usvcs" "status='on' AND port IN ($lports)" "svc") #"
	#nxlog "$lp lports=($lports) services list to stop: '$svcs'"; #debug
	for svc in $svcs; do uservice_stop $svc; done
}

node_terminate_session() {
#args: <session_id> [status]
# Used gvars: nx_dir
# Used config vars: COMMAND_XAUTH, SESSION_LOG_CLEAN SERVER_NAME
	local lp="$FUNCNAME ($$/$BASHPID):";
	#nxlog "$lp Start terminating session_id='$1' with status '$2'" #debug
	local qs=$(usess_get "$1" "display,agent_pid,tail_pid,status")
	[ -z "$qs" ] && {
		nxlog "$lp session_id='$1' not found in usess db. Bye."; return;
	}
	local status=$(cutfn "$qs" 3 '&')
	stringinstring "$status" "Terminating|Finished|Failed" && {
		nxlog "$lp Session status is already '$status'. Bye."; return;
	}
	local display=$(cutfn "$qs" 0 '&'); local sess_id="$SERVER_NAME-$display-$1"
	[ -d "$nx_dir/C-$sess_id" ] || {
		nxlog "$lp Session dir '$nx_dir/C-$sess_id' not found. Bye."; return;
	}
	usess_set "$1" "status" "Terminating"

	local agent_pid=$(cutfn "$qs" 1 '&') tail_pid=$(cutfn "$qs" 2 '&')
	local t_status="$2"; [ -z "$2" ] && t_status="Finished"
	node_stop_services
	kill -0 $agent_pid 2>/dev/null && {
		#nxlog "$lp start killing of nxagent ($agent_pid)" #debug
		kill $agent_pid 2>/dev/null
		wait $agent_pid 2>/dev/null
		kill -0 $agent_pid 2>/dev/null ||
			nxlog "$lp nxagent ($agent_pid) is dead now"
	}
	((tail_pid>0)) && kill -0 $tail_pid 2>/dev/null && { # Kill tail process
		#nxlog "$lp kill tail process ($tail_pid)" #debug
		kill $tail_pid 2>/dev/null
		wait $tail_pid 2>/dev/null
		kill -0 $tail_pid 2>/dev/null || {
			nxlog "$lp tail ($tail_pid) is dead now"; tail_pid="0"
			usess_set "$session_id" "tail_pid" "0"
		}
	}

	#nxlog "$lp Remove session information" #debug
	rm -f /tmp/.X$display-lock; rm -f /tmp/.X11-unix/X$display
	# Remove magic cookie information
	$COMMAND_XAUTH remove "localhost:$display" >/dev/null 2>&1
	$COMMAND_XAUTH remove ":$display" >/dev/null 2>&1
	if [ "$SESSION_LOG_CLEAN" = "1" ]; then
		#nxlog "$lp Clean session information." #debug
		rm -rf "$nx_dir/C-$sess_id/"
		rm -f  "$nx_dir/nxnode-$1.log"
		rm -f  "$nx_dir/nxnode.log"
	elif [ "$2" = "Failed" ]; then mv "$nx_dir/C-$sess_id/" "$nx_dir/F-C-$sess_id"
	else mv "$nx_dir/C-$sess_id/" "$nx_dir/T-C-$sess_id"
	fi
	usess_close "$1" "$t_status"
	#nxlog "$lp end" #debug
}

node_fail_restore_session() {
#arg:  <session_id>
	#local lp="$FUNCNAME ($$/$BASHPID):"; nxlog "$lp starting" #debug
	echo "NX> 1004 Error: Could not resume session. nxagent process could not be found."
	node_terminate_session "$1" "Failed"
	#nxlog "$lp end. Next is 'exit 1'" #debug
	exit_proc 1
}

node_suspend_session() {
#arg:  <session_id>
	local lp="$FUNCNAME ($$/$BASHPID):";
	#nxlog "$lp starting" #debug
	local agent_pid=$(usess_get "$1" "agent_pid")
	nxlog "$lp Killing (HUP) agent_pid ($agent_pid)..."
	kill -0 $agent_pid 2>/dev/null || {
		nxlog "$lp nxagent is already dead. end (1)"; return 1;
	}
	kill -HUP $agent_pid 2>/dev/null && {
		nxlog "$lp end (HUP)"; return 0;
	}
	return 1
}

node_find_application() {
#args: <type>
# Used config vars: $COMMAND_START_KDE, $COMMAND_START_GNOME,
# $COMMAND_START_CDE, $COMMAND_XTERM, $USER_X_STARTUP_SCRIPT,$DEFAULT_X_SESSION
	local lp="$FUNCNAME ($$/$BASHPID):";
	#nxlog "$lp starting with args \"$@\"" #debug
	local node_startx=""
	case $1 in
		shadow|windows|vnc) return
		;;
		unix-kde) node_startx=$COMMAND_START_KDE
		;;
		unix-gnome) node_startx=$COMMAND_START_GNOME
		;;
		unix-cde) node_startx=$COMMAND_START_CDE
		;;
		windows-helper)
			node_startx="$COMMAND_RDESKTOP /v:$agent_server /u:$agent_user"
			[ -n "$agent_domain" ] && node_startx+=" /d:$agent_domain"
			node_startx+=" /t:NX-$session-RDP-$agent_server@$agent_user"
			node_startx+=" /p:$agent_password /size:$geometry $EXTRA_OPTIONS_RDP"
		;;
		vnc-helper)
			if [ ! -x "$COMMAND_VNCVIEWER" ]; then
				echo "$COMMAND_XMSG 'vncviwer not found'"
			else
				mkdir -p "$NXSESSION_DIRECTORY/scripts/"
				echo "$agent_password" | \
					$COMMAND_VNCPASSWD $NXSESSION_DIRECTORY/scripts/.passwd doit
				node_startx="$COMMAND_VNCVIEWER -passwd \
 $NXSESSION_DIRECTORY/scripts/.passwd $EXTRA_OPTIONS_RFB $agent_server"
 			fi
		;;
		unix-application)
			[ "$application" = "xterm" ] && application=$COMMAND_XTERM
			node_startx=$application
		;;
		unix-console) node_startx=$COMMAND_XTERM
		;;
		unix-default|*)
			if [ -x "$HOME/$USER_X_STARTUP_SCRIPT" ]; then
				node_startx="$HOME/$USER_X_STARTUP_SCRIPT"
			elif [ -x "$DEFAULT_X_SESSION" ]; then
				node_startx="$DEFAULT_X_SESSION"
			else node_startx=$COMMAND_XTERM
			fi
		;;
	esac

	# dimbor: another personalyzed way to ACLS control and replace X-application
	[ -n "$NX_ACL_DIR"  ] || { echo "$node_startx"; return; }

	local ucond=$(groups); ucond="'#$USER','*${ucond// /\',\'\*}','@all'"
	local mode=".mode csv settings\n.separator '&'\n"
	local qstr="SELECT key,value,val_depend FROM settings WHERE user IN ($ucond) \
 AND key NOT IN ('@shadow@') ORDER BY user ASC,	val_check ASC;"
	#nxlog "$lp qstr='$qstr'" #debug
	local ts=$(qa_dbe "$mode" "$qstr")
	#nxlog "$lp ts='$ts'" #debug
	local l tpl inv pm l2="" app_match="" app_rep="$COMMAND_XMSG '$NX_ACL_WARN'"
	while read l; do
		tpl="${l%%&*}"; tpl="$(sq2s "$tpl")"
		inv=0; [ "${tpl:0:1}" = "!" ] && { tpl="${tpl:1}"; inv=1; }
		pm=0; [[ "$node_startx" =~ $tpl ]] && pm=1
		#nxlog "$lp $node_startx -> \"$l\" -> $tpl $inv $pm" #debug
		((inv+pm==1)) && { l2=$l; app_match=1; break; }
	done <<< "$ts"
	if [ -n "$app_match" ]; then
		tpl=$(cutfn "$l2" 1 '&'); tpl=$(sq2s "$tpl")
		[ -n "$tpl" ] && { # process list checking
			app_match=""
			inv=0; [ "${tpl:0:1}" = "!" ] && { tpl="${tpl:1}"; inv=1; }
			local psall=""; [ "${tpl:0:2}" = "@@" ] && { tpl="${tpl:2}"; psall=1; }
			local psl;
			if [ -n "$psall" ]; then psl="$(ps ax -o cmd=)"
			else psl="$(ps -o cmd= -U $USER)"
			fi
			pm=0; rematchfn "($tpl)" "$psl" &>/dev/null && pm=1
			((inv+pm==1)) && app_match=1
			#nxlog "$lp $l - $tpl $inv $pm" #debug
		}
		local msg=$(cutfn "$l2" 2 '&'); msg=$(sq2s "$msg")
		[ -n "$msg" ] && { # exe/msg checking
			# need absolute path here?
			[ -x "${msg%% *}" ] && app_rep="$msg" || app_rep="$COMMAND_XMSG '$msg'"
		}
	fi
	if [ -n "$app_match" ]; then
		echo "$node_startx";
	else
		nxlog "$lp App '$node_startx' replaced to '$app_rep'"
		echo "$app_rep"
	fi
}

node_start_applications() {
# Used glob vars: $type, $application, $sess_id, $mediahelper,
# $virtualdesktop, $rootless, $display
# Used config vars: <several>
	local lp="$FUNCNAME ($$/$BASHPID):";
	#nxlog "$lp starting" #debug
	local app node_app napp;
	local slave_apps psess sess_ps sapp;
	local node_app_pid node_wm_pid;
	local ok step timeo
	local q=0 i s l

	# Prepare application startup
	export DISPLAY=:$display

	#nxlog "$lp display='$display', waiting for it's ready" #debug
	ok="" step="0.01" timeo=$((AGENT_STARTUP_TIMEOUT*100))
	for (( i=0; i<=timeo; i++ )); do
		[ -f /tmp/.X$display-lock ] && { ok="1"; break; }
		sleep $step"s"
	done
	[ -n "$ok" ] || {
		nxlog "$lp /tmp/.X$display-lock not found after $AGENT_STARTUP_TIMEOUT s";
		return 1; }

	#numlockx
	if [ "$NUMLOCK_METHOD" != "system" ]; then
		#nxlog "$lp Run \"$NUMLOCKX $NUMLOCKX_STATUS\"" #debug
		"$NUMLOCKX" "$NUMLOCKX_STATUS"
	fi

	# Which application do we start?
	app=$(node_find_application "$type")
	# For rdesktop/VNC, there is no application to start
	[ -n "$app" ] || return

	if [ "$ENABLE_SAMBA_PRELOAD" = "1" ]; then
		NXSAMBA_PORT=$(usess_get "$session_id" "smbport")
		((NXSAMBA_PORT>0)) && {
			export NXSAMBA_PORT
			#nxlog "$lp Preload SAMBA using nxredir. NXSAMBA_PORT is '$NXSAMBA_PORT'" #debug
			node_app="$PATH_BIN/nxredir $node_app"
			echo "Info: NXNODE - Using nxredir wrapper script to forward \
SMB ports 139 and 445 to port $NXSAMBA_PORT." >> "$NXSESSION_DIRECTORY/session"
		}
	fi
	#nxlog "$lp app is $app" #debug

	# do fake eval for params
	q=0; l=$app; node_app=()
	while [ -n "$l" ]; do
		s=${l%%[\'\"]*}; l=${l#*[\'\"]}
		[ "$s" = "$l" ] && l=""
		[ -n "$s" ] || continue
		((q==0)) && { node_app+=($s); q=1; } || { node_app+=("$s"); q=0; }
	done
	#nxlog "$lp ${#node_app[@]}" #debug
	#for ((i=0; i<${#node_app[@]}; i++)); do nxlog "$lp ${node_app[$i]}"; done #debug

	[ "$cups" = "1" -o "$samba" = "1" ] && {
		#nxlog "$lp export CUPS_SERVER=$CUPS_DEFAULT_SOCK" #debug
		export CUPS_SERVER=$CUPS_DEFAULT_SOCK
	}

	# Use Xsession to execute the Desktop session
	case $type in
		unix-gnome)
			export STARTUP="${node_app[@]}"
			[ "$BOOTSTRAP_X_SESSION" = "1" ] && node_app=($COMMAND_GDM_X_SESSION)
		;;
		unix-kde|unix-cde)
			export STARTUP="${node_app[@]}"
			[ "$BOOTSTRAP_X_SESSION" = "1" ] && node_app=($DEFAULT_X_SESSION)
		;;
	esac

	[ $ENABLE_ROOTLESS_TERMINATE_SESSION = "1" -a "$rootless" = "1" ] && {
		psess=$($COMMAND_PS -wo sess= -p $$)
		napp=${node_app[0]}; napp=${napp##*/}
		slave_apps=$(rematchfn "$napp:(.+)" "${APP_WAIT_MAP//;/$'\n'}") #"
		#nxlog "$lp slave(s) will be waiting too. Initial apps: '$slave_apps'" #debug
	}

	# Do we need to PRELOAD any libraries?
	[ "$virtualdesktop" = "0" -a "$rootless" != "1" ] && export LD_PRELOAD="$APPLICATION_LIBRARY_PRELOAD:$LD_PRELOAD"

	# close input and output file descriptors
	exec 0<&-; exec 1>&-; exec 2>&-

	# Should we start a window manager?
	if [ "$virtualdesktop" = "1" -a "$type" = "unix-application" -a \
			-x "$DEFAULT_X_WM" ]; then
		#nxlog "$lp start a window manager - \"DISPLAY=:$display $DEFAULT_X_WM\"" #debug
		DISPLAY=:$display $DEFAULT_X_WM >>"$NXSESSION_DIRECTORY/session" 2>&1 &
		node_wm_pid=$!
		#nxlog "$lp node_wm_pid='$node_wm_pid'" #debug
	fi

	# Startup the application
	#nxlog "$lp Starting node_app with /etc/nxserver/Xsession" #debug
	DISPLAY=:$display /etc/nxserver/Xsession "${node_app[@]}" >> \
		"$NXSESSION_DIRECTORY/session" 2>&1 &
	node_app_pid=$!
	nxlog "$lp Start '${node_app[@]}'. Waiting for node_app_pid='$node_app_pid'"
	wait $node_app_pid
	nxlog "$lp node_app_pid finished"

	# Kill or wait for the started window manager
	[ -n "$node_wm_pid" ] && {
		nxlog "$lp node_wm_pid is not empty"
		# kill the WM after application is finished?
		[ "$KILL_DEFAULT_X_WM" = "1" ] && { nxlog "$lp killing $node_wm_pid"
			kill $node_wm_pid 2>/dev/null; }
		# or just wait until it finishes?
		[ "$KILL_DEFAULT_X_WM" = "1" ] || { nxlog "$lp wait for $node_wm_pid is dead"
			wait $node_wm_pid; }
	}
	sleep "$NODE_APP_WAIT_TIMEOUT"s

	[ $ENABLE_ROOTLESS_TERMINATE_SESSION = "1" -a "$rootless" = "1"  ] && {
		if [ -n "$slave_apps" ] ; then
			nxlog "$lp slave(s) will be waiting too. Initial: '$slave_apps'"
			sapp=$napp
			while [ -n "$sapp" ]; do
				sess_ps=$($COMMAND_PS -wo user=,cmd= -s $psess); sapp=""
				#nxlog "$lp '$sess_ps'" #debug
				while read l; do
					[ "$(cutfn "$l" 0)" = "$USER" ] || continue
					s=$(cutfn "$l" 1); s=${s##*/}
					stringinstring "$s," "$slave_apps," && {
						sapp=$s;
						#nxlog "$lp >> $sapp '$l'" #debug
						break;
					}
				done <<< "$sess_ps"
				sleep "$NODE_APP_WAIT_TIMEOUT"s
			done
			nxlog "$lp Session app(s) are finished"
		fi
	  node_terminate_session "$session_id"
	}

	# Do not terminate agent in case of rootless agent mode.
	# The agent times out after a while by itself anyway.
	if [ "$virtualdesktop" = "1" -o "$rootless" != "1" ] ; then
		#nxlog "$lp Call node_terminate_session for non-rootless or virtualdesktop session type" #debug
		node_terminate_session "$session_id"
	fi
	#nxlog "$lp end" #debug
}

node_agent_persistent_session() {
# Is the user allowed to run a persistent session?
	local username IFS="," p="-nopersistent"
	[ "$ENABLE_PERSISTENT_SESSION" = "all" ] && p="-persistent" || {
		for username in $ENABLE_PERSISTENT_SESSION; do
			[ "${username:0:1}" != "@" ] && [ "$USER" = "$username" ] && \
				p="-persistent" && break;
			[ "${username:0:1}" = "@" ] && \
				[ -z $(groups "$USER" | egrep "^${username:1}:") ] && \
					p="-persistent" && break;
		done
	}
	for username in $DISABLE_PERSISTENT_SESSION; do
		[ "${username:0:1}" != "@" ] && [ "$USER" = "$username" ] && \
			p="-nopersistent" && break;
		[ "${username:0:1}" = "@" ] && \
			[ -z $(groups "$USER" | egrep "^${username:1}:") ] && \
				p="-nopersistent" && break;
	done
	echo "$p"
}

node_start_agent() {
# Ok, now we do some wicked fd magic.
# first part:	nxagent's fd #2 -> fd #3
# second part:	fd #1 -> #4; fd #3 -> #1; tee | node_start_monitor
# third part:	fd #4 -> #1
# => all output of nxagent goes to tee | node_start_monitor, while
#    leaving all other output flow through like normally.

	# preparations
	local lp="$FUNCNAME ($$/$BASHPID):";
	local k g b r fp vncfullscreen u p d agent_port viewonly
	local agent_exit_status node_failed
	#nxlog "$lp starting" #debug
	exec 3>&2; exec 4>&1;

	{

	{

	export DISPLAY="nx/nx,options=$NXSESSION_DIRECTORY/options:$display"
	export XAUTHORITY="$NXSESSION_DIRECTORY/authority"
	export HOME
	export NX_CLIENT="$PATH_BIN/nxdialog"

	# Setup optional parameters for nxagent
	# keyboard
	k=""
	# backwards compatibility
	[ -n "$keyboard" ] && k="-keyboard $keyboard"
	[ -n "$kbtype" ] && k="-kbtype $kbtype"
	# backingstore
	b=""
	if [ -n "$backingstore" ]; then
		[ "$backingstore" != 1 ] && b="-bs $backingstore"
		[ "$backingstore" = 1 ] && b="+bs"
	fi

	# geometry
	g=""
	[ -n "$geometry" ] && g="-geometry $geometry"

	# type of session
	r="-D"; [ "$rootless" = "1" ] && r="-R"

	# Setup fullscreen parameters
	[ "$geometry" = "fullscreen" ] && \
		[ "$type" = "vnc-helper" -o "$type" = "windows-helper" ] && \
		g="-geometry $(rematchfn '^([[:digit:]]+x[[:digit:]]+)' $screeninfo)"

	if [ "$r" = "-R" -a "$rootless" != "1" ]; 	then
		#nxlog "$lp Start nxproxy for single application session mode" #debug
		[ "$SET_LD_LIBRARY_PATH" = "1" ] && \
			export LD_LIBRARY_PATH="$PROXY_LIBRARY_PATH:$LD_LIBRARY_PATH"
		nxlog "$lp Start nxproxy by command: '$PATH_BIN/nxproxy -C :$display $PROXY_EXTRA_OPTIONS'"
		$PATH_BIN/nxproxy -C :$display $PROXY_EXTRA_OPTIONS 2>&3 &
	else
		#nxlog "$lp nxagent session type (X11)" #debug
		[ "$SET_LD_LIBRARY_PATH" = "1" ] && \
			export LD_LIBRARY_PATH="$AGENT_LIBRARY_PATH:$LD_LIBRARY_PATH"
		# Setup optional parameters
		p=$(node_agent_persistent_session)
		fp=""; [ -n "$AGENT_FONT_SERVER" ] && fp="-fp $AGENT_FONT_SERVER"
		if [ "$type" = "shadow" ]; then
			nxlog "$lp Type \"shadow\". Add some args to nxagent"
			local shmode=$ENABLE_INTERACTIVE_SESSION_SHADOWING
			[ "$shmode" = "1" ] && [ "$shadowviewonly" = "1" ] && shmode="0"
			r="-S -shadow $shadowhost:$shadowdisplay -shadowmode $shmode"
			p="-nopersistent"
		fi
		# Start the agent
		#nxlog "$lp env start `env`"; nxlog "$lp env end"
		nxlog "$lp Start nxagent by command: '$COMMAND_NXAGENT $p $r -name \"NX - $user@$SERVER_NAME:$display - $session (GPL Edition)\" -option \"$NXSESSION_DIRECTORY/options\" $b $fp $AGENT_EXTRA_OPTIONS_X :$display'"
		#PATH="$PATH_BIN:$PATH" $COMMAND_NXAGENT $p $r -name "NX - $user@$SERVER_NAME:$display - $session (GPL Edition)" -option "$NXSESSION_DIRECTORY/options" $k $g $b $fp $AGENT_EXTRA_OPTIONS_X :$display 2>&3 &
		PATH="$PATH_BIN:$PATH" $COMMAND_NXAGENT $p $r \
			-name "NX - $user@$SERVER_NAME:$display - $session (GPL Edition)" \
			-option "$NXSESSION_DIRECTORY/options" $b $fp \
			$AGENT_EXTRA_OPTIONS_X :$display 2>&3 &
	fi

	# Wait for the agent
	agent_pid=$!
	usess_set "$session_id" "agent_pid" "$agent_pid"
	echo "NX> 733 Agent pid: $agent_pid:"
	nxlog "$lp Waiting for agent_pid='$agent_pid'"
	wait $agent_pid; agent_exit_status=$?
	nxlog "$lp agent_exit_status='$agent_exit_status'"
	node_failed=""
	if [ $agent_exit_status -ne 0 ]; then
		echo "NX> 1004 Error: NX Agent exited with exit status $agent_exit_status. To troubleshoot set SESSION_LOG_CLEAN=0 in node.conf and investigate \"$nx_dir/F-C-$sess_id/session\". You might also want to try: ssh -X myserver; $PATH_BIN/nxnode --agent to test the basic functionality. Session log follows:"
		echo "$(< $NXSESSION_DIRECTORY/session)" >&2
		node_failed="Failed"
		nxlog "$lp node_failed='$node_failed'"
	fi
	#nxlog "$lp close session" #debug
	echo "NX> 1006 Session status: closed"
	# Cleanup session information
	#nxlog "$lp cleanup session information '$sess_id'" #debug
	#nxlog "$lp call 'node_terminate_session \"$session_id\" \"$node_failed\"'" #debug
	node_terminate_session "$session_id" "$node_failed"

	# remove possible leftovers of nxagent
	#nxlog "$FUNCNAME ($$):remove /tmp/.X$display-lock" #debug
	rm -f /tmp/.X$display-lock
	#nxlog "$lp remove /tmp/.X11-unix/X$display" #debug
	rm -f /tmp/.X11-unix/X$display

	} 3>&1 1>&4 | tee "$NXSESSION_DIRECTORY/session" | \
		node_start_monitor; } 4>&1
}


node_emergency_exit() {
	local lp="$FUNCNAME ($$/$BASHPID):";
	#nxlog "$lp starting" #debug
	#nxlog "$lp call 'node_terminate_session \"$session_id\" \"Failed\"'" #debug
	node_terminate_session "$session_id" "Failed"
	echo "NX> 1004 Error: Emergency exit due to kill signal."
	#nxlog "$lp end" #debug
}

node_start_monitor() {
#arg: <start|restore>
# Monitoring the nxagent: Its also kind of a "state-machine"
#                         as it has to keep track of different
#                         connection states and react differently.
	local lp="$FUNCNAME ($$/$BASHPID):";
	#nxlog "$lp starting with arg: $@" #debug
	local tail_pid="" watchdog_pid tosend pars_sent=""
	local smbport="0" mmport="0" cupsport="0"

	while read line; do
		case "$line" in
			*"Info: Waiting for connection from"*)
				[ -n "$pars_sent" ] && continue; # send params only once
				tosend="NX> 700 Session id: $sess_id
NX> 705 Session display: $display\nNX> 703 Session type: $type
NX> 701 Proxy cookie: $cookie\nNX> 702 Proxy IP: $proxyip
NX> 706 Agent cookie: $cookie\nNX> 704 Session cache: $type
NX> 707 SSL tunneling: $encryption\n"
				# File-sharing port options
				[ "$samba" = "1" ] &&
					tosend+="NX> 709 File-sharing port: 445\n"
				echo -e "$tosend""NX> 710 Session status: running
NX> 1002 Commit\nNX> 1006 Session status: running"
				pars_sent="1"; continue;
			;;
			*"Info: Listening"*"SMB connections on port"*)
				# Catch NXAGENT SMB Port (sometimes the port differs from what we got from nxserver)
				smbport=$(cutfn "$line" 1 "'"); smbport=${smbport##*:}
				usess_set "$session_id" "smbport" "$smbport"
				continue;
			;;
			*"Info: Listening"*"multimedia connections on port"*)
				# Catch NXAGENT Multimedia Port
				mmport=$(cutfn "$line" 1 "'"); mmport=${mmport##*:}
				usess_set "$session_id" "mmport" "$mmport"
				continue;
			;;
			*"Info: Listening"*"CUPS connections on port"*)
				# Catch NXAGENT CUPS Port
				cupsport=$(cutfn "$line" 1 "'"); cupsport=${cupsport##*:}
				usess_set "$session_id" "cupsport" "$cupsport"
				continue;
			;;
			*"Info: Watchdog running with pid"*)
				# Watchdog termination
				watchdog_pid=$(cutfn "$line" 1 "'")
				continue;
			;;
			*"Info: Waiting the watchdog process to complete"*)
				# Kill the watchdog
				kill $watchdog_pid 2>/dev/null
				continue;
			;;
		esac

		if [ "$1" != "restore" ]; then # "start" instance
			case "$line" in
				*"Session: Starting session at"*)
					echo "NX> 1009 Session status: starting"
					usess_set "$session_id" "status" "Starting"
				;;
				*"Session: Session started at"*)
					usess_set "$session_id" "status" "Running"
				;;
				*"Session: Suspending session at"*)
					echo "NX> 1009 Session status: suspending"
					usess_set "$session_id" "status" "Suspending"
				;;
				*"Session: Terminating session at"*)
					echo "NX> 1009 Session status: terminating"
				;;
				*"Session: Session suspended at"*)
					# Session suspend
					echo "NX> 1005 Session status: suspended"
					#nxlog "$lp $line" #debug
					((mmport+smbport+cupsport>0)) && {
						#nxlog "$lp call node_stop_services" #debug
						node_stop_services
					}
					usess_set "$session_id" "status,userip,acc_ip" "Suspended&&"
				;;
			esac
		else # "restore" instance
			#nxlog "$lp nxagent output: $line"
			case "$line" in
				*"Info: tail -f running with pid"*)
					# Catch tail pid
					tail_pid=$(cutfn "$line" 1 "'")
					usess_set "$session_id" "tail_pid" "$tail_pid"
					#echo "$node_tail_pid" >"$nx_dir/C-$sess_id/pids/tail"
				;;
				*"Session: Resuming session at"*)
					echo "NX> 1009 Session status: resuming"
					usess_set "$session_id" "status" "Resuming"
				;;
				*"Session: Session resumed at"*)
					# Reconnection success!
					echo "NX> 718 Session restore succeded"
					usess_set "$session_id" "status,userip,acc_ip"\
						 "Running&$userip&$accept"
					kill $tail_pid 2>/dev/null; break
				;;
				*"Session: Display failure detected at"*)
					# Reconnection failure
					echo "NX> 596 Error: Session $1 failed. Reason was: $line"
					kill $tail_pid 2>/dev/null; break
				;;
			esac
		fi
	done

	trap "" EXIT

	[ "$1" != "restore" -a "$rootless" != "1" ] && {
		nxlog "$lp call node_stop_services at ending"
		node_stop_services;
	}
	# close all open file descriptors
	exec 0<&-; exec 1>&-; exec 2>&-;
	#nxlog "$lp end" #debug
	exit_proc 0
}

startsession() { # Start a new session.
# 1.5.0 options: rdpcolors,rdpcache,http
# nxclient > 1.5.0-106 variables: resize,keybd
# FreeNX specific variables: clientproto,status,host
# NX 3.0 shadow mode related variables: shadowusername,shadowcookie,
# shadowdisplay,shadowhost
# dimbor: additional extra-channels extra[1-3], patched nxcomp both
# on server and client are required

	local lp="$FUNCNAME ($$/$BASHPID):";
	local opt_vars opt_str pack cleanup product clipboard menu;
	local id fullscreen accept vn;
	local i ok step timeo
	#nxlog "$lp starting with args \"$@\"" #debug

	[ "$1" = "start" ] && mkdir -p -m700 "$NXSESSION_DIRECTORY"
	# Setup environment
	[ -n "$SOURCE_SYS_PROFILE" ] && . $SOURCE_SYS_PROFILE
	[ -n "$SOURCE_USER_PROFILE" ] && {
		if [ "${SOURCE_USER_PROFILE:0:1}" != "/" ]; then
			. $HOME/$SOURCE_USER_PROFILE
		else . $SOURCE_USER_PROFILE
		fi
	}

	[ "$PROXY_TCP_NODELAY" = "0" ] && nodelay=0
	[ "$ENABLE_ROOTLESS_MODE" = "0" ] && rootless=0
	[ -z "$samba" ] && samba=0; [ -z "$media" ] && media=0
	[ -z "$shmem" ] && shmem=0; [ -z "$shpix" ] && shpix=0
	[ "$geometry" = "fullscreen" ] && fullscreen="1" || fullscreen="0"
	[ -z "$nodelay" ] && nodelay=1 # ???
	cleanup=10; product=LFE/None/LFEN/None; id=$sess_id;
	clipboard="$ENABLE_CLIPBOARD"; menu="$ENABLE_PULLDOWN_MENU"
	windows_app=$application
	[ -z "$keybd" ] && keybd=$aux # backwards compatibility for keybd parameter

	[ "$EXPORT_USERIP" = "1" ] && export NXUSERIP="$userip"
	[ "$EXPORT_SESSIONID" = "1" ] && export NXSESSIONID="$sess_id"
	export SHADOW_XAUTHORITY="$NXSESSION_DIRECTORY/authority"
	if [ "$type" = "vnc-helper" -o  "$type" = "windows-helper" ]; then
		# We do not want to suspend such a session
		# as RDP/RFB are both suspendable as well
		ENABLE_PERSISTENT_SESSION=""
	fi

	if [ "$encryption" = "1" ]; then
		# we need to use the IP of the "calling" server now
		accept=$(rematchfn "($ip4_pattern)" "$SSH_CLIENT $SSH2_CLIENT") #"
		# If host is the same, use 127.0.0.1, else fallback to default
		[ -z "$accept" -a "$host" = "127.0.0.1" ] && accept="127.0.0.1"
	else encryption=0; accept=$userip
	fi

	# We need our own external IP
	proxyip="$EXTERNAL_PROXY_IP"; [ -z "$proxyip" ] && proxyip="127.0.0.1"

	pack=""
	if [ -z "$imagecompressionlevel" ]; then imagecompressionlevel="9"
	elif [ "$imagecompressionmethod" = "0" ]; then pack="nopack"
	elif [ "$imagecompressionmethod" = "1" ]; then pack="16m-jpeg-$imagecompressionlevel"
	elif [ "$imagecompressionmethod" = "2" ]; then pack="16m-png-9"
	fi

	if [ "$1" = "start" ]; then
		cookie=$(echo $[$RANDOM*$RANDOM] | $COMMAND_MD5SUM); cookie=${cookie%% *}
		# add row to usses with defaults: session_id, status, display, type,
		# client, agent_pid, cookie, tail_pid, userip, acc_ip, mmport, cupsport,\
		# smbport
		usess_add "$sqcols_usess" "$session_id&Starting&$display&$type&$client&\
0&$cookie&0&$userip&$accept&0&0&0&"
	elif [ "$1" = "restore" ]; then
		cookie=$(usess_get "$session_id" "cookie")
	fi

	if [ "$1" = "application" ]; then
		# This needs to be set, else nxagent is terminated
		rootless="1"; virtualdesktop="0"
		#nxlog "$lp call 'node_start_applications'" #debug
		node_start_applications &
		echo "NX> 596 Application $application started successfully."
		return
	fi

	#nxlog "$lp generate \"$NXSESSION_DIRECTORY/options\"" #debug
	opt_vars="keyboard kbtype kbload keymap geometry\
 client resize cache images pack link nodelay type clipboard composite\
 cleanup product shmem backingstore shpix accept cookie id samba media\
 sync cups keybd aux http extra1 extra2 extra3 rdpcolors rdpcache\
 fullscreen menu"
	opt_str="nx/nx";
	for vn in $opt_vars; do [ -n "${!vn}" ] && opt_str+=",$vn=${!vn}"; done
	#[ "$type" = "shadow" ] && opt_str+=",shadow=1"
	opt_str+=":$display"
	# write options file
	umask 0077;	echo "$opt_str" > "$NXSESSION_DIRECTORY/options"; umask $umask0

	if [ "$1" = "start" ]; then # write xauth script file
		#nxlog "$lp write xauth script file" #debug
		txt="add localhost:$display MIT-MAGIC-COOKIE-1 $cookie
add :$display MIT-MAGIC-COOKIE-1 $cookie
exit"
		echo "$txt" | $COMMAND_XAUTH >/dev/null 2>&1
		echo "$txt" | $COMMAND_XAUTH -f "$NXSESSION_DIRECTORY/authority" >/dev/null 2>&1
	fi

	if [ -n "$shadowcookie" ]; then
		#nxlog "$lp If we have a shadow cookie, we add it to xauth session authority file as well" #debug
		$COMMAND_XAUTH -f "$SHADOW_XAUTHORITY" add "$shadowhost:$shadowdisplay" MIT-MAGIC-COOKIE-1 "$shadowcookie"
	elif [ -n "$shadowdisplay" ]; then
		# we need to merge in the normal .Xauthority file
		#nxlog "$lp we need to merge in the normal .Xauthority file" #debug
		$COMMAND_XAUTH -f "$SHADOW_XAUTHORITY" merge "$HOME/.Xauthority"
	fi

	if [ "$1" = "restore" ]; then
		#nxlog "$lp restore session" #debug
		#echo > "$NXSESSION_DIRECTORY/session" #this cause to damage file
		sh -c 'echo "Info: tail -f running with pid '\'\$$\''."; exec tail -n1 -f '"$NXSESSION_DIRECTORY"'/session' | node_start_monitor "restore" &
		MONITOR_PID=$!; export MONITOR_PID
		#nxlog "$lp call 'node_suspend_session \"$session_id\"'" #debug
		node_suspend_session "$session_id" || {
			echo "Info: Reconnection failed: NX Agent process could not be found." >> \
				"$NXSESSION_DIRECTORY/session";
			node_fail_restore_session "$session_id";
			return 1;
		}
	else # start
		#nxlog "$lp call 'node_start_agent'" #debug
		node_start_agent &
		#nxlog "$lp call 'node_start_applications'" #debug
		node_start_applications &
		if [ -x "$NODE_AUTOSTART" ]; then
			#nxlog "$lp NODE_AUTOSTART: waiting for nxagent" #debug
			ok="" step="0.01" timeo=$((AGENT_STARTUP_TIMEOUT*100))
			for (( i=0; i<=timeo; i++ )); do
				[ -f /tmp/.X$display-lock ] && { ok="1"; break; }
				sleep $step"s"
			done
			[ -n "$ok" ] || {
				nxlog "$lp /tmp/.X$display-lock not found after $AGENT_STARTUP_TIMEOUT s";
			}

			# go into background immediately
			NXSESSIONID="$sess_id" DISPLAY=:$display "$NODE_AUTOSTART" "$1" >/dev/null 2>&1 &
			disown $! # dont't wait for this child!
		fi
	fi

	if [ -n "$mediahelper" -a "$1" != "application" ]; then
		#nxlog "$lp display='$display', waiting for it's ready" #debug
		ok="" step="0.01" timeo=$((AGENT_STARTUP_TIMEOUT*100))
		for (( i=0; i<=timeo; i++ )); do
			[ -f /tmp/.X$display-lock ] && { ok="1"; break; }
			sleep $step"s"
		done
		[ -n "$ok" ] || \
			nxlog "$lp /tmp/.X$display-lock not found after $AGENT_STARTUP_TIMEOUT s";
		[ -n "$ok" ] && {
			#nxlog "$lp env: $(env)" #debug
			uservice_start ${mediahelper%%-*} "" "media-pa" "" "" "" "$mediahelper"
		}
	fi

	if [ -n "$MONITOR_PID" ]; then
		wait "$MONITOR_PID"
		usess_set "$session_id" "tail_pid" "0"
	fi
	wait # for all children
	#nxlog "$lp end" #debug
}

cmd_node_terminate() {
	echo "$delim 716 Terminating session $session_id on user request."
	node_terminate_session "$session_id"
}

cmd_node_suspend() {
	echo "$delim 716 Suspending session $session_id on user request."
	node_suspend_session "$session_id"
}

# -----------------------------------------------------------------------------
# Startup of nxnode
# -----------------------------------------------------------------------------
declare -g delim="NX>" CMDLINE="" nx_dir nxuser_logfile umask0=$(umask);
open_dbe $$
attach_db "$sq_settings_fn" ro || {
	echo "$delim 500 Error: NXNODE: Unable to attach db file $sq_settings_fn";
	exit_proc 1;
}
set_vars_from_db "" $USER

[ -n "$2" ] && delim="NX-$2>"
echo "$delim 1000 NXNODE - Version $NX_VERSION $NX_LICENSE"

if [ "$USER" = "nx" ]; then
	nx_dir="/var/lib/nxserver/home" # ???
	nxuser_logfile="/var/log/nx/nxnode.log"
else
	nx_dir="$HOME/.nx"
	[ -d $nx_dir ] || { umask 0077; mkdir -p $nx_dir; umask $umask0; }
	nxuser_logfile="$nx_dir/nxnode.log"
fi

attach_db "$nx_dir/usessions.sq3" && {
	init_usessions_db; chmod 0600 "$nx_dir/usessions.sq3" >/dev/null 2>&1; }

if ! stringinstring "$1" "--check|--setkey|--agent"; then
	read CMDLINE;
	set_vars_from_ampstr "$CMDLINE" "" "recode"
	if [ -z "$session_id" ]; then
		echo "NX> 500 Error: Fatal - Missing parameter session id." 1>&2
		exit_proc 1
	fi
	declare -g sess_id="$SERVER_NAME-$display-$session_id"
	declare -g NXSESSION_DIRECTORY="$nx_dir/C-$sess_id"
	nxuser_logfile="$nx_dir/nxnode-$session_id.log"
	nxlog "$0 ($$): run nxnode with PARAMS:\"$@\"; CMDLINE='$CMDLINE'"
else
	nxlog "$0 ($$): run nxnode with \"$@\""
fi

case "$1" in
	--startsession)
		startsession "start"
	;;
	--resumesession)
		startsession "restore"
	;;
	--applicationsession)
		startsession "application"
	;;
	--terminate)
		cmd_node_terminate
	;;
	--suspend)
		cmd_node_suspend
	;;
	--smbmount)
		[ "$(usess_get $session_id "client")" = "winnt" ] && {
			username=$(getparam "$CMDLINE" "username") # reread with no hex recode
			computername=$(getparam "$CMDLINE" "computername")
			username=$(cp_conv "$username"); password=$(cp_conv "$password")
			share=$(cp_conv "$share"); dir=$(cp_conv "$dir")
			computername=$(cp_conv "$computername")
		}
		uservice_start "//127.0.0.1/$share" "" "smb-share" "$share" \
		 "$username" "$password" "$dir" "$computername"
	;;
	--addprinter)
		[ "$(usess_get $session_id "client")" = "winnt" ] && {
			username=$(getparam "$CMDLINE" "username") # reread with no hex recode
			computername=$(getparam "$CMDLINE" "computername")
			username=$(cp_conv "$username"); password=$(cp_conv "$password")
			share=$(cp_conv "$share"); computername=$(cp_conv "$computername")
		}
		[ -n "$defaultPrinter" ] && defaultprinter=$defaultPrinter
		opts="model=$model;public=$public;defaultprinter=$defaultprinter"
		[ "$type" = "ipp" ] && share=$printer; svc=$share

		[ "${svc:0:1}" = "@" ] && svc=${svc:1} # for backward compatibility
		svc=${svc%-nocheck}; svc="$USER""_${svc%%__*}"

		uservice_start "$svc" "" "$type-prn" "$share"\
			"$username" "$password" "$opts" "$computername"
	;;
	--check)
		echo "NX> 716 finished"
	;;
	--agent)
		echo "NX> 716 Starting NX Agent ..."
		shift
		[ "$SET_LD_LIBRARY_PATH" = "1" ] && export LD_LIBRARY_PATH="$AGENT_LIBRARY_PATH:$LD_LIBRARY_PATH"
		PATH="$PATH:$PATH_BIN" $COMMAND_NXAGENT \
			-name "NX Agent Test - Args: $@" $@
		echo "NX> 716 NX Agent exited with status: $?"
	;;
	--setkey)
		mkdir -m 700 -p $HOME/.ssh
		if ! grep -q "$(cat $NX_ETC_DIR/users.id_dsa.pub)" $HOME/.ssh/$SSH_AUTHORIZED_KEYS 2>/dev/null; then
			cat $NX_ETC_DIR/users.id_dsa.pub >> $HOME/.ssh/$SSH_AUTHORIZED_KEYS
			chmod 600 $HOME/.ssh/$SSH_AUTHORIZED_KEYS
			echo "NX> 716 Public key added to: $HOME/.ssh/$SSH_AUTHORIZED_KEYS"
		else
			echo "NX> 716 Public key is already present in: $HOME/.ssh/$SSH_AUTHORIZED_KEYS"
		fi
	;;
	*)
		echo "NX> 500 Error: Command not found"
	;;
esac

echo "$delim 1001 Bye."
exit_proc 0
