From 2e3126b91f58eef2d38c2d0ccf86e02631bff17d Mon Sep 17 00:00:00 2001 From: cecinestpasunepipe <110607403+cecinestpasunepipe@users.noreply.github.com> Date: Tue, 1 Oct 2024 13:41:05 +0200 Subject: [PATCH 1/5] Add support for runtime aliases to target_shell --- dissect/target/tools/shell.py | 18 ++++++++++++++++++ tests/tools/test_shell.py | 11 +++++++++++ 2 files changed, 29 insertions(+) diff --git a/dissect/target/tools/shell.py b/dissect/target/tools/shell.py index 7185f57ce..c48097953 100644 --- a/dissect/target/tools/shell.py +++ b/dissect/target/tools/shell.py @@ -95,6 +95,7 @@ class ExtendedCmd(cmd.Cmd): """ CMD_PREFIX = "cmd_" + _runtime_aliases = {} def __init__(self, cyber: bool = False): cmd.Cmd.__init__(self) @@ -164,6 +165,10 @@ def _handle_command(self, line: str) -> bool | None: return None def default(self, line: str) -> bool: + cmd, arg, _ = self.parseline(line) + if cmd in self._runtime_aliases: + expanded = " ".join([self._runtime_aliases[cmd], arg]) + return self.onecmd(expanded) if (should_exit := self._handle_command(line)) is not None: return should_exit @@ -230,6 +235,19 @@ def do_man(self, line: str) -> bool: def complete_man(self, *args) -> list[str]: return cmd.Cmd.complete_help(self, *args) + def do_alias(self, line: str) -> bool: + """create a runtime alias""" + args = list(shlex.shlex(line, posix=True)) + if len(args) > 2 and args[1] == "=": + self._runtime_aliases[args[0]] = args[2] + elif len(args) > 0 and args[0] in self._runtime_aliases: + print(f"alias {args[0]} {self._runtime_aliases[args[0]]}") + else: + for aliased, command in self._runtime_aliases.items(): + print(f"alias {aliased} {command}") + + return False + def do_clear(self, line: str) -> bool: """clear the terminal screen""" os.system("cls||clear") diff --git a/tests/tools/test_shell.py b/tests/tools/test_shell.py index b209c7f9c..6669df586 100644 --- a/tests/tools/test_shell.py +++ b/tests/tools/test_shell.py @@ -270,3 +270,14 @@ def test_shell_cmd_alias(monkeypatch: pytest.MonkeyPatch, capsys: pytest.Capture ls_la_out, _ = run_target_shell(monkeypatch, capsys, target_path, "ls -la") ll_out, _ = run_target_shell(monkeypatch, capsys, target_path, "ll") assert ls_la_out == ll_out + + +def test_shell_cmd_alias_runtime(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture) -> None: + """test if alias commands call their parent attribute correctly.""" + target_path = absolute_path("_data/tools/info/image.tar") + + # 'list' and 'ls' should return the same output after runtime aliasing + list_out, _ = run_target_shell(monkeypatch, capsys, target_path, "alias list=ls\nlist") + sys.stdout.flush() + ls_out, _ = run_target_shell(monkeypatch, capsys, target_path, "ls") + assert list_out == "ubuntu:/$ " + ls_out From 2a1dbf04b7c882f77e6647dcb16009de8ddeceae Mon Sep 17 00:00:00 2001 From: cecinestpasunepipe <110607403+cecinestpasunepipe@users.noreply.github.com> Date: Thu, 3 Oct 2024 11:15:33 +0200 Subject: [PATCH 2/5] Improvements --- dissect/target/tools/shell.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/dissect/target/tools/shell.py b/dissect/target/tools/shell.py index c48097953..4335430da 100644 --- a/dissect/target/tools/shell.py +++ b/dissect/target/tools/shell.py @@ -165,10 +165,11 @@ def _handle_command(self, line: str) -> bool | None: return None def default(self, line: str) -> bool: - cmd, arg, _ = self.parseline(line) - if cmd in self._runtime_aliases: - expanded = " ".join([self._runtime_aliases[cmd], arg]) + com, arg, _ = self.parseline(line) + if com in self._runtime_aliases: + expanded = " ".join([self._runtime_aliases[com], arg]) return self.onecmd(expanded) + if (should_exit := self._handle_command(line)) is not None: return should_exit @@ -241,10 +242,10 @@ def do_alias(self, line: str) -> bool: if len(args) > 2 and args[1] == "=": self._runtime_aliases[args[0]] = args[2] elif len(args) > 0 and args[0] in self._runtime_aliases: - print(f"alias {args[0]} {self._runtime_aliases[args[0]]}") + print(f"alias {args[0]}={self._runtime_aliases[args[0]]}") else: for aliased, command in self._runtime_aliases.items(): - print(f"alias {aliased} {command}") + print(f"alias {aliased}={command}") return False From 6f27039629145596b91d38d874f40de0e9e473af Mon Sep 17 00:00:00 2001 From: cecinestpasunepipe <110607403+cecinestpasunepipe@users.noreply.github.com> Date: Thu, 3 Oct 2024 15:28:13 +0200 Subject: [PATCH 3/5] Improvements and tweaks. --- dissect/target/tools/shell.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/dissect/target/tools/shell.py b/dissect/target/tools/shell.py index 4335430da..676835ae9 100644 --- a/dissect/target/tools/shell.py +++ b/dissect/target/tools/shell.py @@ -236,6 +236,14 @@ def do_man(self, line: str) -> bool: def complete_man(self, *args) -> list[str]: return cmd.Cmd.complete_help(self, *args) + def do_unalias(self, aliased: str) -> bool: + """delete runtime alias""" + if aliased in self._runtime_aliases: + del self._runtime_aliases[aliased] + else: + print(f"alias {aliased} not found") + return False + def do_alias(self, line: str) -> bool: """create a runtime alias""" args = list(shlex.shlex(line, posix=True)) From e7735c71a944d401d1f7f5783425bb549d4b4a22 Mon Sep 17 00:00:00 2001 From: cecinestpasunepipe <110607403+cecinestpasunepipe@users.noreply.github.com> Date: Thu, 3 Oct 2024 16:15:02 +0200 Subject: [PATCH 4/5] Improvements and tweaks. --- dissect/target/tools/shell.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/dissect/target/tools/shell.py b/dissect/target/tools/shell.py index 676835ae9..7d05a4a9e 100644 --- a/dissect/target/tools/shell.py +++ b/dissect/target/tools/shell.py @@ -236,19 +236,23 @@ def do_man(self, line: str) -> bool: def complete_man(self, *args) -> list[str]: return cmd.Cmd.complete_help(self, *args) - def do_unalias(self, aliased: str) -> bool: + def do_unalias(self, line: str) -> bool: """delete runtime alias""" - if aliased in self._runtime_aliases: - del self._runtime_aliases[aliased] - else: - print(f"alias {aliased} not found") + aliases = list(shlex.shlex(line, posix=True)) + for aliased in aliases: + if aliased in self._runtime_aliases: + del self._runtime_aliases[aliased] + else: + print(f"alias {aliased} not found") return False def do_alias(self, line: str) -> bool: """create a runtime alias""" args = list(shlex.shlex(line, posix=True)) - if len(args) > 2 and args[1] == "=": - self._runtime_aliases[args[0]] = args[2] + if len(args) > 2 and len(args) % 3 == 0: + for i in range(0, len(args), 3): + if args[i + 1] == "=": + self._runtime_aliases[args[i]] = args[i + 2] elif len(args) > 0 and args[0] in self._runtime_aliases: print(f"alias {args[0]}={self._runtime_aliases[args[0]]}") else: From ad89a3eae16a10c4f33252d7c7291aba93ea9d40 Mon Sep 17 00:00:00 2001 From: cecinestpasunepipe <110607403+cecinestpasunepipe@users.noreply.github.com> Date: Thu, 3 Oct 2024 16:47:41 +0200 Subject: [PATCH 5/5] Improvements and tweaks. --- dissect/target/tools/shell.py | 26 ++++++++++++++++------- tests/tools/test_shell.py | 39 ++++++++++++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 8 deletions(-) diff --git a/dissect/target/tools/shell.py b/dissect/target/tools/shell.py index 7d05a4a9e..b9130e561 100644 --- a/dissect/target/tools/shell.py +++ b/dissect/target/tools/shell.py @@ -249,15 +249,27 @@ def do_unalias(self, line: str) -> bool: def do_alias(self, line: str) -> bool: """create a runtime alias""" args = list(shlex.shlex(line, posix=True)) - if len(args) > 2 and len(args) % 3 == 0: - for i in range(0, len(args), 3): - if args[i + 1] == "=": - self._runtime_aliases[args[i]] = args[i + 2] - elif len(args) > 0 and args[0] in self._runtime_aliases: - print(f"alias {args[0]}={self._runtime_aliases[args[0]]}") - else: + + if not args: for aliased, command in self._runtime_aliases.items(): print(f"alias {aliased}={command}") + return False + + while args: + alias_name = args.pop(0) + try: + equals = args.pop(0) + # our parser works different, so we have to stop this + if equals != "=": + raise RuntimeError("Token not allowed") + expanded = args.pop(0) if args else "" # this is how it works in bash + self._runtime_aliases[alias_name] = expanded + except IndexError: + if alias_name in self._runtime_aliases: + print(f"alias {alias_name}={self._runtime_aliases[alias_name]}") + else: + print(f"alias {alias_name} not found") + pass return False diff --git a/tests/tools/test_shell.py b/tests/tools/test_shell.py index 6669df586..68b13b920 100644 --- a/tests/tools/test_shell.py +++ b/tests/tools/test_shell.py @@ -277,7 +277,44 @@ def test_shell_cmd_alias_runtime(monkeypatch: pytest.MonkeyPatch, capsys: pytest target_path = absolute_path("_data/tools/info/image.tar") # 'list' and 'ls' should return the same output after runtime aliasing - list_out, _ = run_target_shell(monkeypatch, capsys, target_path, "alias list=ls\nlist") + list_out, _ = run_target_shell(monkeypatch, capsys, target_path, "alias list=ls xxl='ls -la'\nlist") sys.stdout.flush() ls_out, _ = run_target_shell(monkeypatch, capsys, target_path, "ls") assert list_out == "ubuntu:/$ " + ls_out + + # list aliases + sys.stdout.flush() + out, _ = run_target_shell(monkeypatch, capsys, target_path, "alias") + assert out == "ubuntu:/$ alias list=ls\nalias xxl=ls -la\nubuntu:/$ \n" + + # list single aliases + sys.stdout.flush() + out, _ = run_target_shell(monkeypatch, capsys, target_path, "alias list") + assert out == "ubuntu:/$ alias list=ls\nubuntu:/$ \n" + + # unalias + sys.stdout.flush() + run_target_shell(monkeypatch, capsys, target_path, "unalias xxl") + out, _ = run_target_shell(monkeypatch, capsys, target_path, "alias") + assert out == "ubuntu:/$ alias list=ls\nubuntu:/$ \n" + + # unalias multiple and non-existant + sys.stdout.flush() + out, _ = run_target_shell(monkeypatch, capsys, target_path, "unalias list abc") + assert out == "ubuntu:/$ alias abc not found\nubuntu:/$ \n" + + # alias multiple broken - b will be empty + sys.stdout.flush() + run_target_shell(monkeypatch, capsys, target_path, "alias a=1 b=") + out, _ = run_target_shell(monkeypatch, capsys, target_path, "alias") + assert out == "ubuntu:/$ alias a=1\nalias b=\nubuntu:/$ \n" + + # alias set/get mixed + sys.stdout.flush() + out, _ = run_target_shell(monkeypatch, capsys, target_path, "alias b=1 a") + assert out == "ubuntu:/$ alias a=1\nubuntu:/$ \n" + + # alias with other symbols not allowed due to parser difference + sys.stdout.flush() + out, _ = run_target_shell(monkeypatch, capsys, target_path, "alias b+1") + assert out.find("*** Unhandled error: Token not allowed") > -1