win32-file-security 1.0.0 → 1.0.1
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.
- data/CHANGES +6 -0
- data/README +3 -1
- data/Rakefile +21 -0
- data/lib/win32/file/{windows → security}/constants.rb +28 -10
- data/lib/win32/file/security/functions.rb +62 -0
- data/lib/win32/file/security/helper.rb +7 -0
- data/lib/win32/file/{windows → security}/structs.rb +16 -1
- data/lib/win32/file/security.rb +274 -13
- data/test/test_win32_file_security_constants.rb +54 -0
- data/test/test_win32_file_security_ffi.rb +33 -0
- data/test/test_win32_file_security_ownership.rb +108 -0
- data/test/test_win32_file_security_version.rb +1 -1
- data/win32-file-security.gemspec +2 -1
- metadata +28 -5
- data/lib/win32/file/windows/functions.rb +0 -46
data/CHANGES
CHANGED
data/README
CHANGED
@@ -13,6 +13,8 @@
|
|
13
13
|
require 'win32/file/security
|
14
14
|
|
15
15
|
p File.get_permissions('file.txt')
|
16
|
+
p File.owner('file.txt')
|
17
|
+
p File.owned?('file.txt')
|
16
18
|
|
17
19
|
== Notes
|
18
20
|
If you have the win32-file gem already installed then you do not need this
|
@@ -33,7 +35,7 @@
|
|
33
35
|
Artistic 2.0
|
34
36
|
|
35
37
|
== Copyright
|
36
|
-
(C) 2003-
|
38
|
+
(C) 2003-2013, Daniel J. Berger, All Rights Reserved
|
37
39
|
|
38
40
|
== Warranty
|
39
41
|
This package is provided "as is" and without any express or
|
data/Rakefile
CHANGED
@@ -25,6 +25,13 @@ namespace 'test' do
|
|
25
25
|
t.verbose = true
|
26
26
|
end
|
27
27
|
|
28
|
+
Rake::TestTask.new('constants') do |t|
|
29
|
+
task :test => :clean
|
30
|
+
t.warning = true
|
31
|
+
t.verbose = true
|
32
|
+
t.test_files = FileList['test/test_win32_file_security_constants']
|
33
|
+
end
|
34
|
+
|
28
35
|
Rake::TestTask.new('encryption') do |t|
|
29
36
|
task :test => :clean
|
30
37
|
t.warning = true
|
@@ -32,6 +39,20 @@ namespace 'test' do
|
|
32
39
|
t.test_files = FileList['test/test_win32_file_security_encryption']
|
33
40
|
end
|
34
41
|
|
42
|
+
Rake::TestTask.new('ffi') do |t|
|
43
|
+
task :test => :clean
|
44
|
+
t.warning = true
|
45
|
+
t.verbose = true
|
46
|
+
t.test_files = FileList['test/test_win32_file_security_ffi']
|
47
|
+
end
|
48
|
+
|
49
|
+
Rake::TestTask.new('ownership') do |t|
|
50
|
+
task :test => :clean
|
51
|
+
t.warning = true
|
52
|
+
t.verbose = true
|
53
|
+
t.test_files = FileList['test/test_win32_file_security_ownership']
|
54
|
+
end
|
55
|
+
|
35
56
|
Rake::TestTask.new('permissions') do |t|
|
36
57
|
task :test => :clean
|
37
58
|
t.warning = true
|
@@ -1,18 +1,33 @@
|
|
1
1
|
module Windows
|
2
2
|
module File
|
3
3
|
module Constants
|
4
|
-
SE_DACL_PRESENT
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
4
|
+
SE_DACL_PRESENT = 4
|
5
|
+
OWNER_SECURITY_INFORMATION = 1
|
6
|
+
DACL_SECURITY_INFORMATION = 4
|
7
|
+
ACCESS_ALLOWED_ACE_TYPE = 0
|
8
|
+
ERROR_INSUFFICIENT_BUFFER = 122
|
9
|
+
ACL_REVISION2 = 2
|
10
|
+
ALLOW_ACE_LENGTH = 62
|
11
|
+
OBJECT_INHERIT_ACE = 0x1
|
12
|
+
CONTAINER_INHERIT_ACE = 0x2
|
13
|
+
INHERIT_ONLY_ACE = 0x8
|
14
|
+
MAXDWORD = 0xFFFFFFFF
|
15
|
+
TOKEN_QUERY = 0x00000008
|
16
|
+
TOKEN_ADJUST_PRIVILEGES = 0x0020
|
17
|
+
TokenUser = 1
|
18
|
+
|
19
|
+
SECURITY_DESCRIPTOR_REVISION = 1
|
14
20
|
SECURITY_DESCRIPTOR_MIN_LENGTH = 20
|
15
21
|
|
22
|
+
SE_KERNEL_OBJECT = 6
|
23
|
+
SE_FILE_OBJECT = 1
|
24
|
+
SE_PRIVILEGE_ENABLED = 0x00000002
|
25
|
+
SE_SECURITY_NAME = "SeSecurityPrivilege"
|
26
|
+
SE_TAKE_OWNERSHIP_NAME = "SeTakeOwnershipPrivilege"
|
27
|
+
SE_BACKUP_NAME = "SeBackupPrivilege"
|
28
|
+
SE_RESTORE_NAME = "SeRestorePrivilege"
|
29
|
+
SE_CHANGE_NOTIFY_NAME = "SeChangeNotifyPrivilege"
|
30
|
+
|
16
31
|
## Security Rights
|
17
32
|
|
18
33
|
SYNCHRONIZE = 0x100000
|
@@ -30,6 +45,9 @@ module Windows
|
|
30
45
|
GENERIC_ALL = 0x10000000
|
31
46
|
GENERIC_RIGHTS_CHK = 0xF0000000
|
32
47
|
REST_RIGHTS_MASK = 0x001FFFFF
|
48
|
+
READ_CONTROL = 0x20000
|
49
|
+
WRITE_DAC = 0x40000
|
50
|
+
WRITE_OWNER = 0x80000
|
33
51
|
|
34
52
|
FILE_READ_DATA = 1
|
35
53
|
FILE_LIST_DIRECTORY = 1
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'ffi'
|
2
|
+
|
3
|
+
module Windows
|
4
|
+
module File
|
5
|
+
module Functions
|
6
|
+
|
7
|
+
# Make FFI functions private
|
8
|
+
module FFI::Library
|
9
|
+
def attach_pfunc(*args)
|
10
|
+
attach_function(*args)
|
11
|
+
private args[0]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
extend FFI::Library
|
16
|
+
|
17
|
+
# For convenience
|
18
|
+
typedef :pointer, :ptr
|
19
|
+
typedef :buffer_in, :buf_in
|
20
|
+
typedef :buffer_out, :buf_out
|
21
|
+
typedef :string, :str
|
22
|
+
|
23
|
+
|
24
|
+
ffi_lib :advapi32
|
25
|
+
|
26
|
+
attach_pfunc :AddAce, [:ptr, :ulong, :ulong, :ptr, :ulong], :bool
|
27
|
+
attach_pfunc :AdjustTokenPrivileges, [:ulong, :bool, :ptr, :ulong, :ptr, :ptr], :bool
|
28
|
+
attach_pfunc :CopySid, [:ulong, :ptr, :ptr], :bool
|
29
|
+
attach_pfunc :EncryptFileW, [:buf_in], :bool
|
30
|
+
attach_pfunc :DecryptFileW, [:buf_in, :ulong], :bool
|
31
|
+
attach_pfunc :FileEncryptionStatusW, [:buf_in, :ptr], :bool
|
32
|
+
attach_pfunc :GetAce, [:ptr, :ulong, :ptr], :bool
|
33
|
+
attach_pfunc :GetFileSecurityW, [:buf_in, :ulong, :ptr, :ulong, :ptr], :bool
|
34
|
+
attach_pfunc :GetLengthSid, [:ptr], :ulong
|
35
|
+
attach_pfunc :GetSecurityDescriptorControl, [:ptr, :ptr, :ptr], :bool
|
36
|
+
attach_pfunc :GetSecurityDescriptorOwner, [:ptr, :ptr, :ptr], :bool
|
37
|
+
attach_pfunc :GetSecurityDescriptorDacl, [:ptr, :ptr, :ptr, :ptr], :ulong
|
38
|
+
attach_pfunc :GetSecurityInfo, [:ulong, :ulong, :ulong, :ptr, :ptr, :ptr, :ptr, :ptr], :ulong
|
39
|
+
attach_pfunc :GetTokenInformation, [:ulong, :int, :ptr, :ulong, :ptr], :bool
|
40
|
+
attach_pfunc :InitializeAcl, [:ptr, :ulong, :ulong], :bool
|
41
|
+
attach_pfunc :InitializeSecurityDescriptor, [:ptr, :ulong], :bool
|
42
|
+
attach_pfunc :LookupAccountNameW, [:buf_in, :buf_in, :ptr, :ptr, :ptr, :ptr, :ptr], :bool
|
43
|
+
attach_pfunc :LookupAccountSidW, [:buf_in, :ptr, :ptr, :ptr, :ptr, :ptr, :ptr], :bool
|
44
|
+
attach_pfunc :LookupPrivilegeValueA, [:str, :str, :ptr], :bool
|
45
|
+
attach_pfunc :OpenProcessToken, [:ulong, :ulong, :ptr], :bool
|
46
|
+
attach_pfunc :SetFileSecurityW, [:buf_in, :ulong, :ptr], :bool
|
47
|
+
attach_pfunc :SetSecurityDescriptorDacl, [:ptr, :bool, :ptr, :bool], :bool
|
48
|
+
attach_pfunc :SetSecurityDescriptorOwner, [:ptr, :ptr, :bool], :bool
|
49
|
+
|
50
|
+
ffi_lib :kernel32
|
51
|
+
|
52
|
+
attach_pfunc :CloseHandle, [:ulong], :bool
|
53
|
+
attach_pfunc :GetCurrentProcess, [], :ulong
|
54
|
+
attach_pfunc :GetVolumeInformationW, [:buf_in, :buf_out, :ulong, :ptr, :ptr, :ptr, :buf_out, :ulong], :bool
|
55
|
+
|
56
|
+
ffi_lib :shlwapi
|
57
|
+
|
58
|
+
attach_pfunc :PathStripToRootW, [:buf_in], :bool
|
59
|
+
attach_pfunc :PathIsRootW, [:buf_in], :bool
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -24,7 +24,7 @@ module Windows
|
|
24
24
|
:Header, ACE_HEADER,
|
25
25
|
:Mask, :ulong,
|
26
26
|
:SidStart, :ulong,
|
27
|
-
|
27
|
+
:dummy, [:uchar, 40]
|
28
28
|
)
|
29
29
|
end
|
30
30
|
|
@@ -37,6 +37,21 @@ module Windows
|
|
37
37
|
:Sbz2, :ushort
|
38
38
|
)
|
39
39
|
end
|
40
|
+
|
41
|
+
class LUID < FFI::Struct
|
42
|
+
layout(:LowPart, :ulong, :HighPart, :long)
|
43
|
+
end
|
44
|
+
|
45
|
+
class LUID_AND_ATTRIBUTES < FFI::Struct
|
46
|
+
layout(:Luid, LUID, :Attributes, :ulong)
|
47
|
+
end
|
48
|
+
|
49
|
+
class TOKEN_PRIVILEGES < FFI::Struct
|
50
|
+
layout(
|
51
|
+
:PrivilegeCount, :ulong,
|
52
|
+
:Privileges, [LUID_AND_ATTRIBUTES, 1]
|
53
|
+
)
|
54
|
+
end
|
40
55
|
end
|
41
56
|
end
|
42
57
|
end
|
data/lib/win32/file/security.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
-
require File.join(File.dirname(__FILE__), '
|
2
|
-
require File.join(File.dirname(__FILE__), '
|
3
|
-
require File.join(File.dirname(__FILE__), '
|
1
|
+
require File.join(File.dirname(__FILE__), 'security', 'constants')
|
2
|
+
require File.join(File.dirname(__FILE__), 'security', 'structs')
|
3
|
+
require File.join(File.dirname(__FILE__), 'security', 'functions')
|
4
|
+
require File.join(File.dirname(__FILE__), 'security', 'helper')
|
5
|
+
require 'socket'
|
4
6
|
|
5
7
|
class File
|
6
8
|
include Windows::File::Constants
|
@@ -10,9 +12,11 @@ class File
|
|
10
12
|
extend Windows::File::Functions
|
11
13
|
|
12
14
|
# The version of the win32-file library
|
13
|
-
WIN32_FILE_SECURITY_VERSION = '1.0.
|
15
|
+
WIN32_FILE_SECURITY_VERSION = '1.0.1'
|
14
16
|
|
15
17
|
class << self
|
18
|
+
remove_method(:owned?)
|
19
|
+
remove_method(:chown)
|
16
20
|
|
17
21
|
# Returns the encryption status of a file as a string. Possible return
|
18
22
|
# values are:
|
@@ -188,9 +192,7 @@ class File
|
|
188
192
|
size_needed_ptr
|
189
193
|
)
|
190
194
|
|
191
|
-
unless bool
|
192
|
-
raise SystemCallError.new("GetFileSecurity", FFI.errno)
|
193
|
-
end
|
195
|
+
raise SystemCallError.new("GetFileSecurity", FFI.errno) unless bool
|
194
196
|
|
195
197
|
control_ptr = FFI::MemoryPointer.new(:ulong)
|
196
198
|
revision_ptr = FFI::MemoryPointer.new(:ulong)
|
@@ -246,7 +248,7 @@ class File
|
|
246
248
|
|
247
249
|
use_ptr = FFI::MemoryPointer.new(:pointer)
|
248
250
|
|
249
|
-
|
251
|
+
bool = LookupAccountSidW(
|
250
252
|
wide_host,
|
251
253
|
ace_pptr.read_pointer + 8,
|
252
254
|
name,
|
@@ -256,9 +258,7 @@ class File
|
|
256
258
|
use_ptr
|
257
259
|
)
|
258
260
|
|
259
|
-
|
260
|
-
raise SystemCallError.new("LookupAccountSid", FFI.errno)
|
261
|
-
end
|
261
|
+
raise SystemCallError.new("LookupAccountSid", FFI.errno) unless bool
|
262
262
|
|
263
263
|
# The x2 multiplier is necessary due to wide char strings.
|
264
264
|
name = name.read_string(name_size.read_ulong * 2).delete(0.chr)
|
@@ -289,7 +289,6 @@ class File
|
|
289
289
|
# * FILE_DELETE_CHILD
|
290
290
|
# * FILE_READ_ATTRIBUTES
|
291
291
|
# * FILE_WRITE_ATTRIBUTES
|
292
|
-
# * STANDARD_RIGHTS_ALL
|
293
292
|
# * FULL
|
294
293
|
# * READ
|
295
294
|
# * ADD
|
@@ -299,11 +298,11 @@ class File
|
|
299
298
|
# * WRITE_DAC
|
300
299
|
# * WRITE_OWNER
|
301
300
|
# * SYNCHRONIZE
|
301
|
+
# * STANDARD_RIGHTS_ALL
|
302
302
|
# * STANDARD_RIGHTS_REQUIRED
|
303
303
|
# * STANDARD_RIGHTS_READ
|
304
304
|
# * STANDARD_RIGHTS_WRITE
|
305
305
|
# * STANDARD_RIGHTS_EXECUTE
|
306
|
-
# * STANDARD_RIGHTS_ALL
|
307
306
|
# * SPECIFIC_RIGHTS_ALL
|
308
307
|
# * ACCESS_SYSTEM_SECURITY
|
309
308
|
# * MAXIMUM_ALLOWED
|
@@ -466,5 +465,267 @@ class File
|
|
466
465
|
|
467
466
|
sec_array
|
468
467
|
end
|
468
|
+
|
469
|
+
# Returns true if the effective user ID of the process is the same as the
|
470
|
+
# owner of the named file.
|
471
|
+
#
|
472
|
+
# Example:
|
473
|
+
#
|
474
|
+
# p File.owned?('some_file.txt') # => true
|
475
|
+
# p File.owned?('C:/Windows/regedit.ext') # => false
|
476
|
+
#--
|
477
|
+
# This method was redefined for MS Windows.
|
478
|
+
#
|
479
|
+
def owned?(file)
|
480
|
+
return_value = false
|
481
|
+
wide_file = file.wincode
|
482
|
+
size_needed_ptr = FFI::MemoryPointer.new(:ulong)
|
483
|
+
|
484
|
+
# First pass, get the size needed
|
485
|
+
bool = GetFileSecurityW(
|
486
|
+
wide_file,
|
487
|
+
OWNER_SECURITY_INFORMATION,
|
488
|
+
nil,
|
489
|
+
0,
|
490
|
+
size_needed_ptr
|
491
|
+
)
|
492
|
+
|
493
|
+
size_needed = size_needed_ptr.read_ulong
|
494
|
+
|
495
|
+
security_ptr = FFI::MemoryPointer.new(size_needed)
|
496
|
+
|
497
|
+
# Second pass, this time with the appropriately sized security pointer
|
498
|
+
bool = GetFileSecurityW(
|
499
|
+
wide_file,
|
500
|
+
OWNER_SECURITY_INFORMATION,
|
501
|
+
security_ptr,
|
502
|
+
security_ptr.size,
|
503
|
+
size_needed_ptr
|
504
|
+
)
|
505
|
+
|
506
|
+
raise SystemCallError.new("GetFileSecurity", FFI.errno) unless bool
|
507
|
+
|
508
|
+
sid_ptr = FFI::MemoryPointer.new(:pointer)
|
509
|
+
defaulted = FFI::MemoryPointer.new(:bool)
|
510
|
+
|
511
|
+
unless GetSecurityDescriptorOwner(security_ptr, sid_ptr, defaulted)
|
512
|
+
raise SystemCallError.new("GetFileSecurity", FFI.errno)
|
513
|
+
end
|
514
|
+
|
515
|
+
sid = sid_ptr.read_pointer
|
516
|
+
|
517
|
+
token = FFI::MemoryPointer.new(:ulong)
|
518
|
+
|
519
|
+
begin
|
520
|
+
# Get the current process sid
|
521
|
+
unless OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, token)
|
522
|
+
raise SystemCallError, FFI.errno, "OpenProcessToken"
|
523
|
+
end
|
524
|
+
|
525
|
+
token = token.read_ulong
|
526
|
+
rlength = FFI::MemoryPointer.new(:ulong)
|
527
|
+
tuser = 0.chr * 512
|
528
|
+
|
529
|
+
bool = GetTokenInformation(
|
530
|
+
token,
|
531
|
+
TokenUser,
|
532
|
+
tuser,
|
533
|
+
tuser.size,
|
534
|
+
rlength
|
535
|
+
)
|
536
|
+
|
537
|
+
unless bool
|
538
|
+
raise SystemCallError, FFI.errno, "GetTokenInformation"
|
539
|
+
end
|
540
|
+
|
541
|
+
string_sid = tuser[8, (rlength.read_ulong - 8)]
|
542
|
+
|
543
|
+
# Now compare the sid strings
|
544
|
+
if string_sid == sid.read_string(string_sid.size)
|
545
|
+
return_value = true
|
546
|
+
end
|
547
|
+
ensure
|
548
|
+
CloseHandle(token)
|
549
|
+
end
|
550
|
+
|
551
|
+
return_value
|
552
|
+
end
|
553
|
+
|
554
|
+
# Changes the owner of the named file(s) to the given owner (userid).
|
555
|
+
# It will typically require elevated privileges in order to change the
|
556
|
+
# owner of a file.
|
557
|
+
#
|
558
|
+
# This group argument is currently ignored, but is included in the method
|
559
|
+
# definition for compatibility with the current spec. Also note that the
|
560
|
+
# owner should be a string, not a numeric ID.
|
561
|
+
#
|
562
|
+
# Example:
|
563
|
+
#
|
564
|
+
# File.chown('some_user', nil, 'some_file.txt')
|
565
|
+
#--
|
566
|
+
# In the future we may allow the owner argument to be a SID or a RID and
|
567
|
+
# simply adjust accordingly.
|
568
|
+
#
|
569
|
+
def chown(owner, group, *files)
|
570
|
+
token = FFI::MemoryPointer.new(:ulong)
|
571
|
+
|
572
|
+
begin
|
573
|
+
bool = OpenProcessToken(
|
574
|
+
GetCurrentProcess(),
|
575
|
+
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
|
576
|
+
token
|
577
|
+
)
|
578
|
+
|
579
|
+
raise SystemCallError.new("OpenProcessToken", FFI.errno) unless bool
|
580
|
+
|
581
|
+
token_handle = token.read_ulong
|
582
|
+
|
583
|
+
privs = [
|
584
|
+
SE_SECURITY_NAME,
|
585
|
+
SE_TAKE_OWNERSHIP_NAME,
|
586
|
+
SE_BACKUP_NAME,
|
587
|
+
SE_RESTORE_NAME,
|
588
|
+
SE_CHANGE_NOTIFY_NAME
|
589
|
+
]
|
590
|
+
|
591
|
+
privs.each{ |name|
|
592
|
+
luid = LUID.new
|
593
|
+
|
594
|
+
unless LookupPrivilegeValueA(nil, name, luid)
|
595
|
+
raise SystemCallError.new("LookupPrivilegeValue", FFI.errno)
|
596
|
+
end
|
597
|
+
|
598
|
+
tp = TOKEN_PRIVILEGES.new
|
599
|
+
tp[:PrivilegeCount] = 1
|
600
|
+
tp[:Privileges][0][:Luid] = luid
|
601
|
+
tp[:Privileges][0][:Attributes] = SE_PRIVILEGE_ENABLED
|
602
|
+
|
603
|
+
unless AdjustTokenPrivileges(token_handle, false, tp, 0, nil, nil)
|
604
|
+
raise SystemCallError.new("AdjustTokenPrivileges", FFI.errno)
|
605
|
+
end
|
606
|
+
}
|
607
|
+
|
608
|
+
sid = FFI::MemoryPointer.new(:uchar)
|
609
|
+
sid_size = FFI::MemoryPointer.new(:ulong)
|
610
|
+
dom = FFI::MemoryPointer.new(:uchar)
|
611
|
+
dom_size = FFI::MemoryPointer.new(:ulong)
|
612
|
+
use = FFI::MemoryPointer.new(:ulong)
|
613
|
+
|
614
|
+
wowner = owner.wincode
|
615
|
+
|
616
|
+
# First run, get needed sizes
|
617
|
+
LookupAccountNameW(nil, wowner, sid, sid_size, dom, dom_size, use)
|
618
|
+
|
619
|
+
sid = FFI::MemoryPointer.new(:uchar, sid_size.read_ulong * 2)
|
620
|
+
dom = FFI::MemoryPointer.new(:uchar, dom_size.read_ulong * 2)
|
621
|
+
|
622
|
+
# Second run with required sizes
|
623
|
+
unless LookupAccountNameW(nil, wowner, sid, sid_size, dom, dom_size, use)
|
624
|
+
raise SystemCallError.new("LookupAccountName", FFI.errno)
|
625
|
+
end
|
626
|
+
|
627
|
+
files.each{ |file|
|
628
|
+
wfile = file.wincode
|
629
|
+
|
630
|
+
size = FFI::MemoryPointer.new(:ulong)
|
631
|
+
sec = FFI::MemoryPointer.new(:ulong)
|
632
|
+
|
633
|
+
# First pass, get the size needed
|
634
|
+
GetFileSecurityW(wfile, OWNER_SECURITY_INFORMATION, sec, sec.size, size)
|
635
|
+
|
636
|
+
security = FFI::MemoryPointer.new(size.read_ulong)
|
637
|
+
|
638
|
+
# Second pass, this time with the appropriately sized security pointer
|
639
|
+
bool = GetFileSecurityW(
|
640
|
+
wfile,
|
641
|
+
OWNER_SECURITY_INFORMATION,
|
642
|
+
security,
|
643
|
+
security.size,
|
644
|
+
size
|
645
|
+
)
|
646
|
+
|
647
|
+
raise SystemCallError.new("GetFileSecurity", FFI.errno) unless bool
|
648
|
+
|
649
|
+
unless InitializeSecurityDescriptor(security, SECURITY_DESCRIPTOR_REVISION)
|
650
|
+
raise SystemCallError.new("InitializeSecurityDescriptor", FFI.errno)
|
651
|
+
end
|
652
|
+
|
653
|
+
unless SetSecurityDescriptorOwner(security, sid, false)
|
654
|
+
raise SystemCallError.new("SetSecurityDescriptorOwner", FFI.errno)
|
655
|
+
end
|
656
|
+
|
657
|
+
unless SetFileSecurityW(wfile, OWNER_SECURITY_INFORMATION, security)
|
658
|
+
raise SystemCallError.new("SetFileSecurity", FFI.errno)
|
659
|
+
end
|
660
|
+
}
|
661
|
+
ensure
|
662
|
+
CloseHandle(token.read_ulong)
|
663
|
+
end
|
664
|
+
|
665
|
+
files.size
|
666
|
+
end
|
667
|
+
|
668
|
+
# Returns the owner of the specified file in domain\\userid format.
|
669
|
+
#
|
670
|
+
# Example:
|
671
|
+
#
|
672
|
+
# p File.owner('some_file.txt') # => "your_domain\\some_user"
|
673
|
+
#
|
674
|
+
def owner(file)
|
675
|
+
size_needed = FFI::MemoryPointer.new(:ulong)
|
676
|
+
|
677
|
+
# First pass, get the size needed
|
678
|
+
bool = GetFileSecurityW(
|
679
|
+
file.wincode,
|
680
|
+
OWNER_SECURITY_INFORMATION,
|
681
|
+
nil,
|
682
|
+
0,
|
683
|
+
size_needed
|
684
|
+
)
|
685
|
+
|
686
|
+
security = FFI::MemoryPointer.new(size_needed.read_ulong)
|
687
|
+
|
688
|
+
# Second pass, this time with the appropriately sized security pointer
|
689
|
+
bool = GetFileSecurityW(
|
690
|
+
file.wincode,
|
691
|
+
OWNER_SECURITY_INFORMATION,
|
692
|
+
security,
|
693
|
+
security.size,
|
694
|
+
size_needed
|
695
|
+
)
|
696
|
+
|
697
|
+
raise SystemCallError.new("GetFileSecurity", FFI.errno) unless bool
|
698
|
+
|
699
|
+
sid = FFI::MemoryPointer.new(:pointer)
|
700
|
+
defaulted = FFI::MemoryPointer.new(:bool)
|
701
|
+
|
702
|
+
unless GetSecurityDescriptorOwner(security, sid, defaulted)
|
703
|
+
raise SystemCallError.new("GetFileSecurity", FFI.errno)
|
704
|
+
end
|
705
|
+
|
706
|
+
sid = sid.read_pointer
|
707
|
+
|
708
|
+
name = FFI::MemoryPointer.new(:uchar)
|
709
|
+
name_size = FFI::MemoryPointer.new(:ulong)
|
710
|
+
dom = FFI::MemoryPointer.new(:uchar)
|
711
|
+
dom_size = FFI::MemoryPointer.new(:ulong)
|
712
|
+
use = FFI::MemoryPointer.new(:pointer)
|
713
|
+
|
714
|
+
# First call, get sizes needed
|
715
|
+
LookupAccountSidW(nil, sid, name, name_size, dom, dom_size, use)
|
716
|
+
|
717
|
+
name = FFI::MemoryPointer.new(:uchar, name_size.read_ulong * 2)
|
718
|
+
dom = FFI::MemoryPointer.new(:uchar, dom_size.read_ulong * 2)
|
719
|
+
|
720
|
+
# Second call, get desired information
|
721
|
+
unless LookupAccountSidW(nil, sid, name, name_size, dom, dom_size, use)
|
722
|
+
raise SystemCallError.new("LookupAccountSid", FFI.errno)
|
723
|
+
end
|
724
|
+
|
725
|
+
name = name.read_string(name.size).tr(0.chr, '').strip
|
726
|
+
domain = dom.read_string(dom.size).tr(0.chr, '').strip
|
727
|
+
|
728
|
+
domain << "\\" << name
|
729
|
+
end
|
469
730
|
end
|
470
731
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
########################################################################
|
2
|
+
# test_win32_file_security_constants.rb
|
3
|
+
#
|
4
|
+
# Tests to ensure that certain constants are defined for the
|
5
|
+
# win32-file-security library.
|
6
|
+
########################################################################
|
7
|
+
require 'test-unit'
|
8
|
+
require 'win32/file/security'
|
9
|
+
|
10
|
+
class TC_Win32_File_Constants < Test::Unit::TestCase
|
11
|
+
test "file security rights constants are defined" do
|
12
|
+
assert_not_nil(File::FILE_READ_DATA)
|
13
|
+
assert_not_nil(File::FILE_WRITE_DATA)
|
14
|
+
assert_not_nil(File::FILE_APPEND_DATA)
|
15
|
+
assert_not_nil(File::FILE_READ_EA)
|
16
|
+
assert_not_nil(File::FILE_EXECUTE)
|
17
|
+
assert_not_nil(File::FILE_DELETE_CHILD)
|
18
|
+
assert_not_nil(File::FILE_READ_ATTRIBUTES)
|
19
|
+
assert_not_nil(File::FILE_WRITE_ATTRIBUTES)
|
20
|
+
end
|
21
|
+
|
22
|
+
test "standard security rights constants are defined" do
|
23
|
+
assert_not_nil(File::STANDARD_RIGHTS_ALL)
|
24
|
+
assert_not_nil(File::STANDARD_RIGHTS_REQUIRED)
|
25
|
+
assert_not_nil(File::STANDARD_RIGHTS_READ)
|
26
|
+
assert_not_nil(File::STANDARD_RIGHTS_WRITE)
|
27
|
+
assert_not_nil(File::STANDARD_RIGHTS_EXECUTE)
|
28
|
+
end
|
29
|
+
|
30
|
+
test "generic security rights constants are defined" do
|
31
|
+
assert_not_nil(File::GENERIC_READ)
|
32
|
+
assert_not_nil(File::GENERIC_WRITE)
|
33
|
+
assert_not_nil(File::GENERIC_EXECUTE)
|
34
|
+
assert_not_nil(File::GENERIC_ALL)
|
35
|
+
end
|
36
|
+
|
37
|
+
test "combined security rights constants are defined" do
|
38
|
+
assert_not_nil(File::FULL)
|
39
|
+
assert_not_nil(File::READ)
|
40
|
+
assert_not_nil(File::CHANGE)
|
41
|
+
assert_not_nil(File::ADD)
|
42
|
+
assert_not_nil(File::DELETE)
|
43
|
+
end
|
44
|
+
|
45
|
+
test "miscellaneous security rights constants are defined" do
|
46
|
+
assert_not_nil(File::READ_CONTROL)
|
47
|
+
assert_not_nil(File::WRITE_DAC)
|
48
|
+
assert_not_nil(File::WRITE_OWNER)
|
49
|
+
assert_not_nil(File::SYNCHRONIZE)
|
50
|
+
assert_not_nil(File::SPECIFIC_RIGHTS_ALL)
|
51
|
+
assert_not_nil(File::ACCESS_SYSTEM_SECURITY)
|
52
|
+
assert_not_nil(File::MAXIMUM_ALLOWED)
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
#############################################################################
|
2
|
+
# test_win32_file_security_ffi.rb
|
3
|
+
#
|
4
|
+
# Tests to ensure that the FFI functions are private
|
5
|
+
#############################################################################
|
6
|
+
require 'test-unit'
|
7
|
+
require 'win32/file/security'
|
8
|
+
|
9
|
+
class TC_Win32_File_Security_FFI < Test::Unit::TestCase
|
10
|
+
def setup
|
11
|
+
@singleton_methods = File.methods.map{ |m| m.to_s }
|
12
|
+
@instance_methods = File.instance_methods.map{ |m| m.to_s }
|
13
|
+
end
|
14
|
+
|
15
|
+
test "internal ffi functions are not public as singleton methods" do
|
16
|
+
assert_false(@singleton_methods.include?('AddAce'))
|
17
|
+
assert_false(@singleton_methods.include?('CloseHandle'))
|
18
|
+
assert_false(@singleton_methods.include?('GetFileSecurityW'))
|
19
|
+
assert_false(@singleton_methods.include?('PathIsRootW'))
|
20
|
+
end
|
21
|
+
|
22
|
+
test "internal ffi functions are not public as instance methods" do
|
23
|
+
assert_false(@instance_methods.include?('AddAce'))
|
24
|
+
assert_false(@instance_methods.include?('CloseHandle'))
|
25
|
+
assert_false(@instance_methods.include?('GetFileSecurityW'))
|
26
|
+
assert_false(@instance_methods.include?('PathIsRootW'))
|
27
|
+
end
|
28
|
+
|
29
|
+
def teardown
|
30
|
+
@singleton_methods = nil
|
31
|
+
@instance_methods = nil
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
#############################################################################
|
2
|
+
# test_win32_file_ownership.rb
|
3
|
+
#
|
4
|
+
# Test case for the file ownership related methods
|
5
|
+
#############################################################################
|
6
|
+
require 'etc'
|
7
|
+
require 'socket'
|
8
|
+
require 'sys/admin'
|
9
|
+
require 'test-unit'
|
10
|
+
require 'win32/security'
|
11
|
+
require 'win32/file/security'
|
12
|
+
|
13
|
+
class TC_Win32_File_Security_Ownership < Test::Unit::TestCase
|
14
|
+
def self.startup
|
15
|
+
Dir.chdir(File.dirname(File.expand_path(File.basename(__FILE__))))
|
16
|
+
@@file = File.join(Dir.pwd, 'ownership_test.txt')
|
17
|
+
File.open(@@file, 'w'){ |fh| fh.puts "This is an ownership test." }
|
18
|
+
|
19
|
+
@@host = Socket.gethostname
|
20
|
+
@@temp = "Temp"
|
21
|
+
@@login = Etc.getlogin
|
22
|
+
|
23
|
+
if Win32::Security.elevated_security?
|
24
|
+
Sys::Admin.add_user(:name => @@temp, :description => "Delete Me")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def setup
|
29
|
+
@elevated = Win32::Security.elevated_security?
|
30
|
+
end
|
31
|
+
|
32
|
+
test "owned? method basic functionality" do
|
33
|
+
assert_respond_to(File, :owned?)
|
34
|
+
assert_nothing_raised{ File.owned?(@@file) }
|
35
|
+
assert_boolean(File.owned?(@@file))
|
36
|
+
end
|
37
|
+
|
38
|
+
test "owned? method returns expected result" do
|
39
|
+
if Win32::Security.elevated_security?
|
40
|
+
assert_false(File.owned?(@@file))
|
41
|
+
else
|
42
|
+
assert_true(File.owned?(@@file))
|
43
|
+
end
|
44
|
+
assert_false(File.owned?("C:\\Windows\\regedit.exe"))
|
45
|
+
end
|
46
|
+
|
47
|
+
test "owned? requires a single argument" do
|
48
|
+
assert_raise(ArgumentError){ File.owned? }
|
49
|
+
assert_raise(ArgumentError){ File.owned?(@@file, @@file) }
|
50
|
+
end
|
51
|
+
|
52
|
+
test "owner method basic functionality" do
|
53
|
+
assert_respond_to(File, :owner)
|
54
|
+
assert_nothing_raised{ File.owner(@@file) }
|
55
|
+
assert_kind_of(String, File.owner(@@file))
|
56
|
+
end
|
57
|
+
|
58
|
+
test "owner method returns the expected value" do
|
59
|
+
if Win32::Security.elevated_security?
|
60
|
+
expected = "BUILTIN\\Administrators"
|
61
|
+
else
|
62
|
+
expected = @@host + "\\" + @@login
|
63
|
+
end
|
64
|
+
assert_equal(expected, File.owner(@@file))
|
65
|
+
end
|
66
|
+
|
67
|
+
test "owner method requires a single argument" do
|
68
|
+
assert_raise(ArgumentError){ File.owner }
|
69
|
+
assert_raise(ArgumentError){ File.owner(@@file, @@file) }
|
70
|
+
end
|
71
|
+
|
72
|
+
test "chown method basic functionality" do
|
73
|
+
assert_respond_to(File, :chown)
|
74
|
+
end
|
75
|
+
|
76
|
+
test "chown works as expected" do
|
77
|
+
omit_unless(@elevated)
|
78
|
+
original_owner = File.owner(@@file)
|
79
|
+
expected_owner = @@host + "\\" + @@temp
|
80
|
+
|
81
|
+
assert_nothing_raised{ File.chown(@@temp, nil, @@file) }
|
82
|
+
assert_equal(expected_owner, File.owner(@@file))
|
83
|
+
assert_nothing_raised{ File.chown(original_owner, nil, @@file) }
|
84
|
+
assert_equal(original_owner, File.owner(@@file))
|
85
|
+
end
|
86
|
+
|
87
|
+
test "chown returns the number of files processed" do
|
88
|
+
omit_unless(@elevated)
|
89
|
+
assert_equal(1, File.chown(@@temp, nil, @@file))
|
90
|
+
end
|
91
|
+
|
92
|
+
test "chown requires at least two arguments" do
|
93
|
+
assert_raise(ArgumentError){ File.chown }
|
94
|
+
assert_raise(ArgumentError){ File.chown(@@temp) }
|
95
|
+
end
|
96
|
+
|
97
|
+
def teardown
|
98
|
+
@elevated = nil
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.shutdown
|
102
|
+
Sys::Admin.delete_user(@@temp) if Win32::Security.elevated_security?
|
103
|
+
File.delete(@@file) if File.exists?(@@file)
|
104
|
+
@@file = nil
|
105
|
+
@@login = nil
|
106
|
+
@@host = nil
|
107
|
+
end
|
108
|
+
end
|
@@ -8,6 +8,6 @@ require 'win32/file/security'
|
|
8
8
|
|
9
9
|
class TC_Win32_File_Security_Version < Test::Unit::TestCase
|
10
10
|
test "version is set to expected value" do
|
11
|
-
assert_equal('1.0.
|
11
|
+
assert_equal('1.0.1', File::WIN32_FILE_SECURITY_VERSION)
|
12
12
|
end
|
13
13
|
end
|
data/win32-file-security.gemspec
CHANGED
@@ -2,7 +2,7 @@ require 'rubygems'
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |spec|
|
4
4
|
spec.name = 'win32-file-security'
|
5
|
-
spec.version = '1.0.
|
5
|
+
spec.version = '1.0.1'
|
6
6
|
spec.authors = ['Daniel J. Berger', 'Park Heesob']
|
7
7
|
spec.license = 'Artistic 2.0'
|
8
8
|
spec.email = 'djberg96@gmail.com'
|
@@ -17,6 +17,7 @@ Gem::Specification.new do |spec|
|
|
17
17
|
spec.add_dependency('ffi')
|
18
18
|
spec.add_development_dependency('test-unit')
|
19
19
|
spec.add_development_dependency('win32-security')
|
20
|
+
spec.add_development_dependency('sys-admin')
|
20
21
|
|
21
22
|
spec.description = <<-EOF
|
22
23
|
The win32-file-security library adds security related methods to the
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: win32-file-security
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2013-01-01 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: ffi
|
@@ -60,6 +60,22 @@ dependencies:
|
|
60
60
|
- - ! '>='
|
61
61
|
- !ruby/object:Gem::Version
|
62
62
|
version: '0'
|
63
|
+
- !ruby/object:Gem::Dependency
|
64
|
+
name: sys-admin
|
65
|
+
requirement: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ! '>='
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
type: :development
|
72
|
+
prerelease: false
|
73
|
+
version_requirements: !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ! '>='
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
63
79
|
description: ! " The win32-file-security library adds security related methods
|
64
80
|
to the\n core File class for MS Windows. This includes the ability to get or\n
|
65
81
|
\ set permissions, as well as encrypt or decrypt files.\n"
|
@@ -72,14 +88,18 @@ extra_rdoc_files:
|
|
72
88
|
- MANIFEST
|
73
89
|
files:
|
74
90
|
- CHANGES
|
91
|
+
- lib/win32/file/security/constants.rb
|
92
|
+
- lib/win32/file/security/functions.rb
|
93
|
+
- lib/win32/file/security/helper.rb
|
94
|
+
- lib/win32/file/security/structs.rb
|
75
95
|
- lib/win32/file/security.rb
|
76
|
-
- lib/win32/file/windows/constants.rb
|
77
|
-
- lib/win32/file/windows/functions.rb
|
78
|
-
- lib/win32/file/windows/structs.rb
|
79
96
|
- MANIFEST
|
80
97
|
- Rakefile
|
81
98
|
- README
|
99
|
+
- test/test_win32_file_security_constants.rb
|
82
100
|
- test/test_win32_file_security_encryption.rb
|
101
|
+
- test/test_win32_file_security_ffi.rb
|
102
|
+
- test/test_win32_file_security_ownership.rb
|
83
103
|
- test/test_win32_file_security_permissions.rb
|
84
104
|
- test/test_win32_file_security_version.rb
|
85
105
|
- win32-file-security.gemspec
|
@@ -109,6 +129,9 @@ signing_key:
|
|
109
129
|
specification_version: 3
|
110
130
|
summary: File security methods for the File class on MS Windows
|
111
131
|
test_files:
|
132
|
+
- test/test_win32_file_security_constants.rb
|
112
133
|
- test/test_win32_file_security_encryption.rb
|
134
|
+
- test/test_win32_file_security_ffi.rb
|
135
|
+
- test/test_win32_file_security_ownership.rb
|
113
136
|
- test/test_win32_file_security_permissions.rb
|
114
137
|
- test/test_win32_file_security_version.rb
|
@@ -1,46 +0,0 @@
|
|
1
|
-
require 'ffi'
|
2
|
-
|
3
|
-
module Windows
|
4
|
-
module File
|
5
|
-
module Functions
|
6
|
-
extend FFI::Library
|
7
|
-
ffi_lib :advapi32
|
8
|
-
|
9
|
-
attach_function :AddAce, [:pointer, :ulong, :ulong, :pointer, :ulong], :bool
|
10
|
-
attach_function :CopySid, [:ulong, :pointer, :pointer], :bool
|
11
|
-
attach_function :EncryptFileW, [:buffer_in], :bool
|
12
|
-
attach_function :DecryptFileW, [:buffer_in, :ulong], :bool
|
13
|
-
attach_function :FileEncryptionStatusW, [:buffer_in, :pointer], :bool
|
14
|
-
attach_function :GetAce, [:pointer, :ulong, :pointer], :bool
|
15
|
-
attach_function :GetFileSecurityW, [:buffer_in, :ulong, :pointer, :ulong, :pointer], :bool
|
16
|
-
attach_function :GetLengthSid, [:pointer], :ulong
|
17
|
-
attach_function :GetSecurityDescriptorControl, [:pointer, :pointer, :pointer], :bool
|
18
|
-
attach_function :GetSecurityDescriptorDacl, [:pointer, :pointer, :pointer, :pointer], :ulong
|
19
|
-
attach_function :InitializeAcl, [:pointer, :ulong, :ulong], :bool
|
20
|
-
attach_function :InitializeSecurityDescriptor, [:pointer, :ulong], :bool
|
21
|
-
attach_function :LookupAccountNameW, [:buffer_in, :buffer_in, :pointer, :pointer, :pointer, :pointer, :pointer], :bool
|
22
|
-
attach_function :LookupAccountSidW, [:buffer_in, :pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :bool
|
23
|
-
attach_function :SetFileSecurityW, [:buffer_in, :ulong, :pointer], :bool
|
24
|
-
attach_function :SetSecurityDescriptorDacl, [:pointer, :bool, :pointer, :bool], :bool
|
25
|
-
|
26
|
-
ffi_lib :kernel32
|
27
|
-
|
28
|
-
attach_function :GetVolumeInformationW,
|
29
|
-
[:buffer_in, :buffer_out, :ulong, :pointer, :pointer, :pointer, :buffer_out, :ulong],
|
30
|
-
:bool
|
31
|
-
|
32
|
-
ffi_lib :shlwapi
|
33
|
-
|
34
|
-
attach_function :PathStripToRootW, [:buffer_in], :bool
|
35
|
-
attach_function :PathIsRootW, [:buffer_in], :bool
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
class String
|
41
|
-
# Convenience method for converting strings to UTF-16LE for wide character
|
42
|
-
# functions that require it.
|
43
|
-
def wincode
|
44
|
-
(self.tr(File::SEPARATOR, File::ALT_SEPARATOR) + 0.chr).encode('UTF-16LE')
|
45
|
-
end
|
46
|
-
end
|