From c8a66c70b51f11bfcaeef22dd17b9b86ea1c3dfe Mon Sep 17 00:00:00 2001
From: Kurtis Rader <krader@skepticism.us>
Date: Tue, 21 Jan 2020 18:21:08 -0800
Subject: [PATCH] Fix interactive restricted shell behavior
 
Fixes #1459
 
(cherry picked from commit 1fd82bddfd36c8d381d4eb75bc1e6b2d4ccb6750)
 
---
 scripts/create_rksh_symlink                   |  8 +++++
 src/cmd/ksh93/edit/edit.c                     |  2 ++
 src/cmd/ksh93/meson.build                     |  6 ++++
 src/cmd/ksh93/tests/meson.build               |  6 ++--
 src/cmd/ksh93/tests/restricted.exp            | 29 +++++++++++++++++++
 src/cmd/ksh93/tests/restricted.exp.out        |  1 +
 .../ksh93/tests/util/interactive.expect.rc    |  1 +
 7 files changed, 51 insertions(+), 2 deletions(-)
 create mode 100755 scripts/create_rksh_symlink
 create mode 100644 src/cmd/ksh93/tests/restricted.exp
 create mode 100644 src/cmd/ksh93/tests/restricted.exp.out
 
diff --git a/scripts/create_rksh_symlink b/scripts/create_rksh_symlink
new file mode 100755
index 00000000..03c013c4
--- /dev/null
+++ b/scripts/create_rksh_symlink
@@ -0,0 +1,8 @@
+#!/bin/sh
+# This is used by a Meson custom_target() to create a "rksh" symlink to the
+# "ksh" binary. This is slightly convoluted to support platforms, such as
+# Cygwin, where the ksh binary might have an extension such as ".exe".
+cmd=$(basename "$1")
+cd "$(dirname "$1")" || exit 1
+rm -f "r$cmd"
+ln -s "$cmd" "r$cmd"
diff --git a/src/cmd/ksh93/edit/edit.c b/src/cmd/ksh93/edit/edit.c
index 666719a9..9c62b1a7 100644
--- a/src/cmd/ksh93/edit/edit.c
+++ b/src/cmd/ksh93/edit/edit.c
@@ -451,9 +451,11 @@ void ed_setup(Edit_t *ep, int fd, int reedit) {
         if (!ep->e_term) ep->e_term = nv_search("TERM", shp->var_tree, 0);
         if (ep->e_term && (term = nv_getval(ep->e_term)) && strlen(term) < sizeof(ep->e_termname) &&
             strcmp(term, ep->e_termname)) {
+            // Avoid an error from the 2>/dev/null redirection in a restricted shell.
             bool r = sh_isoption(shp, SH_RESTRICTED);
             if (r) sh_offoption(shp, SH_RESTRICTED);
             sh_trap(shp, ".sh.subscript=$(tput cuu1 2>/dev/null)", 0);
+            if (r) sh_onoption(shp, SH_RESTRICTED);
             pp = nv_getval(SH_SUBSCRNOD);
             if (pp) {
                 // It should be impossible for the cursor up string to be truncated.
diff --git a/src/cmd/ksh93/meson.build b/src/cmd/ksh93/meson.build
index 491fb9b5..74ace1c7 100644
--- a/src/cmd/ksh93/meson.build
+++ b/src/cmd/ksh93/meson.build
@@ -32,6 +32,11 @@ ksh93_exe = executable('ksh', ['sh/pmain.c'], c_args: shared_c_args,
     dependencies: [libm_dep, libexecinfo_dep, libdl_dep],
     install: true)
 
+# Create a symlink for a restricted version of the ksh shell.
+create_rksh_symlink = find_program(source_dir + '/scripts/create_rksh_symlink')
+rksh93_exe = custom_target('rksh', input: ksh93_exe, output: 'rksh', build_by_default: true,
+    command: [create_rksh_symlink, '@INPUT@'])
+
 shcomp_exe = executable('shcomp', ['sh/shcomp.c'], c_args: shared_c_args,
     include_directories: [configuration_incdir, ksh93_incdir],
     link_with: [libksh, libast, libcmd, libdll],
@@ -47,6 +52,7 @@ test_driver = join_paths(test_dir, 'util', 'run_test.sh')
 src_root = 'SRC_ROOT=' + source_dir
 test_root = 'TEST_ROOT=' + test_dir
 shell_var = 'SHELL=' + ksh93_exe.full_path()
+rshell_var = 'RSHELL=' + rksh93_exe.full_path()
 shcomp_var = 'SHCOMP=' + shcomp_exe.full_path()
 
 libast_build_dir = join_paths(build_dir, 'src', 'lib', 'libast')
diff --git a/src/cmd/ksh93/tests/meson.build b/src/cmd/ksh93/tests/meson.build
index 26f2d43c..4f81ef3c 100644
--- a/src/cmd/ksh93/tests/meson.build
+++ b/src/cmd/ksh93/tests/meson.build
@@ -81,6 +81,7 @@ all_tests = [
     ['readcsv'],
     ['recttype'],
     ['restricted'],
+    ['restricted.exp'],
     ['rksh'],
     ['select'],
     ['sh_match', 120],
@@ -163,7 +164,8 @@ foreach testspec : all_tests
         lang_var = 'LANG=en_US.UTF-8'
         test(test_name, ksh93_exe, timeout: timeout, is_parallel: parallel,
             args: [test_driver, test_name],
-            env: [shell_var, lang_var, src_root, test_root, ld_library_path, libsample_path])
+            env: [shell_var, lang_var, src_root, test_root, ld_library_path, libsample_path,
+                  rshell_var])
 
         # The shcomp variants are only applicable to the non-interactive tests.
         if not test_name.endswith('.exp')
@@ -180,7 +182,7 @@ foreach testspec : all_tests
                 test(test_name + '/shcomp', ksh93_exe, timeout: timeout, is_parallel: parallel,
                     args: [ test_driver, 'shcomp', test_name],
                     env: [shell_var, lang_var, src_root, test_root, shcomp_var, ld_library_path,
-                          libsample_path])
+                          rshell_var, libsample_path])
             endif
         endif
     endif
diff --git a/src/cmd/ksh93/tests/restricted.exp b/src/cmd/ksh93/tests/restricted.exp
new file mode 100644
index 00000000..4192675b
--- /dev/null
+++ b/src/cmd/ksh93/tests/restricted.exp
@@ -0,0 +1,29 @@
+# vim: set filetype=expect:
+#
+# Interactive tests of restricted shell behavior. This is primarily to avoid regressions of
+# https://github.com/att/ast/issues/1459. It doesn't need to be a comprehensive test of restricted
+# shell behavior. That is already done in unit test restricted.sh.
+set pid [spawn $rksh]
+expect_prompt
+# Terminal rows and columns may default to zero so ensure sane values.
+send "stty rows 24 cols 80\r"
+expect_prompt
+
+# ==========
+# Changing a restricted env var should be an error.
+log_test_entry
+send "PATH=/bin\r"
+expect -re ": PATH: restricted\r\n" {
+    puts "modifying PATH is restricted"
+}
+expect_prompt
+
+# ==========
+# Exit the shell.
+log_test_entry
+send "\r"
+expect_prompt
+send [ctrl D]
+catch {expect default exp_continue} output
+log_debug "EOF output: $output"
+wait
diff --git a/src/cmd/ksh93/tests/restricted.exp.out b/src/cmd/ksh93/tests/restricted.exp.out
new file mode 100644
index 00000000..d04d3921
--- /dev/null
+++ b/src/cmd/ksh93/tests/restricted.exp.out
@@ -0,0 +1 @@
+modifying PATH is restricted
diff --git a/src/cmd/ksh93/tests/util/interactive.expect.rc b/src/cmd/ksh93/tests/util/interactive.expect.rc
index dd039a21..f30e5e19 100644
--- a/src/cmd/ksh93/tests/util/interactive.expect.rc
+++ b/src/cmd/ksh93/tests/util/interactive.expect.rc
@@ -6,6 +6,7 @@ log_user 0
 log_file -noappend interactive.tmp.log
 
 set ksh $env(SHELL)
+set rksh $env(RSHELL)
 set timeout 2
 set send_human {.05 .1 5 .02 .2}
 
-- 
2.19.1
 
