#!/bin/bash

# Free implementation of nxserver components
#
# To use nxserver add the user "nx"
# and use nxserver as default shell.
#
# Also make sure that hostkey based authentification works.
#
# Copyright (c) 2004 by Fabian Franz <FreeNX@fabian-franz.de>.
#           (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

log() {
#args: <logstr>
	[ "$NX_LOG_LEVEL" != "0" ] || return
	[ -n "$1" ] && \
		echo -n "[$(date "+%d.%m %T.%3N"): $$/$BASHPID] " >> "$NX_LOGFILE"
	echo -e $@ >> "$NX_LOGFILE"
}

# Log in a way that is secure for passwords / cookies / ...
echo_secure() {
	[ -n "$1" ] && \
		echo -n "[$(date "+%d.%m %T.%3N")] " >> "$NX_LOGFILE"
	local res=${@/password=+([^&])&/password=*&}
	res=${res//password=\"+([^\"])\"/password=\"*\"}
	echo "$res"
}

log_secure() {
#args: <loglevel> <logstr>
	[ "$NX_LOG_LEVEL" != "0" ] || return
	echo_secure $@ >> "$NX_LOGFILE"
}

log_tee() {
	if [ "$NX_LOG_LEVEL" != "0" ]; then exec tee -a "$NX_LOGFILE"
	else exec cat -
	fi
}

log_error() {
	if [ "$NX_LOG_LEVEL" != "0" ]; then exec tee -a "$NX_LOGFILE"
	else exec cat -
	fi
}

echo_x() { log "$@"; echo "$@"; }

############### PACKAGE session.bm #######################
#
# Library of session management functions
#

# Needed global vars: NX_SESS_DIR session_count session_count_user
# COMMAND_MD5SUM SESSION_USER_LIMIT SESSION_LIMIT SESSION_HISTORY
# user

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

sqcols_sess="session_id, session, display, status, userip,\
 rootless, type, user, screeninfo, geometry, host, shadowcookie,\
 startTime, creationTime, endTime, agent_pid"

init_sess_db() {
	local sess_cols="session_id TEXT PRIMARY KEY, session TEXT,\
 display TEXT, status TEXT, userip TEXT, rootless INT, type TEXT,\
 user TEXT, screeninfo TEXT, geometry TEXT, host TEXT,\
 shadowcookie TEXT, startTime INT, creationTime INT, endTime INT,\
 agent_pid TEXT"
	local qstr="CREATE TABLE IF NOT EXISTS sessions.sess($sess_cols);"
	qstr+="CREATE INDEX IF NOT EXISTS sessions.idx_status_user ON\
 sess(status,user);"
   q_dbe "$qstr"
}

#-------------------------------------------------------------------

session_list() {
#params: sessId  ["format_times"]
#if you need to humane output put something in $2
	local qc fls res r2="";
	[ -n "$2" ] && { fls="session_id, session, display, status,\
 userip, rootless, type, screeninfo, geometry, host, shadowcookie,\
 datetime(startTime,'unixepoch','localtime') AS startTime,\
 datetime(creationTime,'unixepoch','localtime') AS creationTime, user,\
 datetime(endTime,'unixepoch','localtime') AS endTime";
	} || { # original order of fields
		fls=$sqcols_sess;
	}
	res=$(qa_dbe ".mode line sess\n" \
		"SELECT $fls FROM sess WHERE session_id='$1' LIMIT 1;")
	[ -n "$2" ] && { 	echo "$res"; return; }
	local line k v;
	while read line; do
		k=$(trim "$(cutfn "$line" 0 '=')"); v=$(trim "$(cutfn "$line" 1 '=')") #"
		r2+="$k=$v"$'\n'
	done <<< "$res"
	echo -n "$r2"
}

session_find_cmdstrs() {
#params: [sessid] [user] [display] [status="Running|Suspended"]
#ret: sessions command strings delimited by \n
	local st wstr res;
	st="Running|Suspended"; [ -n "$4" ] && st="$4"
	wstr="WHERE $(str_eq_cond "status" "$st")"
	[ -n "$1" ] && wstr+=" AND $(str_eq_cond "session_id" "$1")"
	[ -n "$2" -a "$2" != "all" -a "$2" != ".*" ] && \
		wstr+=" AND $(str_eq_cond "user" "$2")"
	[ -n "$3" ] && wstr+=" AND $(str_eq_cond "display" "$3")"
	res=$(qa_dbe ".mode line sess\n" \
		"SELECT $sqcols_sess FROM sess $wstr ORDER BY startTime DESC;")
	qtxt2cmdstrs "$res"
}

# Find all running session-filenames
session_find_all() { session_find_cmdstrs; }

# Find all running sessions of a id
session_find_id() { session_find_cmdstrs "$1"; }

# Finds out if a session belongs to a user
session_find_user() { session_find_cmdstrs "" "$1"; }

# Find all running sessions of a display
session_find_display() { session_find_cmdstrs "" "" "$1"; }

# Finds out if a session belongs to a user
session_find_id_user() {
#params: sessid [user]
	local wstr c;
	wstr="WHERE session_id='$1' AND status IN ('Running', 'Suspended')"
	[ -n "$2" -a "$2" != "all" -a "$2" != ".*" ] && \
		wstr+=" AND $(str_eq_cond "user" "$2")"
	c=$(qa_dbe ".mode tabs sess\n" \
		"SELECT count(session_id) FROM sess $wstr LIMIT 1;")
	[ "$c" -gt "0" 2>/dev/null ] || return 1
	return 0
}

# session_get <session_id>
session_get()  { session_find_cmdstrs "$1"; }

# Get the first session, which can be resumed
session_get_user_suspended() {
#params: user status
#ret: session_id line[s] or empty
	local wstr r a;
	wstr="WHERE status='$2'AND user='$1'"
	r=$(qa_dbe ".mode tabs sess\n" \
		"SELECT count(session_id), session_id FROM sess $wstr ORDER BY startTime DESC LIMIT 1;")
	a=($r); ((${#a[@]}==2)) && echo "${a[1]}"
}

session_count_user() {
#params: user [status]
# Count all sessions of a user
# and save it in session_count and session_count_user
	local st wstr r u="" sc=""
	session_count=0; session_count_user=0
	st="Running|Suspended"; [ -n "$2" ] && st="$2"
	wstr="WHERE $(str_eq_cond "status" "$st")"
	if [ "$1" = ".*" -o "$1" = "all" ]; then
		session_count=$(qa_dbe ".mode tabs sess\n" \
			"SELECT count(*) FROM sess $wstr;") #"
		session_count_user=$session_count
		return 0
	fi
	r=$(qa_dbe ".mode tabs sess\n" \
				"SELECT user,count(*) FROM sess $wstr GROUP BY user;") #"
	while read u sc; do
		[ -z "$sc" ] && continue
		((session_count+=sc))
		[ "$u" = "$1" ] && session_count_user=$sc
	done <<< "$r"
	return 0
}

session_user_acl_load() {
#arg: <user>
# load nx shadow acl for given user and store it to
# shadow_users shadow_oviews shadow_uauths variables
	[ -n "$ugroups" ] || \
		{ ugroups=$(groups $1); ugroups=$(trim "${ugroups#*:}"); }
	local s ucond="'#$1','*${ugroups// /\',\'\*}','@all'"
	local mode=".mode csv settings\n.separator '&'\n"
	local qstr="SELECT value,val_type,val_depend FROM settings WHERE user IN ($ucond) \
 AND key='@shadow@' ORDER BY user ASC, val_check DESC LIMIT 1;"
	#log "$FUNCNAME: qstr='$qstr'" #debug
	local ts=$(qa_dbe "$mode" "$qstr")
	#log "$lp ts='$ts'" #debug
	shadow_users=$(cutfn "$ts" 0 '&')
	s=$(cutfn "$ts" 1 '&'); shadow_oviews=(${s//,/ })
	s=$(cutfn "$ts" 2 '&'); shadow_uauths=(${s//,/ })
}

_session_list_user_suspended() {
# args: <user> <status,> [geometry] <type>
# use conf vars: COMMAND_MD5SUM
# use glob: user
	local p pstrs pattern geom depth render udepth urender mode;
	local options available session_id geo2 displays disp2;
	local puser pshadowcookie pscreeninfo prootless pgeometry pstatus;
	local ptype pdisplay psession_id psession
	local shadow_all=""
	[ "$4" = "shadow" ] && stringinstring ",all," ",$shadow_users," && shadow_all="1"
	echo "NX> 127 Sessions list of user '$1' for reconnect:"
	echo
	if [ -z "$4" ]; then
		echo "Display Type             Session ID                       Options  Depth Screensize     Available Session Name"
		echo "------- ---------------- -------------------------------- -------- ----- -------------- --------- ----------------------"
	else
		echo "Display Type             Session ID                       Options  Depth Screen         Status      Session Name"
		echo "------- ---------------- -------------------------------- -------- ----- -------------- ----------- ------------------------------"
	fi
	pstrs=$(session_find_cmdstrs "" "$1" "" "$2")
	while read p; do
		[ -z "$p" ] && continue
		puser=$(getparam "$p" user); pshadowcookie=$(getparam "$p" shadowcookie);
		pscreeninfo=$(getparam "$p" screeninfo); prootless=$(getparam "$p" rootless)
		pgeometry=$(getparam "$p" geometry); pstatus=$(getparam "$p" status)
		ptype=$(getparam "$p" type); pdisplay=$(getparam "$p" display)
		psession_id=$(getparam "$p" session_id); psession=$(getparam "$p" session)
		if [ "$4" = "shadow" -a "$puser" != "$1" ]; then
			[ "$ENABLE_SESSION_SHADOWING" = "1" ] || continue
			[ -n "$NX_ACL_DIR" -a -z "$shadow_all" ] && {
				#log "in nxacl present CHECK ,$puser, ; ,$shadow_users," #debug
				stringinstring ",$puser," ",$shadow_users," || continue
			}
		fi

		pattern='^([0-9]*x[0-9]*)x([0-9]*)\+?([^+]*)'
		[[ $pscreeninfo =~ $pattern ]]
		geom=${BASH_REMATCH[1]}; depth=${BASH_REMATCH[2]}; render=${BASH_REMATCH[3]}
		[[ $3 =~ $pattern ]]
		udepth=${BASH_REMATCH[2]}; urender=${BASH_REMATCH[3]}
		mode="D"; [ "$prootless" = "1" ] && mode="-"
		options="-"; stringinstring "fullscreen" "$3" && options="F"
		[ "$pgeometry" = "fullscreen" ] || options="-"
		[ "$urender" = "render" ] && options="${options}R${mode}--PSA"
		[ "$urender" = "render" ] || options="${options}-${mode}--PSA"
		[ "$udepth" = "$depth" -a "$urender" = "$render" ] && available=$pstatus
		# FIXME: HACK !!! to keep compatibility with old snapshot version (Knoppix 3.6 based for example)
		if [ -z "$4" -a "$available" != "N/A" ]; then
			available="Yes"
		fi
		if [ "$4" = "shadow" ]; then
			available=$pstatus
			printf "%-7s %-16s %32s %8s %5s %-14s %-11s %s\n" "$pdisplay" "$ptype" "$psession_id" "$options" "$depth" "$geom" "$available" "$psession ($puser) (Shadowed)"
		else
			# only unix-* sessions can be resumed, but other session types can still be terminated
			stringinstring "unix-" "$4" || available="N/A"
			printf "%-7s %-16s %32s %8s %5s %-14s %-11s %s\n" "$pdisplay" "$ptype" "$psession_id" "$options" "$depth" "$geom" "$available" "$psession"
		fi
	done <<< "$pstrs"
	echo ""
	echo ""

	session_count_user "$1"
	if [ "$session_count" -ge "$SESSION_LIMIT" -o \
			"$session_count_user" -ge "$SESSION_USER_LIMIT" ]; then
		echo "NX> 147 Server capacity: reached for user: $1"
	else
		echo "NX> 148 Server capacity: not reached for user: $1"
	fi
}

session_list_user_suspended() {
	local slcd=$(_session_list_user_suspended "$@")
	echo "$slcd" | log_tee
}

session_list_user() {
#params: user [status="Running|Suspended"]
	local st wstr
	echo -n "NX> 127 Sessions list"
	if [ -n "$1" -a "$1" != "all" ]; then echo " of user '$1'"
	else echo ":"
	fi
	echo
	echo "Server	 Display Username		Remote IP	   Session ID"
	echo "------ ------- --------------- --------------- --------------------------------"
	st="Running|Suspended"; [ -n "$2" ] && st="$2"
	wstr="WHERE $(str_eq_cond "status" "$st")"
	[ -n "$1" -a "$1" != "all" ] && wstr+=" AND $(str_eq_cond "user" "$1")"
	qa_dbe ".mode tabs sess\n SELECT host,display,user,userip,session_id\
 FROM sess $wstr ORDER BY startTime DESC;" # ! check order !
}

session_history() {
#params: user session_id
	local wstr=""
	echo "NX> 127 Session list:"
	echo
 	echo "User    Remote IP       Status          Start           Stop           "
	echo "------- --------------- --------------- --------------  ---------------"
	[ -n "$1" -a "$1" != "all" ] && wstr="$(str_eq_cond "user" "$1")"
	[ -n "$wstr" -a -n "$2" ] && wstr+=" AND "
	[ -n "$2" ] && wstr+=" session_id='$2'"
	[ -n "$wstr" ] && wstr="WHERE $wstr"
	qa_dbe ".mode tabs sess\n SELECT user,userip,status,strftime('%d.%m-%H:%M:%S',startTime,'unixepoch','localtime'),\
 strftime('%d.%m-%H:%M:%S',endTime,'unixepoch','localtime')\
 FROM sess $wstr ORDER BY endTime;"
}

# remove all sessions older than $SESSION_HISTORY seconds in failed/closed.
session_cleanup() {
	local checkTime st wstr;
	[ "$SESSION_HISTORY" -gt "-1" ] || return
	let checkTime=$(date +%s)-$SESSION_HISTORY
	st="Finished|Failed";
	wstr="WHERE $(str_eq_cond "status" "$st") AND startTime < $checkTime"
	q_dbe "DELETE FROM sess $wstr;"
}

session_list_all() { session_list_user "all"; }

session_add() {
#params: <col1,col2...> <val1&val2...>
	q_row_ins "sess" "$1" "$2"
}

session_change() {
# <session_id> <col1,col2...> <val1&val2...>
	q_rows_upd "sess" "session_id='$1'" "$2" "$3"
}

# session_running <session_id> (? now dublicated session_find_id_user ?)
# return: true if running, false if not
session_running() { session_find_id_user "$1"; }

session_close() {
#param: <session_id>
	if [ "$SESSION_HISTORY" = "0" ] ; then
		q_dbe "DELETE FROM sess WHERE session_id='$1';"
	else
		session_change "$1" "status,endTime" "Finished&$(date +%s)"
	fi
}

session_fail() {
#param: <session_id>
	if [ "$SESSION_HISTORY" = "0" ] ; then
		q_dbe "DELETE FROM sess WHERE session_id='$1';"
	else
		session_change "$1" "status,endTime" "Failed&$(date +%s)"
	fi
}
#
# end of library
#

msg2agentdisplay() {
#args: <dlg_type> <message> <[host]:display> <cookie> <agent_pid>
#	[window_caption] [timeo=$AGENT_STARTUP_TIMEOUT]
# return exit code of nxdialog
	local capt="FREENX server"; [ -n "$6" ] && capt=$6
	local timeo=$AGENT_STARTUP_TIMEOUT; [ -n "$7" ] && timeo=$7
	local rc=0
	$COMMAND_XAUTH add "$3" MIT-MAGIC-COOKIE-1 "$4" &>/dev/null
	DISPLAY="$3" $PATH_BIN/nxdialog --display "$3" --parent $5 \
				--dialog "$1" --caption "$capt" --message "$2" &>/dev/null &
	local dlg_pid=$!
	if [ $timeo -gt 0 ]; then # make a simple watchdog for nxdialog
		( sleep $timeo"s"
			kill -0 $dlg_pid &>/dev/null && kill $dlg_pid &>/dev/null; ) &
	fi
	if stringinstring "$1" "ok|error|panic"; then
		sleep 0.5s # don't remove xauthority cookie right now
	else # wait for anwers only
		wait $dlg_pid
		rc=$?
	fi
	$COMMAND_XAUTH remove "$3" &>/dev/null
	return $rc
}


#
# Main nxserver <-> nxclient communication module
#

# config variables in use:
# COMMAND_MD5SUM COMMAND_NETCAT
# COMMAND_SESSREG COMMAND_SSH COMMAND_XAUTH
# ENABLE_AUTORECONNECT
# ENABLE_LOAD_BALANCE_PREFERENCE ENABLE_LOG_FAILED_LOGINS
# ENABLE_SESSION_SHADOWING_AUTHORIZATION
# ENABLE_USESSION LOAD_BALANCE_SERVERS
# NX_ETC_DIR NX_HOME_DIR NX_LICENSE NX_LOGFILE NX_LOG_LEVEL
# NX_SESS_DIR NX_VERSION
# SERVER_NAME SESSION_HISTORY SESSION_LIMIT SESSION_USER_LIMIT

# declare global variables:
declare -g cmd in_params proto login_success login_method ugroups=""
declare -g server_mode preferred_host session_count session_count_user
declare -g user2 ENCRYPTION proxy_display server_host
declare -g oifs rc NODE_HOSTNAME user fl_send="1"
declare -g shadow_users shadow_oviews shadow_uauths shadowviewonly shadowauth

server_mode="0"; [ "$USER" = "nx" ] && server_mode="1"

if [ "$server_mode" = "1" ]; then

# Start!
open_dbe $$
attach_db "$sq_settings_fn" ro || {
	echo "NX> 500 Error: Unable to attach db file $sq_settings_fn";
	exit_proc 1;
}
set_vars_from_db
sess_bd="$NX_SESS_DIR/sessions.sq3"
attach_db $sess_bd && init_sess_db

log "-- NX SERVER START: $@ - ORIG_COMMAND=$SSH_ORIGINAL_COMMAND"

# Get the hostname out of SSH_ORIGINAL_COMMAND
preferred_host=$(rematchfn "host=([^&]*)" "$SSH_ORIGINAL_COMMAND") #"

	echo_x "HELLO NXSERVER - Version $NX_VERSION $NX_LICENSE"
	# Login stage
	while true; do
		echo_x -n "NX> 105 "
		read cmd
		[ "$cmd" = "" ] && cmd="quit" # FIXME?
		echo_x "$cmd"
		case "$cmd" in
			quit|QUIT)
				echo_x "Quit"; echo_x "NX> 999 Bye"; exit_proc 0;
			;;
			exit|EXIT)
				echo_x "Exit"; echo_x "NX> 999 Bye"; exit_proc 0;
			;;
			bye|BYE)
				echo_x "Bye"; 	echo_x "NX> 999 Bye"; exit_proc 0;
			;;
			hello*|HELLO*)
				proto=$(rematchfn 'Version ([[:digit:][:punct:]]+)' "$cmd") #'
				echo_x "NX> 134 Accepted protocol: $proto"
			;;
			"set auth_mode*"|"SET AUTH_MODE*")
				if [ "$cmd" = "set auth_mode password" -o \
					"$cmd" = "SET AUTH_MODE PASSWORD" ]; then
					echo_x "Set auth_mode: password"
				else
					echo_x "NX> 500 ERROR: unknown auth mode ''"
				fi
			;;
			login|LOGIN)
				login_success="0"
				echo_x -n "NX> 101 User: "; read user2; echo_x $user2;
				echo_x -n "NX> 102 Password: ";
				oifs="$IFS"; export IFS=$'\n';
				read -r -s PASS; export IFS=$oifs; echo_x ""
				log -n "Info: Auth method: "
				# USER already logged in?
				user=$user2

				# SU based auth used ONLY
				if [ "$login_success" = "0" ]; then
					log -n "su "
					LC_MESSAGES=C $COMMAND_SUDO -u $user -Svk -p "" \
						&>/dev/null <<< "$PASS"
					if [ $? -eq 0 ]; then login_success="1"; login_method="SU"; fi
				fi

				if [ "$login_success" = "1" ]; then
					# Reread the config files (so that $USER.node.conf get sourced)
					set_vars_from_db "" $user "only"
					[ "$ENABLE_SESSION_SHADOWING" = "1" ] && session_user_acl_load $user
					break
				else
					echo_x "NX> 404 ERROR: wrong password or login"
					echo_x "NX> 999 Bye"
					if [ "$ENABLE_LOG_FAILED_LOGINS" = "1" ]; then
						logger -t nxserver -i -p auth.info \
						"($(whoami)) Failed login for user=$user from IP=$(echo $SSH_CLIENT | awk '{print $1}')"
					fi
					exit_proc 1
				fi
			;;
		esac
	done
echo_x "NX> 103 Welcome to: $SERVER_NAME user: $user"

# remove old session infos from history
session_cleanup

#
# call it with: server_get_params $cmd # no ""!
#
server_get_params() {
	shift;
	local server_params=" $@"; server_params=${server_params// --/\&};
	server_params=${server_params//\"/};
	if [ "$server_params" = "" ]; then
		echo_x -n "NX> 106 Parameters: "
		read server_params2; server_params=${server_params2//%2B/+}
		echo_x
	fi
	server_params=${server_params//%20/ };
	server_params=${server_params//\&sessionid/\&session_id};
	server_params=${server_params//\&id/\&session_id};
	echo "$server_params"
}

server_nxnode_start() {
# use vars: session_id
# NODE_HOSTNAME NXNODE_TOSEND
	local cmd="$1" user="$2"; shift; shift;

	# Find NODE_HOSTNAME
	#NODE_HOSTNAME=""
	#CMDLINE="$@"; session_id=$(getparam session_id)
	#CMDLINE=$(session_get "$session_id")
	NODE_HOSTNAME=$host; [ -z "$NODE_HOSTNAME" ] && NODE_HOSTNAME="127.0.0.1"
	export NODE_HOSTNAME
	echo -e "$PASS\n$@" | \
		$COMMAND_SUDO -u $user -HSik -p "" $PATH_BIN/nxnode "$cmd" 2>&1 | \
			log_tee
}

server_add_usession() {
	[ "$ENABLE_USESSION" = "1" ] || return
	$COMMAND_SESSREG -l ":$display" -h "$userip" -a $user 2>&1 | \
		log_error
}

server_remove_usession() {
	[ "$ENABLE_USESSION" = "1" ] || return
	$COMMAND_SESSREG -l ":$display" -h "$userip" -d $user 2>&1 | \
		log_error
}

server_nxnode_echo() {
	if [ "$server_channel" = "1" ]; then
		echo -e "$@"
		log "$FUNCNAME >&$server_channel: $@"
	elif [ "$server_channel" = "2" ]; then
		echo -e "$@" >&2
		log "$FUNCNAME >&$server_channel: $@"
	fi
}

server_nxnode_exit_func() {
	log "$FUNCNAME: Info: Emergency-Shutting down due to kill signal ..."
	session_fail $session_id
	server_remove_usession
	# remove lock file
	[ -e "/tmp/.nX$display-lock" ] && rm -f /tmp/.nX$display-lock
	exit_proc 1
}

server_nxserver_exit_func() {
	log "$FUNCNAME: Info: Emergency-Shutting down!"
	[ -n "$adm_pid" ] && kill -0 $adm_pid &>/dev/null \
		&& kill $adm_pid &>/dev/null
	exit_proc 1
}

server_nxnode_start_wait() {
# use uplevel: $server_wait_pid $user $session_id
	local lp=" $FUNCNAME:";
	local server_channel=1 kill_wait_pid=1 shadowcookie agent_pid
	local fl_start="" to_send=""
	open_dbe $BASHPID
	[ "$1" = "--startsession" ] && fl_start="1";
	if [ -n "$fl_start" ]; then
		server_add_usession
		# We need to stop sending things when a SIGPIPE arrives
		trap "server_channel=0" SIGPIPE
		trap server_nxnode_exit_func EXIT
	fi
	server_nxnode_start "$@" | while read cmd; do
		case "$cmd" in
			"NX> 700"*)
				server_channel=0; to_send="$cmd"
			;;
			"NX> 706"*)
				shadowcookie=$(cutfn "$cmd" 1 ':'); shadowcookie=${shadowcookie// /}
				session_change "$session_id" "shadowcookie" "$shadowcookie"
				to_send+="\n$cmd"
			;;
			"NX> 733"*)
				agent_pid=$(cutfn "$cmd" 1 ':'); agent_pid=${agent_pid// /}
				session_change "$session_id" "agent_pid" "$agent_pid"
			;;
			"NX> 70"*)
				to_send+="\n$cmd"
			;;
			"NX> 710"*)
				to_send+="\n$cmd"; server_channel=1;
				server_nxnode_echo "$to_send";
				server_channel=0;
				to_send="";
				kill -SIGUSR2 $$ 2>/dev/null
				continue
			;;
			"NX> 1006"*|"NX> 1005"*|"NX> 1009"*)
				case "$cmd" in
					*running*)
						[ "$kill_wait_pid" = "1" ] && kill -INT $server_wait_pid 2>/dev/null
						kill_wait_pid=0
						if [ "$server_channel" = "1" ]; then
							server_channel=2
						fi
						log "$lp set status $session_id: Running"
						session_change $session_id "status" "Running"
					;;
					*starting*)
						log "$lp set status $session_id: Starting"
						session_change $session_id "status" "Starting"
						continue;
					;;
					*resuming*)
						log "$lp set status $session_id: Resuming"
						session_change $session_id "status" "Resuming"
						continue;
					;;
					*closed*)
						if [ -n "$fl_start" ]; then
							log "$lp session_close $session_id"
							session_close $session_id; break;
						fi
					;;
					*suspended*)
						if [ -n "$fl_start" ]; then
							[ "$kill_wait_pid" = "1" ] && kill -INT $server_wait_pid 2>/dev/null
							kill_wait_pid=0; log "$lp session suspend $session_id"
							session_change $session_id "status,userip" "Suspended&-"
						fi
					;;
					*suspending*)
						if [ -n "$fl_start" ]; then
							log "$lp set status $session_id: Suspending"
							session_change $session_id "status" "Suspending"
							# we need to stop sending to client as it will have already
							# closed his side of the channel and this will lead to not
							# closed sessions.
							server_channel=0
						fi
					;;
					*terminating*)
						if [ -n "$fl_start" ]; then
							log "$lp set status $session_id: Terminating"
							session_change $session_id "status" "Terminating"
							# we need to stop sending to client as it will have already
							# closed his side of the channel and this will lead to not
							# closed sessions.
							server_channel=0
						fi
					;;
				esac
			;;
			"NX> 1004"*)
				[ "$kill_wait_pid" = "1" ] && {
					kill -INT $server_wait_pid 2>/dev/null
					kill_wait_pid=0
				}
				# This fail is correct here as somehow the
				# monitor process might have died and we don't
				# want the session to be resumed again.
				session_fail $session_id
				server_nxnode_echo "NX> 596 Session startup failed."
				log "NX> 596 Session startup failed."
				[ -z "$fl_start" ] && break; # on restore only?
			;;
			"NX> 1001"*)
				if [ -z "$fl_start" ]; then
					server_channel=0
					log "$lp nxnode finished (1001) on display $display"
					close_dbe $BASHPID; exit_proc 0
				fi
			;;
		esac
		case $cmd in "NX> "*) server_nxnode_echo $cmd;; esac
	done

	if [ "$1" = "--startsession" ]; then
		trap - EXIT
		trap - SIGPIPE
		# Close it in case the session is still running
		session_running $session_id && session_close $session_id
		server_remove_usession
		# remove lock file
		[ -e "/tmp/.nX$display-lock" ] && rm -f /tmp/.nX$display-lock
	fi
	close_dbe $BASHPID
	log "$lp $1 end on display $display"
}

server_check_session_count() {
	session_count_user "$user"
	if [ "$session_count" -ge "$SESSION_LIMIT" ]; then
		echo_x "NX> 599 Reached the maximum number of concurrent sessions on this server."
		echo_x "NX> 500 ERROR: Last operation failed."
		return 1
	fi
	if [ "$session_count_user" -ge "$SESSION_USER_LIMIT" ]; then
		echo_x "NX> 599 Server capacity: reached for user: $user"
		echo_x "NX> 500 ERROR: Last operation failed."
		return 1
	fi
	return 0
}

server_loadbalance_random() {
	# Pick one based on "random" algorithm
	local server_lb_hosts=( $LOAD_BALANCE_SERVERS )
	local server_lb_nr_of_hosts=${#server_lb_hosts[@]}
	let server_lb_nr=(RANDOM % server_lb_nr_of_host)
	local server_lb_host=${server_lb_hosts[$server_lb_nr]}
	echo $server_lb_host
}

# run in subshell!
server_loadbalance_round_robin() {
	local server_lb_hosts=( $LOAD_BALANCE_SERVERS )
	local server_lb_nr_of_hosts=${#server_lb_hosts[@]}
	# Atomic incrementation:
	# Enter critical section
	# - Create .lock file
	server_lb_lockfile=$(mktemp "$NX_SESS_DIR/round-robin.lock.XXXXXXXXX")
	trap "rm -f $server_lb_lockfile" EXIT
	i=0
	while [ $i -lt 200 ]; do
		# ln is an atomic operation
		ln $server_lb_lockfile "$NX_SESS_DIR/round-robin.lock" && break
		LC_MESSAGES=C sleep 0.01
		((i++))
	done

	if [ $i -ge 200 ]; then
		log "Load-Balancing: Round-Robin failed to gain lock file in 200 tries. Falling back to random."
		server_loadbalance_random
		return
	fi
	trap "rm -f \"$server_lb_lockfile\" \"$NX_SESS_DIR/round-robin.lock\"" EXIT

	# Lock held
	server_lb_nr=$(cat $NX_SESS_DIR/round-robin 2>/dev/null)
	let server_lb_nr=(server_lb_nr+1)%server_lb_nr_of_hosts
	echo $server_lb_nr >$NX_SESS_DIR/round-robin

	# Exit critical section
	rm -f "$server_lb_lockfile" "$NX_SESS_DIR/round-robin.lock"
	trap - EXIT

	server_lb_host=${server_lb_hosts[$server_lb_nr]}
	echo $server_lb_host
}

server_loadbalance_load() {
	local server_lb_max=0 server_lb_host="" server_lb_load;
	export PATH_BIN
	for i in $LOAD_BALANCE_SERVERS; do
		server_lb_load=$($COMMAND_NXCHECKLOAD $i)
		[ -z "$server_lb_load" ] && continue
		if [ $server_lb_load -gt $server_lb_max ]; then
			server_lb_max=$server_lb_load
			server_lb_host=$i
		fi
	done
	echo $server_lb_host
}

server_loadbalance() {
	local server_host="127.0.0.1"
	if [ -n "$LOAD_BALANCE_SERVERS" ]; then
		server_host=""
		if [ -n "$preferred_host" -a "$ENABLE_LOAD_BALANCE_PREFERENCE" = "1" ]; then
			stringinstring " $preferred_host " " $LOAD_BALANCE_SERVERS " && \
				server_host="$preferred_host"
		fi
		# Fallback if still empty
		if [ -z "$server_host" ]; then
			case "$LOAD_BALANCE_ALGORITHM" in
				random)
					server_host=$(server_loadbalance_random)
				;;
				round-robin)
					server_host=$(server_loadbalance_round_robin)
				;;
				load)
					server_host=$(server_loadbalance_load)
				;;
			esac
		fi
		[ -z "$server_host" ] && server_host="127.0.0.1"
		[ -n "$server_host" ] && log "Info: Load-Balancing (if possible) to $server_host ..."
	fi
	echo "$server_host"
}

server_startrestore_session() {
	local action="$1" params="$2" rc sess_lockfile
	local agent_display samba_display cups_display media_display restore
	local server_pid server_wait_pid new_params db_params nshu="-1" i s


	params+="&clientproto=$proto&login_method=$login_method"
	echo_x

	if [ "$action" = "shadow" ]; then
		shadowauth="0"; shadowviewonly="0"
		action="start"; params=$(delparam "$params" "display");
		params=$(delparam "$params" "session_id");
		db_params=$(session_get "$in_session_id" 2>/dev/null)
		set_vars_from_ampstr "$db_params" "db_"

		encryption=$in_encryption
		shadowdisplay=$in_display; shadowhost=$db_host;
		shadowuser=$db_user; shadowcookie=$db_shadowcookie
		[ "$shadowcookie" = "none" ] && shadowcookie=""
		[ "$in_shadowviewonly" = "1" ] && shadowviewonly="1"
		if [ -z "$shadowdisplay" ]; then
			echo_x "NX> 596 Could not find shadowed session $session_id. Session failed."
			exit_proc 1
		fi
		[ "$shadowhost" = "127.0.0.1" ] && shadowhost=""

		if [ "$user" != "$shadowuser" -a \
				"$ENABLE_SESSION_SHADOWING_AUTHORIZATION" = "1" ]; then
			shadowauth="1"
			[ -n "$shadow_users" ] && {
				local shu=(${shadow_users//,/ })
				for ((i=0; i<${#shu[@]}; i++)) {
					[ "${shu[$i]}" = "$shadowuser" -o "${shu[$i]}" = "all" ] && {
						nshu=$i; break;
					}
				}
			}
			((nshu>=0)) && {
				[ "$shadowviewonly" = "0" ] && shadowviewonly=${shadow_oviews[$nshu]}
				shadowauth=${shadow_uauths[$nshu]}
			}
		fi
		if [ "$shadowauth" = "1" ]; then
			# Ask for permission first:
			echo_x "NX> 726 Asking user for authorization to attach to session"

			msg2agentdisplay "yesno" \
				"Do you want to allow $user to shadow your session?" \
				$shadowhost:$shadowdisplay $shadowcookie $db_agent_pid \
				"Authorization Request"
			if [ "$?" != "1" ]; then
				# User answered NO or time out
				echo_x "NX> 596 Error: Authorization refused by user: $shadowuser."
				exit_proc 1
			fi
		fi

		params+="&shadowdisplay=$shadowdisplay&shadowhost=$shadowhost&\
shadowcookie=$shadowcookie&shadowuser=$shadowuser&\
shadowviewonly=$shadowviewonly&shadowauth=$shadowauth"
	fi

	[ "$action" = "start" ] && \
		[ "$type" = "windows" -o "$type" = "vnc" ] && \
			params=${params//&type=$type&/&type=$type'-helper'&}

	# If we can't get the userip and SSHD_CHECK_IP is set to 1
	# we bail out.
	if [ -z "$SSH_CLIENT" -a -z "$SSH2_CLIENT" ]; then
		if [ "$SSHD_CHECK_IP" = "1" ]; then
			echo_x "NX> 596 Session startup failed. (Missing SSH_CLIENT environment variable)"
			return 1
		else
			log "Warning: Failed to determine the client IP."
			log "Warning: The SSH_CLIENT or SSH2_CLIENT variable was not provided by SSHD."
			log "Warning: Please set SSHD_CHECK_IP=1 if you want to refuse the connection."
		fi
	fi
	export ENCRYPTION=$encryption
	if [ "$ENABLE_FORCE_ENCRYPTION" = "1" -a "$ENCRYPTION" != "1" ]; then
			echo_x "NX> 596 Unencrypted sessions are not allowed."
			exit_proc 1
	fi

	# check if there is a suspended session, which we could resume
	if [ "$ENABLE_AUTORECONNECT" = "1" -a "$action" = "start" ]; then
		restore=$(session_get_user_suspended "$user" "Suspended")
		if [ -n "$restore" ]; then
			session_id=$restore; action="resume"
		fi
	fi

	# as only $SSH_CLIENT or $SSH2_CLIENT will be set, this should work
	userip=$(rematchfn "($ip4_pattern)" "$SSH_CLIENT $SSH2_CLIENT") #"
	[ -z "$userip" ] && userip="*"
	if [ "$action" = "start" -o "$action" = "shadow" ]; then
		server_check_session_count || exit_proc 1

		# Possibly do loadbalancing
		server_host=$(server_loadbalance)

		# start nxnode
		display=$DISPLAY_BASE
		((sess_display_limit=DISPLAY_BASE+DISPLAY_LIMIT))
		# stupid but working algo ...

		while true; do
			while [ -e /tmp/.X$display-lock -o \
					-e "/tmp/.nX$display-lock"  -o \
					-e "/tmp/.X11-unix/X$display" ]; do
				((display++))
			done

			# Check if there is already an agent running on that display on that host
			((agent_display=display+6000))
			if port_is_listening $agent_display "$server_host"; then
				log "Warning: Stray nxagent without .nX$display-lock found on host:port $server_host:$agent_display."
				((display++))
				continue
			fi

			((proxy_display=display+4000))
			if port_is_listening $proxy_display "$server_host"; then
				log "Warning: nxagent proxy without .nX$display-lock found on host:port $server_host:$agent_display."
				((display++))
				continue
			fi

			# Now check for the other enabled services
			((samba_display=display+3000))
			if [ "$samba" = 1 ] && \
					port_is_listening $samba_display "$server_host"; then
				log "Warning: Skipping $server_host:$agent_display as samba port is not free."
				((display++))
				continue
			fi

			((media_display=display+7000))
			if [ "$media" = 1 ] && \
					port_is_listening $media_display "$server_host"; then
				log "Warning: Skipping $server_host:$agent_display as media port is not free."
				((display++))
				continue
			fi

			((cups_display=display+9000))
			if [ "$cups" = 1 ] && \
					port_is_listening $cups_display "$server_host"; then
				log "Warning: Skipping $server_host:$agent_display as cups port is not free."
				((display++))
				continue
			fi

			sess_lockfile=$(mktemp "/tmp/.nX$display-lock.XXXXXXXXX")
			# ln is an atomic operation
			ln "$sess_lockfile" "/tmp/.nX$display-lock" 2>/dev/null && break
		done

		rm -f "$sess_lockfile"
		if [ "$display" -gt "$sess_display_limit" ]; then
			echo_x "NX> 596 Error: Display limit exceeded. Please remove some files from /tmp/.X*-lock."
			rm -f "/tmp/.nX$display-lock"
			exit_proc 1
		fi

		session_id=$(echo $[$RANDOM*$RANDOM] | $COMMAND_MD5SUM)
		session_id=${session_id%% *}; session_id=${session_id^^}
		params+="&user=$user&userip=$userip&session_id=$session_id&\
display=$display&host=$server_host"
		log_secure "$params"

		# now update the session listing
		local time_now=$(date +%s)
		session_add "session_id,user,session,display,status,userip,rootless,\
type,screeninfo,geometry,host,shadowcookie,startTime,creationTime"\
			"$session_id&$user&$session&$display&Running&$userip&$rootless&\
$type&$screeninfo&$geometry&$server_host&none&$time_now&$time_now"
	else
		[ -z "$(getparam "$params" "session_id")" ] && params+="&session_id=$session_id"
		session_change "$session_id" "userip" "$userip"
		db_params=$(session_get "$session_id");
		display=$(getparam "$db_params" display);
		host=$(getparam "$db_params" host); server_host=$host
		[ -z "$server_host" ] && server_host="127.0.0.1"
		status=$(getparam "$db_params" status)
		params+="&host=$server_host&user=$user&userip=$userip&display=$display&status=$status"

		if [ "$ENABLE_ADVANCED_SESSION_CONTROL" = "1" ]; then
			case "$session" in
				"add "*)
					server_nxnode_start --applicationsession "$user" "$params"
					echo_x "Quit"
					echo_x "NX> 999 Quit"
					exit_proc 1
				;;
			esac
		fi
	fi

	# now start the node
	sleep $AGENT_STARTUP_TIMEOUT &
	server_wait_pid=$!
	( server_nxnode_start_wait --"$action"session $user "$params" ) &
	server_pid=$!; disown $server_pid
	wait $server_wait_pid 2>/dev/null
	if [ $? -eq 0 ]; then
		# Something went wrong ...
		[ "$action" = "start" ] && session_fail $session_id
		echo_x "NX> 1004 Error: Session did not start."
		echo_x "NX> 596 Session $action failed."
		echo_x "NX> 999 Bye"
		# FIXME: Send node signal to terminate
		exit_proc 1
	fi
}

# Session stage
trap "fl_send=1" SIGUSR2
while true; do
	[ "$fl_send" = "0" ] && { sleep 0.01s; continue; }
	echo_x -n "NX> 105 "
	unset cmd
	read cmd 2>/dev/null
	[ "$cmd" = "" ] && cmd="quit" # FIXME?
	# Logging
	case "$cmd" in
		startsession*|restoresession*|addmount*|addprinter*)
			echo_secure "$cmd"; log_secure "$cmd"
		;;
		*)
			echo "$cmd"; log "> $cmd"
		;;
	esac

	case "$cmd" in
		quit|QUIT)
			echo_x "Quit"
			echo_x "NX> 999 Bye"
			exit_proc 0
		;;
		exit|EXIT)
			echo_x "Exit"
			echo_x "NX> 999 Bye"
			exit_proc 0
		;;
		bye|BYE)
			echo_x "Bye" 1>&2
			echo_x "NX> 999 Bye" 1>&2
			if [ "$ENCRYPTION" = "1" ]; then
				((proxy_display=display+4000))
				log "Session stage: proxy display starting $server_host:$proxy_display"
				$COMMAND_NETCAT $server_host $proxy_display 2>/dev/null; rc=$?
				log "Session stage: proxy display finished rc=$rc"
				kill $PPID 2>/dev/null # kill our parent sshd process
				exit_proc $rc
			else
				echo_x "NX> 1001 Bye."
			fi
		;;
		admin)
			[ -n "$ugroups" ] || \
				{ ugroups=$(groups "$user"); ugroups=$(trim "${ugroups#*:}"); }
			if stringinstring " nxadmin " " $ugroups "; then
				trap server_nxserver_exit_func EXIT
				log "User '$user': admin mode starting"
				LC_MESSAGES=C $COMMAND_SUDO -u $user -S -v -p "" <<< "$PASS"
				$COMMAND_SUDO -u $user -H /bin/bash -c \
						'/usr/bin/nxnode --admin'
				log "User '$user': admin mode stopping with rc=$?"
				echo_x "NX> 1001 Bye."
				trap EXIT
				exit_proc 0
			else
				log "User '$user': admin mode start failed"
				echo_x "NX> 2004 admin mode start failed"; exit_proc 1;
				echo_x "NX> 1001 Bye."
			fi
		;;
		startsession*)
			fl_send="0"; in_params=$(server_get_params $cmd)
			set_vars_from_ampstr "$in_params" # set global vars without prefixes
			server_startrestore_session "start" "$in_params"
		;;
		list*)
			# used: user,status, type, screeninfo ==  geometry ???
			in_params=$(server_get_params $cmd)
			set_vars_from_ampstr "$in_params" "in_"
			if [ "$in_status" = "Suspended" -a -n "$in_screeninfo" ]; then
				session_list_user_suspended "$in_user" "Suspended" \
					"$in_screeninfo" "$in_type"
			elif [ "$in_status" = "Suspended,Running" -o "$in_status" = "Suspended" ]; then
				# disabled due to problems with 1.4.0-5 client
				#session_list_user_suspended "$user" 'Suspended$|^status=Running$' "$(getparam geometry)" "$(getparam type)" | log_tee
				session_list_user_suspended "$in_user" \
					'Suspended' "$in_geometry" "$in_type"
			elif [ "$in_status" = "suspended,running" -o "$in_status" = "suspended" ]; then
				# since 1.5.0
				[ "$ENABLE_SHOW_RUNNING_SESSIONS" = "0" ] && in_status="Suspended" || {
					in_status=${in_status/,/|}
					in_status=${in_status/suspended/Suspended}
					in_status=${in_status/running/Running}
				}
				session_list_user_suspended "$in_user" "$in_status" \
					"$in_geometry" "$in_type"
			elif [ "$in_type" = "shadow" ]; then
				session_list_user_suspended ".*" "Suspended|Running" "" "shadow"
			else
				session_list_user "$in_user" | log_tee
			fi
		;;
		suspend*)
			# used: user, session_id
			in_params=$(server_get_params $cmd)
			set_vars_from_ampstr "$in_params"
			[ -z "$display" ] && \
				display=$(q_vals_str_get "sess" \
								"session_id='$session_id'" "display")
			in_params+="&display=$display"
			if session_find_id_user "$session_id" "$user"; then
				server_nxnode_start --suspend "$user" "$in_params"
			fi
		;;
		terminate*)
			in_params=$(server_get_params $cmd)
			set_vars_from_ampstr "$in_params"
			[ -z "$display" ] && \
				display=$(q_vals_str_get "sess" \
								"session_id='$session_id'" "display")
			in_params+="&display=$display"
			if session_find_id_user "$session_id" "$user"; then
				server_nxnode_start --terminate "$user" "$in_params"
			fi
		;;
		restoresession*)
			fl_send="0"; in_params=$(server_get_params $cmd)
			set_vars_from_ampstr "$in_params"
			[ -z "$display" ] &&
				display=$(q_vals_str_get "sess" \
								"session_id='$session_id'" "display")
			in_params+="&display=$display"
			server_startrestore_session "resume" "$in_params"
		;;
		attachsession*)
			in_params=$(server_get_params $cmd)
			set_vars_from_ampstr "$in_params" "in_"
			server_startrestore_session "shadow" "$in_params"
		;;
		addmount*)
			in_params=$(server_get_params $cmd)
			set_vars_from_ampstr "$in_params" "in_"
			in_params+="&display=$display"
			( server_nxnode_start --smbmount "$user" "$in_params" >/dev/null 2>&1 ) &
		;;
		addprinter*)
			in_params=$(server_get_params $cmd)
			set_vars_from_ampstr "$in_params" "in_"
			in_params+="&display=$display"
			( server_nxnode_start --addprinter "$user" "$in_params" >/dev/null 2>&1 ) &
		;;
		*)
			# disabled for 1.4.0-5 snapshot client
			#echo_x "NX> 503 Error: undefined command: '$cmd'"
		;;
	esac
done
trap - SIGUSR2

fi # server_mode == "1"

#
# End of Main nxserver <--> nxclient communication module
#

################### PACKAGE cmd.bm ############################

#
# library functions for nxserver-commandline cmds
#

# Policy: All functions and variables need to start with CMD_ / cmd_
# Needed global vars: $NX_VERSION, $NX_LICENSE, $NX_ETC_DIR, $PATH_BIN,
#			$NX_HOME_DIR, $SSH_AUTHORIZED_KEYS
# Needed package: passdb
cmd_usage() {
	echo "NXSERVER - Version $NX_VERSION $NX_LICENSE" 1>&2
	echo "Usage: nxserver <option>" 1>&2

	if [ "$1" = "root" ]; then
		echo "--start: Start the nx server" 1>&2
		echo "--stop: Stop the nx server" 1>&2
		echo "--status: Show status of nx server" 1>&2
		echo "--restart: Restart the nx server. (start,stop)" 1>&2
		echo "" 1>&2
		echo "--list [ user | session_id ]: List running sessions of user or session_id " 1>&2
		echo "--history [ user | session_id | clear ]: Show history [ of user | session_id ] or clear the history" 1>&2
		echo "--terminate <user | :display | session_id>: Terminate the session pointed to by" 1>&2
		echo "       session_id or display, or all sessions of the specified user." 1>&2
		echo "       Use * for all sessions." 1>&2
		echo "--force-terminate: Like terminate, but removes also session info." 1>&2
		echo "--suspend <user | :display | session_id>: Suspend the session pointed to by" 1>&2
		echo "       session_id or display, or all sessions of the specified user." 1>&2
		echo "       Use * for all sessions." 1>&2
		echo "--cleanup: Terminates all running sessions. Useful after power-outage."
		echo "" 1>&2
		echo "--broadcast <message>: Send a message to all users" 1>&2
		echo "--send <user | :display | session_id> <message>: Send a message to the specified user or session_id" 1>&2
	fi
	exit_proc 1
}


cmd_abort() {
	echo -e "NX> 500" "$@" 1>&2
	echo "NX> 999 Bye" 1>&2
	exit_proc 1
}

cmd_abort_success() {
	echo "NX> 500" "$@" 1>&2
	echo "NX> 999 Bye" 1>&2
	exit_proc 0
}

cmd_start() {
	[ -f $NX_HOME_DIR/.ssh/$SSH_AUTHORIZED_KEYS ] && \
		cmd_abort_success "ERROR: Service already running"
	mv $NX_HOME_DIR/.ssh/$SSH_AUTHORIZED_KEYS.disabled \
		$NX_HOME_DIR/.ssh/$SSH_AUTHORIZED_KEYS
	echo "NX> 122 Service started"
}

cmd_stop() {
	[ -f $NX_HOME_DIR/.ssh/$SSH_AUTHORIZED_KEYS ] || \
	cmd_abort_success "Service was already stopped"
	mv $NX_HOME_DIR/.ssh/$SSH_AUTHORIZED_KEYS \
		$NX_HOME_DIR/.ssh/$SSH_AUTHORIZED_KEYS.disabled
	echo "NX> 123 Service stopped"
}

cmd_status() {
	[ -f $NX_HOME_DIR/.ssh/$SSH_AUTHORIZED_KEYS ] && \
		echo "NX> 110 NX Server is running"
	[ -f $NX_HOME_DIR/.ssh/$SSH_AUTHORIZED_KEYS ] || \
		echo "NX> 110 NX Server is stopped"
}

cmd_restart() { cmd_stop; cmd_start; }

cmd_parse_2_params()
{
	local cmd_params;
	if [ ${#1} -eq 32 ]; then cmd_params="session_id=$1"
	elif [ "$1" != "" ]; then cmd_params="user=$1"
	fi
	echo "$cmd_params"
}

cmd_parse_3_params()
{
	local cmd_params;
	if [ ${#1} -eq 32 ]; then
		cmd_params=$(session_find_id $1)
		[ -n "$cmd_params" ] || cmd_abort "Error: Session $1 could not be found."
	elif [ "${1:0:1}" = ":" ]; then
		cmd_params=$(session_find_display "${1:1}")
		[ -n "$cmd_params" ] || cmd_abort "Error: No running sessions found for display $1."
	elif [ "$1" = "*" ]; then
		cmd_params=$(session_find_all)
		[ -n "$cmd_params" ] || cmd_abort "Error: No running sessions found."
	elif [ "$1" != "" ]; then
		cmd_params=$(session_find_user "$1")
		[ -n "$cmd_params" ] || cmd_abort "Error: No running sessions found for user $1."
	else cmd_abort "Error: Not enough parameters."
	fi
	echo "$cmd_params"
}

cmd_list_suspended() {
	local cmd_params=$(cmd_parse_2_params "$2")
	[ -n "$2" -a -z "$cmd_params" ] && exit_proc 1
	case $cmd_params in
		user=*)
			session_list_user_suspended $2 "Suspended"
		;;
	esac
}

cmd_list() {
	local cmd_params=$(cmd_parse_2_params "$2")
	[ -n "$2" -a -z "$cmd_params" ] && return
	case $cmd_params in
		user=*)
			session_list_user $2
		;;
		session_id=*)
			session_list $2
		;;
		*)
			session_list_all
		;;
	esac
}

cmd_history_clear() {
	q_dbe "DELETE FROM sess WHERE status IN ('Finished','Failed');"
}

cmd_history() {
	local cmd_params user="" sessid=""
	if [ "$2" = "clear" ]; then cmd_history_clear; return; fi
	cmd_params=$(cmd_parse_2_params "$2")
	case $cmd_params in
		user=*)
			user="$2"
		;;
		session_id=*)
			sessid="$2"
		;;
	esac
	session_history "$user" "$sessid"
}

cmd_execute() {
	local cmd_host="$1" cmd_user="$2" cmd_cmd="$3"
	if [ "$cmd_host" = "127.0.0.1" -o "$cmd_host" = "localhost" ]; then
		/bin/su - "$cmd_user" -c "$cmd_cmd"
	else
		ssh "$cmd_host" su - "$cmd_user" -c "'$cmd_cmd'"
	fi
}

cmd_terminate_or_send() {
	local cmd="$1" cmd_params lineamp cmd_shadowcookie cmd_agent_pid;
	local cmd_session_id cmd_display cmd_user cmd_type cmd_status cmd_host;
	if [ "$cmd" = "--broadcast" ]; then
		cmd_params=$(session_find_all)
		[ -z "$cmd_params" ] && cmd_abort "Error: No running session could be found."
	else
		cmd_params=$(cmd_parse_3_params "$2")
		[ -z "$cmd_params" ] && exit_proc 1
		shift
	fi
	shift

	while read lineamp; do
		[ -z "$lineamp" ] && continue
			cmd_session_id=$(getparam "$lineamp" session_id)
			cmd_display=$(getparam "$lineamp" display)
			cmd_user=$(getparam "$lineamp" user)
			cmd_type=$(getparam "$lineamp" type)
			cmd_status=$(getparam "$lineamp" status)
			cmd_host=$(getparam "$lineamp" host)

			# is it a "good" session?
			case "$cmd" in
			--suspend)
				if [ "$cmd_status" = "Running" ] && stringinstring "unix-" "$cmd_type"
				then
					echo "session_id=$cmd_session_id&display=$cmd_display" | \
						cmd_execute "$cmd_host" "$cmd_user" "$PATH_BIN/nxnode --suspend"
				fi
			;;
			--terminate)
				echo "session_id=$cmd_session_id&display=$cmd_display" | \
					cmd_execute "$cmd_host" "$cmd_user" "$PATH_BIN/nxnode --terminate"
			;;
			--force-terminate)
				echo "session_id=$cmd_session_id&display=$cmd_display" | \
					cmd_execute "$cmd_host" "$cmd_user" "$PATH_BIN/nxnode --terminate"
				session_close $cmd_session_id
			;;
			--send|--broadcast)
				# is it a "good" session?
				if [ "$cmd_status" = "Running" ]; then
					cmd_agent_pid=$(getparam "$lineamp" agent_pid)
					cmd_shadowcookie=$(getparam "$lineamp" shadowcookie)
					msg2agentdisplay "ok" "$(date "+%d.%m %H:%M"): $@" \
						:$cmd_display $cmd_shadowcookie $cmd_agent_pid \
						"NX Admin Message" 0
				fi
			esac
	done <<< "$cmd_params"
}

sqcols_sess_adm="user, session, status, creationTime, startTime, endTime,\
 type, session_id, display, userip, rootless, screeninfo, geometry, host"

cmd_list0() {
#args: term1[&term2...] [sort1[!][,sort2...]]
#  term: <exp><cond><val_str>
#    cond: = != > < >= <= ; val_str: val1[|val2...] or val_start,val_end
#  if '!' present in sortN then sort order is DESC else ASC
	local colstr="" wstr="" sortstr="" res
	[ "$admin_mode" = "1" ] && colstr=$sqcols_sess_adm || colstr=$sqcols_sess
	[ -n "$1" ] && wstr="WHERE $(q_where_str "$1")"
	[ -n "$2" ] && sortstr="ORDER BY $(q_sort_str "$2")"
	local mode=".mode csv sess\n.separator '&'\n"
	local qstr="SELECT $colstr FROM sess $wstr $sortstr;" #; echo "$qstr" #debug
	res=$(qa_dbe0 "$mode" "$qstr")
	echo_x "NX> 2126 $colstr"
	echo_x "NX> 2127 strings list start"
	echo "${res//\"/}"
	echo_x "NX> 2148 strings list end"
}

open_dbe $$
attach_db "$sq_settings_fn" ro || {
	echo "NX> 500 Error: Unable to attach db file $sq_settings_fn";
	exit_proc 1;
}
set_vars_from_db
sess_bd="$NX_SESS_DIR/sessions.sq3"
if [ "$NX_LOG_LEVEL" != "0" -a ! -f "$NX_LOGFILE" ]; then
	touch "$NX_LOGFILE" >/dev/null 2>&1
fi
if [ "$UID" -eq "0" ]; then
	chown nx "$NX_LOGFILE" >/dev/null 2>&1; chmod 660 "$NX_LOGFILE"
	chown nx "$sess_bd" >/dev/null 2>&1; chmod 660 "$sess_bd"
fi
attach_db $sess_bd && init_sess_db
#
# normal user available functions
if [ $UID -ne 0 ]; then
	if [ "$1" = "--agent" ]; then exec $PATH_BIN/nxnode "$@"
	else cmd_usage
	fi
	exit_proc 0
fi

#
# root mode available functions
[ $# -lt 1 ] && cmd_usage "root"
[ "$1" = "--help" ] && cmd_usage "root"

if [ "$1" = "--version" ]; then
  echo "NXSERVER - Version $NX_VERSION $NX_LICENSE"
  exit_proc 0
fi

declare -g admin_mode="" mloop="1" cmd0 q s l
[ "$1" = "--admin" ] && { # admin mode loop
	admin_mode="1"; shift;
	trap exit_proc SIGINT; trap exit_proc SIGKILL; trap exit_proc EXIT
	echo_x "NX> 2103 admin mode started"
	while [ "$mloop" = "1" ]; do
		echo_x -n "NX> 2105 "
		unset cmd0; read cmd0 2>/dev/null
		[ "$cmd0" = "" ] && cmd0="quit" # FIXME?
		# do fake eval for params
		q=0; l=$cmd0; cmd=()
		while [ -n "$l" ]; do
			s=${l%%[\'\"]*}; l=${l#*[\'\"]}; [ "$s" = "$l" ] && l=""
			if ((q==0)); then
				[ -n "$s" ] || continue
				cmd+=($s); q=1
			else cmd+=("$s"); q=0
			fi
		done
		#log "${#cmd[@]}" #debug
		#for ((i=0; i<${#cmd[@]}; i++)); do log "${cmd[$i]}"; done #debug
		case ${cmd[0]} in
		ping) echo_x "pong" ;;
		l) cmd_list0 "${cmd[@]:1}" ;;
		list) cmd_list "${cmd[@]:1}" ;;
		--send|--broadcast) cmd_terminate_or_send "${cmd[@]}" ;;
		quit|q) mloop=0 ;;
		*) echo "Error: Function '$cmd' not implemented yet."
		esac
	done
	echo_x "NX> 2999 admin mode stopped"
	trap SIGINT; trap SIGKILL; trap EXIT; exit_proc 0
} # admin mode end


cmd=$1
echo "NX> 100 NXSERVER - Version $NX_VERSION $NX_LICENSE"
case $cmd in
	--start)
		cmd_start
	;;
	--stop)
		cmd_stop
	;;
	--status)
		cmd_status
	;;
	--restart)
		cmd_restart
	;;
	--list)
		cmd_list "$@"
	;;
	--list-suspended)
		cmd_list_suspended "$@"
	;;
	--history)
		cmd_history "$@"
	;;
	--terminate|--suspend|--force-terminate)
		cmd_terminate_or_send "$@"
	;;
	--cleanup)
		cmd_terminate_or_send "--force-terminate" "*"
	;;
	--send|--broadcast)
		cmd_terminate_or_send "$@"
	;;
	*)
		cmd_abort "Error: Function $cmd not implemented yet."
esac
echo "NX> 999 Bye"
exit_proc 0
