win32-file-security 1.0.0

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 ADDED
@@ -0,0 +1,2 @@
1
+ = 1.0.0 - 19-Dec-2012
2
+ * Initial release as an independent library.
data/MANIFEST ADDED
@@ -0,0 +1,12 @@
1
+ * CHANGES
2
+ * MANIFEST
3
+ * Rakefile
4
+ * README
5
+ * win32-file-security.gemspec
6
+ * lib/win32/file/security.rb
7
+ * lib/win32/file/windows/constants.rb
8
+ * lib/win32/file/windows/functions.rb
9
+ * lib/win32/file/windows/structs.rb
10
+ * test/test_win32_file_security_encryption.rb
11
+ * test/test_win32_file_security_permissions.rb
12
+ * test/test_win32_file_security_version.rb
data/README ADDED
@@ -0,0 +1,45 @@
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
+ == Notes
18
+ If you have the win32-file gem already installed then you do not need this
19
+ gem. This library was split out from the win32-file gem in order to ease
20
+ testing and maintenance.
21
+
22
+ Otherwise, the only difference is that this library uses FFI instead
23
+ of win32-api. This also means that it works with JRuby.
24
+
25
+ == Known issues or bugs
26
+ None that I'm aware of.
27
+
28
+ Please report any issues you find on the github page at:
29
+
30
+ https://github.com/djberg96/win32-file-security/issues
31
+
32
+ == License
33
+ Artistic 2.0
34
+
35
+ == Copyright
36
+ (C) 2003-2012, Daniel J. Berger, All Rights Reserved
37
+
38
+ == Warranty
39
+ This package is provided "as is" and without any express or
40
+ implied warranties, including, without limitation, the implied
41
+ warranties of merchantability and fitness for a particular purpose.
42
+
43
+ == Authors
44
+ * Daniel J. Berger
45
+ * Park Heesob
data/Rakefile ADDED
@@ -0,0 +1,43 @@
1
+ require 'rake'
2
+ require 'rake/clean'
3
+ require 'rake/testtask'
4
+
5
+ CLEAN.include('**/*.gem', '**/*.rbc')
6
+
7
+ namespace :gem do
8
+ desc 'Build the win32-file-security gem'
9
+ task :create => [:clean] do
10
+ spec = eval(IO.read('win32-file-security.gemspec'))
11
+ Gem::Builder.new(spec).build
12
+ end
13
+
14
+ desc "Install the win32-file-security gem"
15
+ task :install => [:create] do
16
+ file = Dir["*.gem"].first
17
+ sh "gem install #{file}"
18
+ end
19
+ end
20
+
21
+ namespace 'test' do
22
+ Rake::TestTask.new('all') do |t|
23
+ task :test => :clean
24
+ t.warning = true
25
+ t.verbose = true
26
+ end
27
+
28
+ Rake::TestTask.new('encryption') do |t|
29
+ task :test => :clean
30
+ t.warning = true
31
+ t.verbose = true
32
+ t.test_files = FileList['test/test_win32_file_security_encryption']
33
+ end
34
+
35
+ Rake::TestTask.new('permissions') do |t|
36
+ task :test => :clean
37
+ t.warning = true
38
+ t.verbose = true
39
+ t.test_files = FileList['test/test_win32_file_security_permissions']
40
+ end
41
+ end
42
+
43
+ task :default => 'test:all'
@@ -0,0 +1,470 @@
1
+ require File.join(File.dirname(__FILE__), 'windows', 'constants')
2
+ require File.join(File.dirname(__FILE__), 'windows', 'structs')
3
+ require File.join(File.dirname(__FILE__), 'windows', 'functions')
4
+
5
+ class File
6
+ include Windows::File::Constants
7
+ include Windows::File::Functions
8
+ extend Windows::File::Constants
9
+ extend Windows::File::Structs
10
+ extend Windows::File::Functions
11
+
12
+ # The version of the win32-file library
13
+ WIN32_FILE_SECURITY_VERSION = '1.0.0'
14
+
15
+ class << self
16
+
17
+ # Returns the encryption status of a file as a string. Possible return
18
+ # values are:
19
+ #
20
+ # * encryptable
21
+ # * encrypted
22
+ # * readonly
23
+ # * root directory (i.e. not encryptable)
24
+ # * system fiel (i.e. not encryptable)
25
+ # * unsupported
26
+ # * unknown
27
+ #
28
+ def encryption_status(file)
29
+ wide_file = file.wincode
30
+ status_ptr = FFI::MemoryPointer.new(:ulong)
31
+
32
+ unless FileEncryptionStatusW(wide_file, status_ptr)
33
+ raise SystemCallError.new("FileEncryptionStatus", FFI.errno)
34
+ end
35
+
36
+ status = status_ptr.read_ulong
37
+
38
+ rvalue = case status
39
+ when FILE_ENCRYPTABLE
40
+ "encryptable"
41
+ when FILE_IS_ENCRYPTED
42
+ "encrypted"
43
+ when FILE_READ_ONLY
44
+ "readonly"
45
+ when FILE_ROOT_DIR
46
+ "root directory"
47
+ when FILE_SYSTEM_ATTR
48
+ "system file"
49
+ when FILE_SYSTEM_NOT_SUPPORTED
50
+ "unsupported"
51
+ else
52
+ "unknown"
53
+ end
54
+
55
+ rvalue
56
+ end
57
+
58
+ # Returns whether or not the root path of the specified file is
59
+ # encryptable. If a relative path is specified, it will check against
60
+ # the root of the current directory.
61
+ #
62
+ # Be sure to include a trailing slash if specifying a root path.
63
+ #
64
+ # Examples:
65
+ #
66
+ # p File.encryptable?
67
+ # p File.encryptable?("D:\\")
68
+ # p File.encryptable?("C:/foo/bar.txt") # Same as 'C:\'
69
+ #
70
+ def encryptable?(file = nil)
71
+ bool = false
72
+ flags_ptr = FFI::MemoryPointer.new(:ulong)
73
+
74
+ if file
75
+ file = File.expand_path(file)
76
+ wide_file = file.wincode
77
+
78
+ if !PathIsRootW(wide_file)
79
+ unless PathStripToRootW(wide_file)
80
+ raise SystemCallError.new("PathStripToRoot", FFI.errno)
81
+ end
82
+ end
83
+ else
84
+ wide_file = nil
85
+ end
86
+
87
+ unless GetVolumeInformationW(wide_file, nil, 0, nil, nil, flags_ptr, nil, 0)
88
+ raise SystemCallError.new("GetVolumeInformation", FFI.errno)
89
+ end
90
+
91
+ flags = flags_ptr.read_ulong
92
+
93
+ if flags & FILE_SUPPORTS_ENCRYPTION > 0
94
+ bool = true
95
+ end
96
+
97
+ bool
98
+ end
99
+
100
+ # Encrypts a file or directory. All data streams in a file are encrypted.
101
+ # All new files created in an encrypted directory are encrypted.
102
+ #
103
+ # The caller must have the FILE_READ_DATA, FILE_WRITE_DATA,
104
+ # FILE_READ_ATTRIBUTES, FILE_WRITE_ATTRIBUTES, and SYNCHRONIZE access
105
+ # rights.
106
+ #
107
+ # Requires exclusive access to the file being encrypted, and will fail if
108
+ # another process is using the file or the file is marked read-only. If the
109
+ # file is compressed the file will be decompressed before encrypting it.
110
+ #
111
+ def encrypt(file)
112
+ unless EncryptFileW(file.wincode)
113
+ raise SystemCallError.new("EncryptFile", FFI.errno)
114
+ end
115
+ self
116
+ end
117
+
118
+ # Decrypts an encrypted file or directory.
119
+ #
120
+ # The caller must have the FILE_READ_DATA, FILE_WRITE_DATA,
121
+ # FILE_READ_ATTRIBUTES, FILE_WRITE_ATTRIBUTES, and SYNCHRONIZE access
122
+ # rights.
123
+ #
124
+ # Requires exclusive access to the file being decrypted, and will fail if
125
+ # another process is using the file. If the file is not encrypted an error
126
+ # is NOT raised, it's simply a no-op.
127
+ #
128
+ def decrypt(file)
129
+ unless DecryptFileW(file.wincode, 0)
130
+ raise SystemCallError.new("DecryptFile", FFI.errno)
131
+ end
132
+ self
133
+ end
134
+
135
+ # Returns a hash describing the current file permissions for the given
136
+ # file. The account name is the key, and the value is an integer mask
137
+ # that corresponds to the security permissions for that file.
138
+ #
139
+ # To get a human readable version of the permissions, pass the value to
140
+ # the +File.securities+ method.
141
+ #
142
+ # You may optionally specify a host as the second argument. If no host is
143
+ # specified then the current host is used.
144
+ #
145
+ # Examples:
146
+ #
147
+ # hash = File.get_permissions('test.txt')
148
+ #
149
+ # p hash # => {"NT AUTHORITY\\SYSTEM"=>2032127, "BUILTIN\\Administrators"=>2032127, ...}
150
+ #
151
+ # hash.each{ |name, mask|
152
+ # p name
153
+ # p File.securities(mask)
154
+ # }
155
+ #
156
+ def get_permissions(file, host=nil)
157
+ size_needed_ptr = FFI::MemoryPointer.new(:ulong)
158
+ security_ptr = FFI::MemoryPointer.new(:ulong)
159
+
160
+ wide_file = file.wincode
161
+ wide_host = host ? host.wincode : nil
162
+
163
+ # First pass, get the size needed
164
+ bool = GetFileSecurityW(
165
+ wide_file,
166
+ DACL_SECURITY_INFORMATION,
167
+ security_ptr,
168
+ security_ptr.size,
169
+ size_needed_ptr
170
+ )
171
+
172
+ errno = FFI.errno
173
+
174
+ if !bool && errno != ERROR_INSUFFICIENT_BUFFER
175
+ raise SystemCallError.new("GetFileSecurity", errno)
176
+ end
177
+
178
+ size_needed = size_needed_ptr.read_ulong
179
+
180
+ security_ptr = FFI::MemoryPointer.new(size_needed)
181
+
182
+ # Second pass, this time with the appropriately sized security pointer
183
+ bool = GetFileSecurityW(
184
+ wide_file,
185
+ DACL_SECURITY_INFORMATION,
186
+ security_ptr,
187
+ security_ptr.size,
188
+ size_needed_ptr
189
+ )
190
+
191
+ unless bool
192
+ raise SystemCallError.new("GetFileSecurity", FFI.errno)
193
+ end
194
+
195
+ control_ptr = FFI::MemoryPointer.new(:ulong)
196
+ revision_ptr = FFI::MemoryPointer.new(:ulong)
197
+
198
+ unless GetSecurityDescriptorControl(security_ptr, control_ptr, revision_ptr)
199
+ raise SystemCallError.new("GetSecurityDescriptorControl", FFI.errno)
200
+ end
201
+
202
+ control = control_ptr.read_ulong
203
+
204
+ if control & SE_DACL_PRESENT == 0
205
+ raise ArgumentError, "No DACL present: explicit deny all"
206
+ end
207
+
208
+ dacl_pptr = FFI::MemoryPointer.new(:pointer)
209
+ dacl_present_ptr = FFI::MemoryPointer.new(:bool)
210
+ dacl_defaulted_ptr = FFI::MemoryPointer.new(:ulong)
211
+
212
+ val = GetSecurityDescriptorDacl(
213
+ security_ptr,
214
+ dacl_present_ptr,
215
+ dacl_pptr,
216
+ dacl_defaulted_ptr
217
+ )
218
+
219
+ if val == 0
220
+ raise SystemCallError.new("GetSecurityDescriptorDacl", FFI.errno)
221
+ end
222
+
223
+ acl = ACL.new(dacl_pptr.read_pointer)
224
+
225
+ if acl[:AclRevision] == 0
226
+ raise ArgumentError, "DACL is NULL: implicit access grant"
227
+ end
228
+
229
+ ace_count = acl[:AceCount]
230
+ perms_hash = {}
231
+
232
+ 0.upto(ace_count - 1){ |i|
233
+ ace_pptr = FFI::MemoryPointer.new(:pointer)
234
+ next unless GetAce(acl, i, ace_pptr)
235
+
236
+ access = ACCESS_ALLOWED_ACE.new(ace_pptr.read_pointer)
237
+
238
+ if access[:Header][:AceType] == ACCESS_ALLOWED_ACE_TYPE
239
+ name = FFI::MemoryPointer.new(:uchar, 260)
240
+ name_size = FFI::MemoryPointer.new(:ulong)
241
+ name_size.write_ulong(name.size)
242
+
243
+ domain = FFI::MemoryPointer.new(:uchar, 260)
244
+ domain_size = FFI::MemoryPointer.new(:ulong)
245
+ domain_size.write_ulong(domain.size)
246
+
247
+ use_ptr = FFI::MemoryPointer.new(:pointer)
248
+
249
+ val = LookupAccountSidW(
250
+ wide_host,
251
+ ace_pptr.read_pointer + 8,
252
+ name,
253
+ name_size,
254
+ domain,
255
+ domain_size,
256
+ use_ptr
257
+ )
258
+
259
+ if val == 0
260
+ raise SystemCallError.new("LookupAccountSid", FFI.errno)
261
+ end
262
+
263
+ # The x2 multiplier is necessary due to wide char strings.
264
+ name = name.read_string(name_size.read_ulong * 2).delete(0.chr)
265
+ domain = domain.read_string(domain_size.read_ulong * 2).delete(0.chr)
266
+
267
+ unless domain.empty?
268
+ name = domain + '\\' + name
269
+ end
270
+
271
+ perms_hash[name] = access[:Mask]
272
+ end
273
+ }
274
+
275
+ perms_hash
276
+ end
277
+
278
+ # Sets the file permissions for the given file name. The 'permissions'
279
+ # argument is a hash with an account name as the key, and the various
280
+ # permission constants as possible values. The possible constant values
281
+ # are:
282
+ #
283
+ # * FILE_READ_DATA
284
+ # * FILE_WRITE_DATA
285
+ # * FILE_APPEND_DATA
286
+ # * FILE_READ_EA
287
+ # * FILE_WRITE_EA
288
+ # * FILE_EXECUTE
289
+ # * FILE_DELETE_CHILD
290
+ # * FILE_READ_ATTRIBUTES
291
+ # * FILE_WRITE_ATTRIBUTES
292
+ # * STANDARD_RIGHTS_ALL
293
+ # * FULL
294
+ # * READ
295
+ # * ADD
296
+ # * CHANGE
297
+ # * DELETE
298
+ # * READ_CONTROL
299
+ # * WRITE_DAC
300
+ # * WRITE_OWNER
301
+ # * SYNCHRONIZE
302
+ # * STANDARD_RIGHTS_REQUIRED
303
+ # * STANDARD_RIGHTS_READ
304
+ # * STANDARD_RIGHTS_WRITE
305
+ # * STANDARD_RIGHTS_EXECUTE
306
+ # * STANDARD_RIGHTS_ALL
307
+ # * SPECIFIC_RIGHTS_ALL
308
+ # * ACCESS_SYSTEM_SECURITY
309
+ # * MAXIMUM_ALLOWED
310
+ # * GENERIC_READ
311
+ # * GENERIC_WRITE
312
+ # * GENERIC_EXECUTE
313
+ # * GENERIC_ALL
314
+ #
315
+ # Example:
316
+ #
317
+ # # Set locally
318
+ # File.set_permissions(file, "userid" => File::GENERIC_ALL)
319
+ #
320
+ # # Set a remote system
321
+ # File.set_permissions(file, "host\\userid" => File::GENERIC_ALL)
322
+ #
323
+ def set_permissions(file, perms)
324
+ raise TypeError unless file.is_a?(String)
325
+ raise TypeError unless perms.kind_of?(Hash)
326
+
327
+ wide_file = file.wincode
328
+
329
+ account_rights = 0
330
+ sec_desc = FFI::MemoryPointer.new(:pointer, SECURITY_DESCRIPTOR_MIN_LENGTH)
331
+
332
+ unless InitializeSecurityDescriptor(sec_desc, 1)
333
+ raise SystemCallError.new("InitializeSecurityDescriptor", FFI.errno)
334
+ end
335
+
336
+ acl_new = FFI::MemoryPointer.new(ACL, 100)
337
+
338
+ unless InitializeAcl(acl_new, acl_new.size, ACL_REVISION2)
339
+ raise SystemCallError.new("InitializeAcl", FFI.errno)
340
+ end
341
+
342
+ perms.each{ |account, mask|
343
+ next if mask.nil?
344
+
345
+ server, account = account.split("\\")
346
+
347
+ if ['BUILTIN', 'NT AUTHORITY'].include?(server.upcase)
348
+ wide_server = nil
349
+ else
350
+ wide_server = server.wincode
351
+ end
352
+
353
+ wide_account = account.wincode
354
+
355
+ sid = FFI::MemoryPointer.new(:uchar, 1024)
356
+ sid_size = FFI::MemoryPointer.new(:ulong)
357
+ sid_size.write_ulong(sid.size)
358
+
359
+ domain = FFI::MemoryPointer.new(:uchar, 260)
360
+ domain_size = FFI::MemoryPointer.new(:ulong)
361
+ domain_size.write_ulong(domain.size)
362
+
363
+ use_ptr = FFI::MemoryPointer.new(:ulong)
364
+
365
+ val = LookupAccountNameW(
366
+ wide_server,
367
+ wide_account,
368
+ sid,
369
+ sid_size,
370
+ domain,
371
+ domain_size,
372
+ use_ptr
373
+ )
374
+
375
+ raise SystemCallError.new("LookupAccountName", FFI.errno) unless val
376
+
377
+ all_ace = ACCESS_ALLOWED_ACE2.new
378
+
379
+ val = CopySid(
380
+ ALLOW_ACE_LENGTH - ACCESS_ALLOWED_ACE.size,
381
+ all_ace.to_ptr+8,
382
+ sid
383
+ )
384
+
385
+ raise SystemCallError.new("CopySid", FFI.errno) unless val
386
+
387
+ if (GENERIC_ALL & mask).nonzero?
388
+ account_rights = GENERIC_ALL & mask
389
+ elsif (GENERIC_RIGHTS_CHK & mask).nonzero?
390
+ account_rights = GENERIC_RIGHTS_MASK & mask
391
+ else
392
+ # Do nothing, leave it set to zero.
393
+ end
394
+
395
+ all_ace[:Header][:AceFlags] = INHERIT_ONLY_ACE | OBJECT_INHERIT_ACE
396
+
397
+ 2.times{
398
+ if account_rights != 0
399
+ all_ace[:Header][:AceSize] = 8 + GetLengthSid(sid)
400
+ all_ace[:Mask] = account_rights
401
+
402
+ val = AddAce(
403
+ acl_new,
404
+ ACL_REVISION2,
405
+ MAXDWORD,
406
+ all_ace,
407
+ all_ace[:Header][:AceSize]
408
+ )
409
+
410
+ raise SystemCallError.new("AddAce", FFI.errno) unless val
411
+
412
+ all_ace[:Header][:AceFlags] = CONTAINER_INHERIT_ACE
413
+ else
414
+ all_ace[:Header][:AceFlags] = 0
415
+ end
416
+
417
+ account_rights = REST_RIGHTS_MASK & mask
418
+ }
419
+ }
420
+
421
+ unless SetSecurityDescriptorDacl(sec_desc, true, acl_new, false)
422
+ raise SystemCallError.new("SetSecurityDescriptorDacl", FFI.errno)
423
+ end
424
+
425
+ unless SetFileSecurityW(wide_file, DACL_SECURITY_INFORMATION, sec_desc)
426
+ raise SystemCallError.new("SetFileSecurity", FFI.errno)
427
+ end
428
+
429
+ self
430
+ end
431
+
432
+ # Returns an array of human-readable strings that correspond to the
433
+ # permission flags.
434
+ #
435
+ # Example:
436
+ #
437
+ # File.get_permissions('test.txt').each{ |name, mask|
438
+ # puts name
439
+ # p File.securities(mask)
440
+ # }
441
+ #
442
+ def securities(mask)
443
+ sec_array = []
444
+
445
+ security_rights = {
446
+ 'FULL' => FULL,
447
+ 'DELETE' => DELETE,
448
+ 'READ' => READ,
449
+ 'CHANGE' => CHANGE,
450
+ 'ADD' => ADD
451
+ }
452
+
453
+ if mask == 0
454
+ sec_array.push('NONE')
455
+ else
456
+ if (mask & FULL) ^ FULL == 0
457
+ sec_array.push('FULL')
458
+ else
459
+ security_rights.each{ |string, numeric|
460
+ if (numeric & mask) ^ numeric == 0
461
+ sec_array.push(string)
462
+ end
463
+ }
464
+ end
465
+ end
466
+
467
+ sec_array
468
+ end
469
+ end
470
+ end
@@ -0,0 +1,125 @@
1
+ module Windows
2
+ module File
3
+ module Constants
4
+ SE_DACL_PRESENT = 4
5
+ DACL_SECURITY_INFORMATION = 4
6
+ ACCESS_ALLOWED_ACE_TYPE = 0
7
+ ERROR_INSUFFICIENT_BUFFER = 122
8
+ ACL_REVISION2 = 2
9
+ ALLOW_ACE_LENGTH = 62
10
+ OBJECT_INHERIT_ACE = 0x1
11
+ CONTAINER_INHERIT_ACE = 0x2
12
+ INHERIT_ONLY_ACE = 0x8
13
+ MAXDWORD = 0xFFFFFFFF
14
+ SECURITY_DESCRIPTOR_MIN_LENGTH = 20
15
+
16
+ ## Security Rights
17
+
18
+ SYNCHRONIZE = 0x100000
19
+ STANDARD_RIGHTS_REQUIRED = 0xf0000
20
+ STANDARD_RIGHTS_READ = 0x20000
21
+ STANDARD_RIGHTS_WRITE = 0x20000
22
+ STANDARD_RIGHTS_EXECUTE = 0x20000
23
+ STANDARD_RIGHTS_ALL = 0x1F0000
24
+ SPECIFIC_RIGHTS_ALL = 0xFFFF
25
+ ACCESS_SYSTEM_SECURITY = 0x1000000
26
+ MAXIMUM_ALLOWED = 0x2000000
27
+ GENERIC_READ = 0x80000000
28
+ GENERIC_WRITE = 0x40000000
29
+ GENERIC_EXECUTE = 0x20000000
30
+ GENERIC_ALL = 0x10000000
31
+ GENERIC_RIGHTS_CHK = 0xF0000000
32
+ REST_RIGHTS_MASK = 0x001FFFFF
33
+
34
+ FILE_READ_DATA = 1
35
+ FILE_LIST_DIRECTORY = 1
36
+ FILE_WRITE_DATA = 2
37
+ FILE_ADD_FILE = 2
38
+ FILE_APPEND_DATA = 4
39
+ FILE_ADD_SUBDIRECTORY = 4
40
+ FILE_CREATE_PIPE_INSTANCE = 4
41
+ FILE_READ_EA = 8
42
+ FILE_READ_PROPERTIES = 8
43
+ FILE_WRITE_EA = 16
44
+ FILE_WRITE_PROPERTIES = 16
45
+ FILE_EXECUTE = 32
46
+ FILE_TRAVERSE = 32
47
+ FILE_DELETE_CHILD = 64
48
+ FILE_READ_ATTRIBUTES = 128
49
+ FILE_WRITE_ATTRIBUTES = 256
50
+
51
+ FILE_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x1FF
52
+
53
+ FILE_GENERIC_READ =
54
+ STANDARD_RIGHTS_READ |
55
+ FILE_READ_DATA |
56
+ FILE_READ_ATTRIBUTES |
57
+ FILE_READ_EA |
58
+ SYNCHRONIZE
59
+
60
+ FILE_GENERIC_WRITE =
61
+ STANDARD_RIGHTS_WRITE |
62
+ FILE_WRITE_DATA |
63
+ FILE_WRITE_ATTRIBUTES |
64
+ FILE_WRITE_EA |
65
+ FILE_APPEND_DATA |
66
+ SYNCHRONIZE
67
+
68
+ FILE_GENERIC_EXECUTE =
69
+ STANDARD_RIGHTS_EXECUTE |
70
+ FILE_READ_ATTRIBUTES |
71
+ FILE_EXECUTE |
72
+ SYNCHRONIZE
73
+
74
+ FILE_SHARE_READ = 1
75
+ FILE_SHARE_WRITE = 2
76
+ FILE_SHARE_DELETE = 4
77
+ FILE_NOTIFY_CHANGE_FILE_NAME = 1
78
+ FILE_NOTIFY_CHANGE_DIR_NAME = 2
79
+ FILE_NOTIFY_CHANGE_ATTRIBUTES = 4
80
+ FILE_NOTIFY_CHANGE_SIZE = 8
81
+ FILE_NOTIFY_CHANGE_LAST_WRITE = 16
82
+ FILE_NOTIFY_CHANGE_LAST_ACCESS = 32
83
+ FILE_NOTIFY_CHANGE_CREATION = 64
84
+ FILE_NOTIFY_CHANGE_SECURITY = 256
85
+ FILE_CASE_SENSITIVE_SEARCH = 1
86
+ FILE_CASE_PRESERVED_NAMES = 2
87
+ FILE_UNICODE_ON_DISK = 4
88
+ FILE_PERSISTENT_ACLS = 8
89
+ FILE_FILE_COMPRESSION = 16
90
+ FILE_VOLUME_QUOTAS = 32
91
+ FILE_SUPPORTS_SPARSE_FILES = 64
92
+ FILE_SUPPORTS_REPARSE_POINTS = 128
93
+ FILE_SUPPORTS_REMOTE_STORAGE = 256
94
+ FILE_VOLUME_IS_COMPRESSED = 0x8000
95
+ FILE_SUPPORTS_OBJECT_IDS = 0x10000
96
+ FILE_SUPPORTS_ENCRYPTION = 0x20000
97
+
98
+ FILE_ENCRYPTABLE = 0
99
+ FILE_IS_ENCRYPTED = 1
100
+ FILE_ROOT_DIR = 3
101
+ FILE_SYSTEM_ATTR = 2
102
+ FILE_SYSTEM_DIR = 4
103
+ FILE_UNKNOWN = 5
104
+ FILE_SYSTEM_NOT_SUPPORT = 6
105
+ FILE_READ_ONLY = 8
106
+
107
+ # Read and execute privileges
108
+ READ = FILE_GENERIC_READ | FILE_EXECUTE
109
+
110
+ # Add privileges
111
+ ADD = 0x001201bf
112
+
113
+ # Delete privileges
114
+ DELETE = 0x00010000
115
+
116
+ # Generic write, generic read, execute and delete privileges
117
+ CHANGE = FILE_GENERIC_WRITE | FILE_GENERIC_READ | FILE_EXECUTE | DELETE
118
+
119
+ # Full security rights - read, write, append, execute, and delete.
120
+ FULL = STANDARD_RIGHTS_ALL | FILE_READ_DATA | FILE_WRITE_DATA |
121
+ FILE_APPEND_DATA | FILE_READ_EA | FILE_WRITE_EA | FILE_EXECUTE |
122
+ FILE_DELETE_CHILD | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,46 @@
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
@@ -0,0 +1,42 @@
1
+ require 'ffi'
2
+
3
+ module Windows
4
+ module File
5
+ module Structs
6
+ class ACE_HEADER < FFI::Struct
7
+ layout(
8
+ :AceType, :uchar,
9
+ :AceFlags, :uchar,
10
+ :AceSize, :ushort
11
+ )
12
+ end
13
+
14
+ class ACCESS_ALLOWED_ACE < FFI::Struct
15
+ layout(
16
+ :Header, ACE_HEADER,
17
+ :Mask, :ulong,
18
+ :SidStart, :ulong
19
+ )
20
+ end
21
+
22
+ class ACCESS_ALLOWED_ACE2 < FFI::Struct
23
+ layout(
24
+ :Header, ACE_HEADER,
25
+ :Mask, :ulong,
26
+ :SidStart, :ulong,
27
+ :dummy, [:uchar, 40]
28
+ )
29
+ end
30
+
31
+ class ACL < FFI::Struct
32
+ layout(
33
+ :AclRevision, :uchar,
34
+ :Sbz1, :uchar,
35
+ :AclSize, :ushort,
36
+ :AceCount, :ushort,
37
+ :Sbz2, :ushort
38
+ )
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,90 @@
1
+ #############################################################################
2
+ # test_win32_file_encryption.rb
3
+ #
4
+ # Test case for the encryption related methods of win32-file. You should
5
+ # run this test via the 'rake test' or 'rake test_encryption' task.
6
+ #
7
+ # Note: These tests may fail based on the security setup of your system.
8
+ #############################################################################
9
+ require 'test-unit'
10
+ require 'win32/security'
11
+ require 'win32/file/security'
12
+ require 'socket'
13
+
14
+ class TC_Win32_File_Security_Encryption < Test::Unit::TestCase
15
+ def self.startup
16
+ Dir.chdir(File.dirname(File.expand_path(File.basename(__FILE__))))
17
+ @@file = File.join(Dir.pwd, 'encryption_test.txt')
18
+ File.open(@@file, 'w'){ |fh| fh.puts "This is an encryption test." }
19
+ @@host = Socket.gethostname
20
+ end
21
+
22
+ def setup
23
+ @msg = '=> Ignore. May not work due to security setup of your system.'
24
+ @elevated = Win32::Security.elevated_security?
25
+ @statuses = ['encrypted', 'encryptable', 'unknown']
26
+ end
27
+
28
+ test "encrypt method basic functionality" do
29
+ omit_unless(@elevated)
30
+ assert_respond_to(File, :encrypt)
31
+ assert_nothing_raised(@msg){ File.encrypt(@@file) }
32
+ end
33
+
34
+ test "encrypt requires one argument" do
35
+ omit_unless(@elevated)
36
+ assert_raise(ArgumentError){ File.encrypt }
37
+ assert_raise(ArgumentError){ File.encrypt(@@file, @@file) }
38
+ end
39
+
40
+ test "encrypt requires a string argument" do
41
+ omit_unless(@elevated)
42
+ assert_raise(TypeError, NoMethodError){ File.encrypt(1) }
43
+ end
44
+
45
+ test "decrypt method basic functionality" do
46
+ omit_unless(@elevated)
47
+ assert_respond_to(File, :decrypt)
48
+ assert_nothing_raised(@msg){ File.decrypt(@@file) }
49
+ end
50
+
51
+ test "decrypt accepts a single argument only" do
52
+ omit_unless(@elevated)
53
+ assert_raise(ArgumentError){ File.decrypt }
54
+ end
55
+
56
+ test "decrypt requires a string argument" do
57
+ omit_unless(@elevated)
58
+ assert_raise(TypeError, NoMethodError){ File.decrypt(1) }
59
+ end
60
+
61
+ test "encryptable? basic functionality" do
62
+ assert_respond_to(File, :encryptable?)
63
+ end
64
+
65
+ test "encryptable? returns a boolean value" do
66
+ assert_boolean(File.encryptable?("C:\\"))
67
+ end
68
+
69
+ test "encryption_status basic functionality" do
70
+ assert_respond_to(File, :encryption_status)
71
+ end
72
+
73
+ test "encryption_status returns the expected result" do
74
+ status = File.encryption_status(@@file)
75
+ assert_kind_of(String, status)
76
+ assert_true(@statuses.include?(status))
77
+ end
78
+
79
+ def teardown
80
+ @msg = nil
81
+ @statuses = nil
82
+ @elevated = nil
83
+ end
84
+
85
+ def self.shutdown
86
+ File.delete(@@file) if File.exists?(@@file)
87
+ @@file = nil
88
+ @@host = nil
89
+ end
90
+ end
@@ -0,0 +1,88 @@
1
+ ##############################################################################
2
+ # test_win32_file_permissions.rb
3
+ #
4
+ # Test case for permission related methods of win32-file-security. You should
5
+ # use the 'rake test' or 'rake test:perms' task to run this.
6
+ ##############################################################################
7
+ require 'test-unit'
8
+ require 'test/unit'
9
+ require 'win32/file/security'
10
+ require 'socket'
11
+ require 'etc'
12
+
13
+ class TC_Win32_File_Security_Permissions < Test::Unit::TestCase
14
+ def self.startup
15
+ @@user = Etc.getlogin
16
+ @@host = Socket.gethostname
17
+ @@file = File.join(Dir.pwd, 'security_test.txt')
18
+
19
+ Dir.chdir(File.dirname(File.expand_path(File.basename(__FILE__))))
20
+ File.open(@@file, 'w'){ |fh| fh.puts "This is a security test." }
21
+ end
22
+
23
+ def setup
24
+ @perms = nil
25
+ end
26
+
27
+ test "get_permissions basic functionality" do
28
+ assert_respond_to(File, :get_permissions)
29
+ assert_nothing_raised{ File.get_permissions(@@file) }
30
+ end
31
+
32
+ test "get_permissions returns a hash" do
33
+ assert_kind_of(Hash, File.get_permissions(@@file))
34
+ end
35
+
36
+ test "get_permissions accepts an optional hostname argument" do
37
+ assert_nothing_raised{ File.get_permissions(@@file, @@host) }
38
+ end
39
+
40
+ test "get_permissions requires at least one argument" do
41
+ assert_raise(ArgumentError){ File.get_permissions }
42
+ end
43
+
44
+ test "set_permissions basic functionality" do
45
+ assert_respond_to(File, :set_permissions)
46
+ end
47
+
48
+ test "set_permissions works as expected" do
49
+ assert_nothing_raised{ @perms = File.get_permissions(@@file) }
50
+ assert_nothing_raised{ File.set_permissions(@@file, @perms) }
51
+ assert_equal(@perms, File.get_permissions(@@file))
52
+ end
53
+
54
+ test "set_permissions works if host is specified" do
55
+ @perms = {"#{@@host}\\#{@@user}" => File::GENERIC_ALL}
56
+ assert_nothing_raised{ File.set_permissions(@@file, @perms) }
57
+ assert_equal(@perms, File.get_permissions(@@file))
58
+ end
59
+
60
+ test "securities method basic functionality" do
61
+ assert_respond_to(File, :securities)
62
+ end
63
+
64
+ test "securities method works as expected" do
65
+ @perms = File.get_permissions(@@file)
66
+
67
+ @perms.each{ |acct, mask|
68
+ assert_nothing_raised{ File.securities(mask) }
69
+ assert_kind_of(Array, File.securities(mask))
70
+ }
71
+ end
72
+
73
+ test "securities method accepts a single argument only" do
74
+ assert_raise(ArgumentError){ File.securities }
75
+ assert_raise(ArgumentError){ File.securities({}, {}) }
76
+ end
77
+
78
+ def teardown
79
+ @perms = nil
80
+ end
81
+
82
+ def self.shutdown
83
+ File.delete(@@file) if File.exists?(@@file)
84
+ @@file = nil
85
+ @@host = nil
86
+ @@user = nil
87
+ end
88
+ end
@@ -0,0 +1,13 @@
1
+ #############################################################################
2
+ # test_win32_file_security_version.rb
3
+ #
4
+ # Just a test for the version of the library.
5
+ #############################################################################
6
+ require 'test-unit'
7
+ require 'win32/file/security'
8
+
9
+ class TC_Win32_File_Security_Version < Test::Unit::TestCase
10
+ test "version is set to expected value" do
11
+ assert_equal('1.0.0', File::WIN32_FILE_SECURITY_VERSION)
12
+ end
13
+ end
@@ -0,0 +1,26 @@
1
+ require 'rubygems'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = 'win32-file-security'
5
+ spec.version = '1.0.0'
6
+ spec.authors = ['Daniel J. Berger', 'Park Heesob']
7
+ spec.license = 'Artistic 2.0'
8
+ spec.email = 'djberg96@gmail.com'
9
+ spec.homepage = 'http://github.com/djberg96/win32-file-security'
10
+ spec.summary = 'File security methods for the File class on MS Windows'
11
+ spec.test_files = Dir['test/test*']
12
+ spec.files = Dir['**/*'].reject{ |f| f.include?('git') }
13
+
14
+ spec.rubyforge_project = 'win32utils'
15
+ spec.extra_rdoc_files = ['README', 'CHANGES', 'MANIFEST']
16
+
17
+ spec.add_dependency('ffi')
18
+ spec.add_development_dependency('test-unit')
19
+ spec.add_development_dependency('win32-security')
20
+
21
+ spec.description = <<-EOF
22
+ The win32-file-security library adds security related methods to the
23
+ core File class for MS Windows. This includes the ability to get or
24
+ set permissions, as well as encrypt or decrypt files.
25
+ EOF
26
+ end
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: win32-file-security
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Daniel J. Berger
9
+ - Park Heesob
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2012-12-19 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: ffi
17
+ requirement: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ! '>='
29
+ - !ruby/object:Gem::Version
30
+ version: '0'
31
+ - !ruby/object:Gem::Dependency
32
+ name: test-unit
33
+ requirement: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ! '>='
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ type: :development
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: win32-security
49
+ requirement: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ description: ! " The win32-file-security library adds security related methods
64
+ to the\n core File class for MS Windows. This includes the ability to get or\n
65
+ \ set permissions, as well as encrypt or decrypt files.\n"
66
+ email: djberg96@gmail.com
67
+ executables: []
68
+ extensions: []
69
+ extra_rdoc_files:
70
+ - README
71
+ - CHANGES
72
+ - MANIFEST
73
+ files:
74
+ - CHANGES
75
+ - 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
+ - MANIFEST
80
+ - Rakefile
81
+ - README
82
+ - test/test_win32_file_security_encryption.rb
83
+ - test/test_win32_file_security_permissions.rb
84
+ - test/test_win32_file_security_version.rb
85
+ - win32-file-security.gemspec
86
+ homepage: http://github.com/djberg96/win32-file-security
87
+ licenses:
88
+ - Artistic 2.0
89
+ post_install_message:
90
+ rdoc_options: []
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ none: false
101
+ requirements:
102
+ - - ! '>='
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ requirements: []
106
+ rubyforge_project: win32utils
107
+ rubygems_version: 1.8.24
108
+ signing_key:
109
+ specification_version: 3
110
+ summary: File security methods for the File class on MS Windows
111
+ test_files:
112
+ - test/test_win32_file_security_encryption.rb
113
+ - test/test_win32_file_security_permissions.rb
114
+ - test/test_win32_file_security_version.rb