]> granicus.if.org Git - php/commitdiff
- MFB: #44859, fixed support for windows ACL, drop win9x code
authorPierre Joye <pajoye@php.net>
Sun, 17 May 2009 19:51:13 +0000 (19:51 +0000)
committerPierre Joye <pajoye@php.net>
Sun, 17 May 2009 19:51:13 +0000 (19:51 +0000)
TSRM/tsrm_win32.c
TSRM/tsrm_win32.h
ext/standard/tests/file/windows_acls/bug44859.phpt [new file with mode: 0644]
ext/standard/tests/file/windows_acls/bug44859_2.phpt [new file with mode: 0644]
ext/standard/tests/file/windows_acls/bug44859_3.phpt [new file with mode: 0644]
ext/standard/tests/file/windows_acls/common.inc [new file with mode: 0644]
ext/standard/tests/file/windows_acls/tiny.bat [new file with mode: 0644]
ext/standard/tests/file/windows_acls/tiny.exe [new file with mode: 0644]

index 2201e9e98f4ef50fc87799c77e1831487213d270..acddc46868c38e205417ce3503ecdd6a918f6cc4 100644 (file)
@@ -23,6 +23,7 @@
 #include <io.h>
 #include <process.h>
 #include <time.h>
+#include <errno.h>
 
 #define TSRM_INCLUDE_FULL_WINDOWS_HEADERS
 
@@ -45,6 +46,7 @@ static void tsrm_win32_ctor(tsrm_win32_globals *globals TSRMLS_DC)
        globals->process_size = 0;
        globals->shm_size         = 0;
        globals->comspec = _strdup((GetVersion()<0x80000000)?"cmd.exe":"command.com");
+       globals->impersonation_token = NULL;
 }
 
 static void tsrm_win32_dtor(tsrm_win32_globals *globals TSRMLS_DC)
@@ -86,21 +88,82 @@ TSRM_API void tsrm_win32_shutdown(void)
 
 TSRM_API int tsrm_win32_access(const char *pathname, int mode)
 {
+       SECURITY_INFORMATION sec_info = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION;
+       GENERIC_MAPPING gen_map = { FILE_GENERIC_READ, FILE_GENERIC_WRITE, FILE_GENERIC_EXECUTE, FILE_ALL_ACCESS };
+       DWORD priv_set_length = sizeof(PRIVILEGE_SET);
+
+       PRIVILEGE_SET privilege_set = {0};
+       DWORD sec_desc_length = 0, desired_access = 0, granted_access = 0;
+       BYTE * psec_desc = NULL;
+       BOOL fAccess = FALSE;
+       HANDLE process_token = NULL;
+       TSRMLS_FETCH();
+
        if (mode == 1 /*X_OK*/) {
-#if 1
-               /* This code is not supported by Windows 98,
-                * but we don't support it anymore */
                DWORD type;
+               return GetBinaryType(pathname, &type) ? 0 : -1;
+       } else {
+               if(access(pathname, mode)) {
+                               return errno;
+               }
 
-               return GetBinaryType(pathname, &type)?0:-1;
-#else
-               SHFILEINFO sfi;
+               /* Do a full access check because access() will only check read-only attribute */
+               if(mode == 0 || mode > 6) {
+                       desired_access = FILE_GENERIC_READ;
+               } else if(mode <= 2) {
+                               desired_access = FILE_GENERIC_WRITE;
+               } else if(mode <= 4) {
+                               desired_access = FILE_GENERIC_READ;
+               } else { // if(mode <= 6)
+                               desired_access = FILE_GENERIC_READ | FILE_GENERIC_WRITE;
+               }
 
-               return access(pathname, 0) == 0 &&
-                       SHGetFileInfo(pathname, 0, &sfi, sizeof(SHFILEINFO), SHGFI_EXETYPE) != 0 ? 0 : -1;
-#endif
-       } else {
-               return access(pathname, mode);
+               /* Get size of security buffer. Call is expected to fail */
+               if(GetFileSecurity(pathname, sec_info, NULL, 0, &sec_desc_length)) {
+                       goto Finished;
+               }
+
+               psec_desc = (BYTE *)malloc(sec_desc_length);
+               if(psec_desc == NULL ||
+                        !GetFileSecurity(pathname, sec_info, (PSECURITY_DESCRIPTOR)psec_desc, sec_desc_length, &sec_desc_length)) {
+                       goto Finished;
+               }
+
+               if(TWG(impersonation_token) == NULL) {
+
+                       if(!OpenProcessToken(GetCurrentProcess(), TOKEN_DUPLICATE | TOKEN_QUERY, &process_token)) {
+                               goto Finished;
+                       }
+
+                       /* Access check requires impersonation token. Create a duplicate token. */
+                       if(!DuplicateToken(process_token, SecurityImpersonation, &TWG(impersonation_token))) {
+                               goto Finished;
+                       }
+               }
+
+               if(!AccessCheck((PSECURITY_DESCRIPTOR)psec_desc, TWG(impersonation_token), desired_access, &gen_map, &privilege_set, &priv_set_length, &granted_access, &fAccess)) {
+                               goto Finished;
+               }
+
+Finished:
+
+               /* impersonation_token will be closed when the process dies */
+               if(process_token != NULL) {
+                       CloseHandle(process_token);
+                       process_token = NULL;
+               }
+
+               if(psec_desc != NULL) {
+                       free(psec_desc);
+                       psec_desc = NULL;
+               }
+
+               if(fAccess == FALSE) {
+                       errno = EACCES;
+                       return errno;
+               } else {
+                       return 0;
+               }
        }
 }
 
index 09b3cafd09b9dbbcad35ff2b19688c1665e7a7b8..0fc952ad5aab3979c13bdfd516864540312c7c9d 100644 (file)
@@ -63,6 +63,7 @@ typedef struct {
        int                             process_size;
        int                             shm_size;
        char                    *comspec;
+       HANDLE impersonation_token;
 } tsrm_win32_globals;
 
 #ifdef ZTS
diff --git a/ext/standard/tests/file/windows_acls/bug44859.phpt b/ext/standard/tests/file/windows_acls/bug44859.phpt
new file mode 100644 (file)
index 0000000..0716ee5
--- /dev/null
@@ -0,0 +1,60 @@
+--TEST--
+bug #44859 (incorrect result with NTFS ACL permissions, is_writable)
+--SKIPIF--
+<?php 
+include_once __DIR__ . '/common.inc';
+skipif();
+?>
+--FILE--
+<?php
+include_once __DIR__ . '/common.inc';
+
+$iteration = array(
+       PHPT_ACL_READ => false,
+       PHPT_ACL_NONE => false,
+       PHPT_ACL_WRITE => true,
+       PHPT_ACL_WRITE|PHPT_ACL_READ => true,
+);
+
+echo "Testing file:\n";
+$i = 1;
+$path = __DIR__ . '/a.txt';
+foreach ($iteration as $perms => $exp) {
+       create_file($path, $perms);
+       echo 'Iteration #' . $i++ . ': ';
+       if (is_writable($path) == $exp) {
+               echo "passed.\n";
+       } else {
+               var_dump(is_writable($path), $exp);
+               echo "failed.\n";
+       }
+       delete_file($path);
+}
+
+echo "Testing directory:\n";
+$path = __DIR__ . '/adir';
+$i = 1;
+foreach ($iteration as $perms => $exp) {
+       create_file($path, $perms);
+       echo 'Iteration #' . $i++ . ': ';
+       if (is_writable($path) == $exp) {
+               echo "passed.\n";
+       } else {
+               var_dump(is_writable($path), $exp);
+               echo "failed.\n";
+       }
+       delete_file($path);
+}
+
+?>
+--EXPECT--
+Testing file:
+Iteration #1: passed.
+Iteration #2: passed.
+Iteration #3: passed.
+Iteration #4: passed.
+Testing directory:
+Iteration #1: passed.
+Iteration #2: passed.
+Iteration #3: passed.
+Iteration #4: passed.
diff --git a/ext/standard/tests/file/windows_acls/bug44859_2.phpt b/ext/standard/tests/file/windows_acls/bug44859_2.phpt
new file mode 100644 (file)
index 0000000..8326eb4
--- /dev/null
@@ -0,0 +1,60 @@
+--TEST--
+bug #44859 (incorrect result with NTFS ACL permissions, is_readable)
+--SKIPIF--
+<?php 
+include_once __DIR__ . '/common.inc';
+skipif();
+?>
+--FILE--
+<?php
+include_once __DIR__ . '/common.inc';
+
+$iteration = array(
+       PHPT_ACL_READ => true,
+       PHPT_ACL_NONE => false,
+       PHPT_ACL_WRITE => false,
+       PHPT_ACL_WRITE|PHPT_ACL_READ => true,
+);
+
+echo "Testing file:\n";
+$i = 1;
+$path = __DIR__ . '/a.txt';
+foreach ($iteration as $perms => $exp) {
+       create_file($path, $perms);
+       echo 'Iteration #' . $i++ . ': ';
+       if (is_readable($path) == $exp) {
+               echo "passed.\n";
+       } else {
+               var_dump(is_writable($path), $exp);
+               echo "failed.\n";
+       }
+       delete_file($path);
+}
+
+echo "Testing directory:\n";
+$path = __DIR__ . '/adir';
+$i = 1;
+foreach ($iteration as $perms => $exp) {
+       create_file($path, $perms);
+       echo 'Iteration #' . $i++ . ': ';
+       if (is_readable($path) == $exp) {
+               echo "passed.\n";
+       } else {
+               var_dump(is_writable($path), $exp);
+               echo "failed.\n";
+       }
+       delete_file($path);
+}
+
+?>
+--EXPECT--
+Testing file:
+Iteration #1: passed.
+Iteration #2: passed.
+Iteration #3: passed.
+Iteration #4: passed.
+Testing directory:
+Iteration #1: passed.
+Iteration #2: passed.
+Iteration #3: passed.
+Iteration #4: passed.
diff --git a/ext/standard/tests/file/windows_acls/bug44859_3.phpt b/ext/standard/tests/file/windows_acls/bug44859_3.phpt
new file mode 100644 (file)
index 0000000..7300112
--- /dev/null
@@ -0,0 +1,36 @@
+--TEST--
+bug #44859 (incorrect result with NTFS ACL permissions, is_executable)
+--SKIPIF--
+<?php 
+include_once __DIR__ . '/common.inc';
+skipif();
+?>
+--FILE--
+<?php
+include_once __DIR__ . '/common.inc';
+
+$iteration = array(
+       'tiny.exe' => true,
+       //'tiny.bat' => true, To be fixed in _access
+       __FILE__ => false
+);
+
+$i = 1;
+$path = __DIR__;
+
+foreach ($iteration as $file => $exp) {
+       $path = __DIR__ . '/' . $file;
+       echo 'Iteration #' . $i++ . ': ';
+       if (is_executable($path) == $exp) {
+               echo "passed.\n";
+       } else {
+               var_dump(is_executable($path), $exp);
+               echo "failed.\n";
+       }
+}
+
+
+?>
+--EXPECT--
+Iteration #1: passed.
+Iteration #2: passed.
diff --git a/ext/standard/tests/file/windows_acls/common.inc b/ext/standard/tests/file/windows_acls/common.inc
new file mode 100644 (file)
index 0000000..6bcaf2b
--- /dev/null
@@ -0,0 +1,177 @@
+<?php
+error_reporting(E_ALL);
+define('PHPT_ACL_READ',  1 << 1);
+define('PHPT_ACL_WRITE', 1 << 2);
+define('PHPT_ACL_EXEC',  1 << 3);
+define('PHPT_ACL_NONE',  1 << 4);
+define('PHPT_ACL_FULL',  1 << 5);
+
+define('PHPT_ACL_GRANT',  1);
+define('PHPT_ACL_DENY',  2);
+
+function skipif() {
+       if(substr(PHP_OS, 0, 3) != 'WIN' ) {
+               die('skip windows only test');
+       }
+       if(stripos(php_uname(), 'XP') !== FALSE) {
+               die('skip windows 2003 or newer only test');
+       }
+}
+
+function get_username(){
+       return getenv('USERNAME');
+}
+
+function get_domainname()
+{
+       return getenv('USERDOMAIN');
+}
+
+function icacls_set($path, $mode, $perm) {
+       $user = get_username();
+       $path_escaped =  '"' . $path . '"';
+       $perm_entry = array();
+
+       if ($perm & PHPT_ACL_READ) $perm_entry[]  = 'R';
+       if ($perm & PHPT_ACL_WRITE) $perm_entry[] = 'W';
+       if ($perm & PHPT_ACL_EXEC) $perm_entry[]  = 'RX';
+       if ($perm & PHPT_ACL_FULL) $perm_entry[]  = 'F';
+
+       // Deny all
+       $cmd = 'icacls ' . $path_escaped . ' /inheritance:r /deny ' . $user . ':(F,M,R,RX,W)';
+       exec($cmd);
+
+       if ($perm & PHPT_ACL_NONE) {
+               /*
+                This is required to remove all the previously denied
+                permission for the USER. Just granting permission doesn't
+                remove the previously denied permission.
+               */
+               $cmd = 'icacls ' . $path_escaped . ' /' . 'remove:d';
+               $cmd .= ' ' . $user;
+               exec($cmd);
+               $cmd = 'icacls ' . $path_escaped . ' /' . 'remove:g';
+               $cmd .= ' ' . $user;
+               exec($cmd);
+               return;
+       }
+
+       if ($mode == PHPT_ACL_GRANT) {
+               $mode = 'grant';
+       } else {
+               $mode = 'deny';
+       }
+
+
+       // Deny all
+       $cmd = 'icacls ' . $path_escaped . ' /deny ' . $user . ':(F,M,R,RX,W)';
+       exec($cmd);
+
+       /*
+        This is required to remove all the previously denied
+        permission for the USER. Just granting permission doesn't
+        remove the previously denied permission.
+       */
+       $cmd = 'icacls ' . $path_escaped . ' /' . 'remove:d';
+       $cmd .= ' ' . $user;
+       exec($cmd);
+       $cmd = 'icacls ' . $path_escaped . ' /' . 'remove:g';
+       $cmd .= ' ' . $user;
+       exec($cmd);
+
+
+       /*
+        Required to set no permission and check that is_readable()
+        returns false. If the $perm_entry contains 'N' skip this step.
+        This will make the file/dir with NO aceess.
+       */
+       if (!in_array('N', $perm_entry)) {
+               /*
+                This is required to remove all the previously denied
+                permission for the USER. Just granting permission doesn't
+                remove the previously denied permission.
+               */
+               $cmd = 'icacls ' . $path_escaped . ' /' . 'remove:d';
+               $cmd .= ' ' . get_username();
+               exec($cmd);
+               $cmd = 'icacls ' . $path_escaped . ' /' . 'remove:g';
+               $cmd .= ' ' . get_username();
+               exec($cmd);
+
+               $cmd = 'icacls ' . $path_escaped . ' /' . $mode;
+               $cmd .= ' ' . get_username();
+               $cmd .= ':' . '(' . implode($perm_entry, ',') . ')';
+               exec($cmd);
+       }
+}
+
+function create_dir($name, $perms) {
+       if (empty($name)) {
+               echo "create_dir: Empty name is not allowed\n";
+               return;
+       }
+
+       mkdir($name);
+       $dst = realpath($name);
+       icacls_set($name, PHPT_ACL_GRANT, $perms);
+}
+
+function create_file($name, $perms) {
+       if (empty($name)) {
+               echo "create_dir: Empty name is not allowed\n";
+               return;
+       }
+
+       touch($name);
+       $dst = realpath($name);
+       icacls_set($name, PHPT_ACL_GRANT, $perms);
+}
+
+function delete_file($path) {
+       icacls_set($path, PHPT_ACL_GRANT, PHPT_ACL_FULL);
+       if (is_file($path)) {
+               unlink($path);
+       } else {
+               echo "delete_file: '$path' is not a file\n";
+               return;
+       }
+}
+
+function delete_dir($path) {
+       if (is_dir($path)) {
+               icacls_set($path, PHPT_ACL_GRANT, PHPT_ACL_FULL);
+               rmdir($path);
+       } else {
+               echo "delete_dir: '$path' is not a directory\n";
+               return;
+       }
+}
+if (0) {
+$path = __DIR__ . '/a.txt';
+create_file($path, PHPT_ACL_NONE);
+if (!is_writable($path)) {
+       echo "PHPT_ACL_NONE success!!\n";
+} else {
+       echo "PHPT_ACL_NONE failed!!\n";
+}
+delete_file($path);
+
+$path = __DIR__ . '/a.txt';
+create_file($path, PHPT_ACL_READ);
+if (!is_writable($path)) {
+       echo "PHPT_ACL_READ success!!\n";
+} else {
+       echo "PHPT_ACL_READ failed!!\n";
+}
+delete_file($path);
+
+$path = __DIR__ . '/adir';
+create_dir($path, PHPT_ACL_READ);
+if (!is_writable($path)) {
+       echo "PHPT_ACL_READ dir success!!\n";
+} else {
+       echo "PHPT_ACL_READ dir failed!!\n";
+}
+delete_dir($path);
+
+}
diff --git a/ext/standard/tests/file/windows_acls/tiny.bat b/ext/standard/tests/file/windows_acls/tiny.bat
new file mode 100644 (file)
index 0000000..c5464d3
--- /dev/null
@@ -0,0 +1 @@
+echo FOO
diff --git a/ext/standard/tests/file/windows_acls/tiny.exe b/ext/standard/tests/file/windows_acls/tiny.exe
new file mode 100644 (file)
index 0000000..a27e3e6
Binary files /dev/null and b/ext/standard/tests/file/windows_acls/tiny.exe differ