win32-file-security 1.0.6 → 1.0.7

Sign up to get free protection for your applications and to get access to all the features.
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