diff options
author | jquast <contact@jeffquast.com> | 2014-08-24 23:46:10 -0700 |
---|---|---|
committer | jquast <contact@jeffquast.com> | 2014-08-24 23:46:10 -0700 |
commit | 67e6c4ac018a0dabe50962beba537612cfb4fa22 (patch) | |
tree | 1188f80867cdf7c76447e92faec3c7391f8399ec /tests/test_which.py | |
parent | 8d96042177a6986ae5b117e31916638309b2fd03 (diff) | |
download | pexpect-git-67e6c4ac018a0dabe50962beba537612cfb4fa22.tar.gz |
Closes issue #104 -- cannot execute sudo(8)
Previously, misinterpreted that os.access(file, X_OK)
always returns True on Solaris. Yes, but only for
the uid of 0. Python issue #13706 closed "not a bug"
reads to "just use os.stat()", so we went to great
lengths to do so quite exhaustively.
But this is wrong -- *only* when root, should we check
the file modes -- os.access of X_OK works perfectly
fine for non-root users.
And, we should only check if any of the executable bits
are set. Alas, it is true, you may execute that which
you may not read -- because as root, you can always read
it anyway.
Verified similar solution in NetBSD test.c (/bin/test),
OpenBSD ksh for its built-in test, and what FreeBSD/Darwin
for their implementation of which.c.
Diffstat (limited to 'tests/test_which.py')
-rw-r--r-- | tests/test_which.py | 105 |
1 files changed, 90 insertions, 15 deletions
diff --git a/tests/test_which.py b/tests/test_which.py index 83575fb..ed9493d 100644 --- a/tests/test_which.py +++ b/tests/test_which.py @@ -1,9 +1,14 @@ +import subprocess import tempfile +import shutil +import errno import os import pexpect from . import PexpectTestCase +import pytest + class TestCaseWhich(PexpectTestCase.PexpectTestCase): " Tests for pexpect.which(). " @@ -162,27 +167,97 @@ class TestCaseWhich(PexpectTestCase.PexpectTestCase): try: # setup os.environ['PATH'] = bin_dir - with open(bin_path, 'w') as fp: - fp.write('#!/bin/sh\necho hello, world\n') - for should_match, mode in ((False, 0o000), - (True, 0o005), - (True, 0o050), - (True, 0o500), - (False, 0o004), - (False, 0o040), - (False, 0o400)): + + # an interpreted script requires the ability to read, + # whereas a binary program requires only to be executable. + # + # to gain access to a binary program, we make a copy of + # the existing system program echo(1). + bin_echo = None + for pth in ('/bin/echo', '/usr/bin/echo'): + if os.path.exists(pth): + bin_echo = pth + break + bin_which = None + for pth in ('/bin/which', '/usr/bin/which'): + if os.path.exists(pth): + bin_which = pth + break + if not bin_echo or not bin_which: + pytest.skip('needs `echo` and `which` binaries') + shutil.copy(bin_echo, bin_path) + + for should_match, mode in ( + (False, 0o000), # ----------, no + (False, 0o001), # ---------x, no + (False, 0o010), # ------x---, no + (True, 0o100), # ---x------, yes + (False, 0o002), # --------w-, no + (False, 0o020), # -----w----, no + (False, 0o200), # --w-------, no + (False, 0o003), # --------wx, no + (False, 0o030), # -----wx---, no + (True, 0o300), # --wx------, yes + (False, 0o004), # -------r--, no + (False, 0o040), # ----r-----, no + (False, 0o400), # -r--------, no + (False, 0o005), # -------r-x, no + (False, 0o050), # ----r-x---, no + (True, 0o500), # -r-x------, yes + (False, 0o006), # -------rw-, no + (False, 0o060), # ----rw----, no + (False, 0o600), # -rw-------, no + (False, 0o007), # -------rwx, no + (False, 0o070), # ----rwx---, no + (True, 0o700), # -rwx------, yes + (False, 0o4001), # ---S-----x, no + (False, 0o4010), # ---S--x---, no + (True, 0o4100), # ---s------, yes + (False, 0o4003), # ---S----wx, no + (False, 0o4030), # ---S-wx---, no + (True, 0o4300), # --ws------, yes + (False, 0o2001), # ------S--x, no + (False, 0o2010), # ------s---, no + (True, 0o2100), # ---x--S---, yes + + ): + mode_str = '{0:0>4o}'.format(mode) + + # given file mode, os.chmod(bin_path, mode) - if not should_match: - # should not be found because it is not executable - assert pexpect.which(fname) is None - else: - # should match full path - assert pexpect.which(fname) == bin_path + # exercise whether we may execute + can_execute = True + try: + subprocess.Popen(fname).wait() == 0 + except OSError as err: + if err.errno != errno.EACCES: + raise + # permission denied + can_execute = False + + assert should_match == can_execute, ( + should_match, can_execute, mode_str) + + # exercise whether which(1) would match + proc = subprocess.Popen((bin_which, fname), + env={'PATH': bin_dir}, + stdout=subprocess.PIPE) + bin_which_match = bool(not proc.wait()) + assert should_match == bin_which_match, ( + should_match, bin_which_match, mode_str) + + # finally, exercise pexpect's which(1) matches + # the same. + pexpect_match = bool(pexpect.which(fname)) + + assert should_match == pexpect_match == bin_which_match, ( + should_match, pexpect_match, bin_which_match, mode_str) finally: # restore, os.environ['PATH'] = save_path + # destroy scratch files and folders, if os.path.exists(bin_path): os.unlink(bin_path) |