#!/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 . # (c) 2008-23 by Dmitry Borisov # # License: GNU GPL, version 2 shopt -s extglob SHARED_CONFS="/usr/share/freenx-server" . $SHARED_CONFS/nxfuncs log() { #args: [ "$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: [ "$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_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: # 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: [geometry] # 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: q_row_ins "sess" "$1" "$2" } session_change() { # q_rows_upd "sess" "session_id='$1'" "$2" "$3" } # session_running (? now dublicated session_find_id_user ?) # return: true if running, false if not session_running() { session_find_id_user "$1"; } session_close() { #param: 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: 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: <[host]:display> # [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