freenx-server/nxnode
2025-08-08 20:28:57 +02:00

1488 lines
52 KiB
Bash
Executable File

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