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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +2 -0
- data/CHANGES +45 -36
- data/MANIFEST +18 -16
- data/README +47 -47
- data/Rakefile +66 -69
- data/appveyor.yml +50 -0
- data/certs/djberg96_pub.pem +21 -0
- data/lib/win32-file-security.rb +1 -0
- data/lib/win32/file/security.rb +959 -959
- data/lib/win32/file/security/constants.rb +149 -149
- data/lib/win32/file/security/functions.rb +63 -63
- data/lib/win32/file/security/helper.rb +23 -23
- data/lib/win32/file/security/structs.rb +68 -68
- data/test/test_win32_file_security_acls.rb +34 -28
- data/test/test_win32_file_security_constants.rb +54 -54
- data/test/test_win32_file_security_encryption.rb +90 -90
- data/test/test_win32_file_security_ffi.rb +33 -33
- data/test/test_win32_file_security_ownership.rb +174 -174
- data/test/test_win32_file_security_permissions.rb +88 -87
- data/test/test_win32_file_security_version.rb +13 -13
- data/win32-file-security.gemspec +27 -27
- metadata +30 -5
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7d78cf022bde05e83c69f8448eee645a671cec91
|
4
|
+
data.tar.gz: 0b653b5f89467a6d5a849e239541ad8d77a56eb5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9c86f9d2f234c94a2a7eedd40f1ce2697ed457a61160f586e6beb298536937f4cf8840185e05ccab5d58344046cb4c0189f7a984fb9d56dee2e1d2d7c495e905
|
7
|
+
data.tar.gz: f316e0361d33009dd23de19c9d21d06c0c0c93416f004d0cbf02a3c6bb3d8edd9dc85201418292dc0cc7ed46ec321556f91c0ee657810c9042cc954b6a0f10dc
|
checksums.yaml.gz.sig
ADDED
Binary file
|
data.tar.gz.sig
ADDED
data/CHANGES
CHANGED
@@ -1,36 +1,45 @@
|
|
1
|
-
= 1.0.
|
2
|
-
*
|
3
|
-
|
4
|
-
*
|
5
|
-
*
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
*
|
19
|
-
|
20
|
-
|
21
|
-
*
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
*
|
27
|
-
|
28
|
-
|
29
|
-
= 1.0.
|
30
|
-
* Added
|
31
|
-
* Added a working implementation of File.
|
32
|
-
*
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
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
|
-
*
|
7
|
-
* lib/win32
|
8
|
-
* lib/win32/file/
|
9
|
-
* lib/win32/file/windows/
|
10
|
-
*
|
11
|
-
*
|
12
|
-
* test/
|
13
|
-
* test/
|
14
|
-
* test/
|
15
|
-
* test/
|
16
|
-
* test/
|
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
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
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'
|
data/lib/win32/file/security.rb
CHANGED
@@ -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.
|
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,
|
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(:
|
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,
|
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,
|
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(:
|
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(:
|
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(:
|
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
|