#!/bin/bash

# Coypright (c) 2004-2005 by Fabian Franz <freenx@fabian-franz.de>.
#                    2005 by Jon Severinsson <jonno@users.berlios.de>.
#                    2023 by Dmitry Borisov <i@dimbor.ru>
#
# License: GNU GPL, version 2

HELP="no"; INSTALL="no"; SETUP_NOMACHINE_KEY="no"
SETUP_UID=""; SETUP_GID=""; LOCAL_USER_OPTION=""
# luseradd / luserdel are only available on RedHat
[ -f /etc/redhat-release ] && LOCAL_USER_OPTION="yes"
# altlinux have /etc/redhat-release file but don't have luseradd / luserdel commands
[ -f /etc/altlinux-release ] && LOCAL_USER_OPTION="no"
SETUP_LOCAL_USER="no"; CLEAN="no"; UNINSTALL="no"
PURGE="no"; BUILD_KNOWN_HOSTS="yes"; AUTOMATIC="no"
IGNORE_ERRORS="no"; MKDB="no"

if [ $UID -ne 0 ]; then
	echo "You need to be root to use this program."
	exit 1
fi

parse_cmdline() {
	while [ "$1" ]; do
		case "$1" in
			--help) HELP="yes"; shift ;;
			--install) INSTALL="yes"; shift ;;
			--mkdb|mkdb|--reload) MKDB="yes"; shift ;;
			--ignore-errors) IGNORE_ERRORS="yes"; shift;;
			--setup-nomachine-key) SETUP_NOMACHINE_KEY="yes"; shift ;;
			--dont-build-known-hosts) BUILD_KNOWN_HOSTS="no"; shift;;
			--uid) SETUP_UID=$2; shift 2 ;;
			--gid) SETUP_GID=$2; shift 2 ;;
			--localuser) SETUP_LOCAL_USER="yes"; shift;;
			--clean) CLEAN="yes"; shift ;;
			--uninstall) UNINSTALL="yes"; shift ;;
			--purge) PURGE="yes"; shift ;;
			--auto) AUTOMATIC="yes"; shift ;;
			--test) TEST="yes"; shift ;;
			--) shift ; break ;;
			*) echo "Invalid flag $1" ; HELP="yes"; shift ; break ;;
		esac
	done

	[ "$TEST" = "yes" ] && return # If we just test, we can return directly

	#Check for invalid combinations:
	[ "$SETUP_LOCAL_USER" = "yes" -a -z "$LOCAL_USER_OPTION" ] && HELP="yes"
	[ "$INSTALL" = "yes" -a "$UNINSTALL" = "yes" ] && HELP="yes"
	[ "$INSTALL" = "yes" -a "$CLEAN" = "no" -a "$PURGE" = "yes" ] && HELP="yes"
	[ "$UNINSTALL" = "yes" ] && [ "$SETUP_NOMACHINE_KEY" = "yes" -o \
		-n "$SETUP_UID" -o "$CLEAN" = "yes" ] && HELP="yes"
	[ "$UNINSTALL" = "yes" -a "$CLEAN" = "yes" ] && HELP="yes"

	if [ "$INSTALL" = "no" -a "$UNINSTALL" = "no" -a \
		"$AUTOMATIC" = "no" -a "$HELP" = "no" -a "$MKDB" = "no" ]; then
		HELP="yes"
	fi

	[ "$INSTALL" = "yes" -o "$AUTOMATIC" = "yes" ] && MKDB="yes"

	if [ "$HELP" = "yes" ]; then
		echo "nxsetup - Setup the FreeNX server."
		echo "Syntax: nxsetup --help"
		echo "        nxsetup --reload"
		echo "        nxsetup --mkdb"
		echo "        nxsetup --test [--ignore-errors]"
		echo "        nxsetup --install [--setup-nomachine-key] [--uid <nummber>] [--clean [--purge]]"
		echo "        nxsetup --uninstall [--purge]"
		echo
		echo "  --help                 Display this help message."
		echo "  --reload               Reload nxsettings db."
		echo "  --mkdb                 Reload nxsettings db."
		echo "  --test                 Test the configuration and connection to localhost NX Server."
		echo "  --install              Install necessary files and add the special user \"nx\"."
		echo "  --ignore-errors        Check for false configuration, but don't complain."
		echo "  --setup-nomachine-key  Allow login with the key shipped with the NoMachine"
		echo "                         client. This is fairly secure, and it simplifies the "
		echo "                         configuration of clients. (Using a custom key pair."
		echo "                         increases security even more, but complicates the"
		echo "                         configuration of clients.)"
		echo "                         Use this option at your own risk."
		echo "  --uid <number>         Give the uid <number> to the user \"nx\"."
		echo "  --gid <number>         Give the gid <number> to the user \"nx\"."
		[ -n "$LOCAL_USER_OPTION" ] && \
		echo "  --localuser            The special user \"nx\" will be created locally using"
		echo "                         \"luseradd\", for use in NIS and NISplus environments."
		echo "  --clean                Perform an uninstallation prior to installation."
		echo "  --uninstall            Remove log and session files, as well as the special"
		echo "                         user \"nx\"."
		echo "  --purge                Remove extra configuration files and ssh keys when"
		echo "                         performing a \"--uninstall\"."
		echo "                         Note that node.conf will always be saved."
		echo "  --auto                 Perform automatic installation without testing or asking."
		exit 0
	fi
	#Undocumented
	#
	#    --dont-build-known-hosts For system without /etc/ssh/ssh_host_key.rsa.pub and anyway
	#		expect should handle nx-users known-hosts keys so why borther
	#		in nxsetup?

	if [ "$INSTALL" = "yes" -a "$AUTOMATIC" = "no" -a \
		"$SETUP_NOMACHINE_KEY" = "no" ]; then
		echo "------> It is recommended that you use the NoMachine key for"
		echo "        easier setup. If you answer \"y\", FreeNX creates a custom"
		echo "        KeyPair and expects you to setup your clients manually. "
		echo "        \"N\" is default and uses the NoMachine key for installation."
		echo ""
		echo -n " Do you want to use your own custom KeyPair? [y/N] "
		read -n 1 CHOICE
		[ "$CHOICE" = "y" ] || SETUP_NOMACHINE_KEY="yes"
	fi
}

parse_cmdline "$@"
#======================= nxsettings db manipulatios =========================
SHARED_CONFS="/usr/share/freenx-server"
. $SHARED_CONFS/nxfuncs

ex_rules_cols="val_type,val_depend,val_check"; acl_user="#ACL#"
all_cols="user,key,value,$ex_rules_cols"
conf_buf=""; txt_err=""; txt_warn=""; txt_info="";
qs_create_settings="CREATE TABLE IF NOT EXISTS nxsettings.settings(\
 user TEXT, key TEXT NOT NULL, value, val_type TEXT, \
 val_depend TEXT, val_check TEXT, PRIMARY KEY(user,key));"

keyslst_for_user() {
#arg: [username] [table='settings']
#ret: keynames delimited by '\n'
	local qstr res wstr table;
	[ -n "$1" ] && wstr="WHERE user='$1'" || wstr="WHERE user IS NULL"
	table="settings"; [ -n "$2" ] && table="$2"
	qstr="SELECT key FROM $table $wstr;"
	res=$(qa_dbe0 "$qstr"); echo "$res"
}

values_str_from_db() {
#args: key <col1,col2...> [username] [table='settings']
	local ucond table qstr r a res;
	ucond=" IS NULL"; [ -n "$3" -a "$3" != "NULL" ] && ucond="='$3'"
	table="settings"; [ -n "$4" ] && table="$4"
	qstr="SELECT $2 FROM $table WHERE user$ucond AND key='$1' LIMIT 1;"
	r=$(qa_dbe0 "$qstr"); OIFS="$IFS"; IFS='&'; local a=($r)
	res="${a[*]}"; IFS="$OIFS"
	echo "${res//\"/}"
}

values_update_db() {
#args: key <col1,col2...> <val1&val2...> [username] [table='settings']
	local ucond table qstr upd_s ins_s ik0="" iv0="" ik iv;
	ucond=" IS NULL"; [ -n "$4" -a "$4" != "NULL" ] && ucond="='$4'"
	table="settings"; [ -n "$5" ] && table="$5"
	qstr=$(colval_set_or_cond "$2" "$3"); [ -n "$qstr" ] || return
	upd_s="UPDATE $table SET $qstr WHERE user$ucond AND key='$1';"
	[ -n "$4" ] && { ik0="user,"; iv0="'$4',"; }
	qstr=$(colval_set_or_cond "$2" "$3" "INS"); ik=${qstr%&*}; iv=${qstr#*&}
	ins_s="INSERT INTO $table($ik0""key,$ik) SELECT $iv0""'$1',$iv WHERE (SELECT Changes()=0);"
	#echo "$upd_s" "$ins_s"
	q_dbe0 "$upd_s" "$ins_s"
}

# ===========================================================================

parse_conf_buf() {
#args: user [is_defs] [table='settings']
	local ex_vals="type depend check" l0 l ukv="" ik="" iv="";
	local key val a0 a first cn cv upd_s ins_s v;
	local table="settings"; [ -n "$3" ] && table="$3"
	while read l0; do
 		[ -n "$l0" ] || continue
		l=($l0); first=${l[0]:0:1}
		if [ "$first" = "#" ]; then
			[ -z "$2" ] && continue
			# parse comment for extras
			l[0]=${l[0]:1}
			[ "${l[0]}" = "@" ] && { # set extra column
				unset l[0];
				a0="${l[@]}"; OIFS="$IFS"; IFS=':'; a=($a0); IFS="$OIFS"
				cn=$(trim "${a[0]}"); stringinstring "$cn" "$ex_vals" || continue
				#cv=$(trim "${a[@]:1}"); # this kills all colons :(
				cv=$(trim "${l0#*:}"); cv=$(s2sq "$cv")
				ukv="$ukv,val_$cn='$cv'";
				ik="$ik,val_$cn"; iv="$iv,'$cv'"
				#echo "!$cn !$cv"
			}
			continue
		fi
		[[ "${l[0]:0:1}" =~ [[:upper:]] ]] || continue
		# there can be more than one char '=' per line
		key=$(trim "${l%%=*}"); [ "$l" = "$key" ] && continue
		val=$(trim "${l[@]#*=}"); val=$(s2sq "$val")
		# fake upsert to db
		uk0="user IS NULL AND "; ik0=""; iv0="";
		[ -n "$1" ] && { uk0="user='$1' AND "; ik0="user,"; iv0="'$1',"; }
		upd_s="UPDATE $table SET value='$val'""$ukv WHERE $uk0""key='$key';"
		ins_s="INSERT INTO $table($ik0""key,value""$ik) SELECT $iv0""'$key','$val'$iv WHERE (SELECT Changes()=0);"
		q_dbe0 "$upd_s" "$ins_s"
		ukv=""; ik=""; iv=""
	done <<< "$conf_buf"
}

check_value() {
#args: <key> <value> <type> <cond> <user> [err_log] [ro=""] [table='settings']
	local err_log="" msg="" key=$1 val="$2" cond="$4" nval="$2" umsg="";
	local mpref="! $umsg""$key = '$val' " notempty="" act="";
	local nump='^'"$num_pattern"'$' cl min max a OIFS;
	local cnd cval lst nexe ug local mode mask;
	local ma set_ug set_mode res st table;
	[ -n "$6" ] && err_log=$6; [ -n "$5" -a "$5" != "NULL" ] && umsg="[$5] "
	table="settings"; [ -n "$8" ] && table="$8"
	stringinstring "notempty" "$cond" && \
		{ notempty="1"; cond=$(trim "${cond/notempty/}"); }
	stringinstring "perform" "$cond" && \
		{ act="1"; cond=$(trim "${cond/perform/}"); }
	case "$3" in
		bool) [ "$val" = "0" -o "$val" = "1" ] || {
				msg="$mpref must be 0/1, forced to;"$'\n'
				[ -n "$val" ] && nval="1" || nval="0"
			} ;;
		int)
			[[ $val =~ $nump ]] || {
				msg="$mpref must be the number, forced to 0;"$'\n'; nval="0";
			}
			[ -n "$cond" ] && {
				cl=($cond); min="${cl[0]}"; max="${cl[1]}";
				[ -n "$min" ] && {
					if ! [[ $min =~ $nump ]]; then
						min="${!min}"; [[ $min =~ $nump ]] || min=""
					fi
				}
				[ -n "$max" ] && {
					if ! [[ $max =~ $nump ]]; then
						max="${!max}"; [[ $max =~ $nump ]] || max=""
					fi
				}
				[ -n "$min" ] && ((val<min)) && \
					msg="$mpref is less than MIN value $min;"$'\n'
				[ -n "$max" ] && ((val>max)) && \
					msg="$mpref is greater than MAX value $max;"$'\n'
			} ;;
		string)
			if [ -n "$notempty" ]; then
				[ -z "$val" ] && \
					msg="$mpref. Is empty! Set it please;"$'\n'
			elif [ -n "$cond" ]; then
				cnd=($cond); cval=($val);
				[ "${cnd[0]}" = "path" ] && { cval[0]=${cval[0]%/*}; cnd[0]="dir"; }
				if [ "${cnd[0]}" = "list:" ]; then
					OIFS="$IFS"; local IFS='%'; a=($cond); IFS="$OIFS"; lst=${a[1]}
					stringinstring "${cval[0]}" "$lst" || \
						msg="! $umsg""$key = '${cval[0]}' value is not in ($lst);"$'\n'
				elif stringinstring "${cnd[0]}" "dir,exe,file";  then
					if [ -z "${cval[0]}" ]; then
						msg="$mpref. Is empty! Set it;"$'\n'
					else
						if [ "${cnd[0]}" = "exe" -a ! -x "${cval[0]}" ]; then
							nexe="$(which ${cval[0]} 2>/dev/null)"
							[ -n "$nexe" ] && {
								cval[0]=$nexe; nval="${cval[@]}"; err_log=""
							} || \
								msg="! $umsg""$key = '${cval[0]}': file is not executable;"$'\n'
						elif [ "${cnd[0]}" = "dir" ]; then
							[ -d "${cval[0]}" ] || \
								msg="! $umsg""$key = '${cval[0]}': dir is not found;"$'\n'
						elif [ "${cnd[0]}" = "file" ]; then
							[ -f "${cval[0]}" ] || \
								msg="! $umsg""$key = '${cval[0]}': file is not found;"$'\n'
						fi
						ug=""; mode=""; mask="";
						if [ -n "${cnd[1]}" ]; then
							for ((i=1; i<${#cnd[*]}; i++)) do # to collect attrs
								if [ "${cnd[$i]:0:1}" = "&" ]; then
									cnd[$i]=${cnd[$i]:1}; mask="1";
								fi
								if [[ ${cnd[$i]:0:1} =~ $nump ]]; then
									mode=${cnd[$i]};
									while [ "${mode:0:1}" = "0" ]; do mode=${mode:1}; done
								else ug=${cnd[$i]}
								fi
							done
							ma=($(stat -c '%U:%G %a' "${cval[0]}" 2>/dev/null));
							set_ug=""; set_mode=""; res="";
							if [ -n "$msg" -a -n "$act" -a "${cnd[0]}" != "exe" ]; then
								if [ "${cnd[0]}" = "dir" ]; then
									mkdir -p "${cval[0]}" 2>/dev/null
									[ -d "${cval[0]}" ] && { set_mode="1"; set_ug="1"; }
								else
									touch "${cval[0]}" 2>/dev/null
									[ -f "${cval[0]}" ] && { set_mode="1"; set_ug="1"; }
								fi
								[ -n "$set_mode" ] && \
									msg="  $umsg""$key = '${cval[0]}': ${cnd[0]} is not found > Created;"$'\n'
							else # check file attrs
								ma=($(stat -c '%U:%G %a' "${cval[0]}"));
								if [ -n "$ug" -a "$ug" != "${ma[0]}" ]; then
									msg+="! $umsg""$key = '${cval[0]}': owners [${ma[0]}] is not \"$ug\";"$'\n'
									[ -n "$act" ] && set_ug="1"
								fi
								if [ -n "$mode" ]; then
									if [ -n "$mask" ]; then
										res=$(printf '%o' $(( 0$mode & 0${ma[1]} )))
										[ "$res" = "$mode" ] || {
											msg+="! $umsg""$key = '${cval[0]}': mode [${ma[1]}] does not match the mask '$mode';"$'\n'
											[ -n "$act" ] && set_mode="1"
										}
									elif [ "${ma[1]}" != "$mode" ]; then
										msg+="! $umsg""$key = '${cval[0]}': mode [${ma[1]}] is not equal '$mode';"$'\n'
										[ -n "$act" ] && set_mode="1"
									fi
								fi
							fi
							if [ -n "$set_ug" -a -n "$ug" ]; then
								msg+="$umsg""  $key = '${cval[0]}' < owners change to '$ug'"
								chown $ug "${cval[0]}" 2>/dev/null && st="OK" || st="FAILED"
								msg+=" > $st;"$'\n'
							fi
							if [ -n "$set_mode" -a -n "$mode" ]; then
								[ -n "$mask" ] && \
									mode=$(printf '%o' $(( 0$mode | 0${ma[1]} )))
								msg+="$umsg""  $key = '${cval[0]}' < mode change to '$mode'"
								[ "${#mode}" = "3" ] && mode="00$mode"
								chmod $mode "${cval[0]}" 2>/dev/null && st="OK" || st="FAILED"
								msg+=" > $st;"$'\n'
							fi
						fi
					fi

				fi
			fi
			#msg="$umsg"" key $key ($3)='$val' checking: '$cond'"$'\n'
		;;
	esac
	[ "$val" = "$nval" ] || {
		[ -n "$7" ] || \
			values_update_db $key "value" "$(s2sq "$nval")" "$5" "$table"
		msg+="    $key < '$nval';"$'\n'; val="$nval"
	}
	[ -n "$5" ] || declare -g $key="$val" # expand not user's vals only ???
	[ -n "$msg" ] && {
		[ -n "$err_log" ] && txt_err+="$msg" || txt_warn+="$msg"
	}
}

check_value_advanced() {
#args: <key> <value> <type> <cond> <user> [err_log] [ro=""] [table='settings']
	local err_log=""; [ -n "$6" ] && err_log=$6
	local msg="" key=$1 val="$2" nval="$2"
	local umsg=""; [ -n "$5" ] && umsg="[$5] "
	local table="settings"; [ -n "$8" ] && table="$8"
	local mpref="! $umsg""$key = '$val'" ver;
	case "$1" in
		NX_LICENSE)
			ver=$(rematchfn 'NXAGENT - Version ([[:digit:][:punct:]]+)' \
				"$($COMMAND_NXAGENT -version 2>&1)") #'
				# it was "$(strings $COMMAND_NXAGENT)"
			if [ -n "$ver" ]; then
				nval=${NX_LICENSE/\%BACKEND\%/$ver}; err_log=""
			else msg="$mpref: backend version is not found;"$'\n'
			fi
		;;
		COMMAND_GDM_X_SESSION)
			[ ! -x "${val%% *}" ] && {
				nval="/etc/X11/Xsession"; err_log=""
			}
		;;
	esac
	#echo "# $1($3)='$2' advanced checking." #debug
	[ "$val" = "$nval" ] || {
		[ -n "$7" ] || \
			values_update_db $key "value" "$(s2sq "$nval")" "$5" "$table"
		msg+="    $key < '$nval';"$'\n'; val="$nval"
	}
	[ -n "$4" ] || declare -g $key="$val" # expand not user's vals only ???
	[ -n "$msg" ] && {
		[ -n "$err_log" ] && txt_err+="$msg" || txt_warn+="$msg"
	}
}

check_keyvals() {
#args: [username] [ro=""] [table='settings']
	#echo "$(date "+%T.%3N"): $FUNCNAME" #debug
	local table="settings"; [ -n "$3" ] && table="$3"
	local keys=$(keyslst_for_user "$1" "$table") r a ad;
	local value val_type val_depend val_check;
	local err_log dep_for_null vdep tdep nval;
	for key in $keys; do
		#echo "$(date "+%T.%3N"): ----- $key" #debug
		r=$(values_str_from_db $key "value,$ex_rules_cols" "$1" "$table")
		OIFS="$IFS"; IFS='&'; a=($r); IFS="$OIFS";
		value=$(sq2s "${r%%&*}"); val_type=$(sq2s "${a[1]}");
		val_depend=$(sq2s "${a[2]}"); val_check=$(sq2s "${a[3]}");
		err_log=""; stringinstring "error" "$val_check" && \
			{ err_log="1"; val_check=$(trim "${val_check/error/}"); }
		# don't check if depend condition is set and not match
		dep_for_null=""
		[ -n "$val_depend" ] && {
			local first=${val_depend:0:1}
			[ "$first" = "!" ] && { val_depend=${val_depend:1}; dep_for_null="1"; }
			r=$(values_str_from_db $val_depend "value,val_type" "$1" "$table")
			OIFS="$IFS"; IFS='&'; ad=($r); IFS="$OIFS"
			vdep=${ad[0]}; tdep=${ad[1]}
			[ -z "$dep_for_null" ] && {
				case "$tdep" in
					bool)		[ "$vdep" = "1" ] || continue ;;
					string)	[ -n "$vdep" ] || continue ;;
					int)		[ "$vdep" -ne "0" ] || continue ;;
				esac
			} || {
				case "$tdep" in
					bool)		[ "$vdep" = "1" ] && continue ;;
					string)	[ -n "$vdep" ] && continue ;;
					int)		[ "$vdep" -ne "0" ] && continue ;;
				esac
			}
			#echo "$key -> $val_depend ($tdep): '$vdep'  $dep_for_null"
		}
		if stringinstring "rt_expand" "$val_check"; then
			val_check=$(trim "${val_check/rt_expand/}")
		elif [ "$val_type" = "string" ]; then # expand if no rt_expand only
			#nval="${value@P}" # value from db (need to "ORDER BY rowid" in keylist)
			nval="${!key}" # expands/chg defaults by means 'bash source *.conf'
			[ -n "$nval"  -a "$value" != "$nval" ] && {
				[ -n "$2" ] || \
					values_update_db $key "value" "$(s2sq "$nval")" "$1" "$table"
				#txt_warn+="  $key ['$value'] < '$nval';"$'\n'
				txt_warn+="    $key < '$nval';"$'\n'
				value="$nval"
			}
		fi
		stringinstring "advanced" "$val_check" && {
			val_check=$(trim "${val_check/advanced/}")
			check_value_advanced $key "$value" "$val_type" \
				"$val_check" "$1" "$err_log" "$2" "$table"
			continue
		}
		check_value "$key" "$value" "$val_type" \
			"$val_check" "$1" "$err_log" "$2" "$table"
		#echo "$key ($val_type) /$val_check/: $value"
	done
}

parse_defaults() {
#arg: [table='settings']
	local table="settings"; [ -n "$1" ] && table="$1"
	local confd=$SHARED_CONFS/node.conf.def curd=$(pwd)
	echo "Parse default settings in $confd:"; cd $confd
	for fn in *.{cnf,conf}; do [ -r $fn ] && . $fn; done
	for fn in *.{cnf,conf}; do
		[ -r $fn ] || continue; echo -n "  $fn ..";
		conf_buf="$(< $fn)"; parse_conf_buf "" 1 "$table"; echo " done."
	done
	cd $curd
}

parse_settings() {
#arg: [table='settings']
	local table="settings"; [ -n "$1" ] && table="$1"
	local confd="node.conf.d" fn;
	echo "Parse system settings in $NX_ETC_DIR:"; curd=$(pwd)
	cd $NX_ETC_DIR
	for fn in $confd/*.conf node.conf; do [ -r $fn ] && . $fn; done
	for fn in $confd/*.conf node.conf; do
		[ -r $fn ] || continue; echo -n "  $fn ...";
		conf_buf="$(< $fn)"; parse_conf_buf "" "" "$table";
		echo " done."
	done
	cd $curd
}

parse_users_settings() {
#arg: [table='settings']
	local table="settings"; [ -n "$1" ] && table="$1"
	local fn un qstr ukeys key exvals;
	echo "Parse users settings:"; curd=$(pwd)
	cd $NX_ETC_DIR
	for fn in *.conf; do
		[ -r $fn ] || continue; un=${fn//.conf/}
		getent passwd "$un" >/dev/null || continue
		echo -n "  $fn ...";
		. $fn; conf_buf="$(< $fn)"; parse_conf_buf "$un" "" "$table"
		echo -n " check rules for $un ..."
		# user extras setup scrutch
		ukeys=$(keyslst_for_user "$un" "$table")
		for key in $ukeys; do
			echo -n " $key ..."
			qstr="SELECT $ex_rules_cols FROM "$table""
			qstr+=" WHERE user IS NULL AND key='$key' LIMIT 1;"
			exvals=$(qa_dbe0 "$qstr"); exvals=${exvals//\"/}
			values_update_db $key "$ex_rules_cols" "$exvals" "$un" "$table"
		done
	done
	echo " done."
	cd $curd
}

parse_acl() {
#args: filename [table='settings'] userlist grouplist
	local table="settings"; [ -n "$2" ] && table="$2"
	local un=${1##*/} user; un=${un%%\.*}; user="#$un" # user
	local buf l key val val_dep val_chk ns=0 upd_s ins_s v;
	local val_type i ca ans s ov a
	if [ "$un" = "all" ]; then user="@$un"
	elif  ! stringinstring "$un" "$3"; then
		stringinstring "$un" "$4" && user="*$un" # group
	fi
	buf="$(< $1)"
	#echo $1
	while read l; do
		l="$(trim "$l")"; ((ns++))
		[ -n "$l" ] || continue
		[ "${l:0:1}" = "#" ] && continue
		#echo "$l" #debug
		key="$(trim "${l%%\%\%\%*}")"; key=$(s2sq "$key");
		stringinstring "%%%" "$l" && l="$(trim "${l#*\%\%\%}")" || l=""
		val="$(trim "${l%%\%\%\%*}")";
		if [ "$key" != "@shadow@" ]; then
			val=$(s2sq "$val"); val_type=""
			stringinstring "%%%" "$l" && l="$(trim "${l#*\%\%\%}")" || l=""
			val_dep="$(trim "${l%%\%\%\%*}")"; val_dep=$(s2sq "$val_dep")
		else # parse shadow acl
			val=$(trim "${val//\,/ }"); ans=($val); ca=${#ans[@]}
			val=""; val_type=""; val_dep="";
			for ((i=0; i<$ca; i++)) {
				s=${ans[i]}
				[ "${s:0:1}" != "#" ] && ov=0 || { ov=1; s=${s:1}; }
				[ "${s:(-1):1}" != "!" ] && a=1 || { a=0; s=${s::-1}; }
				val+="${val:+,}$s"; val_type+="${val_type:+,}$ov";
				val_dep+="${val_dep:+,}$a";
		}
		fi
		val_chk=$(printf "%04d" $ns)
		#echo "$user; $key; $val; $val_dep; $val_chk"; #continue #debug
		# fake upsert to db
		upd_s="UPDATE $table SET value='$val',val_type='$val_type', \
 val_depend='$val_dep', val_check='$val_chk' WHERE user='$user' AND key='$key';"
		ins_s="INSERT INTO $table(user,key,value,val_type,val_depend,val_check) \
 SELECT '$user','$key','$val','$val_type','$val_dep','$val_chk' \
 WHERE (SELECT Changes()=0);"
		q_dbe "$upd_s" "$ins_s"
		#echo "$upd_s" "$ins_s" #debug
		val=""; val_dep=""; val_chk=""
	done <<< "$buf"
}

parse_acl_dir() {
#arg: [table='settings']
	local table="settings"; [ -n "$1" ] && table="$1"
	local ffn str ulist="" glist=""
	while read str; do
		str=${str%%:*}; ulist+="${ulist:+$' '}$str"
	done <<< "$(getent passwd)"
	while read str; do
		str=${str%%:*}; glist+="${glist:+$' '}$str"
	done <<< "$(getent group)"
	echo "Parse $NX_ACL_DIR:";
	for ffn in $NX_ACL_DIR/*; do
		stringinstring "README" "$ffn" && continue
		[ -r $ffn ] || continue; echo -n " ${ffn##*/} ...";
		parse_acl "$ffn" "$table" "$ulist" "$glist";
	done
	echo " done."
}

open_dbe $$
[ ! -f $sq_settings_fn ] && MKDB="yes"
if [ "$MKDB" = "yes" ]; then
	table="mem.settings"
	echo "$(date "+%T.%3N"): mem.settings mkdb starting"
	q_dbe0 "ATTACH DATABASE ':memory:' AS mem;" \
		"${qs_create_settings/nxsettings/mem}";
	parse_defaults "$table";
	parse_settings "$table";
	parse_users_settings "$table";
	check_keyvals "" "" "$table"
	[ -d "$NX_ACL_DIR" ] && parse_acl_dir "$table"
	[ -n "$txt_warn" ] && { echo; echo "Checking results:"; echo "$txt_warn"; }
	[ -n "$txt_err" ] && { echo; echo "!!! ERRORS !!!"; echo "$txt_err"; }
	txt_err=""; txt_warn=""; txt_info="";
	echo "$(date "+%T.%3N"): nxsettings mkdb attach"
	attach_db "$sq_settings_fn"|| {
		echo "Unable to attach $sq_settings_fn"; exit_proc 1; }
	q_dbe0 "DROP TABLE IF EXISTS nxsettings.settings;" "$qs_create_settings" \
		"INSERT INTO nxsettings.settings($all_cols) SELECT $all_cols FROM $table;"
	echo "$(date "+%T.%3N"): nxsettings mkdb stop"
else
	attach_db "$sq_settings_fn" ro || {
		echo "Unable to attach db file $sq_settings_fn"; exit_proc 1;
	}
	set_vars_from_db
fi
#============================================================================

run_nscd() {
	NSCD="nscd"
	if [ -f /var/run/nscd/nscd.pid ]; then
		$NSCD "$@" 2>/dev/null || true
	fi
}

install_nx() {

	if [ ! -f $NX_ETC_DIR/users.id_dsa ]; then
		$COMMAND_SSH_KEYGEN -f $NX_ETC_DIR/users.id_dsa -t dsa -N ""
	fi

	echo "For backward compatibility to Nomachine, freenx works with dss-keys only."
	echo "Please add string \"PubkeyAcceptedKeyTypes=+ssh-dss\" in sshd_config"
	echo "and ssh_config if required. This is actual for modern openssh servers."

	echo -n "Setting up user and group nx ..."
	useradd -g nx -G utmp -d /var/lib/nxserver/home/ \
		-s /usr/bin/nxserver -c "NX System User" nx 2>/dev/null
	echo "done"

	echo -n "Setting up $NX_SESS_DIR ..."
	chmod 770 $NX_SESS_DIR
	echo "done"

	echo -n "Setting up $NX_LOGFILE ..."
	mkdir -p $(dirname "$NX_LOGFILE")
	touch "$NX_LOGFILE"
	chmod 660 "$NX_LOGFILE"
	echo "done"

	echo -n "Setting up known_hosts and $SSH_AUTHORIZED_KEYS ..."

	SETUP_NX_KEY="no"

	mkdir -p $NX_HOME_DIR/.ssh
	chmod 700 $NX_HOME_DIR/ $NX_HOME_DIR/.ssh

	if [ ! -f $NX_HOME_DIR/.ssh/$SSH_AUTHORIZED_KEYS -o \
		"$SETUP_NOMACHINE_KEY" = "yes" ]; then
		SETUP_NX_KEY="yes"
		if [ "$SETUP_NOMACHINE_KEY" = "yes" ]; then
			cat << EOF >$NX_HOME_DIR/.ssh/$SSH_AUTHORIZED_KEYS
no-port-forwarding,no-agent-forwarding,command="$PATH_BIN/nxserver" ssh-dss AAAAB3NzaC1kc3MAAACBAJe/0DNBePG9dYLWq7cJ0SqyRf1iiZN/IbzrmBvgPTZnBa5FT/0Lcj39sRYt1paAlhchwUmwwIiSZaON5JnJOZ6jKkjWIuJ9MdTGfdvtY1aLwDMpxUVoGwEaKWOyin02IPWYSkDQb6cceuG9NfPulS9iuytdx0zIzqvGqfvudtufAAAAFQCwosRXR2QA8OSgFWSO6+kGrRJKiwAAAIEAjgvVNAYWSrnFD+cghyJbyx60AAjKtxZ0r/Pn9k94Qt2rvQoMnGgt/zU0v/y4hzg+g3JNEmO1PdHh/wDPVOxlZ6Hb5F4IQnENaAZ9uTZiFGqhBO1c8Wwjiq/MFZy3jZaidarLJvVs8EeT4mZcWxwm7nIVD4lRU2wQ2lj4aTPcepMAAACANlgcCuA4wrC+3Cic9CFkqiwO/Rn1vk8dvGuEQqFJ6f6LVfPfRTfaQU7TGVLk2CzY4dasrwxJ1f6FsT8DHTNGnxELPKRuLstGrFY/PR7KeafeFZDf+fJ3mbX5nxrld3wi5titTnX+8s4IKv29HJguPvOK/SI7cjzA+SqNfD7qEo8= root@nettuno
EOF
			chmod 600 $NX_HOME_DIR/.ssh/$SSH_AUTHORIZED_KEYS
			cat << EOF >$NX_HOME_DIR/.ssh/client.id_dsa.key
-----BEGIN DSA PRIVATE KEY-----
MIIBuwIBAAKBgQCXv9AzQXjxvXWC1qu3CdEqskX9YomTfyG865gb4D02ZwWuRU/9
C3I9/bEWLdaWgJYXIcFJsMCIkmWjjeSZyTmeoypI1iLifTHUxn3b7WNWi8AzKcVF
aBsBGiljsop9NiD1mEpA0G+nHHrhvTXz7pUvYrsrXcdMyM6rxqn77nbbnwIVALCi
xFdHZADw5KAVZI7r6QatEkqLAoGBAI4L1TQGFkq5xQ/nIIciW8setAAIyrcWdK/z
5/ZPeELdq70KDJxoLf81NL/8uIc4PoNyTRJjtT3R4f8Az1TsZWeh2+ReCEJxDWgG
fbk2YhRqoQTtXPFsI4qvzBWct42WonWqyyb1bPBHk+JmXFscJu5yFQ+JUVNsENpY
+Gkz3HqTAoGANlgcCuA4wrC+3Cic9CFkqiwO/Rn1vk8dvGuEQqFJ6f6LVfPfRTfa
QU7TGVLk2CzY4dasrwxJ1f6FsT8DHTNGnxELPKRuLstGrFY/PR7KeafeFZDf+fJ3
mbX5nxrld3wi5titTnX+8s4IKv29HJguPvOK/SI7cjzA+SqNfD7qEo8CFDIm1xRf
8xAPsSKs6yZ6j1FNklfu
-----END DSA PRIVATE KEY-----
EOF
			chmod 600 $NX_HOME_DIR/.ssh/client.id_dsa.key
		else
			# generate a new key, backup the old and copy it to $SSH_AUTHORIZED_KEYS
			$PATH_BIN/nxkeygen
		fi
	fi

	if [ ! -f $NX_HOME_DIR/.ssh/known_hosts -a "$BUILD_KNOWN_HOSTS" = "yes" ]; then
		echo -n "127.0.0.1 " > $NX_HOME_DIR/.ssh/known_hosts
		if [ -e "/etc/openssh/ssh_host_rsa_key.pub" ] ; then
			cat /etc/openssh/ssh_host_rsa_key.pub >> \
				$NX_HOME_DIR/.ssh/known_hosts
		else
			[ -e "/etc/ssh/ssh_host_rsa_key.pub" ] && \
				cat /etc/ssh/ssh_host_rsa_key.pub >> $NX_HOME_DIR/.ssh/known_hosts
		fi
	fi

	echo "done"

	echo -n "Setting up permissions ..."
	chown -R nx:nx /var/lib/nxserver
	chown -R nx:nx $NX_SESS_DIR
	chown -R nx:nx $NX_HOME_DIR
	chown nx:nx "$NX_LOGFILE"

	echo "done"
}

test_nx() {
	echo ""
	echo "----> Testing your nxserver connection ..."

	CONNECTION=""
	while read -t 3 line; do
		echo $line

		case "$line" in
			*"HELLO NXSERVER - Version $NX_VERSION"*)
				CONNECTION="yes"
			;;
			*"HELLO NXSERVER - Version"*)
				echo "Warning: Version mismatch. Expected $NX_VERSION got: $line."
				CONNECTION="yes"
			;;
			*"NX> 999 Bye"*)
				break;
			;;
		esac

	done < <(NODE_PUBLICKEY="$NX_HOME_DIR/.ssh/client.id_dsa.key" \
		$PATH_BIN/nxnode-login test-nx nx 22 nxserver --check)

	if [ -z "$CONNECTION" ]; then
		echo "Fatal error: Could not connect to NX Server."
		echo
		echo "Please check your ssh setup:"
		echo ""
		echo "The following are _examples_ of what you might need to check."
		echo ""
		echo "  - Make sure \"nx\" is one of the AllowUsers in sshd_config."
		echo "    (or that the line is outcommented/not there)"
		echo "  - Make sure \"nx\" is one of the AllowGroups in sshd_config."
		echo "    (or that the line is outcommented/not there)"
		echo "  - Make sure your sshd allows public key authentication."
		echo "  - Make sure your sshd is really running on port 22."
		echo "  - Make sure your sshd_config AuthorizedKeysFile in sshd_config is set to $SSH_AUTHORIZED_KEYS."
		echo "    (this should be a filename not a pathname+filename)"
		echo "  - Make sure you allow ssh on localhost, this could come from some"
		echo "    restriction of:"
		echo "      -the tcp wrapper. Then add in /etc/hosts.allow: ALL:localhost"
		echo "      -the iptables. add to it:"
		echo "         $ iptables -A INPUT  -i lo -j ACCEPT"
		echo "         $ iptables -A OUTPUT -o lo -j ACCEPT"
		exit_proc 1
	fi
	echo "<--- done"
	echo ""
}

uninstall_nx() {
	if [ -e "$NX_SESS_DIR" ]; then
		echo -n "Removing session database ..."
		rmdir -p $NX_SESS_DIR 2>/dev/null
		echo "done"
	fi

	if [ -e "$NX_LOGFILE" ]; then
		echo -n "Removing logfile ..."
		rm -f "$NX_LOGFILE" 2>/dev/null
		rmdir -p $(dirname "$NX_LOGFILE") 2>/dev/null
		echo "done"
	fi

	if [ "$PURGE" = "yes" -a -e "$NX_HOME_DIR" ]; then
		echo -n "Removing home directory of special user \"nx\" ..."
		rm -f -r "$NX_HOME_DIR" 2>/dev/null
		rmdir -p $(dirname "$NX_HOME_DIR") 2>/dev/null
		echo "done"
	fi

	if [ "$PURGE" = "yes" -a -e "$NX_ETC_DIR" ]; then
		echo -n "Removing configuration files ..."
		rm -f "$NX_ETC_DIR/users.id_dsa" \
			"$NX_ETC_DIR/users.id_dsa.pub" 2>/dev/null
		for i in `ls $NX_ETC_DIR/*.node.conf 2>/dev/null`; do
			rm -f "$i" 2>/dev/null;
		done
		echo "done"
	fi
}

if [ "$TEST" = "yes" ]; then
	test_nx
	exit_proc 0
fi

if [ "$INSTALL" = "yes" ]; then
	#Perform cleanup?
	[ "$CLEAN" = "yes" ] && uninstall_nx

	[ -f /etc/nscd.conf ] && { run_nscd --invalidate passwd; run_nscd --invalidate group; }
	install_nx

	[ "$AUTOMATIC" = "no" ] && test_nx

	echo "Ok, nxserver is ready."

	if [ "$SETUP_NOMACHINE_KEY" = "no" -a "$SETUP_NX_KEY" = "yes" ]; then
		echo
		echo "Warning: Clients will not be able to login to this server with the standard key."
		echo "         Please replace /usr/NX/share/client.id_dsa.key on all clients you want"
		echo "         to use with $NX_HOME_DIR/.ssh/client.id_dsa.key"
		echo "         and protect it accordingly."
		echo ""
		echo "         Since 1.5.0 you need to import the correct key via the GUI."
		echo
		echo "         If you really want to use the NoMachine key please remove"
		echo "         '$NX_HOME_DIR/.ssh/$SSH_AUTHORIZED_KEYS'"
		echo "         and then run this script with the --setup-nomachine-key parameter."
	fi

	echo "Have Fun!"
elif [ "$UNINSTALL" = "yes" ]; then
	uninstall_nx

	echo "Ok, nxserver is uninstalled"
	echo
	if [ "$PURGE" = "yes" ]; then
		echo "To complete the uninstallation process, remove the FreeNX scripts in $PATH_BIN"
		echo "and the $NX_ETC_DIR/node.conf configuration file."
	else
		echo "To complete the uninstallation process, remove the FreeNX scripts in $PATH_BIN"
		echo
		echo "Configuration files and ssh keys are saved in case you would like to reinstall"
		echo "freenx at a later time. To remove them, please run 'nxsetup --uninstall --purge'"
	fi
fi
exit_proc 0
