win32-file-security 1.0.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|