win32-file-security 1.0.0

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