From 2dd7c3b939328cafae2ccc6a05d54415edb69bbd Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 21 Feb 2022 10:50:39 +0530 Subject: [PATCH] More work on ssh bootstrap --- kittens/ssh/main.py | 23 ++++++++- kitty/screen.c | 4 +- kitty/window.py | 5 ++ kitty_tests/ssh.py | 12 +++++ shell-integration/ssh/bootstrap.sh | 79 ++++++++++++++++++++++++++++++ 5 files changed, 119 insertions(+), 4 deletions(-) create mode 100644 shell-integration/ssh/bootstrap.sh diff --git a/kittens/ssh/main.py b/kittens/ssh/main.py index 67059643b..bc946c885 100644 --- a/kittens/ssh/main.py +++ b/kittens/ssh/main.py @@ -1,16 +1,37 @@ #!/usr/bin/env python3 # License: GPL v3 Copyright: 2018, Kovid Goyal +import io import os import shlex import subprocess import sys +import tarfile from contextlib import suppress from typing import List, NoReturn, Optional, Set, Tuple -from .completion import ssh_options, complete +from kitty.constants import shell_integration_dir, terminfo_dir from kitty.utils import SSHConnectionData +from .completion import complete, ssh_options + + +def make_tarfile() -> bytes: + + def filter_files(tarinfo: tarfile.TarInfo) -> Optional[tarfile.TarInfo]: + if tarinfo.name.endswith('ssh/bootstrap.sh'): + return None + tarinfo.uname = tarinfo.gname = 'kitty' + tarinfo.uid = tarinfo.gid = 0 + return tarinfo + + buf = io.BytesIO() + with tarfile.open(mode='w:bz2', fileobj=buf, encoding='utf-8') as tf: + tf.add(shell_integration_dir, arcname='.local/share/kitty-ssh-kitten/shell-integration', filter=filter_files) + tf.add(terminfo_dir, arcname='.terminfo', filter=filter_files) + return buf.getvalue() + + SHELL_SCRIPT = '''\ #!/bin/sh # macOS ships with an ancient version of tic that cannot read from stdin, so we diff --git a/kitty/screen.c b/kitty/screen.c index 453608721..bee895b61 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -2106,9 +2106,7 @@ screen_handle_print(Screen *self, PyObject *msg) { void screen_handle_echo(Screen *self, PyObject *msg) { - Py_ssize_t sz; - const char *bytes = PyUnicode_AsUTF8AndSize(msg, &sz); - write_to_child(self, bytes, sz); + CALLBACK("handle_remote_echo", "O", msg); } void diff --git a/kitty/window.py b/kitty/window.py index 10a4087cb..a268979b5 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -875,6 +875,11 @@ class Window: def handle_remote_cmd(self, cmd: str) -> None: get_boss().handle_remote_cmd(cmd, self) + def handle_remote_echo(self, msg: bytes) -> None: + from base64 import standard_b64decode + data = standard_b64decode(msg) + self.write_to_child(data) + def handle_remote_print(self, msg: bytes) -> None: text = process_remote_print(msg) print(text, end='', file=sys.stderr) diff --git a/kitty_tests/ssh.py b/kitty_tests/ssh.py index b5aa0cef4..3ae5b171e 100644 --- a/kitty_tests/ssh.py +++ b/kitty_tests/ssh.py @@ -3,6 +3,8 @@ import os +import shutil +import tempfile from kittens.ssh.main import get_connection_data from kitty.utils import SSHConnectionData @@ -35,3 +37,13 @@ print(' '.join(map(str, buf)))'''), lines=13, cols=77) t('ssh un@ip -i ident -p34', host='un@ip', port=34, identity_file='ident') t('ssh un@ip -iident -p34', host='un@ip', port=34, identity_file='ident') t('ssh -p 33 main', port=33) + + def test_ssh_launcher_script(self): + for sh in ('sh', 'zsh', 'bash', 'dash'): + q = shutil.which(sh) + if q: + with self.subTest(sh=sh), tempfile.TemporaryDirectory() as tdir: + self.run_launcher(sh, tdir) + + def run_launcher(self, sh, tdir): + pass diff --git a/shell-integration/ssh/bootstrap.sh b/shell-integration/ssh/bootstrap.sh new file mode 100644 index 000000000..a4d58029c --- /dev/null +++ b/shell-integration/ssh/bootstrap.sh @@ -0,0 +1,79 @@ +#!/bin/sh +# Copyright (C) 2022 Kovid Goyal +# Distributed under terms of the GPLv3 license. + +# read the transmitted data from STDIN +saved_tty_settings=$(command stty -g) +command stty -echo +encoded_data_file=$(mktemp) + +cleanup_on_bootstrap_exit() { + [[ ! -z "$encoded_data_file" ]] && command rm -f "$encoded_data_file" + [[ ! -z "$saved_tty_settings" ]] && command stty "$saved_tty_settings" +} +trap 'cleanup_on_bootstrap_exit' EXIT + +data_started="n" +data_complete="n" +pending_data="" +if [[ -z "$HOSTNAME" ]]; then + hostname=$(hostname) + if [[ -z "$hostname" ]]; then hostname="_"; fi +else + hostname=$(HOSTNAME) +fi +# ask for the SSH data +data_password="DATA_PASSWORD" +password_filename="PASSWORD_FILENAME" +printf "\eP@kitty-ssh|$password_filename:$data_password:$hostname\e\\" + +while IFS= read -r line; do + case "$line" in + *"KITTY_SSH_DATA_START") + prefix=$(command expr "$line" : "\(.*\)KITTY_SSH_DATA_START") + pending_data="$pending_data$prefix" + data_started="y"; + ;; + "KITTY_SSH_DATA_END") + data_complete="y"; + ;; + *) + if [[ "$data_started" == "y" ]]; then + echo -n "$line" >> "$encoded_data_file" + else + pending_data="$pending_data$line\n" + fi + ;; + esac + if [[ "$data_complete" == "y" ]]; then break; fi +done +command stty "$saved_tty_settings" +saved_tty_settings="" +if [[ ! -z "$pending_data" ]]; then + printf "\eP@kitty-echo|$(echo -n "$pending_data" | base64)\e\\" +fi +command base64 -d < "$encoded_data_file" | command tar xjf - --no-same-owner +rc=$? +command rm -f "$encoded_data_file" +encoded_data_file="" +if [ "$rc" != "0" ]; then echo "Failed to extract data transmitted by ssh kitten over the TTY device" > /dev/stderr; exit 1; fi + +# compile terminfo for this system +if [ -x "$(command -v tic)" ]; then + tname=".terminfo" + if [ -e "/usr/share/misc/terminfo.cdb" ]; then + # NetBSD requires this see https://github.com/kovidgoyal/kitty/issues/4622 + tname=".terminfo.cdb" + fi + tic_out=$(command tic -x -o "$HOME/$tname" ".terminfo/kitty.terminfo" 2>&1) + rc=$? + if [ "$rc" != "0" ]; then echo "$tic_out"; exit 1; fi + export TERMINFO="$HOME/$tname" +fi + +# If a command was passed to SSH execute it here +EXEC_CMD + +# ensure $USER is set +if [ -z "$USER" ]; then export USER=$(whoami); fi +