win32-file-security 1.0.6 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6c24eb63a2d55e6fcf4ee4b4c4769f76f48be445
4
- data.tar.gz: ab52ba7a0c646b38879a515e15f2df5689f084cb
3
+ metadata.gz: 7d78cf022bde05e83c69f8448eee645a671cec91
4
+ data.tar.gz: 0b653b5f89467a6d5a849e239541ad8d77a56eb5
5
5
  SHA512:
6
- metadata.gz: 7c8dc91168b6eb32ced302c3c3f4336aabdeb40819187fb233db82e64b5aeb95f769feebf6127a6a0f1c48aad32719ba8cd7026b41c6b72c3de1252aea5a682b
7
- data.tar.gz: ea1905dce18c874e5dce984265c90d0ad8cf51df042af8b4af54a34811c331122f58abf0d695e999ec2d87bbb7612b26f8737a2868abd2d2bac53413861a50d2
6
+ metadata.gz: 9c86f9d2f234c94a2a7eedd40f1ce2697ed457a61160f586e6beb298536937f4cf8840185e05ccab5d58344046cb4c0189f7a984fb9d56dee2e1d2d7c495e905
7
+ data.tar.gz: f316e0361d33009dd23de19c9d21d06c0c0c93416f004d0cbf02a3c6bb3d8edd9dc85201418292dc0cc7ed46ec321556f91c0ee657810c9042cc954b6a0f10dc
checksums.yaml.gz.sig ADDED
Binary file
data.tar.gz.sig ADDED
@@ -0,0 +1,2 @@
1
+ ��?�s�)���}�LJ�f7��c@�������d:�FYn�/<��<�{Mj4��*Ζ|�x�S#t˜}�]�YG���"��,�B�QT>�ؠOJS�K+�2��$�Y�" ���H-�"���]w�f/A�Ⱥ�W����t��g���!DVI �w��D�V���s�&X�1֏���H G
2
+ #�z�K,���|bR�b��ž�'�
data/CHANGES CHANGED
@@ -1,36 +1,45 @@
1
- = 1.0.6 - 28-May-2015
2
- * Handle the possibility of an empty/nil domain. Thanks go to n-nishizawa
3
- for the spot.
4
- * Helper methods are only defined if not already defined.
5
- * Tests that were failing when run as admins in non-domain environments have
6
- been modified to check for a domain first.
7
-
8
- = 1.0.5 - 2-May-2015
9
- * Added the File.supports_acls? singleton method.
10
- * The File.get_permissions and File.set_permissions methods now explicitly
11
- raise an error if the filesystem does not support ACL's.
12
- * Fixed some deprecation warnings in the test suite.
13
-
14
- = 1.0.4 - 2-May-2014
15
- * All methods that accept a filename argument now honor objects that implement
16
- either to_str or to_path.
17
- * Added some pathname tests.
18
- * Updated the gem:create Rakefile task.
19
-
20
- = 1.0.3 - 15-Apr-2013
21
- * Added the File.group method.
22
- * Added a working implementation of File.grpowned?
23
- * Pointer addition fixes that affected 64 bit versions of Ruby.
24
-
25
- = 1.0.2 - 8-Apr-2013
26
- * Fixed HANDLE prototype in underlying FFI code. This affects 64 bit
27
- versions of Ruby.
28
-
29
- = 1.0.1 - 1-Jan-2013
30
- * Added a working implementation of File.owned?
31
- * Added a working implementation of File.chown.
32
- * Added the File.owner method.
33
- * Made the FFI functions private.
34
-
35
- = 1.0.0 - 19-Dec-2012
36
- * Initial release as an independent library.
1
+ = 1.0.7 - 5-Nov-2015
2
+ * Updated some tests so that they ignore case on ownership and permissions
3
+ checks. Caught by Appveyor.
4
+ * This gem is now signed.
5
+ * All gem related tasks in the Rakefile now assume Rubygems 2.x.
6
+ * Added a win32-file-security.rb file for convenience.
7
+ * Bug fix for the ownership test suite where a segfault could occur.
8
+ * Added appveyor.yml file.
9
+
10
+ = 1.0.6 - 28-May-2015
11
+ * Handle the possibility of an empty/nil domain. Thanks go to n-nishizawa
12
+ for the spot.
13
+ * Helper methods are only defined if not already defined.
14
+ * Tests that were failing when run as admins in non-domain environments have
15
+ been modified to check for a domain first.
16
+
17
+ = 1.0.5 - 2-May-2015
18
+ * Added the File.supports_acls? singleton method.
19
+ * The File.get_permissions and File.set_permissions methods now explicitly
20
+ raise an error if the filesystem does not support ACL's.
21
+ * Fixed some deprecation warnings in the test suite.
22
+
23
+ = 1.0.4 - 2-May-2014
24
+ * All methods that accept a filename argument now honor objects that implement
25
+ either to_str or to_path.
26
+ * Added some pathname tests.
27
+ * Updated the gem:create Rakefile task.
28
+
29
+ = 1.0.3 - 15-Apr-2013
30
+ * Added the File.group method.
31
+ * Added a working implementation of File.grpowned?
32
+ * Pointer addition fixes that affected 64 bit versions of Ruby.
33
+
34
+ = 1.0.2 - 8-Apr-2013
35
+ * Fixed HANDLE prototype in underlying FFI code. This affects 64 bit
36
+ versions of Ruby.
37
+
38
+ = 1.0.1 - 1-Jan-2013
39
+ * Added a working implementation of File.owned?
40
+ * Added a working implementation of File.chown.
41
+ * Added the File.owner method.
42
+ * Made the FFI functions private.
43
+
44
+ = 1.0.0 - 19-Dec-2012
45
+ * Initial release as an independent library.
data/MANIFEST CHANGED
@@ -1,16 +1,18 @@
1
- * CHANGES
2
- * MANIFEST
3
- * Rakefile
4
- * README
5
- * win32-file-security.gemspec
6
- * lib/win32/file/security.rb
7
- * lib/win32/file/windows/constants.rb
8
- * lib/win32/file/windows/functions.rb
9
- * lib/win32/file/windows/structs.rb
10
- * test/test_win32_file_security_acls.rb
11
- * test/test_win32_file_security_constants.rb
12
- * test/test_win32_file_security_encryption.rb
13
- * test/test_win32_file_security_ffi.rb
14
- * test/test_win32_file_security_ownership.rb
15
- * test/test_win32_file_security_permissions.rb
16
- * test/test_win32_file_security_version.rb
1
+ * CHANGES
2
+ * MANIFEST
3
+ * Rakefile
4
+ * README
5
+ * win32-file-security.gemspec
6
+ * certs/djberg96_pub.pem
7
+ * lib/win32-file-security.rb
8
+ * lib/win32/file/security.rb
9
+ * lib/win32/file/windows/constants.rb
10
+ * lib/win32/file/windows/functions.rb
11
+ * lib/win32/file/windows/structs.rb
12
+ * test/test_win32_file_security_acls.rb
13
+ * test/test_win32_file_security_constants.rb
14
+ * test/test_win32_file_security_encryption.rb
15
+ * test/test_win32_file_security_ffi.rb
16
+ * test/test_win32_file_security_ownership.rb
17
+ * test/test_win32_file_security_permissions.rb
18
+ * test/test_win32_file_security_version.rb
data/README CHANGED
@@ -1,47 +1,47 @@
1
- == Description
2
- Additional methods for the File class that relate to file security on
3
- the Microsoft Windows operating system.
4
-
5
- == Prerequisites
6
- * ffi
7
-
8
- == Installation
9
- gem install win32-file-security
10
-
11
- == Synopsis
12
-
13
- require 'win32/file/security
14
-
15
- p File.get_permissions('file.txt')
16
-
17
- p File.owner('file.txt')
18
- p File.owned?('file.txt')
19
-
20
- p File.group('file.txt')
21
- p File.grpowned?('file.txt')
22
-
23
- == Notes
24
- Some of this library's methods already exist in the win32-file gem, so
25
- you may not need this gem if you already have win32-file installed.
26
-
27
- == Known issues or bugs
28
- None that I'm aware of.
29
-
30
- Please report any issues you find on the github page at:
31
-
32
- https://github.com/djberg96/win32-file-security/issues
33
-
34
- == License
35
- Artistic 2.0
36
-
37
- == Copyright
38
- (C) 2003-2015, Daniel J. Berger, All Rights Reserved
39
-
40
- == Warranty
41
- This package is provided "as is" and without any express or
42
- implied warranties, including, without limitation, the implied
43
- warranties of merchantability and fitness for a particular purpose.
44
-
45
- == Authors
46
- * Daniel J. Berger
47
- * Park Heesob
1
+ == Description
2
+ Additional methods for the File class that relate to file security on
3
+ the Microsoft Windows operating system.
4
+
5
+ == Prerequisites
6
+ * ffi
7
+
8
+ == Installation
9
+ gem install win32-file-security
10
+
11
+ == Synopsis
12
+
13
+ require 'win32/file/security
14
+
15
+ p File.get_permissions('file.txt')
16
+
17
+ p File.owner('file.txt')
18
+ p File.owned?('file.txt')
19
+
20
+ p File.group('file.txt')
21
+ p File.grpowned?('file.txt')
22
+
23
+ == Notes
24
+ Some of this library's methods already exist in the win32-file gem, so
25
+ you may not need this gem if you already have win32-file installed.
26
+
27
+ == Known issues or bugs
28
+ None that I'm aware of.
29
+
30
+ Please report any issues you find on the github page at:
31
+
32
+ https://github.com/djberg96/win32-file-security/issues
33
+
34
+ == License
35
+ Artistic 2.0
36
+
37
+ == Copyright
38
+ (C) 2003-2015, Daniel J. Berger, All Rights Reserved
39
+
40
+ == Warranty
41
+ This package is provided "as is" and without any express or
42
+ implied warranties, including, without limitation, the implied
43
+ warranties of merchantability and fitness for a particular purpose.
44
+
45
+ == Authors
46
+ * Daniel J. Berger
47
+ * Park Heesob
data/Rakefile CHANGED
@@ -1,69 +1,66 @@
1
- require 'rake'
2
- require 'rake/clean'
3
- require 'rake/testtask'
4
-
5
- CLEAN.include('**/*.gem', '**/*.rbc')
6
-
7
- namespace :gem do
8
- desc 'Build the win32-file-security gem'
9
- task :create => [:clean] do
10
- spec = eval(IO.read('win32-file-security.gemspec'))
11
- if Gem::VERSION < "2.0"
12
- Gem::Builder.new(spec).build
13
- else
14
- require 'rubygems/package'
15
- Gem::Package.build(spec)
16
- end
17
- end
18
-
19
- desc "Install the win32-file-security gem"
20
- task :install => [:create] do
21
- file = Dir["*.gem"].first
22
- sh "gem install -l #{file}"
23
- end
24
- end
25
-
26
- namespace 'test' do
27
- Rake::TestTask.new('all') do |t|
28
- task :test => :clean
29
- t.warning = true
30
- t.verbose = true
31
- end
32
-
33
- Rake::TestTask.new('constants') do |t|
34
- task :test => :clean
35
- t.warning = true
36
- t.verbose = true
37
- t.test_files = FileList['test/test_win32_file_security_constants']
38
- end
39
-
40
- Rake::TestTask.new('encryption') do |t|
41
- task :test => :clean
42
- t.warning = true
43
- t.verbose = true
44
- t.test_files = FileList['test/test_win32_file_security_encryption']
45
- end
46
-
47
- Rake::TestTask.new('ffi') do |t|
48
- task :test => :clean
49
- t.warning = true
50
- t.verbose = true
51
- t.test_files = FileList['test/test_win32_file_security_ffi']
52
- end
53
-
54
- Rake::TestTask.new('ownership') do |t|
55
- task :test => :clean
56
- t.warning = true
57
- t.verbose = true
58
- t.test_files = FileList['test/test_win32_file_security_ownership']
59
- end
60
-
61
- Rake::TestTask.new('permissions') do |t|
62
- task :test => :clean
63
- t.warning = true
64
- t.verbose = true
65
- t.test_files = FileList['test/test_win32_file_security_permissions']
66
- end
67
- end
68
-
69
- task :default => 'test:all'
1
+ require 'rake'
2
+ require 'rake/clean'
3
+ require 'rake/testtask'
4
+
5
+ CLEAN.include('**/*.gem', '**/*.rbc')
6
+
7
+ namespace :gem do
8
+ desc 'Build the win32-file-security gem'
9
+ task :create => [:clean] do
10
+ require 'rubygems/package'
11
+ spec = eval(IO.read('win32-file-security.gemspec'))
12
+ spec.signing_key = File.join(Dir.home, '.ssh', 'gem-private_key.pem')
13
+ Gem::Package.build(spec)
14
+ end
15
+
16
+ desc "Install the win32-file-security gem"
17
+ task :install => [:create] do
18
+ file = Dir["*.gem"].first
19
+ sh "gem install -l #{file}"
20
+ end
21
+ end
22
+
23
+ namespace 'test' do
24
+ Rake::TestTask.new('all') do |t|
25
+ task :test => :clean
26
+ t.warning = true
27
+ t.verbose = true
28
+ end
29
+
30
+ Rake::TestTask.new('constants') do |t|
31
+ task :test => :clean
32
+ t.warning = true
33
+ t.verbose = true
34
+ t.test_files = FileList['test/test_win32_file_security_constants']
35
+ end
36
+
37
+ Rake::TestTask.new('encryption') do |t|
38
+ task :test => :clean
39
+ t.warning = true
40
+ t.verbose = true
41
+ t.test_files = FileList['test/test_win32_file_security_encryption']
42
+ end
43
+
44
+ Rake::TestTask.new('ffi') do |t|
45
+ task :test => :clean
46
+ t.warning = true
47
+ t.verbose = true
48
+ t.test_files = FileList['test/test_win32_file_security_ffi']
49
+ end
50
+
51
+ Rake::TestTask.new('ownership') do |t|
52
+ task :test => :clean
53
+ t.warning = true
54
+ t.verbose = true
55
+ t.test_files = FileList['test/test_win32_file_security_ownership']
56
+ end
57
+
58
+ Rake::TestTask.new('permissions') do |t|
59
+ task :test => :clean
60
+ t.warning = true
61
+ t.verbose = true
62
+ t.test_files = FileList['test/test_win32_file_security_permissions']
63
+ end
64
+ end
65
+
66
+ task :default => 'test:all'
data/appveyor.yml ADDED
@@ -0,0 +1,50 @@
1
+ version: '{build}'
2
+ branches:
3
+ only:
4
+ - master
5
+ skip_tags: true
6
+ clone_depth: 10
7
+ environment:
8
+ matrix:
9
+ - ruby_version: 193
10
+ ruby_dir: 1.9.1
11
+ - ruby_version: 200
12
+ ruby_dir: 2.0.0
13
+ - ruby_version: 200-x64
14
+ ruby_dir: 2.0.0
15
+ - ruby_version: 21
16
+ ruby_dir: 2.1.0
17
+ - ruby_version: 21-x64
18
+ ruby_dir: 2.1.0
19
+ - ruby_version: 22
20
+ ruby_dir: 2.2.0
21
+ - ruby_version: 22-x64
22
+ ruby_dir: 2.2.0
23
+ install:
24
+ - ps: >-
25
+ $env:path = "C:\Ruby" + $env:ruby_version + "\bin;" + $env:path
26
+
27
+ $tpath = "C:\Ruby" + $env:ruby_version + "\lib\ruby\" + $env:ruby_dir + "\test"
28
+
29
+ if ((test-path $tpath) -eq $True){ rm -recurse -force $tpath }
30
+
31
+ gem update --system > $null
32
+
33
+ if ((gem query -i ffi) -eq $False){ gem install ffi --no-document }
34
+
35
+ if ((gem query -i test-unit -v ">= 3.0") -eq $False){ gem install test-unit --no-document }
36
+
37
+ if ((gem query -i sys-admin) -eq $False){ gem install sys-admin --no-document }
38
+
39
+ if ((gem query -i win32-security) -eq $False){ gem install win32-security --no-document }
40
+ cache:
41
+ - C:\Ruby193\lib\ruby\gems\1.9.1
42
+ - C:\Ruby200\lib\ruby\gems\2.0.0
43
+ - C:\Ruby200-x64\lib\ruby\gems\2.0.0
44
+ - C:\Ruby21\lib\ruby\gems\2.1.0
45
+ - C:\Ruby21-x64\lib\ruby\gems\2.1.0
46
+ - C:\Ruby22\lib\ruby\gems\2.2.0
47
+ - C:\Ruby22-x64\lib\ruby\gems\2.2.0
48
+ build: off
49
+ test_script:
50
+ - cmd: rake
@@ -0,0 +1,21 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDcDCCAligAwIBAgIBATANBgkqhkiG9w0BAQUFADA/MREwDwYDVQQDDAhkamJl
3
+ cmc5NjEVMBMGCgmSJomT8ixkARkWBWdtYWlsMRMwEQYKCZImiZPyLGQBGRYDY29t
4
+ MB4XDTE1MDkwMjIwNDkxOFoXDTE2MDkwMTIwNDkxOFowPzERMA8GA1UEAwwIZGpi
5
+ ZXJnOTYxFTATBgoJkiaJk/IsZAEZFgVnbWFpbDETMBEGCgmSJomT8ixkARkWA2Nv
6
+ bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMyTkvXqRp6hLs9eoJOS
7
+ Hmi8kRYbq9Vkf15/hMxJpotYMgJVHHWrmDcC5Dye2PbnXjTkKf266Zw0PtT9h+lI
8
+ S3ts9HO+vaCFSMwFFZmnWJSpQ3CNw2RcHxjWkk9yF7imEM8Kz9ojhiDXzBetdV6M
9
+ gr0lV/alUr7TNVBDngbXEfTWscyXh1qd7xZ4EcOdsDktCe5G45N/o3662tPQvJsi
10
+ FOF0CM/KuBsa/HL1/eoEmF4B3EKIRfTHrQ3hu20Kv3RJ88QM4ec2+0dd97uX693O
11
+ zv6981fyEg+aXLkxrkViM/tz2qR2ZE0jPhHTREPYeMEgptRkTmWSKAuLVWrJEfgl
12
+ DtkCAwEAAaN3MHUwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0OBBYEFEwe
13
+ nn6bfJADmuIDiMSOzedOrL+xMB0GA1UdEQQWMBSBEmRqYmVyZzk2QGdtYWlsLmNv
14
+ bTAdBgNVHRIEFjAUgRJkamJlcmc5NkBnbWFpbC5jb20wDQYJKoZIhvcNAQEFBQAD
15
+ ggEBAHmNOCWoDVD75zHFueY0viwGDVP1BNGFC+yXcb7u2GlK+nEMCORqzURbYPf7
16
+ tL+/hzmePIRz7i30UM//64GI1NLv9jl7nIwjhPpXpf7/lu2I9hOTsvwSumb5UiKC
17
+ /sqBxI3sfj9pr79Wpv4MuikX1XPik7Ncb7NPsJPw06Lvyc3Hkg5X2XpPtLtS+Gr2
18
+ wKJnmzb5rIPS1cmsqv0M9LPWflzfwoZ/SpnmhagP+g05p8bRNKjZSA2iImM/GyYZ
19
+ EJYzxdPOrx2n6NYR3Hk+vHP0U7UBSveI6+qx+ndQYaeyCn+GRX2PKS9h66YF/Q1V
20
+ tGSHgAmcLlkdGgan182qsE/4kKM=
21
+ -----END CERTIFICATE-----
@@ -0,0 +1 @@
1
+ require_relative 'win32/file/security'
@@ -1,959 +1,959 @@
1
- require_relative 'security/constants'
2
- require_relative 'security/structs'
3
- require_relative 'security/functions'
4
- require_relative 'security/helper'
5
- require 'socket'
6
-
7
- class File
8
- include Windows::File::Constants
9
- include Windows::File::Functions
10
- extend Windows::File::Constants
11
- extend Windows::File::Structs
12
- extend Windows::File::Functions
13
-
14
- # The version of the win32-file library
15
- WIN32_FILE_SECURITY_VERSION = '1.0.6'
16
-
17
- class << self
18
- remove_method(:chown)
19
- remove_method(:grpowned?)
20
- remove_method(:owned?)
21
-
22
- # Returns the encryption status of a file as a string. Possible return
23
- # values are:
24
- #
25
- # * encryptable
26
- # * encrypted
27
- # * readonly
28
- # * root directory (i.e. not encryptable)
29
- # * system file (i.e. not encryptable)
30
- # * unsupported
31
- # * unknown
32
- #
33
- def encryption_status(file)
34
- wide_file = string_check(file).wincode
35
- status_ptr = FFI::MemoryPointer.new(:ulong)
36
-
37
- unless FileEncryptionStatusW(wide_file, status_ptr)
38
- raise SystemCallError.new("FileEncryptionStatus", FFI.errno)
39
- end
40
-
41
- status = status_ptr.read_ulong
42
-
43
- rvalue = case status
44
- when FILE_ENCRYPTABLE
45
- "encryptable"
46
- when FILE_IS_ENCRYPTED
47
- "encrypted"
48
- when FILE_READ_ONLY
49
- "readonly"
50
- when FILE_ROOT_DIR
51
- "root directory"
52
- when FILE_SYSTEM_ATTR
53
- "system file"
54
- when FILE_SYSTEM_NOT_SUPPORTED
55
- "unsupported"
56
- else
57
- "unknown"
58
- end
59
-
60
- rvalue
61
- end
62
-
63
- # Returns whether or not the root path of the specified file is
64
- # encryptable. If no path or a relative path is specified, it will
65
- # check against the root of the current directory.
66
- #
67
- # Be sure to include a trailing slash if specifying a root path.
68
- #
69
- # Examples:
70
- #
71
- # p File.encryptable?
72
- # p File.encryptable?("D:/")
73
- # p File.encryptable?("C:/foo/bar.txt") # Same as 'C:\'
74
- #
75
- def encryptable?(file = nil)
76
- bool = false
77
- flags_ptr = FFI::MemoryPointer.new(:ulong)
78
-
79
- if file
80
- file = File.expand_path(string_check(file))
81
- wide_file = file.wincode
82
-
83
- if !PathIsRootW(wide_file)
84
- unless PathStripToRootW(wide_file)
85
- raise SystemCallError.new("PathStripToRoot", FFI.errno)
86
- end
87
- end
88
- else
89
- wide_file = nil
90
- end
91
-
92
- unless GetVolumeInformationW(wide_file, nil, 0, nil, nil, flags_ptr, nil, 0)
93
- raise SystemCallError.new("GetVolumeInformation", FFI.errno)
94
- end
95
-
96
- flags = flags_ptr.read_ulong
97
-
98
- if flags & FILE_SUPPORTS_ENCRYPTION > 0
99
- bool = true
100
- end
101
-
102
- bool
103
- end
104
-
105
-
106
- # Returns whether or not the root path of the specified file supports
107
- # ACL's. If no path or a relative path is specified, it will check against
108
- # the root of the current directory.
109
- #
110
- # Be sure to include a trailing slash if specifying a root path.
111
- #
112
- # Examples:
113
- #
114
- # p File.supports_acls?
115
- # p File.supports_acls?("D:/")
116
- # p File.supports_acls?("C:/foo/bar.txt") # Same as 'C:\'
117
- #
118
- def supports_acls?(file = nil)
119
- bool = false
120
- flags_ptr = FFI::MemoryPointer.new(:ulong)
121
-
122
- if file
123
- file = File.expand_path(string_check(file))
124
- wide_file = file.wincode
125
-
126
- if !PathIsRootW(wide_file)
127
- unless PathStripToRootW(wide_file)
128
- raise SystemCallError.new("PathStripToRoot", FFI.errno)
129
- end
130
- end
131
- else
132
- wide_file = nil
133
- end
134
-
135
- unless GetVolumeInformationW(wide_file, nil, 0, nil, nil, flags_ptr, nil, 0)
136
- raise SystemCallError.new("GetVolumeInformation", FFI.errno)
137
- end
138
-
139
- flags = flags_ptr.read_ulong
140
-
141
- if flags & FILE_PERSISTENT_ACLS > 0
142
- bool = true
143
- end
144
-
145
- bool
146
- end
147
-
148
- # Encrypts a file or directory. All data streams in a file are encrypted.
149
- # All new files created in an encrypted directory are encrypted.
150
- #
151
- # The caller must have the FILE_READ_DATA, FILE_WRITE_DATA,
152
- # FILE_READ_ATTRIBUTES, FILE_WRITE_ATTRIBUTES, and SYNCHRONIZE access
153
- # rights.
154
- #
155
- # Requires exclusive access to the file being encrypted, and will fail if
156
- # another process is using the file or the file is marked read-only. If the
157
- # file is compressed the file will be decompressed before encrypting it.
158
- #
159
- def encrypt(file)
160
- unless EncryptFileW(string_check(file).wincode)
161
- raise SystemCallError.new("EncryptFile", FFI.errno)
162
- end
163
- self
164
- end
165
-
166
- # Decrypts an encrypted file or directory.
167
- #
168
- # The caller must have the FILE_READ_DATA, FILE_WRITE_DATA,
169
- # FILE_READ_ATTRIBUTES, FILE_WRITE_ATTRIBUTES, and SYNCHRONIZE access
170
- # rights.
171
- #
172
- # Requires exclusive access to the file being decrypted, and will fail if
173
- # another process is using the file. If the file is not encrypted an error
174
- # is NOT raised, it's simply a no-op.
175
- #
176
- def decrypt(file)
177
- unless DecryptFileW(string_check(file).wincode, 0)
178
- raise SystemCallError.new("DecryptFile", FFI.errno)
179
- end
180
- self
181
- end
182
-
183
- # Returns a hash describing the current file permissions for the given
184
- # file. The account name is the key, and the value is an integer mask
185
- # that corresponds to the security permissions for that file.
186
- #
187
- # To get a human readable version of the permissions, pass the value to
188
- # the +File.securities+ method.
189
- #
190
- # You may optionally specify a host as the second argument. If no host is
191
- # specified then the current host is used.
192
- #
193
- # Examples:
194
- #
195
- # hash = File.get_permissions('test.txt')
196
- #
197
- # p hash # => {"NT AUTHORITY\\SYSTEM"=>2032127, "BUILTIN\\Administrators"=>2032127, ...}
198
- #
199
- # hash.each{ |name, mask|
200
- # p name
201
- # p File.securities(mask)
202
- # }
203
- #
204
- def get_permissions(file, host=nil)
205
- # Check local filesystem to see if it supports ACL's. If not, bail early
206
- # because the GetFileSecurity function will not fail on an unsupported FS.
207
- if host.nil?
208
- unless supports_acls?(file)
209
- raise ArgumentError, "Filesystem does not implement ACL support"
210
- end
211
- end
212
-
213
- wide_file = string_check(file).wincode
214
- wide_host = host ? host.wincode : nil
215
-
216
- size_needed_ptr = FFI::MemoryPointer.new(:ulong)
217
- security_ptr = FFI::MemoryPointer.new(:ulong)
218
-
219
- # First pass, get the size needed
220
- bool = GetFileSecurityW(
221
- wide_file,
222
- DACL_SECURITY_INFORMATION,
223
- security_ptr,
224
- security_ptr.size,
225
- size_needed_ptr
226
- )
227
-
228
- errno = FFI.errno
229
-
230
- if !bool && errno != ERROR_INSUFFICIENT_BUFFER
231
- raise SystemCallError.new("GetFileSecurity", errno)
232
- end
233
-
234
- size_needed = size_needed_ptr.read_ulong
235
-
236
- security_ptr = FFI::MemoryPointer.new(size_needed)
237
-
238
- # Second pass, this time with the appropriately sized security pointer
239
- bool = GetFileSecurityW(
240
- wide_file,
241
- DACL_SECURITY_INFORMATION,
242
- security_ptr,
243
- security_ptr.size,
244
- size_needed_ptr
245
- )
246
-
247
- raise SystemCallError.new("GetFileSecurity", FFI.errno) unless bool
248
-
249
- control_ptr = FFI::MemoryPointer.new(:ulong)
250
- revision_ptr = FFI::MemoryPointer.new(:ulong)
251
-
252
- unless GetSecurityDescriptorControl(security_ptr, control_ptr, revision_ptr)
253
- raise SystemCallError.new("GetSecurityDescriptorControl", FFI.errno)
254
- end
255
-
256
- control = control_ptr.read_ulong
257
-
258
- if control & SE_DACL_PRESENT == 0
259
- raise ArgumentError, "No DACL present: explicit deny all"
260
- end
261
-
262
- dacl_pptr = FFI::MemoryPointer.new(:pointer)
263
- dacl_present_ptr = FFI::MemoryPointer.new(:bool)
264
- dacl_defaulted_ptr = FFI::MemoryPointer.new(:ulong)
265
-
266
- bool = GetSecurityDescriptorDacl(
267
- security_ptr,
268
- dacl_present_ptr,
269
- dacl_pptr,
270
- dacl_defaulted_ptr
271
- )
272
-
273
- unless bool
274
- raise SystemCallError.new("GetSecurityDescriptorDacl", FFI.errno)
275
- end
276
-
277
- acl = ACL.new(dacl_pptr.read_pointer)
278
-
279
- if acl[:AclRevision] == 0
280
- raise ArgumentError, "DACL is NULL: implicit access grant"
281
- end
282
-
283
- ace_count = acl[:AceCount]
284
- perms_hash = {}
285
-
286
- 0.upto(ace_count - 1){ |i|
287
- ace_pptr = FFI::MemoryPointer.new(:pointer)
288
- next unless GetAce(acl, i, ace_pptr)
289
-
290
- access = ACCESS_ALLOWED_ACE.new(ace_pptr.read_pointer)
291
-
292
- if access[:Header][:AceType] == ACCESS_ALLOWED_ACE_TYPE
293
- name = FFI::MemoryPointer.new(:uchar, 260)
294
- name_size = FFI::MemoryPointer.new(:ulong)
295
- name_size.write_ulong(name.size)
296
-
297
- domain = FFI::MemoryPointer.new(:uchar, 260)
298
- domain_size = FFI::MemoryPointer.new(:ulong)
299
- domain_size.write_ulong(domain.size)
300
-
301
- use_ptr = FFI::MemoryPointer.new(:pointer)
302
-
303
- bool = LookupAccountSidW(
304
- wide_host,
305
- ace_pptr.read_pointer + 8,
306
- name,
307
- name_size,
308
- domain,
309
- domain_size,
310
- use_ptr
311
- )
312
-
313
- raise SystemCallError.new("LookupAccountSid", FFI.errno) unless bool
314
-
315
- # The x2 multiplier is necessary due to wide char strings.
316
- name = name.read_string(name_size.read_ulong * 2).wstrip
317
-
318
- dsize = domain_size.read_ulong
319
-
320
- # It's possible there is no domain.
321
- if dsize > 0
322
- domain = domain.read_string(dsize * 2).wstrip
323
-
324
- unless domain.empty?
325
- name = domain + '\\' + name
326
- end
327
- end
328
-
329
- perms_hash[name] = access[:Mask]
330
- end
331
- }
332
-
333
- perms_hash
334
- end
335
-
336
- # Sets the file permissions for the given file name. The 'permissions'
337
- # argument is a hash with an account name as the key, and the various
338
- # permission constants as possible values. The possible constant values
339
- # are:
340
- #
341
- # * FILE_READ_DATA
342
- # * FILE_WRITE_DATA
343
- # * FILE_APPEND_DATA
344
- # * FILE_READ_EA
345
- # * FILE_WRITE_EA
346
- # * FILE_EXECUTE
347
- # * FILE_DELETE_CHILD
348
- # * FILE_READ_ATTRIBUTES
349
- # * FILE_WRITE_ATTRIBUTES
350
- # * FULL
351
- # * READ
352
- # * ADD
353
- # * CHANGE
354
- # * DELETE
355
- # * READ_CONTROL
356
- # * WRITE_DAC
357
- # * WRITE_OWNER
358
- # * SYNCHRONIZE
359
- # * STANDARD_RIGHTS_ALL
360
- # * STANDARD_RIGHTS_REQUIRED
361
- # * STANDARD_RIGHTS_READ
362
- # * STANDARD_RIGHTS_WRITE
363
- # * STANDARD_RIGHTS_EXECUTE
364
- # * SPECIFIC_RIGHTS_ALL
365
- # * ACCESS_SYSTEM_SECURITY
366
- # * MAXIMUM_ALLOWED
367
- # * GENERIC_READ
368
- # * GENERIC_WRITE
369
- # * GENERIC_EXECUTE
370
- # * GENERIC_ALL
371
- #
372
- # Example:
373
- #
374
- # # Set locally
375
- # File.set_permissions(file, "userid" => File::GENERIC_ALL)
376
- #
377
- # # Set a remote system
378
- # File.set_permissions(file, "host\\userid" => File::GENERIC_ALL)
379
- #
380
- def set_permissions(file, perms)
381
- # Check local filesystem to see if it supports ACL's. If not, bail early
382
- # because the GetFileSecurity function will not fail on an unsupported FS.
383
- unless supports_acls?(file)
384
- raise ArgumentError, "Filesystem does not implement ACL support"
385
- end
386
-
387
- wide_file = string_check(file).wincode
388
- raise TypeError unless perms.kind_of?(Hash)
389
-
390
- account_rights = 0
391
- sec_desc = FFI::MemoryPointer.new(:pointer, SECURITY_DESCRIPTOR_MIN_LENGTH)
392
-
393
- unless InitializeSecurityDescriptor(sec_desc, 1)
394
- raise SystemCallError.new("InitializeSecurityDescriptor", FFI.errno)
395
- end
396
-
397
- acl_new = FFI::MemoryPointer.new(ACL, 100)
398
-
399
- unless InitializeAcl(acl_new, acl_new.size, ACL_REVISION2)
400
- raise SystemCallError.new("InitializeAcl", FFI.errno)
401
- end
402
-
403
- perms.each{ |account, mask|
404
- next if mask.nil?
405
-
406
- server, account = account.split("\\")
407
-
408
- if ['BUILTIN', 'NT AUTHORITY'].include?(server.upcase)
409
- wide_server = nil
410
- else
411
- wide_server = server.wincode
412
- end
413
-
414
- wide_account = account.wincode
415
-
416
- sid = FFI::MemoryPointer.new(:uchar, 1024)
417
- sid_size = FFI::MemoryPointer.new(:ulong)
418
- sid_size.write_ulong(sid.size)
419
-
420
- domain = FFI::MemoryPointer.new(:uchar, 260)
421
- domain_size = FFI::MemoryPointer.new(:ulong)
422
- domain_size.write_ulong(domain.size)
423
-
424
- use_ptr = FFI::MemoryPointer.new(:ulong)
425
-
426
- val = LookupAccountNameW(
427
- wide_server,
428
- wide_account,
429
- sid,
430
- sid_size,
431
- domain,
432
- domain_size,
433
- use_ptr
434
- )
435
-
436
- raise SystemCallError.new("LookupAccountName", FFI.errno) unless val
437
-
438
- all_ace = ACCESS_ALLOWED_ACE2.new
439
-
440
- val = CopySid(
441
- ALLOW_ACE_LENGTH - ACCESS_ALLOWED_ACE.size,
442
- all_ace.to_ptr+8,
443
- sid
444
- )
445
-
446
- raise SystemCallError.new("CopySid", FFI.errno) unless val
447
-
448
- if (GENERIC_ALL & mask).nonzero?
449
- account_rights = GENERIC_ALL & mask
450
- elsif (GENERIC_RIGHTS_CHK & mask).nonzero?
451
- account_rights = GENERIC_RIGHTS_MASK & mask
452
- else
453
- # Do nothing, leave it set to zero.
454
- end
455
-
456
- all_ace[:Header][:AceFlags] = INHERIT_ONLY_ACE | OBJECT_INHERIT_ACE
457
-
458
- 2.times{
459
- if account_rights != 0
460
- all_ace[:Header][:AceSize] = 8 + GetLengthSid(sid)
461
- all_ace[:Mask] = account_rights
462
-
463
- val = AddAce(
464
- acl_new,
465
- ACL_REVISION2,
466
- MAXDWORD,
467
- all_ace,
468
- all_ace[:Header][:AceSize]
469
- )
470
-
471
- raise SystemCallError.new("AddAce", FFI.errno) unless val
472
-
473
- all_ace[:Header][:AceFlags] = CONTAINER_INHERIT_ACE
474
- else
475
- all_ace[:Header][:AceFlags] = 0
476
- end
477
-
478
- account_rights = REST_RIGHTS_MASK & mask
479
- }
480
- }
481
-
482
- unless SetSecurityDescriptorDacl(sec_desc, true, acl_new, false)
483
- raise SystemCallError.new("SetSecurityDescriptorDacl", FFI.errno)
484
- end
485
-
486
- unless SetFileSecurityW(wide_file, DACL_SECURITY_INFORMATION, sec_desc)
487
- raise SystemCallError.new("SetFileSecurity", FFI.errno)
488
- end
489
-
490
- self
491
- end
492
-
493
- # Returns an array of human-readable strings that correspond to the
494
- # permission flags.
495
- #
496
- # Example:
497
- #
498
- # File.get_permissions('test.txt').each{ |name, mask|
499
- # puts name
500
- # p File.securities(mask)
501
- # }
502
- #
503
- def securities(mask)
504
- sec_array = []
505
-
506
- security_rights = {
507
- 'FULL' => FULL,
508
- 'DELETE' => DELETE,
509
- 'READ' => READ,
510
- 'CHANGE' => CHANGE,
511
- 'ADD' => ADD
512
- }
513
-
514
- if mask == 0
515
- sec_array.push('NONE')
516
- else
517
- if (mask & FULL) ^ FULL == 0
518
- sec_array.push('FULL')
519
- else
520
- security_rights.each{ |string, numeric|
521
- if (numeric & mask) ^ numeric == 0
522
- sec_array.push(string)
523
- end
524
- }
525
- end
526
- end
527
-
528
- sec_array
529
- end
530
-
531
- # Returns true if the effective user ID of the process is the same as the
532
- # owner of the named file.
533
- #
534
- # Example:
535
- #
536
- # p File.owned?('some_file.txt') # => true
537
- # p File.owned?('C:/Windows/regedit.ext') # => false
538
- #--
539
- # This method was redefined for MS Windows.
540
- #
541
- def owned?(file)
542
- wide_file = string_check(file).wincode
543
-
544
- return_value = false
545
- size_needed_ptr = FFI::MemoryPointer.new(:ulong)
546
-
547
- # First pass, get the size needed
548
- bool = GetFileSecurityW(
549
- wide_file,
550
- OWNER_SECURITY_INFORMATION,
551
- nil,
552
- 0,
553
- size_needed_ptr
554
- )
555
-
556
- size_needed = size_needed_ptr.read_ulong
557
-
558
- security_ptr = FFI::MemoryPointer.new(size_needed)
559
-
560
- # Second pass, this time with the appropriately sized security pointer
561
- bool = GetFileSecurityW(
562
- wide_file,
563
- OWNER_SECURITY_INFORMATION,
564
- security_ptr,
565
- security_ptr.size,
566
- size_needed_ptr
567
- )
568
-
569
- raise SystemCallError.new("GetFileSecurity", FFI.errno) unless bool
570
-
571
- sid_ptr = FFI::MemoryPointer.new(:pointer)
572
- defaulted = FFI::MemoryPointer.new(:bool)
573
-
574
- unless GetSecurityDescriptorOwner(security_ptr, sid_ptr, defaulted)
575
- raise SystemCallError.new("GetFileSecurity", FFI.errno)
576
- end
577
-
578
- sid = sid_ptr.read_pointer
579
-
580
- token = FFI::MemoryPointer.new(:uintptr_t)
581
-
582
- begin
583
- # Get the current process sid
584
- unless OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, token)
585
- raise SystemCallError, FFI.errno, "OpenProcessToken"
586
- end
587
-
588
- token = token.read_pointer.to_i
589
- rlength = FFI::MemoryPointer.new(:pointer)
590
- tuser = 0.chr * 512
591
-
592
- bool = GetTokenInformation(
593
- token,
594
- TokenUser,
595
- tuser,
596
- tuser.size,
597
- rlength
598
- )
599
-
600
- unless bool
601
- raise SystemCallError, FFI.errno, "GetTokenInformation"
602
- end
603
-
604
- string_sid = tuser[FFI.type_size(:pointer)*2, (rlength.read_ulong - FFI.type_size(:pointer)*2)]
605
-
606
- # Now compare the sid strings
607
- if string_sid == sid.read_string(string_sid.size)
608
- return_value = true
609
- end
610
- ensure
611
- CloseHandle(token)
612
- end
613
-
614
- return_value
615
- end
616
-
617
- # Changes the owner of the named file(s) to the given owner (userid).
618
- # It will typically require elevated privileges in order to change the
619
- # owner of a file.
620
- #
621
- # This group argument is currently ignored, but is included in the method
622
- # definition for compatibility with the current spec. Also note that the
623
- # owner should be a string, not a numeric ID.
624
- #
625
- # Example:
626
- #
627
- # File.chown('some_user', nil, 'some_file.txt')
628
- #--
629
- # In the future we may allow the owner argument to be a SID or a RID and
630
- # simply adjust accordingly.
631
- #
632
- def chown(owner, group, *files)
633
- token = FFI::MemoryPointer.new(:ulong)
634
-
635
- begin
636
- bool = OpenProcessToken(
637
- GetCurrentProcess(),
638
- TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
639
- token
640
- )
641
-
642
- raise SystemCallError.new("OpenProcessToken", FFI.errno) unless bool
643
-
644
- token_handle = token.read_ulong
645
-
646
- privs = [
647
- SE_SECURITY_NAME,
648
- SE_TAKE_OWNERSHIP_NAME,
649
- SE_BACKUP_NAME,
650
- SE_RESTORE_NAME,
651
- SE_CHANGE_NOTIFY_NAME
652
- ]
653
-
654
- privs.each{ |name|
655
- luid = LUID.new
656
-
657
- unless LookupPrivilegeValueA(nil, name, luid)
658
- raise SystemCallError.new("LookupPrivilegeValue", FFI.errno)
659
- end
660
-
661
- tp = TOKEN_PRIVILEGES.new
662
- tp[:PrivilegeCount] = 1
663
- tp[:Privileges][0][:Luid] = luid
664
- tp[:Privileges][0][:Attributes] = SE_PRIVILEGE_ENABLED
665
-
666
- unless AdjustTokenPrivileges(token_handle, false, tp, 0, nil, nil)
667
- raise SystemCallError.new("AdjustTokenPrivileges", FFI.errno)
668
- end
669
- }
670
-
671
- sid = FFI::MemoryPointer.new(:uchar)
672
- sid_size = FFI::MemoryPointer.new(:ulong)
673
- dom = FFI::MemoryPointer.new(:uchar)
674
- dom_size = FFI::MemoryPointer.new(:ulong)
675
- use = FFI::MemoryPointer.new(:ulong)
676
-
677
- wowner = owner.wincode
678
-
679
- # First run, get needed sizes
680
- LookupAccountNameW(nil, wowner, sid, sid_size, dom, dom_size, use)
681
-
682
- sid = FFI::MemoryPointer.new(:uchar, sid_size.read_ulong * 2)
683
- dom = FFI::MemoryPointer.new(:uchar, dom_size.read_ulong * 2)
684
-
685
- # Second run with required sizes
686
- unless LookupAccountNameW(nil, wowner, sid, sid_size, dom, dom_size, use)
687
- raise SystemCallError.new("LookupAccountName", FFI.errno)
688
- end
689
-
690
- files.each{ |file|
691
- wfile = string_check(file).wincode
692
-
693
- size = FFI::MemoryPointer.new(:ulong)
694
- sec = FFI::MemoryPointer.new(:ulong)
695
-
696
- # First pass, get the size needed
697
- GetFileSecurityW(wfile, OWNER_SECURITY_INFORMATION, sec, sec.size, size)
698
-
699
- security = FFI::MemoryPointer.new(size.read_ulong)
700
-
701
- # Second pass, this time with the appropriately sized security pointer
702
- bool = GetFileSecurityW(
703
- wfile,
704
- OWNER_SECURITY_INFORMATION,
705
- security,
706
- security.size,
707
- size
708
- )
709
-
710
- raise SystemCallError.new("GetFileSecurity", FFI.errno) unless bool
711
-
712
- unless InitializeSecurityDescriptor(security, SECURITY_DESCRIPTOR_REVISION)
713
- raise SystemCallError.new("InitializeSecurityDescriptor", FFI.errno)
714
- end
715
-
716
- unless SetSecurityDescriptorOwner(security, sid, false)
717
- raise SystemCallError.new("SetSecurityDescriptorOwner", FFI.errno)
718
- end
719
-
720
- unless SetFileSecurityW(wfile, OWNER_SECURITY_INFORMATION, security)
721
- raise SystemCallError.new("SetFileSecurity", FFI.errno)
722
- end
723
- }
724
- ensure
725
- CloseHandle(token.read_ulong)
726
- end
727
-
728
- files.size
729
- end
730
-
731
- # Returns the owner of the specified file in domain\\userid format.
732
- #
733
- # Example:
734
- #
735
- # p File.owner('some_file.txt') # => "your_domain\\some_user"
736
- #
737
- def owner(file)
738
- file = string_check(file).wincode
739
- size_needed = FFI::MemoryPointer.new(:ulong)
740
-
741
- # First pass, get the size needed
742
- bool = GetFileSecurityW(
743
- file,
744
- OWNER_SECURITY_INFORMATION,
745
- nil,
746
- 0,
747
- size_needed
748
- )
749
-
750
- security = FFI::MemoryPointer.new(size_needed.read_ulong)
751
-
752
- # Second pass, this time with the appropriately sized security pointer
753
- bool = GetFileSecurityW(
754
- file,
755
- OWNER_SECURITY_INFORMATION,
756
- security,
757
- security.size,
758
- size_needed
759
- )
760
-
761
- raise SystemCallError.new("GetFileSecurity", FFI.errno) unless bool
762
-
763
- sid = FFI::MemoryPointer.new(:pointer)
764
- defaulted = FFI::MemoryPointer.new(:bool)
765
-
766
- unless GetSecurityDescriptorOwner(security, sid, defaulted)
767
- raise SystemCallError.new("GetFileSecurity", FFI.errno)
768
- end
769
-
770
- sid = sid.read_pointer
771
-
772
- name = FFI::MemoryPointer.new(:uchar)
773
- name_size = FFI::MemoryPointer.new(:ulong)
774
- dom = FFI::MemoryPointer.new(:uchar)
775
- dom_size = FFI::MemoryPointer.new(:ulong)
776
- use = FFI::MemoryPointer.new(:int)
777
-
778
- # First call, get sizes needed
779
- LookupAccountSidW(nil, sid, name, name_size, dom, dom_size, use)
780
-
781
- name = FFI::MemoryPointer.new(:uchar, name_size.read_ulong * 2)
782
- dom = FFI::MemoryPointer.new(:uchar, dom_size.read_ulong * 2)
783
-
784
- # Second call, get desired information
785
- unless LookupAccountSidW(nil, sid, name, name_size, dom, dom_size, use)
786
- raise SystemCallError.new("LookupAccountSid", FFI.errno)
787
- end
788
-
789
- name = name.read_string(name.size).wstrip
790
- domain = dom.read_string(dom.size).wstrip
791
-
792
- domain << "\\" << name
793
- end
794
-
795
- # Returns the primary group of the specified file in domain\\userid format.
796
- #
797
- # Example:
798
- #
799
- # p File.group('some_file.txt') # => "your_domain\\some_group"
800
- #
801
- def group(file)
802
- file = string_check(file).wincode
803
- size_needed = FFI::MemoryPointer.new(:ulong)
804
-
805
- # First pass, get the size needed
806
- bool = GetFileSecurityW(
807
- file,
808
- GROUP_SECURITY_INFORMATION,
809
- nil,
810
- 0,
811
- size_needed
812
- )
813
-
814
- security = FFI::MemoryPointer.new(size_needed.read_ulong)
815
-
816
- # Second pass, this time with the appropriately sized security pointer
817
- bool = GetFileSecurityW(
818
- file,
819
- GROUP_SECURITY_INFORMATION,
820
- security,
821
- security.size,
822
- size_needed
823
- )
824
-
825
- raise SystemCallError.new("GetFileSecurity", FFI.errno) unless bool
826
-
827
- sid = FFI::MemoryPointer.new(:pointer)
828
- defaulted = FFI::MemoryPointer.new(:bool)
829
-
830
- unless GetSecurityDescriptorGroup(security, sid, defaulted)
831
- raise SystemCallError.new("GetFileSecurity", FFI.errno)
832
- end
833
-
834
- sid = sid.read_pointer
835
-
836
- name = FFI::MemoryPointer.new(:uchar)
837
- name_size = FFI::MemoryPointer.new(:ulong)
838
- dom = FFI::MemoryPointer.new(:uchar)
839
- dom_size = FFI::MemoryPointer.new(:ulong)
840
- use = FFI::MemoryPointer.new(:int)
841
-
842
- # First call, get sizes needed
843
- LookupAccountSidW(nil, sid, name, name_size, dom, dom_size, use)
844
-
845
- name = FFI::MemoryPointer.new(:uchar, name_size.read_ulong * 2)
846
- dom = FFI::MemoryPointer.new(:uchar, dom_size.read_ulong * 2)
847
-
848
- # Second call, get desired information
849
- unless LookupAccountSidW(nil, sid, name, name_size, dom, dom_size, use)
850
- raise SystemCallError.new("LookupAccountSid", FFI.errno)
851
- end
852
-
853
- name = name.read_string(name.size).wstrip
854
- domain = dom.read_string(dom.size).wstrip
855
-
856
- domain << "\\" << name
857
- end
858
-
859
- # Returns true if the primary group ID of the process is the same
860
- # as the owner of the named file.
861
- #
862
- # Example:
863
- #
864
- # p File.grpowned?('some_file.txt') # => true
865
- # p File.grpowned?('C:/Windows/regedit.exe') # => false
866
- #--
867
- # This method was redefined for MS Windows.
868
- #
869
- def grpowned?(file)
870
- wide_file = string_check(file).wincode
871
-
872
- return_value = false
873
- size_needed_ptr = FFI::MemoryPointer.new(:ulong)
874
-
875
- # First pass, get the size needed
876
- bool = GetFileSecurityW(
877
- wide_file,
878
- GROUP_SECURITY_INFORMATION,
879
- nil,
880
- 0,
881
- size_needed_ptr
882
- )
883
-
884
- size_needed = size_needed_ptr.read_ulong
885
-
886
- security_ptr = FFI::MemoryPointer.new(size_needed)
887
-
888
- # Second pass, this time with the appropriately sized security pointer
889
- bool = GetFileSecurityW(
890
- wide_file,
891
- GROUP_SECURITY_INFORMATION,
892
- security_ptr,
893
- security_ptr.size,
894
- size_needed_ptr
895
- )
896
-
897
- raise SystemCallError.new("GetFileSecurity", FFI.errno) unless bool
898
-
899
- sid_ptr = FFI::MemoryPointer.new(:pointer)
900
- defaulted = FFI::MemoryPointer.new(:bool)
901
- pstring_sid = FFI::MemoryPointer.new(:string)
902
-
903
- unless GetSecurityDescriptorGroup(security_ptr, sid_ptr, defaulted)
904
- raise SystemCallError.new("GetFileSecurity", FFI.errno)
905
- end
906
-
907
- sid = sid_ptr.read_pointer
908
- ConvertSidToStringSidA(sid, pstring_sid)
909
- file_string_sid = pstring_sid.read_pointer.read_string
910
-
911
- token = FFI::MemoryPointer.new(:uintptr_t)
912
-
913
- begin
914
- # Get the current process sid
915
- unless OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, token)
916
- raise SystemCallError, FFI.errno, "OpenProcessToken"
917
- end
918
-
919
- token = token.read_pointer.to_i
920
- rlength = FFI::MemoryPointer.new(:ulong)
921
- tgroup = TOKEN_GROUP.new
922
-
923
- bool = GetTokenInformation(
924
- token,
925
- TokenGroups,
926
- tgroup,
927
- tgroup.size,
928
- rlength
929
- )
930
-
931
- unless bool
932
- raise SystemCallError.new("GetTokenInformation", FFI.errno)
933
- end
934
-
935
- ConvertSidToStringSidA(tgroup[:Groups][0][:Sid], pstring_sid)
936
- string_sid = pstring_sid.read_pointer.read_string
937
-
938
- # Now compare the sid strings
939
- if string_sid == file_string_sid
940
- return_value = true
941
- end
942
- ensure
943
- CloseHandle(token)
944
- end
945
-
946
- return_value
947
- end
948
-
949
- private
950
-
951
- # Simulate MRI's contortions for a stringiness check.
952
- def string_check(arg)
953
- return arg if arg.is_a?(String)
954
- return arg.send(:to_str) if arg.respond_to?(:to_str, true) # MRI honors it, even if private
955
- return arg.to_path if arg.respond_to?(:to_path)
956
- raise TypeError
957
- end
958
- end
959
- end
1
+ require_relative 'security/constants'
2
+ require_relative 'security/structs'
3
+ require_relative 'security/functions'
4
+ require_relative 'security/helper'
5
+ require 'socket'
6
+
7
+ class File
8
+ include Windows::File::Constants
9
+ include Windows::File::Functions
10
+ extend Windows::File::Constants
11
+ extend Windows::File::Structs
12
+ extend Windows::File::Functions
13
+
14
+ # The version of the win32-file library
15
+ WIN32_FILE_SECURITY_VERSION = '1.0.7'
16
+
17
+ class << self
18
+ remove_method(:chown)
19
+ remove_method(:grpowned?)
20
+ remove_method(:owned?)
21
+
22
+ # Returns the encryption status of a file as a string. Possible return
23
+ # values are:
24
+ #
25
+ # * encryptable
26
+ # * encrypted
27
+ # * readonly
28
+ # * root directory (i.e. not encryptable)
29
+ # * system file (i.e. not encryptable)
30
+ # * unsupported
31
+ # * unknown
32
+ #
33
+ def encryption_status(file)
34
+ wide_file = string_check(file).wincode
35
+ status_ptr = FFI::MemoryPointer.new(:ulong)
36
+
37
+ unless FileEncryptionStatusW(wide_file, status_ptr)
38
+ raise SystemCallError.new("FileEncryptionStatus", FFI.errno)
39
+ end
40
+
41
+ status = status_ptr.read_ulong
42
+
43
+ rvalue = case status
44
+ when FILE_ENCRYPTABLE
45
+ "encryptable"
46
+ when FILE_IS_ENCRYPTED
47
+ "encrypted"
48
+ when FILE_READ_ONLY
49
+ "readonly"
50
+ when FILE_ROOT_DIR
51
+ "root directory"
52
+ when FILE_SYSTEM_ATTR
53
+ "system file"
54
+ when FILE_SYSTEM_NOT_SUPPORTED
55
+ "unsupported"
56
+ else
57
+ "unknown"
58
+ end
59
+
60
+ rvalue
61
+ end
62
+
63
+ # Returns whether or not the root path of the specified file is
64
+ # encryptable. If no path or a relative path is specified, it will
65
+ # check against the root of the current directory.
66
+ #
67
+ # Be sure to include a trailing slash if specifying a root path.
68
+ #
69
+ # Examples:
70
+ #
71
+ # p File.encryptable?
72
+ # p File.encryptable?("D:/")
73
+ # p File.encryptable?("C:/foo/bar.txt") # Same as 'C:\'
74
+ #
75
+ def encryptable?(file = nil)
76
+ bool = false
77
+ flags_ptr = FFI::MemoryPointer.new(:ulong)
78
+
79
+ if file
80
+ file = File.expand_path(string_check(file))
81
+ wide_file = file.wincode
82
+
83
+ if !PathIsRootW(wide_file)
84
+ unless PathStripToRootW(wide_file)
85
+ raise SystemCallError.new("PathStripToRoot", FFI.errno)
86
+ end
87
+ end
88
+ else
89
+ wide_file = nil
90
+ end
91
+
92
+ unless GetVolumeInformationW(wide_file, nil, 0, nil, nil, flags_ptr, nil, 0)
93
+ raise SystemCallError.new("GetVolumeInformation", FFI.errno)
94
+ end
95
+
96
+ flags = flags_ptr.read_ulong
97
+
98
+ if flags & FILE_SUPPORTS_ENCRYPTION > 0
99
+ bool = true
100
+ end
101
+
102
+ bool
103
+ end
104
+
105
+
106
+ # Returns whether or not the root path of the specified file supports
107
+ # ACL's. If no path or a relative path is specified, it will check against
108
+ # the root of the current directory.
109
+ #
110
+ # Be sure to include a trailing slash if specifying a root path.
111
+ #
112
+ # Examples:
113
+ #
114
+ # p File.supports_acls?
115
+ # p File.supports_acls?("D:/")
116
+ # p File.supports_acls?("C:/foo/bar.txt") # Same as 'C:\'
117
+ #
118
+ def supports_acls?(file = nil)
119
+ bool = false
120
+ flags_ptr = FFI::MemoryPointer.new(:ulong)
121
+
122
+ if file
123
+ file = File.expand_path(string_check(file))
124
+ wide_file = file.wincode
125
+
126
+ if !PathIsRootW(wide_file)
127
+ unless PathStripToRootW(wide_file)
128
+ raise SystemCallError.new("PathStripToRoot", FFI.errno)
129
+ end
130
+ end
131
+ else
132
+ wide_file = nil
133
+ end
134
+
135
+ unless GetVolumeInformationW(wide_file, nil, 0, nil, nil, flags_ptr, nil, 0)
136
+ raise SystemCallError.new("GetVolumeInformation", FFI.errno)
137
+ end
138
+
139
+ flags = flags_ptr.read_ulong
140
+
141
+ if flags & FILE_PERSISTENT_ACLS > 0
142
+ bool = true
143
+ end
144
+
145
+ bool
146
+ end
147
+
148
+ # Encrypts a file or directory. All data streams in a file are encrypted.
149
+ # All new files created in an encrypted directory are encrypted.
150
+ #
151
+ # The caller must have the FILE_READ_DATA, FILE_WRITE_DATA,
152
+ # FILE_READ_ATTRIBUTES, FILE_WRITE_ATTRIBUTES, and SYNCHRONIZE access
153
+ # rights.
154
+ #
155
+ # Requires exclusive access to the file being encrypted, and will fail if
156
+ # another process is using the file or the file is marked read-only. If the
157
+ # file is compressed the file will be decompressed before encrypting it.
158
+ #
159
+ def encrypt(file)
160
+ unless EncryptFileW(string_check(file).wincode)
161
+ raise SystemCallError.new("EncryptFile", FFI.errno)
162
+ end
163
+ self
164
+ end
165
+
166
+ # Decrypts an encrypted file or directory.
167
+ #
168
+ # The caller must have the FILE_READ_DATA, FILE_WRITE_DATA,
169
+ # FILE_READ_ATTRIBUTES, FILE_WRITE_ATTRIBUTES, and SYNCHRONIZE access
170
+ # rights.
171
+ #
172
+ # Requires exclusive access to the file being decrypted, and will fail if
173
+ # another process is using the file. If the file is not encrypted an error
174
+ # is NOT raised, it's simply a no-op.
175
+ #
176
+ def decrypt(file)
177
+ unless DecryptFileW(string_check(file).wincode, 0)
178
+ raise SystemCallError.new("DecryptFile", FFI.errno)
179
+ end
180
+ self
181
+ end
182
+
183
+ # Returns a hash describing the current file permissions for the given
184
+ # file. The account name is the key, and the value is an integer mask
185
+ # that corresponds to the security permissions for that file.
186
+ #
187
+ # To get a human readable version of the permissions, pass the value to
188
+ # the +File.securities+ method.
189
+ #
190
+ # You may optionally specify a host as the second argument. If no host is
191
+ # specified then the current host is used.
192
+ #
193
+ # Examples:
194
+ #
195
+ # hash = File.get_permissions('test.txt')
196
+ #
197
+ # p hash # => {"NT AUTHORITY\\SYSTEM"=>2032127, "BUILTIN\\Administrators"=>2032127, ...}
198
+ #
199
+ # hash.each{ |name, mask|
200
+ # p name
201
+ # p File.securities(mask)
202
+ # }
203
+ #
204
+ def get_permissions(file, host=nil)
205
+ # Check local filesystem to see if it supports ACL's. If not, bail early
206
+ # because the GetFileSecurity function will not fail on an unsupported FS.
207
+ if host.nil?
208
+ unless supports_acls?(file)
209
+ raise ArgumentError, "Filesystem does not implement ACL support"
210
+ end
211
+ end
212
+
213
+ wide_file = string_check(file).wincode
214
+ wide_host = host ? host.wincode : nil
215
+
216
+ size_needed_ptr = FFI::MemoryPointer.new(:ulong)
217
+ security_ptr = FFI::MemoryPointer.new(:ulong)
218
+
219
+ # First pass, get the size needed
220
+ bool = GetFileSecurityW(
221
+ wide_file,
222
+ DACL_SECURITY_INFORMATION,
223
+ security_ptr,
224
+ security_ptr.size,
225
+ size_needed_ptr
226
+ )
227
+
228
+ errno = FFI.errno
229
+
230
+ if !bool && errno != ERROR_INSUFFICIENT_BUFFER
231
+ raise SystemCallError.new("GetFileSecurity", errno)
232
+ end
233
+
234
+ size_needed = size_needed_ptr.read_ulong
235
+
236
+ security_ptr = FFI::MemoryPointer.new(size_needed)
237
+
238
+ # Second pass, this time with the appropriately sized security pointer
239
+ bool = GetFileSecurityW(
240
+ wide_file,
241
+ DACL_SECURITY_INFORMATION,
242
+ security_ptr,
243
+ security_ptr.size,
244
+ size_needed_ptr
245
+ )
246
+
247
+ raise SystemCallError.new("GetFileSecurity", FFI.errno) unless bool
248
+
249
+ control_ptr = FFI::MemoryPointer.new(:ulong)
250
+ revision_ptr = FFI::MemoryPointer.new(:ulong)
251
+
252
+ unless GetSecurityDescriptorControl(security_ptr, control_ptr, revision_ptr)
253
+ raise SystemCallError.new("GetSecurityDescriptorControl", FFI.errno)
254
+ end
255
+
256
+ control = control_ptr.read_ulong
257
+
258
+ if control & SE_DACL_PRESENT == 0
259
+ raise ArgumentError, "No DACL present: explicit deny all"
260
+ end
261
+
262
+ dacl_pptr = FFI::MemoryPointer.new(:pointer)
263
+ dacl_present_ptr = FFI::MemoryPointer.new(:bool)
264
+ dacl_defaulted_ptr = FFI::MemoryPointer.new(:ulong)
265
+
266
+ bool = GetSecurityDescriptorDacl(
267
+ security_ptr,
268
+ dacl_present_ptr,
269
+ dacl_pptr,
270
+ dacl_defaulted_ptr
271
+ )
272
+
273
+ unless bool
274
+ raise SystemCallError.new("GetSecurityDescriptorDacl", FFI.errno)
275
+ end
276
+
277
+ acl = ACL.new(dacl_pptr.read_pointer)
278
+
279
+ if acl[:AclRevision] == 0
280
+ raise ArgumentError, "DACL is NULL: implicit access grant"
281
+ end
282
+
283
+ ace_count = acl[:AceCount]
284
+ perms_hash = {}
285
+
286
+ 0.upto(ace_count - 1){ |i|
287
+ ace_pptr = FFI::MemoryPointer.new(:pointer)
288
+ next unless GetAce(acl, i, ace_pptr)
289
+
290
+ access = ACCESS_ALLOWED_ACE.new(ace_pptr.read_pointer)
291
+
292
+ if access[:Header][:AceType] == ACCESS_ALLOWED_ACE_TYPE
293
+ name = FFI::MemoryPointer.new(:uchar, 260)
294
+ name_size = FFI::MemoryPointer.new(:ulong)
295
+ name_size.write_ulong(name.size)
296
+
297
+ domain = FFI::MemoryPointer.new(:uchar, 260)
298
+ domain_size = FFI::MemoryPointer.new(:ulong)
299
+ domain_size.write_ulong(domain.size)
300
+
301
+ use_ptr = FFI::MemoryPointer.new(:pointer)
302
+
303
+ bool = LookupAccountSidW(
304
+ wide_host,
305
+ ace_pptr.read_pointer + 8,
306
+ name,
307
+ name_size,
308
+ domain,
309
+ domain_size,
310
+ use_ptr
311
+ )
312
+
313
+ raise SystemCallError.new("LookupAccountSid", FFI.errno) unless bool
314
+
315
+ # The x2 multiplier is necessary due to wide char strings.
316
+ name = name.read_string(name_size.read_ulong * 2).wstrip
317
+
318
+ dsize = domain_size.read_ulong
319
+
320
+ # It's possible there is no domain.
321
+ if dsize > 0
322
+ domain = domain.read_string(dsize * 2).wstrip
323
+
324
+ unless domain.empty?
325
+ name = domain + '\\' + name
326
+ end
327
+ end
328
+
329
+ perms_hash[name] = access[:Mask]
330
+ end
331
+ }
332
+
333
+ perms_hash
334
+ end
335
+
336
+ # Sets the file permissions for the given file name. The 'permissions'
337
+ # argument is a hash with an account name as the key, and the various
338
+ # permission constants as possible values. The possible constant values
339
+ # are:
340
+ #
341
+ # * FILE_READ_DATA
342
+ # * FILE_WRITE_DATA
343
+ # * FILE_APPEND_DATA
344
+ # * FILE_READ_EA
345
+ # * FILE_WRITE_EA
346
+ # * FILE_EXECUTE
347
+ # * FILE_DELETE_CHILD
348
+ # * FILE_READ_ATTRIBUTES
349
+ # * FILE_WRITE_ATTRIBUTES
350
+ # * FULL
351
+ # * READ
352
+ # * ADD
353
+ # * CHANGE
354
+ # * DELETE
355
+ # * READ_CONTROL
356
+ # * WRITE_DAC
357
+ # * WRITE_OWNER
358
+ # * SYNCHRONIZE
359
+ # * STANDARD_RIGHTS_ALL
360
+ # * STANDARD_RIGHTS_REQUIRED
361
+ # * STANDARD_RIGHTS_READ
362
+ # * STANDARD_RIGHTS_WRITE
363
+ # * STANDARD_RIGHTS_EXECUTE
364
+ # * SPECIFIC_RIGHTS_ALL
365
+ # * ACCESS_SYSTEM_SECURITY
366
+ # * MAXIMUM_ALLOWED
367
+ # * GENERIC_READ
368
+ # * GENERIC_WRITE
369
+ # * GENERIC_EXECUTE
370
+ # * GENERIC_ALL
371
+ #
372
+ # Example:
373
+ #
374
+ # # Set locally
375
+ # File.set_permissions(file, "userid" => File::GENERIC_ALL)
376
+ #
377
+ # # Set a remote system
378
+ # File.set_permissions(file, "host\\userid" => File::GENERIC_ALL)
379
+ #
380
+ def set_permissions(file, perms)
381
+ # Check local filesystem to see if it supports ACL's. If not, bail early
382
+ # because the GetFileSecurity function will not fail on an unsupported FS.
383
+ unless supports_acls?(file)
384
+ raise ArgumentError, "Filesystem does not implement ACL support"
385
+ end
386
+
387
+ wide_file = string_check(file).wincode
388
+ raise TypeError unless perms.kind_of?(Hash)
389
+
390
+ account_rights = 0
391
+ sec_desc = FFI::MemoryPointer.new(:pointer, SECURITY_DESCRIPTOR_MIN_LENGTH)
392
+
393
+ unless InitializeSecurityDescriptor(sec_desc, 1)
394
+ raise SystemCallError.new("InitializeSecurityDescriptor", FFI.errno)
395
+ end
396
+
397
+ acl_new = FFI::MemoryPointer.new(ACL, 100)
398
+
399
+ unless InitializeAcl(acl_new, acl_new.size, ACL_REVISION2)
400
+ raise SystemCallError.new("InitializeAcl", FFI.errno)
401
+ end
402
+
403
+ perms.each{ |account, mask|
404
+ next if mask.nil?
405
+
406
+ server, account = account.split("\\")
407
+
408
+ if ['BUILTIN', 'NT AUTHORITY'].include?(server.upcase)
409
+ wide_server = nil
410
+ else
411
+ wide_server = server.wincode
412
+ end
413
+
414
+ wide_account = account.wincode
415
+
416
+ sid = FFI::MemoryPointer.new(:uchar, 1024)
417
+ sid_size = FFI::MemoryPointer.new(:ulong)
418
+ sid_size.write_ulong(sid.size)
419
+
420
+ domain = FFI::MemoryPointer.new(:uchar, 260)
421
+ domain_size = FFI::MemoryPointer.new(:ulong)
422
+ domain_size.write_ulong(domain.size)
423
+
424
+ use_ptr = FFI::MemoryPointer.new(:ulong)
425
+
426
+ val = LookupAccountNameW(
427
+ wide_server,
428
+ wide_account,
429
+ sid,
430
+ sid_size,
431
+ domain,
432
+ domain_size,
433
+ use_ptr
434
+ )
435
+
436
+ raise SystemCallError.new("LookupAccountName", FFI.errno) unless val
437
+
438
+ all_ace = ACCESS_ALLOWED_ACE2.new
439
+
440
+ val = CopySid(
441
+ ALLOW_ACE_LENGTH - ACCESS_ALLOWED_ACE.size,
442
+ all_ace.to_ptr+8,
443
+ sid
444
+ )
445
+
446
+ raise SystemCallError.new("CopySid", FFI.errno) unless val
447
+
448
+ if (GENERIC_ALL & mask).nonzero?
449
+ account_rights = GENERIC_ALL & mask
450
+ elsif (GENERIC_RIGHTS_CHK & mask).nonzero?
451
+ account_rights = GENERIC_RIGHTS_MASK & mask
452
+ else
453
+ # Do nothing, leave it set to zero.
454
+ end
455
+
456
+ all_ace[:Header][:AceFlags] = INHERIT_ONLY_ACE | OBJECT_INHERIT_ACE
457
+
458
+ 2.times{
459
+ if account_rights != 0
460
+ all_ace[:Header][:AceSize] = 8 + GetLengthSid(sid)
461
+ all_ace[:Mask] = account_rights
462
+
463
+ val = AddAce(
464
+ acl_new,
465
+ ACL_REVISION2,
466
+ MAXDWORD,
467
+ all_ace,
468
+ all_ace[:Header][:AceSize]
469
+ )
470
+
471
+ raise SystemCallError.new("AddAce", FFI.errno) unless val
472
+
473
+ all_ace[:Header][:AceFlags] = CONTAINER_INHERIT_ACE
474
+ else
475
+ all_ace[:Header][:AceFlags] = 0
476
+ end
477
+
478
+ account_rights = REST_RIGHTS_MASK & mask
479
+ }
480
+ }
481
+
482
+ unless SetSecurityDescriptorDacl(sec_desc, 1, acl_new, 0)
483
+ raise SystemCallError.new("SetSecurityDescriptorDacl", FFI.errno)
484
+ end
485
+
486
+ unless SetFileSecurityW(wide_file, DACL_SECURITY_INFORMATION, sec_desc)
487
+ raise SystemCallError.new("SetFileSecurity", FFI.errno)
488
+ end
489
+
490
+ self
491
+ end
492
+
493
+ # Returns an array of human-readable strings that correspond to the
494
+ # permission flags.
495
+ #
496
+ # Example:
497
+ #
498
+ # File.get_permissions('test.txt').each{ |name, mask|
499
+ # puts name
500
+ # p File.securities(mask)
501
+ # }
502
+ #
503
+ def securities(mask)
504
+ sec_array = []
505
+
506
+ security_rights = {
507
+ 'FULL' => FULL,
508
+ 'DELETE' => DELETE,
509
+ 'READ' => READ,
510
+ 'CHANGE' => CHANGE,
511
+ 'ADD' => ADD
512
+ }
513
+
514
+ if mask == 0
515
+ sec_array.push('NONE')
516
+ else
517
+ if (mask & FULL) ^ FULL == 0
518
+ sec_array.push('FULL')
519
+ else
520
+ security_rights.each{ |string, numeric|
521
+ if (numeric & mask) ^ numeric == 0
522
+ sec_array.push(string)
523
+ end
524
+ }
525
+ end
526
+ end
527
+
528
+ sec_array
529
+ end
530
+
531
+ # Returns true if the effective user ID of the process is the same as the
532
+ # owner of the named file.
533
+ #
534
+ # Example:
535
+ #
536
+ # p File.owned?('some_file.txt') # => true
537
+ # p File.owned?('C:/Windows/regedit.ext') # => false
538
+ #--
539
+ # This method was redefined for MS Windows.
540
+ #
541
+ def owned?(file)
542
+ wide_file = string_check(file).wincode
543
+
544
+ return_value = false
545
+ size_needed_ptr = FFI::MemoryPointer.new(:ulong)
546
+
547
+ # First pass, get the size needed
548
+ bool = GetFileSecurityW(
549
+ wide_file,
550
+ OWNER_SECURITY_INFORMATION,
551
+ nil,
552
+ 0,
553
+ size_needed_ptr
554
+ )
555
+
556
+ size_needed = size_needed_ptr.read_ulong
557
+
558
+ security_ptr = FFI::MemoryPointer.new(size_needed)
559
+
560
+ # Second pass, this time with the appropriately sized security pointer
561
+ bool = GetFileSecurityW(
562
+ wide_file,
563
+ OWNER_SECURITY_INFORMATION,
564
+ security_ptr,
565
+ security_ptr.size,
566
+ size_needed_ptr
567
+ )
568
+
569
+ raise SystemCallError.new("GetFileSecurity", FFI.errno) unless bool
570
+
571
+ sid_ptr = FFI::MemoryPointer.new(:pointer)
572
+ defaulted = FFI::MemoryPointer.new(:int)
573
+
574
+ unless GetSecurityDescriptorOwner(security_ptr, sid_ptr, defaulted)
575
+ raise SystemCallError.new("GetFileSecurity", FFI.errno)
576
+ end
577
+
578
+ sid = sid_ptr.read_pointer
579
+
580
+ token = FFI::MemoryPointer.new(:uintptr_t)
581
+
582
+ begin
583
+ # Get the current process sid
584
+ unless OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, token)
585
+ raise SystemCallError, FFI.errno, "OpenProcessToken"
586
+ end
587
+
588
+ token = token.read_pointer.to_i
589
+ rlength = FFI::MemoryPointer.new(:pointer)
590
+ tuser = 0.chr * 512
591
+
592
+ bool = GetTokenInformation(
593
+ token,
594
+ TokenUser,
595
+ tuser,
596
+ tuser.size,
597
+ rlength
598
+ )
599
+
600
+ unless bool
601
+ raise SystemCallError, FFI.errno, "GetTokenInformation"
602
+ end
603
+
604
+ string_sid = tuser[FFI.type_size(:pointer)*2, (rlength.read_ulong - FFI.type_size(:pointer)*2)]
605
+
606
+ # Now compare the sid strings
607
+ if string_sid == sid.read_string(string_sid.size)
608
+ return_value = true
609
+ end
610
+ ensure
611
+ CloseHandle(token)
612
+ end
613
+
614
+ return_value
615
+ end
616
+
617
+ # Changes the owner of the named file(s) to the given owner (userid).
618
+ # It will typically require elevated privileges in order to change the
619
+ # owner of a file.
620
+ #
621
+ # This group argument is currently ignored, but is included in the method
622
+ # definition for compatibility with the current spec. Also note that the
623
+ # owner should be a string, not a numeric ID.
624
+ #
625
+ # Example:
626
+ #
627
+ # File.chown('some_user', nil, 'some_file.txt')
628
+ #--
629
+ # In the future we may allow the owner argument to be a SID or a RID and
630
+ # simply adjust accordingly.
631
+ #
632
+ def chown(owner, group, *files)
633
+ token = FFI::MemoryPointer.new(:ulong)
634
+
635
+ begin
636
+ bool = OpenProcessToken(
637
+ GetCurrentProcess(),
638
+ TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
639
+ token
640
+ )
641
+
642
+ raise SystemCallError.new("OpenProcessToken", FFI.errno) unless bool
643
+
644
+ token_handle = token.read_ulong
645
+
646
+ privs = [
647
+ SE_SECURITY_NAME,
648
+ SE_TAKE_OWNERSHIP_NAME,
649
+ SE_BACKUP_NAME,
650
+ SE_RESTORE_NAME,
651
+ SE_CHANGE_NOTIFY_NAME
652
+ ]
653
+
654
+ privs.each{ |name|
655
+ luid = LUID.new
656
+
657
+ unless LookupPrivilegeValueA(nil, name, luid)
658
+ raise SystemCallError.new("LookupPrivilegeValue", FFI.errno)
659
+ end
660
+
661
+ tp = TOKEN_PRIVILEGES.new
662
+ tp[:PrivilegeCount] = 1
663
+ tp[:Privileges][0][:Luid] = luid
664
+ tp[:Privileges][0][:Attributes] = SE_PRIVILEGE_ENABLED
665
+
666
+ unless AdjustTokenPrivileges(token_handle, 0, tp, 0, nil, nil)
667
+ raise SystemCallError.new("AdjustTokenPrivileges", FFI.errno)
668
+ end
669
+ }
670
+
671
+ sid = FFI::MemoryPointer.new(:uchar)
672
+ sid_size = FFI::MemoryPointer.new(:ulong)
673
+ dom = FFI::MemoryPointer.new(:uchar)
674
+ dom_size = FFI::MemoryPointer.new(:ulong)
675
+ use = FFI::MemoryPointer.new(:ulong)
676
+
677
+ wowner = owner.wincode
678
+
679
+ # First run, get needed sizes
680
+ LookupAccountNameW(nil, wowner, sid, sid_size, dom, dom_size, use)
681
+
682
+ sid = FFI::MemoryPointer.new(:uchar, sid_size.read_ulong * 2)
683
+ dom = FFI::MemoryPointer.new(:uchar, dom_size.read_ulong * 2)
684
+
685
+ # Second run with required sizes
686
+ unless LookupAccountNameW(nil, wowner, sid, sid_size, dom, dom_size, use)
687
+ raise SystemCallError.new("LookupAccountName", FFI.errno)
688
+ end
689
+
690
+ files.each{ |file|
691
+ wfile = string_check(file).wincode
692
+
693
+ size = FFI::MemoryPointer.new(:ulong)
694
+ sec = FFI::MemoryPointer.new(:ulong)
695
+
696
+ # First pass, get the size needed
697
+ GetFileSecurityW(wfile, OWNER_SECURITY_INFORMATION, sec, sec.size, size)
698
+
699
+ security = FFI::MemoryPointer.new(size.read_ulong)
700
+
701
+ # Second pass, this time with the appropriately sized security pointer
702
+ bool = GetFileSecurityW(
703
+ wfile,
704
+ OWNER_SECURITY_INFORMATION,
705
+ security,
706
+ security.size,
707
+ size
708
+ )
709
+
710
+ raise SystemCallError.new("GetFileSecurity", FFI.errno) unless bool
711
+
712
+ unless InitializeSecurityDescriptor(security, SECURITY_DESCRIPTOR_REVISION)
713
+ raise SystemCallError.new("InitializeSecurityDescriptor", FFI.errno)
714
+ end
715
+
716
+ unless SetSecurityDescriptorOwner(security, sid, 0)
717
+ raise SystemCallError.new("SetSecurityDescriptorOwner", FFI.errno)
718
+ end
719
+
720
+ unless SetFileSecurityW(wfile, OWNER_SECURITY_INFORMATION, security)
721
+ raise SystemCallError.new("SetFileSecurity", FFI.errno)
722
+ end
723
+ }
724
+ ensure
725
+ CloseHandle(token.read_ulong)
726
+ end
727
+
728
+ files.size
729
+ end
730
+
731
+ # Returns the owner of the specified file in domain\\userid format.
732
+ #
733
+ # Example:
734
+ #
735
+ # p File.owner('some_file.txt') # => "your_domain\\some_user"
736
+ #
737
+ def owner(file)
738
+ file = string_check(file).wincode
739
+ size_needed = FFI::MemoryPointer.new(:ulong)
740
+
741
+ # First pass, get the size needed
742
+ bool = GetFileSecurityW(
743
+ file,
744
+ OWNER_SECURITY_INFORMATION,
745
+ nil,
746
+ 0,
747
+ size_needed
748
+ )
749
+
750
+ security = FFI::MemoryPointer.new(size_needed.read_ulong)
751
+
752
+ # Second pass, this time with the appropriately sized security pointer
753
+ bool = GetFileSecurityW(
754
+ file,
755
+ OWNER_SECURITY_INFORMATION,
756
+ security,
757
+ security.size,
758
+ size_needed
759
+ )
760
+
761
+ raise SystemCallError.new("GetFileSecurity", FFI.errno) unless bool
762
+
763
+ sid = FFI::MemoryPointer.new(:pointer)
764
+ defaulted = FFI::MemoryPointer.new(:int)
765
+
766
+ unless GetSecurityDescriptorOwner(security, sid, defaulted)
767
+ raise SystemCallError.new("GetFileSecurity", FFI.errno)
768
+ end
769
+
770
+ sid = sid.read_pointer
771
+
772
+ name = FFI::MemoryPointer.new(:uchar)
773
+ name_size = FFI::MemoryPointer.new(:ulong)
774
+ dom = FFI::MemoryPointer.new(:uchar)
775
+ dom_size = FFI::MemoryPointer.new(:ulong)
776
+ use = FFI::MemoryPointer.new(:int)
777
+
778
+ # First call, get sizes needed
779
+ LookupAccountSidW(nil, sid, name, name_size, dom, dom_size, use)
780
+
781
+ name = FFI::MemoryPointer.new(:uchar, name_size.read_ulong * 2)
782
+ dom = FFI::MemoryPointer.new(:uchar, dom_size.read_ulong * 2)
783
+
784
+ # Second call, get desired information
785
+ unless LookupAccountSidW(nil, sid, name, name_size, dom, dom_size, use)
786
+ raise SystemCallError.new("LookupAccountSid", FFI.errno)
787
+ end
788
+
789
+ name = name.read_string(name.size).wstrip
790
+ domain = dom.read_string(dom.size).wstrip
791
+
792
+ domain << "\\" << name
793
+ end
794
+
795
+ # Returns the primary group of the specified file in domain\\userid format.
796
+ #
797
+ # Example:
798
+ #
799
+ # p File.group('some_file.txt') # => "your_domain\\some_group"
800
+ #
801
+ def group(file)
802
+ file = string_check(file).wincode
803
+ size_needed = FFI::MemoryPointer.new(:ulong)
804
+
805
+ # First pass, get the size needed
806
+ bool = GetFileSecurityW(
807
+ file,
808
+ GROUP_SECURITY_INFORMATION,
809
+ nil,
810
+ 0,
811
+ size_needed
812
+ )
813
+
814
+ security = FFI::MemoryPointer.new(size_needed.read_ulong)
815
+
816
+ # Second pass, this time with the appropriately sized security pointer
817
+ bool = GetFileSecurityW(
818
+ file,
819
+ GROUP_SECURITY_INFORMATION,
820
+ security,
821
+ security.size,
822
+ size_needed
823
+ )
824
+
825
+ raise SystemCallError.new("GetFileSecurity", FFI.errno) unless bool
826
+
827
+ sid = FFI::MemoryPointer.new(:pointer)
828
+ defaulted = FFI::MemoryPointer.new(:int)
829
+
830
+ unless GetSecurityDescriptorGroup(security, sid, defaulted)
831
+ raise SystemCallError.new("GetFileSecurity", FFI.errno)
832
+ end
833
+
834
+ sid = sid.read_pointer
835
+
836
+ name = FFI::MemoryPointer.new(:uchar)
837
+ name_size = FFI::MemoryPointer.new(:ulong)
838
+ dom = FFI::MemoryPointer.new(:uchar)
839
+ dom_size = FFI::MemoryPointer.new(:ulong)
840
+ use = FFI::MemoryPointer.new(:int)
841
+
842
+ # First call, get sizes needed
843
+ LookupAccountSidW(nil, sid, name, name_size, dom, dom_size, use)
844
+
845
+ name = FFI::MemoryPointer.new(:uchar, name_size.read_ulong * 2)
846
+ dom = FFI::MemoryPointer.new(:uchar, dom_size.read_ulong * 2)
847
+
848
+ # Second call, get desired information
849
+ unless LookupAccountSidW(nil, sid, name, name_size, dom, dom_size, use)
850
+ raise SystemCallError.new("LookupAccountSid", FFI.errno)
851
+ end
852
+
853
+ name = name.read_string(name.size).wstrip
854
+ domain = dom.read_string(dom.size).wstrip
855
+
856
+ domain << "\\" << name
857
+ end
858
+
859
+ # Returns true if the primary group ID of the process is the same
860
+ # as the owner of the named file.
861
+ #
862
+ # Example:
863
+ #
864
+ # p File.grpowned?('some_file.txt') # => true
865
+ # p File.grpowned?('C:/Windows/regedit.exe') # => false
866
+ #--
867
+ # This method was redefined for MS Windows.
868
+ #
869
+ def grpowned?(file)
870
+ wide_file = string_check(file).wincode
871
+
872
+ return_value = false
873
+ size_needed_ptr = FFI::MemoryPointer.new(:ulong)
874
+
875
+ # First pass, get the size needed
876
+ bool = GetFileSecurityW(
877
+ wide_file,
878
+ GROUP_SECURITY_INFORMATION,
879
+ nil,
880
+ 0,
881
+ size_needed_ptr
882
+ )
883
+
884
+ size_needed = size_needed_ptr.read_ulong
885
+
886
+ security_ptr = FFI::MemoryPointer.new(size_needed)
887
+
888
+ # Second pass, this time with the appropriately sized security pointer
889
+ bool = GetFileSecurityW(
890
+ wide_file,
891
+ GROUP_SECURITY_INFORMATION,
892
+ security_ptr,
893
+ security_ptr.size,
894
+ size_needed_ptr
895
+ )
896
+
897
+ raise SystemCallError.new("GetFileSecurity", FFI.errno) unless bool
898
+
899
+ sid_ptr = FFI::MemoryPointer.new(:pointer)
900
+ defaulted = FFI::MemoryPointer.new(:int)
901
+ pstring_sid = FFI::MemoryPointer.new(:string)
902
+
903
+ unless GetSecurityDescriptorGroup(security_ptr, sid_ptr, defaulted)
904
+ raise SystemCallError.new("GetFileSecurity", FFI.errno)
905
+ end
906
+
907
+ sid = sid_ptr.read_pointer
908
+ ConvertSidToStringSidA(sid, pstring_sid)
909
+ file_string_sid = pstring_sid.read_pointer.read_string
910
+
911
+ token = FFI::MemoryPointer.new(:uintptr_t)
912
+
913
+ begin
914
+ # Get the current process sid
915
+ unless OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, token)
916
+ raise SystemCallError, FFI.errno, "OpenProcessToken"
917
+ end
918
+
919
+ token = token.read_pointer.to_i
920
+ rlength = FFI::MemoryPointer.new(:ulong)
921
+ tgroup = TOKEN_GROUP.new
922
+
923
+ bool = GetTokenInformation(
924
+ token,
925
+ TokenGroups,
926
+ tgroup,
927
+ tgroup.size,
928
+ rlength
929
+ )
930
+
931
+ unless bool
932
+ raise SystemCallError.new("GetTokenInformation", FFI.errno)
933
+ end
934
+
935
+ ConvertSidToStringSidA(tgroup[:Groups][0][:Sid], pstring_sid)
936
+ string_sid = pstring_sid.read_pointer.read_string
937
+
938
+ # Now compare the sid strings
939
+ if string_sid == file_string_sid
940
+ return_value = true
941
+ end
942
+ ensure
943
+ CloseHandle(token)
944
+ end
945
+
946
+ return_value
947
+ end
948
+
949
+ private
950
+
951
+ # Simulate MRI's contortions for a stringiness check.
952
+ def string_check(arg)
953
+ return arg if arg.is_a?(String)
954
+ return arg.send(:to_str) if arg.respond_to?(:to_str, true) # MRI honors it, even if private
955
+ return arg.to_path if arg.respond_to?(:to_path)
956
+ raise TypeError
957
+ end
958
+ end
959
+ end