4
0
Эх сурвалжийг харах

refactor(stop): use bash /dev/tcp for telnet shutdown, drop expect (#5051)

7 Days to Die (stopmode 8) and Soulmask (stopmode 13) stopped the
server by spawning telnet inside an expect script. That made expect
and telnet hard dependencies for both games and had several problems:

- The telnet password was pasted into the Tcl script, so a password
  containing quotes, brackets, braces, $ or backslashes broke it.
- The address loop overwrote the global telnetip.
- Success meant "the telnet port closed", not "the server exited".
  The tmux session was then killed straight away, which could cut
  off the world save on slow shutdowns.
- Each "is it closed yet" check opened a new session and, with a
  password set, logged in and sent shutdown again.
- A 30 second timeout with the port still open ended silently.

The conversation now runs over a bash /dev/tcp file descriptor:
fn_stop_telnet_open, fn_stop_telnet_expect (wait for text, with a
timeout), fn_stop_telnet_send and fn_stop_telnet_close. The password
is written with printf, so it is never interpreted. Writes run in a
subshell so a connection the server already closed cannot SIGPIPE
the script, and the open is wrapped in a function-level stderr
redirect because a redirect on the exec line itself would be
permanent.

fn_stop_graceful_telnet is now shared by both games. It tries
127.0.0.1, then telnetip and ip, without overwriting telnetip. Then it
waits on check_status.sh like the other graceful stop methods (60s
for 7 Days to Die, 30s for Soulmask) and reports FAIL if the server
is still running, so the existing tmux fallback still applies. Text
matching ignores case.

Soulmask graceful stop was already broken: current builds send no
"Hello:" greeting, and saveworld now replies "[WORLD IS SAVED TO:]
... succeed" rather than "the world is saved.", so the old script
always timed out and fell through to a tmux kill without saving. The
session now sends "saveworld 1" straight away, waits up to 120s for
"world is saved", then sends "quit 1".

telnet and expect are removed from the sdtd and sm rows in every
distro dependency CSV. unbuffer (from expect) is still used for
SteamCMD output when present, as for every other game.

Tested against a live 7 Days to Die V3.3 server: stop with no
password, and stop with the password p[d]'x\y#!{z} (server listening
on all interfaces). Both sent shutdown over telnet, the server logged
the save and OnApplicationQuit, and LinuxGSM reported OK once the
process exited.

Tested against a fresh Soulmask server: the develop expect code timed
out with no save. With this change the server logged saveworld, the
world save and quit 1. An idle server ticks very slowly and had not
exited 120s after quit 1, so the stop still finishes with the tmux
fallback, but now after the world is saved.

Resolves #2201
Daniel Gibbs 3 өдөр өмнө
parent
commit
4ebb89f94e

+ 2 - 2
lgsm/data/almalinux-8.csv

@@ -106,10 +106,10 @@ samp
 sb
 sbots
 scpsl
-sdtd,telnet,expect,libxml2
+sdtd,libxml2
 sf
 sfc,ncurses-libs.i686
-sm,telnet,expect
+sm
 sof2
 sol
 squad

+ 2 - 2
lgsm/data/almalinux-9.csv

@@ -106,10 +106,10 @@ samp
 sb
 sbots
 scpsl
-sdtd,telnet,expect,libxml2
+sdtd,libxml2
 sf
 sfc,ncurses-libs.i686
-sm,telnet,expect
+sm
 sof2
 sol
 squad

+ 2 - 2
lgsm/data/centos-7.csv

@@ -106,10 +106,10 @@ samp
 sb
 sbots
 scpsl
-sdtd,telnet,expect,libxml2
+sdtd,libxml2
 sf
 sfc,ncurses-libs.i686
-sm,telnet,expect
+sm
 sof2
 sol
 squad

+ 2 - 2
lgsm/data/centos-8.csv

@@ -106,10 +106,10 @@ samp
 sb
 sbots
 scpsl
-sdtd,telnet,expect,libxml2
+sdtd,libxml2
 sf
 sfc,ncurses-libs.i686
-sm,telnet,expect
+sm
 sof2
 sol
 squad

+ 2 - 2
lgsm/data/centos-9.csv

@@ -106,10 +106,10 @@ samp
 sb
 sbots
 scpsl
-sdtd,telnet,expect,libxml2
+sdtd,libxml2
 sf
 sfc,ncurses-libs.i686
-sm,telnet,expect
+sm
 sof2
 sol
 squad

+ 2 - 2
lgsm/data/debian-10.csv

@@ -106,10 +106,10 @@ samp
 sb
 sbots
 scpsl,mono-complete
-sdtd,telnet,expect,libxml2-utils
+sdtd,libxml2-utils
 sf
 sfc,libtinfo5:i386
-sm,telnet,expect
+sm
 sof2
 sol
 squad

+ 2 - 2
lgsm/data/debian-11.csv

@@ -107,10 +107,10 @@ samp
 sb
 sbots
 scpsl,mono-complete
-sdtd,telnet,expect,libxml2-utils
+sdtd,libxml2-utils
 sf
 sfc,libtinfo5:i386
-sm,telnet,expect
+sm
 sof2
 sol
 squad

+ 2 - 2
lgsm/data/debian-12.csv

@@ -107,10 +107,10 @@ samp
 sb
 sbots
 scpsl,mono-complete
-sdtd,telnet,expect,libxml2-utils
+sdtd,libxml2-utils
 sf
 sfc,libtinfo5:i386
-sm,telnet,expect
+sm
 sof2
 sol
 squad

+ 2 - 2
lgsm/data/debian-13.csv

@@ -107,10 +107,10 @@ samp
 sb
 sbots
 scpsl,mono-complete,libicu76
-sdtd,telnet,expect,libxml2
+sdtd,libxml2
 sf
 sfc
-sm,telnet,expect
+sm
 sof2
 sol
 squad

+ 2 - 2
lgsm/data/debian-9.csv

@@ -107,10 +107,10 @@ samp
 sb
 sbots
 scpsl,mono-complete
-sdtd,telnet,expect,libxml2-utils
+sdtd,libxml2-utils
 sf
 sfc,libtinfo5:i386
-sm,telnet,expect
+sm
 sof2
 sol
 squad

+ 2 - 2
lgsm/data/rhel-7.csv

@@ -106,10 +106,10 @@ samp
 sb
 sbots
 scpsl
-sdtd,telnet,expect,libxml2
+sdtd,libxml2
 sf
 sfc,ncurses-libs.i686
-sm,telnet,expect
+sm
 sof2
 sol
 squad

+ 2 - 2
lgsm/data/rhel-8.csv

@@ -106,10 +106,10 @@ samp
 sb
 sbots
 scpsl
-sdtd,telnet,expect,libxml2
+sdtd,libxml2
 sf
 sfc,ncurses-libs.i686
-sm,telnet,expect
+sm
 sof2
 sol
 squad

+ 2 - 2
lgsm/data/rhel-9.csv

@@ -106,10 +106,10 @@ samp
 sb
 sbots
 scpsl
-sdtd,telnet,expect,libxml2
+sdtd,libxml2
 sf
 sfc,ncurses-libs.i686
-sm,telnet,expect
+sm
 sof2
 sol
 squad

+ 2 - 2
lgsm/data/rocky-8.csv

@@ -106,10 +106,10 @@ samp
 sb
 sbots
 scpsl
-sdtd,telnet,expect,libxml2
+sdtd,libxml2
 sf
 sfc,ncurses-libs.i686
-sm,telnet,expect
+sm
 sof2
 sol
 squad

+ 2 - 2
lgsm/data/rocky-9.csv

@@ -106,10 +106,10 @@ samp
 sb
 sbots
 scpsl
-sdtd,telnet,expect,libxml2
+sdtd,libxml2
 sf
 sfc,ncurses-libs.i686
-sm,telnet,expect
+sm
 sof2
 sol
 squad

+ 2 - 2
lgsm/data/ubuntu-16.04.csv

@@ -107,10 +107,10 @@ samp
 sb
 sbots
 scpsl,mono-complete
-sdtd,telnet,expect,libxml2-utils
+sdtd,libxml2-utils
 sf
 sfc,libtinfo5:i386
-sm,telnet,expect
+sm
 sof2
 sol
 squad

+ 2 - 2
lgsm/data/ubuntu-18.04.csv

@@ -107,10 +107,10 @@ samp
 sb
 sbots
 scpsl,mono-complete
-sdtd,telnet,expect,libxml2-utils
+sdtd,libxml2-utils
 sf
 sfc,libtinfo5:i386
-sm,telnet,expect
+sm
 sof2
 sol
 squad

+ 2 - 2
lgsm/data/ubuntu-20.04.csv

@@ -107,10 +107,10 @@ samp
 sb
 sbots
 scpsl,mono-complete
-sdtd,telnet,expect,libxml2-utils
+sdtd,libxml2-utils
 sf
 sfc,libtinfo5:i386
-sm,telnet,expect
+sm
 sof2
 sol
 squad

+ 2 - 2
lgsm/data/ubuntu-22.04.csv

@@ -107,10 +107,10 @@ samp
 sb
 sbots
 scpsl,mono-complete
-sdtd,telnet,expect,libxml2-utils
+sdtd,libxml2-utils
 sf
 sfc,libtinfo5:i386
-sm,telnet,expect
+sm
 sof2
 sol
 squad

+ 2 - 2
lgsm/data/ubuntu-23.04.csv

@@ -107,10 +107,10 @@ samp
 sb
 sbots
 scpsl,mono-complete
-sdtd,telnet,expect,libxml2-utils
+sdtd,libxml2-utils
 sf
 sfc,libtinfo5:i386
-sm,telnet,expect
+sm
 sof2
 sol
 squad

+ 2 - 2
lgsm/data/ubuntu-23.10.csv

@@ -107,10 +107,10 @@ samp
 sb
 sbots
 scpsl,mono-complete
-sdtd,telnet,expect,libxml2-utils
+sdtd,libxml2-utils
 sf
 sfc,libtinfo5:i386
-sm,telnet,expect
+sm
 sof2
 sol
 squad

+ 2 - 2
lgsm/data/ubuntu-24.04.csv

@@ -106,10 +106,10 @@ samp
 sb
 sbots
 scpsl,mono-complete
-sdtd,telnet,expect,libxml2-utils
+sdtd,libxml2-utils
 sf
 sfc
-sm,telnet,expect
+sm
 sof2
 sol
 squad

+ 2 - 2
lgsm/data/ubuntu-26.04.csv

@@ -106,10 +106,10 @@ samp
 sb
 sbots
 scpsl,mono-complete
-sdtd,telnet,expect,libxml2-utils
+sdtd,libxml2-utils
 sf
 sfc
-sm,telnet,expect
+sm
 sof2
 sol
 squad

+ 119 - 174
lgsm/modules/command_stop.sh

@@ -292,196 +292,141 @@ fn_stop_graceful_goldsrc() {
 	fi
 }
 
-# telnet command for sdtd graceful shutdown.
-fn_stop_graceful_sdtd_telnet() {
-	if [ -z "${telnetpassword}" ] || [ "${telnetpassword}" == "NOT SET" ]; then
-		sdtdtelnetshutdown=$(expect -c '
-		proc abort {} {
-			puts "Timeout or EOF\n"
-			exit 1
-		}
-		spawn telnet '"${telnetip}"' '"${telnetport}"'
-		expect {
-			"session."  { send "shutdown\r" }
-			default         abort
-		}
-		expect { eof }
-		puts "Completed.\n"
-		')
-	else
-		sdtdtelnetshutdown=$(expect -c '
-		proc abort {} {
-			puts "Timeout or EOF\n"
-			exit 1
-		}
-		spawn telnet '"${telnetip}"' '"${telnetport}"'
-		expect {
-			"password:"     { send "'"${telnetpassword}"'\r" }
-			default         abort
-		}
-		expect {
-			"session."  { send "shutdown\r" }
-			default         abort
-		}
-		expect { eof }
-		puts "Completed.\n"
-		')
+# Opens a telnet connection using bash /dev/tcp.
+# Usage: fn_stop_telnet_open "ip"
+fn_stop_telnet_open() {
+	telnetoutput=""
+	exec {telnetfd}<> "/dev/tcp/${1}/${telnetport}"
+} 2> /dev/null
+
+# Closes the telnet connection.
+fn_stop_telnet_close() {
+	if [ -n "${telnetfd}" ]; then
+		exec {telnetfd}>&-
+		unset telnetfd
 	fi
 }
 
-# Attempts graceful shutdown of 7 Days To Die using telnet.
-fn_stop_graceful_sdtd() {
-	fn_print_dots "Graceful: telnet"
-	fn_script_log_info "Graceful: telnet"
-	if [ "${telnetenabled}" == "false" ]; then
-		fn_print_info_nl "Graceful: telnet: DISABLED: Enable in ${servercfg}"
-	elif [ "$(command -v expect 2> /dev/null)" ]; then
-		# Tries to shutdown with both localhost and server IP.
-		for telnetip in 127.0.0.1 ${ip}; do
-			fn_print_dots "Graceful: telnet: ${telnetip}:${telnetport}"
-			fn_script_log_info "Graceful: telnet: ${telnetip}:${telnetport}"
-			fn_stop_graceful_sdtd_telnet
-			completed=$(echo -en "\n ${sdtdtelnetshutdown}" | grep "Completed.")
-			refused=$(echo -en "\n ${sdtdtelnetshutdown}" | grep "Timeout or EOF")
-			if [ "${refused}" ]; then
-				fn_print_error "Graceful: telnet: ${telnetip}:${telnetport} : "
-				fn_print_fail_eol_nl
-				fn_script_log_error "Graceful: telnet:  ${telnetip}:${telnetport} : FAIL"
-			elif [ "${completed}" ]; then
-				break
-			fi
-		done
+# Reads from the telnet connection until the text is received, ignoring case.
+# Fails if the connection closes or nothing is received for the timeout.
+# Usage: fn_stop_telnet_expect "text" "timeout_in_seconds"
+fn_stop_telnet_expect() {
+	local char
+	while IFS= read -r -N 1 -t "${2:-10}" -u "${telnetfd}" char; do
+		telnetoutput+="${char}"
+		if [[ "${telnetoutput,,}" == *"${1,,}"* ]]; then
+			return 0
+		fi
+	done
+	return 1
+}
 
-		# If telnet shutdown was successful will use telnet again to check
-		# the connection has closed, confirming that the tmux session can now be killed.
-		if [ "${completed}" ]; then
-			for seconds in {1..30}; do
-				fn_stop_graceful_sdtd_telnet
-				refused=$(echo -en "\n ${sdtdtelnetshutdown}" | grep "Timeout or EOF")
-				if [ "${refused}" ]; then
-					fn_print_ok "Graceful: telnet: ${telnetip}:${telnetport} : "
-					fn_print_ok_eol_nl
-					fn_script_log_pass "Graceful: telnet: ${telnetip}:${telnetport} : ${seconds} seconds"
-					if [ "${statusalert}" == "on" ] && [ "${firstcommandname}" == "STOP" ]; then
-						alert="stopped"
-						alert.sh
-					fi
-					break
-				fi
-				fn_sleep_time_1
-				fn_print_dots "Graceful: telnet: ${seconds}"
-			done
-		# If telnet shutdown fails tmux shutdown will be used, this risks loss of world save.
-		else
-			if [ "${refused}" ]; then
-				fn_print_error "Graceful: telnet: "
-				fn_print_fail_eol_nl
-				fn_script_log_error "Graceful: telnet: ${telnetip}:${telnetport} : FAIL"
-			else
-				fn_print_error_nl "Graceful: telnet: Unknown error"
-				fn_script_log_error "Graceful: telnet: Unknown error"
-			fi
-			echo -en "\n" | tee -a "${lgsmlog}"
-			echo -en "Telnet output:" | tee -a "${lgsmlog}"
-			echo -en "\n ${sdtdtelnetshutdown}" | tee -a "${lgsmlog}"
-			echo -en "\n\n" | tee -a "${lgsmlog}"
+# Reads from the telnet connection until the server closes it.
+fn_stop_telnet_read_to_close() {
+	local char
+	while IFS= read -r -N 1 -t 10 -u "${telnetfd}" char; do
+		telnetoutput+="${char}"
+	done
+}
+
+# Sends a line to the telnet connection.
+# Usage: fn_stop_telnet_send "text"
+fn_stop_telnet_send() {
+	(printf '%s\r\n' "${1}" >&"${telnetfd}") 2> /dev/null
+}
+
+# Telnet session for 7 Days to Die graceful shutdown.
+# Usage: fn_stop_graceful_sdtd_telnet "ip"
+fn_stop_graceful_sdtd_telnet() {
+	fn_stop_telnet_open "${1}" || return 1
+	if [ -n "${telnetpassword}" ] && [ "${telnetpassword}" != "NOT SET" ]; then
+		if ! fn_stop_telnet_expect "password:" || ! fn_stop_telnet_send "${telnetpassword}"; then
+			fn_stop_telnet_close
+			return 1
 		fi
-	else
-		fn_print_warn "Graceful: telnet: expect not installed: "
-		fn_print_fail_eol_nl
-		fn_script_log_warn "Graceful: telnet: expect not installed: FAIL"
 	fi
+	if fn_stop_telnet_expect "session." && fn_stop_telnet_send "shutdown"; then
+		fn_stop_telnet_read_to_close
+		fn_stop_telnet_close
+		return 0
+	fi
+	fn_stop_telnet_close
+	return 1
 }
 
-# Attempts graceful shutdown of Soulmask using telnet.
-fn_stop_graceful_sm() {
+# Telnet session for Soulmask graceful shutdown.
+# Usage: fn_stop_graceful_sm_telnet "ip"
+fn_stop_graceful_sm_telnet() {
+	fn_stop_telnet_open "${1}" || return 1
+	if fn_stop_telnet_send "saveworld 1" && fn_stop_telnet_expect "world is saved" 120 && fn_stop_telnet_send "quit 1"; then
+		fn_stop_telnet_read_to_close
+		fn_stop_telnet_close
+		return 0
+	fi
+	fn_stop_telnet_close
+	return 1
+}
+
+# Attempts graceful shutdown using a telnet session.
+# Tries localhost first, then the telnet and server IPs.
+# Usage: fn_stop_graceful_telnet "session_function" "timeout_in_seconds"
+# e.g.: fn_stop_graceful_telnet "fn_stop_graceful_sdtd_telnet" "60"
+fn_stop_graceful_telnet() {
 	fn_print_dots "Graceful: telnet"
 	fn_script_log_info "Graceful: telnet"
 	if [ "${telnetenabled}" == "false" ]; then
 		fn_print_info_nl "Graceful: telnet: DISABLED: Enable in ${servercfg}"
-	elif [ "$(command -v expect 2> /dev/null)" ]; then
-		# Tries to shutdown with both localhost and server IP.
-		for telnetip in 127.0.0.1 ${ip}; do
-			fn_print_dots "Graceful: telnet: ${telnetip}:${telnetport}"
-			fn_script_log_info "Graceful: telnet: ${telnetip}:${telnetport}"
-			fn_stop_graceful_sm_telnet
-			completed=$(echo -en "\n ${smtelnetshutdown}" | grep "Completed.")
-			refused=$(echo -en "\n ${smtelnetshutdown}" | grep "Timeout or EOF")
-			if [ "${refused}" ]; then
-				fn_print_error "Graceful: telnet: ${telnetip}:${telnetport} : "
-				fn_print_fail_eol_nl
-				fn_script_log_error "Graceful: telnet:  ${telnetip}:${telnetport} : FAIL"
-			elif [ "${completed}" ]; then
-				break
-			fi
-		done
-
-		# If telnet shutdown was successful will use telnet again to check
-		# the connection has closed, confirming that the tmux session can now be killed.
-		if [ "${completed}" ]; then
-			for seconds in {1..30}; do
-				fn_stop_graceful_sm_telnet
-				refused=$(echo -en "\n ${smtelnetshutdown}" | grep "Timeout or EOF")
-				if [ "${refused}" ]; then
-					fn_print_ok "Graceful: telnet: ${telnetip}:${telnetport} : "
-					fn_print_ok_eol_nl
-					fn_script_log_pass "Graceful: telnet: ${telnetip}:${telnetport} : ${seconds} seconds"
-					if [ "${statusalert}" == "on" ] && [ "${firstcommandname}" == "STOP" ]; then
-						alert="stopped"
-						alert.sh
-					fi
-					break
-				fi
-				fn_sleep_time_1
-				fn_print_dots "Graceful: telnet: ${seconds}"
-			done
-		# If telnet shutdown fails, show it and stop
-		else
-			if [ "${refused}" ]; then
-				fn_print_error "Graceful: telnet: "
-				fn_print_fail_eol_nl
-				fn_script_log_error "Graceful: telnet: ${telnetip}:${telnetport} : FAIL"
-			else
-				fn_print_error_nl "Graceful: telnet: Unknown error"
-				fn_script_log_error "Graceful: telnet: Unknown error"
+		fn_script_log_info "Graceful: telnet: DISABLED"
+		return
+	fi
+	local telnetaddresses=("127.0.0.1")
+	local telnetaddress telnetsent
+	for telnetaddress in "${telnetip[@]}" ${ip}; do
+		if [ -n "${telnetaddress}" ] && [[ " ${telnetaddresses[*]} " != *" ${telnetaddress} "* ]]; then
+			telnetaddresses+=("${telnetaddress}")
+		fi
+	done
+	for telnetaddress in "${telnetaddresses[@]}"; do
+		fn_print_dots "Graceful: telnet: ${telnetaddress}:${telnetport}"
+		fn_script_log_info "Graceful: telnet: ${telnetaddress}:${telnetport}"
+		if "${1}" "${telnetaddress}"; then
+			telnetsent=1
+			break
+		fi
+		fn_print_error "Graceful: telnet: ${telnetaddress}:${telnetport}: "
+		fn_print_fail_eol_nl
+		fn_script_log_error "Graceful: telnet: ${telnetaddress}:${telnetport}: FAIL"
+		if [ -n "${telnetoutput}" ]; then
+			fn_script_log_error "Graceful: telnet: output: ${telnetoutput//$'\r'/}"
+		fi
+	done
+	if [ -z "${telnetsent}" ]; then
+		return
+	fi
+	# Waits up to ${seconds} seconds giving the server time to shutdown gracefully.
+	for ((seconds = 1; seconds <= ${2}; seconds++)); do
+		check_status.sh
+		if [ "${status}" == "0" ]; then
+			fn_print_ok "Graceful: telnet: ${telnetaddress}:${telnetport}: ${seconds}"
+			fn_print_ok_eol_nl
+			fn_script_log_pass "Graceful: telnet: ${telnetaddress}:${telnetport}: OK: ${seconds} seconds"
+			if [ "${statusalert}" == "on" ] && [ "${firstcommandname}" == "STOP" ]; then
+				alert="stopped"
+				alert.sh
 			fi
-			echo -en "\n" | tee -a "${lgsmlog}"
-			echo -en "Telnet output:" | tee -a "${lgsmlog}"
-			echo -en "\n ${smtelnetshutdown}" | tee -a "${lgsmlog}"
-			echo -en "\n\n" | tee -a "${lgsmlog}"
+			break
 		fi
-	else
-		fn_print_warn "Graceful: telnet: expect not installed: "
+		fn_sleep_time_1
+		fn_print_dots "Graceful: telnet: ${telnetaddress}:${telnetport}: ${seconds}"
+	done
+	check_status.sh
+	if [ "${status}" != "0" ]; then
+		fn_print_error "Graceful: telnet: ${telnetaddress}:${telnetport}: "
 		fn_print_fail_eol_nl
-		fn_script_log_warn "Graceful: telnet: expect not installed: FAIL"
+		fn_script_log_error "Graceful: telnet: ${telnetaddress}:${telnetport}: FAIL"
 	fi
 }
 
-# telnet command for soulmask graceful shutdown.
-fn_stop_graceful_sm_telnet() {
-	smtelnetshutdown=$(expect -c '
-		proc abort {} {
-			puts "Timeout or EOF\n"
-			exit 1
-		}
-		spawn telnet '"${telnetip}"' '"${telnetport}"'
-		expect {
-			"Hello:"	{ send "saveworld 1\r" }
-			default	abort
-		}
-		expect {
-			"the world is saved."	{ send "quit 1\r" }
-			default	abort
-		}
-		expect {
-			"World is closing..."	{}
-			default	abort
-		}
-		expect { eof }
-		puts "Completed.\n"
-	')
-}
-
 # Attempts graceful shutdown by sending /save /stop.
 fn_stop_graceful_avorion() {
 	fn_print_dots "Graceful: /save /stop"
@@ -575,7 +520,7 @@ fn_stop_graceful_select() {
 	elif [ "${stopmode}" == "7" ]; then
 		fn_stop_graceful_cmd "exit" 30
 	elif [ "${stopmode}" == "8" ]; then
-		fn_stop_graceful_sdtd
+		fn_stop_graceful_telnet "fn_stop_graceful_sdtd_telnet" "60"
 	elif [ "${stopmode}" == "9" ]; then
 		fn_stop_graceful_goldsrc
 	elif [ "${stopmode}" == "10" ]; then
@@ -585,7 +530,7 @@ fn_stop_graceful_select() {
 	elif [ "${stopmode}" == "12" ]; then
 		fn_stop_graceful_cmd "shutdown" 30
 	elif [ "${stopmode}" == "13" ]; then
-		fn_stop_graceful_sm
+		fn_stop_graceful_telnet "fn_stop_graceful_sm_telnet" "30"
 	elif [ "${stopmode}" == "14" ]; then
 		fn_stop_graceful_rwr
 	elif [ "${stopmode}" == "15" ]; then