Found a problem with the no screen reader path that could have a user type password when unnecessary and potentially leak the password via speech if key echo is enabled.
This commit is contained in:
@@ -8,9 +8,9 @@ import string
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
import pwd
|
||||
import threading
|
||||
|
||||
|
||||
stormuxAdmin = ("storm",)
|
||||
@@ -24,16 +24,41 @@ pingIntervalSeconds = 180
|
||||
pingCount = 5
|
||||
maxWormholeFailures = 3
|
||||
|
||||
sudoRefreshIntervalSeconds = 60
|
||||
sudoKeepaliveThread = None
|
||||
sudoKeepaliveStop = threading.Event()
|
||||
sudoSessionPrepared = False
|
||||
sudoAuthenticationError = ""
|
||||
|
||||
|
||||
def speak_message(message):
|
||||
def speak_message(
|
||||
message,
|
||||
fallbackFile=None,
|
||||
wait=False,
|
||||
applicationName=None,
|
||||
connectionName=None,
|
||||
):
|
||||
command = ["spd-say"]
|
||||
if applicationName:
|
||||
command += ["-N", applicationName]
|
||||
if connectionName:
|
||||
command += ["-n", connectionName]
|
||||
if wait:
|
||||
command.append("-w")
|
||||
command.append(message)
|
||||
try:
|
||||
subprocess.run(["spd-say", message], check=False)
|
||||
result = subprocess.run(
|
||||
command,
|
||||
check=False,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
start_new_session=wait,
|
||||
)
|
||||
except FileNotFoundError:
|
||||
print(message, flush=True)
|
||||
|
||||
print(message, file=fallbackFile or sys.stdout, flush=True)
|
||||
return
|
||||
if result.returncode != 0:
|
||||
print(message, file=fallbackFile or sys.stdout, flush=True)
|
||||
|
||||
def say_or_print(message, useSpeech):
|
||||
if useSpeech:
|
||||
@@ -53,16 +78,76 @@ def run_command(command, inputText=None, check=False, env=None):
|
||||
)
|
||||
|
||||
|
||||
def run_terminal_sudo_authentication():
|
||||
speak_message(
|
||||
"Your sudo password will be requested next. Do not type your password yet. "
|
||||
"If your microphone can carry computer speech into the support meeting, mute it "
|
||||
"before continuing and unmute it afterward. Press Enter to continue. After you "
|
||||
"press Enter, wait one second, then type your password and press Enter. If you "
|
||||
"hear any part of your password spoken, stop immediately, do not finish it, and "
|
||||
"tell the support assistant.",
|
||||
wait=True,
|
||||
applicationName="sas",
|
||||
connectionName=f"sudo-password-{os.getpid()}",
|
||||
)
|
||||
input()
|
||||
return run_command(["sudo", "-v"])
|
||||
|
||||
|
||||
def authenticate_sudo(useSpeech):
|
||||
global sudoAuthenticationError
|
||||
sudoAuthenticationError = ""
|
||||
command = ["sudo"]
|
||||
if useSpeech:
|
||||
result = run_terminal_sudo_authentication()
|
||||
else:
|
||||
result = run_command(command + ["-v"])
|
||||
if result.returncode != 0:
|
||||
sudoAuthenticationError = concise_command_error(result)
|
||||
return False
|
||||
start_sudo_keepalive()
|
||||
return True
|
||||
|
||||
|
||||
def prepare_sudo_session(useSpeech):
|
||||
global sudoAuthenticationError, sudoSessionPrepared
|
||||
if os.geteuid() == 0:
|
||||
sudoSessionPrepared = True
|
||||
return True
|
||||
invalidateResult = run_command(["sudo", "-k"])
|
||||
if invalidateResult.returncode != 0:
|
||||
sudoAuthenticationError = concise_command_error(invalidateResult)
|
||||
return False
|
||||
probeEnv = os.environ.copy()
|
||||
probeEnv["LC_ALL"] = "C"
|
||||
probeEnv["LANG"] = "C"
|
||||
probeResult = run_command(["sudo", "-n", "-v"], env=probeEnv)
|
||||
if probeResult.returncode == 0:
|
||||
start_sudo_keepalive()
|
||||
sudoSessionPrepared = True
|
||||
return True
|
||||
probeError = concise_command_error(probeResult)
|
||||
if "password is required" not in probeError.lower():
|
||||
sudoAuthenticationError = probeError
|
||||
return False
|
||||
if not authenticate_sudo(useSpeech=useSpeech):
|
||||
return False
|
||||
sudoSessionPrepared = True
|
||||
return True
|
||||
|
||||
|
||||
def ensure_sudo(useSpeech):
|
||||
if os.geteuid() == 0:
|
||||
return True
|
||||
if useSpeech:
|
||||
speak_message("Sudo password required. Please enter your password now.")
|
||||
result = run_command(["sudo", "-v"])
|
||||
if result.returncode == 0:
|
||||
start_sudo_keepalive()
|
||||
return True
|
||||
return False
|
||||
if sudoSessionPrepared:
|
||||
return run_command(["sudo", "-n", "-v"]).returncode == 0
|
||||
return authenticate_sudo(useSpeech)
|
||||
|
||||
|
||||
def sudo_command_prefix():
|
||||
if sudoSessionPrepared:
|
||||
return ["sudo", "-n"]
|
||||
return ["sudo"]
|
||||
|
||||
|
||||
def start_sudo_keepalive():
|
||||
@@ -71,7 +156,7 @@ def start_sudo_keepalive():
|
||||
return
|
||||
|
||||
def keepalive_loop():
|
||||
while not sudoKeepaliveStop.wait(240):
|
||||
while not sudoKeepaliveStop.wait(sudoRefreshIntervalSeconds):
|
||||
run_command(["sudo", "-n", "-v"])
|
||||
|
||||
sudoKeepaliveThread = threading.Thread(target=keepalive_loop, daemon=True)
|
||||
@@ -84,7 +169,7 @@ def run_privileged(command, useSpeech, inputText=None, check=True):
|
||||
else:
|
||||
if not ensure_sudo(useSpeech):
|
||||
raise RuntimeError("sudo authentication failed")
|
||||
fullCommand = ["sudo"] + command
|
||||
fullCommand = sudo_command_prefix() + command
|
||||
return run_command(fullCommand, inputText=inputText, check=check)
|
||||
|
||||
|
||||
@@ -94,7 +179,7 @@ def run_as_user(userName, command, useSpeech, check=True):
|
||||
else:
|
||||
if not ensure_sudo(useSpeech):
|
||||
raise RuntimeError("sudo authentication failed")
|
||||
fullCommand = ["sudo", "-u", userName, "-H"] + command
|
||||
fullCommand = sudo_command_prefix() + ["-u", userName, "-H"] + command
|
||||
return run_command(fullCommand, check=check)
|
||||
|
||||
|
||||
@@ -102,6 +187,15 @@ def command_error_text(result):
|
||||
return (result.stderr or result.stdout or "").strip()
|
||||
|
||||
|
||||
def concise_command_error(result):
|
||||
errorText = " ".join(command_error_text(result).split())
|
||||
if not errorText:
|
||||
return "sudo returned no error details"
|
||||
if len(errorText) > 400:
|
||||
return errorText[:400] + "..."
|
||||
return errorText
|
||||
|
||||
|
||||
def service_is_active(serviceName):
|
||||
result = run_command(["systemctl", "is-active", "--quiet", serviceName])
|
||||
return result.returncode == 0
|
||||
@@ -373,6 +467,15 @@ def main():
|
||||
answer = input().strip().lower()
|
||||
useSpeech = answer in ("n", "no")
|
||||
|
||||
if useSpeech and not prepare_sudo_session(useSpeech=True):
|
||||
failureMessage = (
|
||||
f"Sudo authentication failed. {sudoAuthenticationError}. "
|
||||
"The support session was not started."
|
||||
)
|
||||
say_or_print(failureMessage, True)
|
||||
print(failureMessage, flush=True)
|
||||
return 1
|
||||
|
||||
shouldRemoveUser = False
|
||||
cleanupDone = False
|
||||
tempDir = tempfile.mkdtemp(prefix="sas-ii-")
|
||||
@@ -425,7 +528,10 @@ def main():
|
||||
useSpeech,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
say_or_print(
|
||||
"Cleanup warning: failed to remove sas user. Please remove it manually.",
|
||||
useSpeech,
|
||||
)
|
||||
|
||||
sudoKeepaliveStop.set()
|
||||
if sudoKeepaliveThread:
|
||||
@@ -615,7 +721,7 @@ def main():
|
||||
privateKeyPath,
|
||||
f"{sasUser}@{remoteHost}",
|
||||
]
|
||||
sshCommand = ["sudo", "-u", sasUser, "-H"] + sshCommand
|
||||
sshCommand = sudo_command_prefix() + ["-u", sasUser, "-H"] + sshCommand
|
||||
sshProcess = subprocess.Popen(sshCommand)
|
||||
sshProcess.wait()
|
||||
say_or_print("The support connection ended. Cleaning up.", useSpeech)
|
||||
@@ -649,5 +755,13 @@ def remove_tree(path):
|
||||
pass
|
||||
|
||||
|
||||
def command_line_main(arguments=None):
|
||||
arguments = sys.argv[1:] if arguments is None else arguments
|
||||
if arguments:
|
||||
print("Usage: sas", file=sys.stderr)
|
||||
return 2
|
||||
return main()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
sys.exit(command_line_main())
|
||||
|
||||
Reference in New Issue
Block a user