sys-admin 1.8.0-universal-mingw32

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,134 @@
1
+ require 'ffi'
2
+
3
+ # The Sys module serves as a namespace only.
4
+ module Sys
5
+
6
+ # The Admin class provides a unified, cross platform replacement
7
+ # for the Etc module.
8
+ class Admin
9
+ extend FFI::Library
10
+ ffi_lib FFI::Library::LIBC
11
+
12
+ attach_function :strerror, [:int], :string
13
+
14
+ attach_function :getlogin, [], :string
15
+ attach_function :getuid, [], :long
16
+ attach_function :geteuid, [], :long
17
+ attach_function :getpwnam, [:string], :pointer
18
+ attach_function :getpwuid, [:long], :pointer
19
+ attach_function :getpwent, [], :pointer
20
+ attach_function :setpwent, [], :void
21
+ attach_function :endpwent, [], :void
22
+
23
+ attach_function :getgrgid, [:long], :pointer
24
+ attach_function :getgrnam, [:string], :pointer
25
+ attach_function :getgrent, [], :pointer
26
+ attach_function :endgrent, [], :void
27
+ attach_function :setgrent, [], :void
28
+
29
+ private_class_method :getlogin, :getuid, :geteuid, :getpwnam, :getpwuid
30
+ private_class_method :getpwent, :setpwent, :endpwent, :getgrgid, :getgrnam
31
+ private_class_method :getgrent, :endgrent, :setgrent, :strerror
32
+
33
+ # Error typically raised if any of the Sys::Admin methods fail.
34
+ class Error < StandardError; end
35
+
36
+ # The User class encapsulates the information found in /etc/passwd.
37
+ class User
38
+ # The user name associated with the account.
39
+ attr_accessor :name
40
+
41
+ # The user's encrypted password. Deprecated by /etc/shadow.
42
+ attr_accessor :passwd
43
+
44
+ # The user's user ID.
45
+ attr_accessor :uid
46
+
47
+ # The user's group ID.
48
+ attr_accessor :gid
49
+
50
+ # Next date a password change will be needed.
51
+ attr_accessor :change
52
+
53
+ # A comment field. Rarely used now.
54
+ attr_accessor :gecos
55
+
56
+ # The user's alloted amount of disk space.
57
+ attr_accessor :quota
58
+
59
+ # The absolute path name of the user's home directory.
60
+ attr_accessor :dir
61
+
62
+ # The user's login shell.
63
+ attr_accessor :shell
64
+
65
+ # The account's expiration date
66
+ attr_accessor :expire
67
+
68
+ # TODO: Forgot what this is.
69
+ attr_accessor :fields
70
+
71
+ # The user's access class.
72
+ attr_accessor :access_class
73
+
74
+ # Another comment field.
75
+ attr_accessor :comment
76
+
77
+ # Used in the past for password aging. Deprecated by /etc/shadow.
78
+ attr_accessor :age
79
+
80
+ # The last time the user logged in.
81
+ attr_accessor :login_time
82
+
83
+ # The host name from which the user last logged in.
84
+ attr_accessor :login_host
85
+
86
+ # The name of the terminal device the user last logged on with.
87
+ attr_accessor :login_device
88
+
89
+ # Creates and returns a User object, which encapsulates the information
90
+ # typically found within an /etc/passwd entry, i.e. a struct passwd.
91
+ #
92
+ # If a block is provided, yields the object back to the block.
93
+ #
94
+ def initialize
95
+ yield self if block_given?
96
+ end
97
+
98
+ # An array of groups to which the user belongs.
99
+ def groups
100
+ array = []
101
+
102
+ Sys::Admin.groups.each{ |grp|
103
+ array << grp.name if grp.members.include?(self.name)
104
+ }
105
+
106
+ array
107
+ end
108
+ end
109
+
110
+ # The Group class encapsulates information found in /etc/group.
111
+ class Group
112
+ # The name of the group.
113
+ attr_accessor :name
114
+
115
+ # The group's group ID.
116
+ attr_accessor :gid
117
+
118
+ # An array of members associated with the group.
119
+ attr_accessor :members
120
+
121
+ # The group password, if any.
122
+ attr_accessor :passwd
123
+
124
+ # Creates and returns a Group object, which encapsulates the information
125
+ # typically found within an /etc/group entry, i.e. a struct group.
126
+ #
127
+ # If a block is provided, yields the object back to the block.
128
+ #
129
+ def initialize
130
+ yield self if block_given?
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,16 @@
1
+ require 'ffi'
2
+
3
+ class FFI::Pointer
4
+ def read_array_of_string
5
+ elements = []
6
+
7
+ loc = self
8
+
9
+ until ((element = loc.read_pointer).null?)
10
+ elements << element.read_string
11
+ loc += FFI::Type::POINTER.size
12
+ end
13
+
14
+ elements
15
+ end
16
+ end
data/lib/sys/admin.rb ADDED
@@ -0,0 +1,24 @@
1
+ module Sys
2
+ class Admin
3
+ # The version of the sys-admin library.
4
+ VERSION = '1.8.0'.freeze
5
+ end
6
+ end
7
+
8
+ # Stub to require the correct based on platform
9
+ require 'rbconfig'
10
+
11
+ case RbConfig::CONFIG['host_os']
12
+ when /linux/i
13
+ require 'linux/sys/admin'
14
+ when /sunos|solaris/i
15
+ require 'sunos/sys/admin'
16
+ when /cygwin|mingw|mswin|windows|dos/i
17
+ require 'windows/sys/admin'
18
+ when /darwin|mach/i
19
+ require 'darwin/sys/admin'
20
+ when /bsd/i
21
+ require 'bsd/sys/admin'
22
+ else
23
+ require 'unix/sys/admin'
24
+ end
data/lib/sys-admin.rb ADDED
@@ -0,0 +1 @@
1
+ require_relative 'sys/admin'
@@ -0,0 +1,165 @@
1
+ require 'sys/admin/custom'
2
+ require 'sys/admin/common'
3
+
4
+ # Code used as a fallback for UNIX platforms.
5
+
6
+ module Sys
7
+ class Admin
8
+ class PasswdStruct < FFI::Struct
9
+ layout(
10
+ :pw_name, :string,
11
+ :pw_passwd, :string,
12
+ :pw_uid, :uint,
13
+ :pw_gid, :uint,
14
+ :pw_gecos, :string,
15
+ :pw_dir, :string,
16
+ :pw_shell, :string
17
+ )
18
+ end
19
+
20
+ private_constant :PasswdStruct
21
+
22
+ class GroupStruct < FFI::Struct
23
+ layout(
24
+ :gr_name, :string,
25
+ :gr_passwd, :string,
26
+ :gr_gid, :uint,
27
+ :gr_mem, :pointer
28
+ )
29
+ end
30
+
31
+ private_constant :GroupStruct
32
+
33
+ # Returns the login for the current process.
34
+ #
35
+ def self.get_login
36
+ getlogin()
37
+ end
38
+
39
+ # Returns a User object for the given name or uid. Raises an error
40
+ # if a user cannot be found.
41
+ #
42
+ # Examples:
43
+ #
44
+ # Sys::Admin.get_user('joe')
45
+ # Sys::Admin.get_user(501)
46
+ #
47
+ def self.get_user(uid)
48
+ if uid.is_a?(String)
49
+ pwd = PasswdStruct.new(getpwnam(uid))
50
+ else
51
+ pwd = PasswdStruct.new(getpwuid(uid))
52
+ end
53
+
54
+ if pwd.null?
55
+ raise Error, "no user found for: #{uid}"
56
+ end
57
+
58
+ user = User.new do |u|
59
+ u.name = pwd[:pw_name]
60
+ u.passwd = pwd[:pw_passwd]
61
+ u.uid = pwd[:pw_uid]
62
+ u.gid = pwd[:pw_gid]
63
+ u.gecos = pwd[:pw_gecos]
64
+ u.dir = pwd[:pw_dir]
65
+ u.shell = pwd[:pw_shell]
66
+ end
67
+
68
+ user
69
+ end
70
+
71
+ # Returns a Group object for the given name or uid. Raises an error
72
+ # if a group cannot be found.
73
+ #
74
+ # Examples:
75
+ #
76
+ # Sys::Admin.get_group('admin')
77
+ # Sys::Admin.get_group(101)
78
+ #
79
+ def self.get_group(gid)
80
+ if gid.is_a?(String)
81
+ grp = GroupStruct.new(getgrnam(gid))
82
+ else
83
+ grp = GroupStruct.new(getgrgid(gid))
84
+ end
85
+
86
+ if grp.null?
87
+ raise Error, "no group found for: #{gid}"
88
+ end
89
+
90
+ Group.new do |g|
91
+ g.name = grp[:gr_name]
92
+ g.passwd = grp[:gr_passwd]
93
+ g.gid = grp[:gr_gid]
94
+ g.members = grp[:gr_mem].read_array_of_string
95
+ end
96
+ end
97
+
98
+ # Returns an array of User objects for each user on the system.
99
+ #
100
+ def self.users
101
+ users = []
102
+
103
+ begin
104
+ setpwent()
105
+
106
+ until (ptr = getpwent()).null?
107
+ pwd = PasswdStruct.new(ptr)
108
+ users << get_user_from_struct(pwd)
109
+ end
110
+ ensure
111
+ endpwent()
112
+ end
113
+
114
+ users
115
+ end
116
+
117
+ # Returns an array of Group objects for each user on the system.
118
+ #
119
+ def self.groups
120
+ groups = []
121
+
122
+ begin
123
+ setgrent()
124
+
125
+ until (ptr = getgrent()).null?
126
+ grp = GroupStruct.new(ptr)
127
+ groups << get_group_from_struct(grp)
128
+ end
129
+ ensure
130
+ endgrent()
131
+ end
132
+
133
+ groups
134
+ end
135
+
136
+ # Takes a GroupStruct and converts it to a Group object.
137
+ def self.get_group_from_struct(grp)
138
+ Group.new do |g|
139
+ g.name = grp[:gr_name]
140
+ g.passwd = grp[:gr_passwd]
141
+ g.gid = grp[:gr_gid]
142
+ g.members = grp[:gr_mem].read_array_of_string
143
+ end
144
+ end
145
+
146
+ private_class_method :get_group_from_struct
147
+
148
+ # Takes a UserStruct and converts it to a User object.
149
+ def self.get_user_from_struct(pwd)
150
+ user = User.new do |u|
151
+ u.name = pwd[:pw_name]
152
+ u.passwd = pwd[:pw_passwd]
153
+ u.uid = pwd[:pw_uid]
154
+ u.gid = pwd[:pw_gid]
155
+ u.gecos = pwd[:pw_gecos]
156
+ u.dir = pwd[:pw_dir]
157
+ u.shell = pwd[:pw_shell]
158
+ end
159
+
160
+ user
161
+ end
162
+
163
+ private_class_method :get_user_from_struct
164
+ end
165
+ end
@@ -0,0 +1,1018 @@
1
+ require 'ffi'
2
+ require 'win32ole'
3
+ require 'win32/security'
4
+ require 'win32/registry'
5
+ require 'socket'
6
+
7
+ module Sys
8
+ class Admin
9
+ extend FFI::Library
10
+
11
+ # This is the error raised in the majority of cases if anything goes wrong
12
+ # with any of the Sys::Admin methods.
13
+ #
14
+ class Error < StandardError; end
15
+
16
+ SidTypeUser = 1
17
+ SidTypeGroup = 2
18
+ SidTypeDomain = 3
19
+ SidTypeAlias = 4
20
+ SidTypeWellKnownGroup = 5
21
+ SidTypeDeletedAccount = 6
22
+ SidTypeInvalid = 7
23
+ SidTypeUnknown = 8
24
+ SidTypeComputer = 9
25
+
26
+ HKEY = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\"
27
+ private_constant :HKEY
28
+
29
+ # Retrieves the user's home directory. For local accounts query the
30
+ # registry. For domain accounts use ADSI and use the HomeDirectory.
31
+ #
32
+ def self.get_home_dir(user, local = false, domain = nil)
33
+ if local
34
+ sec = Win32::Security::SID.open(user)
35
+ key = HKEY + sec.to_s
36
+ dir = nil
37
+
38
+ begin
39
+ Win32::Registry::HKEY_LOCAL_MACHINE.open(key) do |reg|
40
+ dir = reg['ProfileImagePath']
41
+ end
42
+ rescue Win32::Registry::Error
43
+ # Not every local user has a home directory
44
+ end
45
+ else
46
+ domain ||= Socket.gethostname
47
+ adsi = WIN32OLE.connect("WinNT://#{domain}/#{user},user")
48
+ dir = adsi.get('HomeDirectory')
49
+ end
50
+
51
+ dir
52
+ end
53
+
54
+ private_class_method :get_home_dir
55
+
56
+ # A private method that lower cases all keys, and converts them
57
+ # all to symbols.
58
+ #
59
+ def self.munge_options(opts)
60
+ rhash = {}
61
+
62
+ opts.each{ |k, v|
63
+ k = k.to_s.downcase.to_sym
64
+ rhash[k] = v
65
+ }
66
+
67
+ rhash
68
+ end
69
+
70
+ private_class_method :munge_options
71
+
72
+ # An internal, private method for getting a list of groups for
73
+ # a particular user. The first member is a list of group names,
74
+ # the second member is the primary group ID.
75
+ #
76
+ def self.get_groups(domain, user)
77
+ array = []
78
+ adsi = WIN32OLE.connect("WinNT://#{domain}/#{user},user")
79
+ adsi.groups.each{ |g| array << g.name }
80
+ [array, adsi.PrimaryGroupId]
81
+ end
82
+
83
+ private_class_method :get_groups
84
+
85
+ # An internal, private method for getting a list of members for
86
+ # any particular group.
87
+ #
88
+ def self.get_members(domain, group)
89
+ array = []
90
+ adsi = WIN32OLE.connect("WinNT://#{domain}/#{group}")
91
+ adsi.members.each{ |g| array << g.name }
92
+ array
93
+ end
94
+
95
+ private_class_method :get_members
96
+
97
+ # Used by the get_login method
98
+ ffi_lib :advapi32
99
+ attach_function :GetUserNameW, [:pointer, :pointer], :bool
100
+ private_class_method :GetUserNameW
101
+
102
+ # Creates the given +user+. If no domain option is specified,
103
+ # then it defaults to your local host, i.e. a local account is
104
+ # created.
105
+ #
106
+ # Any options provided are treated as IADsUser interface methods
107
+ # and are called before SetInfo is finally called.
108
+ #
109
+ # Examples:
110
+ #
111
+ # # Create a local user with no options
112
+ # Sys::Admin.add_user(:name => 'asmith')
113
+ #
114
+ # # Create a local user with options
115
+ # Sys::Admin.add_user(
116
+ # :name => 'asmith',
117
+ # :description => 'Really cool guy',
118
+ # :password => 'abc123'
119
+ # )
120
+ #
121
+ # # Create a user on a specific domain
122
+ # Sys::Admin.add_user(
123
+ # :name => 'asmith',
124
+ # :domain => 'XX',
125
+ # :fullname => 'Al Smith'
126
+ # )
127
+ #--
128
+ # Most options are passed to the 'put' method. However, we handle the
129
+ # password specially since it's a separate method, and some environments
130
+ # require that it be set up front.
131
+ #
132
+ def self.add_user(options = {})
133
+ options = munge_options(options)
134
+
135
+ name = options.delete(:name) or raise ArgumentError, 'No user given'
136
+ domain = options[:domain]
137
+
138
+ if domain.nil?
139
+ domain = Socket.gethostname
140
+ moniker = "WinNT://#{domain},Computer"
141
+ else
142
+ moniker = "WinNT://#{domain}"
143
+ end
144
+
145
+ begin
146
+ adsi = WIN32OLE.connect(moniker)
147
+ user = adsi.create('user', name)
148
+
149
+ options.each{ |option, value|
150
+ if option.to_s == 'password'
151
+ user.setpassword(value)
152
+ else
153
+ user.put(option.to_s, value)
154
+ end
155
+ }
156
+
157
+ user.setinfo
158
+ rescue WIN32OLERuntimeError => err
159
+ raise Error, err
160
+ end
161
+ end
162
+
163
+ # Configures the +user+ using +options+. If no domain option is
164
+ # specified then your local host is used, i.e. you are configuring
165
+ # a local user account.
166
+ #
167
+ # See http://tinyurl.com/3hjv9 for a list of valid options.
168
+ #
169
+ # In the case of a password change, pass a two element array as the
170
+ # old value and new value.
171
+ #
172
+ # Examples:
173
+ #
174
+ # # Configure a local user
175
+ # Sys::Admin.configure_user(
176
+ # :name => 'djberge',
177
+ # :description => 'Awesome'
178
+ # )
179
+ #
180
+ # # Change the password
181
+ # Sys::Admin.configure_user(
182
+ # :name => 'asmith',
183
+ # :password => 'new_password'
184
+ # )
185
+ #
186
+ # # Configure a user on a specific domain
187
+ # Sys::Admin.configure_user(
188
+ # :name => 'jsmrz',
189
+ # :domain => 'XX',
190
+ # :firstname => 'Jo'
191
+ # )
192
+ #
193
+ def self.configure_user(options = {})
194
+ options = munge_options(options)
195
+
196
+ name = options.delete(:name) or raise ArgumentError, 'No name given'
197
+ domain = options[:domain] || Socket.gethostname
198
+
199
+ begin
200
+ adsi = WIN32OLE.connect("WinNT://#{domain}/#{name},user")
201
+
202
+ options.each{ |option, value|
203
+ if option.to_s == 'password'
204
+ adsi.setpassword(value)
205
+ else
206
+ adsi.put(option.to_s, value)
207
+ end
208
+ }
209
+
210
+ adsi.setinfo
211
+ rescue WIN32OLERuntimeError => err
212
+ raise Error, err
213
+ end
214
+ end
215
+
216
+ # Deletes the given +user+ on +domain+. If no domain is specified,
217
+ # then it defaults to your local host, i.e. a local account is
218
+ # deleted.
219
+ #
220
+ def self.delete_user(user, domain = nil)
221
+ if domain.nil?
222
+ domain = Socket.gethostname
223
+ moniker = "WinNT://#{domain},Computer"
224
+ else
225
+ moniker = "WinNT://#{domain}"
226
+ end
227
+
228
+ begin
229
+ adsi = WIN32OLE.connect(moniker)
230
+ adsi.delete('user', user)
231
+ rescue WIN32OLERuntimeError => err
232
+ raise Error, err
233
+ end
234
+ end
235
+
236
+ # Create a new +group+ using +options+. If no domain option is specified
237
+ # then a local group is created instead.
238
+ #
239
+ # Examples:
240
+ #
241
+ # # Create a local group with no options
242
+ # Sys::Admin.add_group(:name => 'Dudes')
243
+ #
244
+ # # Create a local group with options
245
+ # Sys::Admin.add_group(:name => 'Dudes', :description => 'Boys')
246
+ #
247
+ # # Create a group on a specific domain
248
+ # Sys::Admin.add_group(
249
+ # :name => 'Ladies',
250
+ # :domain => 'XYZ',
251
+ # :description => 'Girls'
252
+ # )
253
+ #
254
+ def self.add_group(options = {})
255
+ options = munge_options(options)
256
+
257
+ group = options.delete(:name) or raise ArgumentError, 'No name given'
258
+ domain = options[:domain]
259
+
260
+ if domain.nil?
261
+ domain = Socket.gethostname
262
+ moniker = "WinNT://#{domain},Computer"
263
+ else
264
+ moniker = "WinNT://#{domain}"
265
+ end
266
+
267
+ begin
268
+ adsi = WIN32OLE.connect(moniker)
269
+ group = adsi.create('group', group)
270
+ group.setinfo
271
+ configure_group(options) unless options.empty?
272
+ rescue WIN32OLERuntimeError => err
273
+ raise Error, err
274
+ end
275
+ end
276
+
277
+ # Adds +user+ to +group+ on the specified +domain+, or the localhost
278
+ # if no domain is specified.
279
+ #
280
+ def self.add_group_member(user, group, domain=nil)
281
+ domain ||= Socket.gethostname
282
+ adsi = WIN32OLE.connect("WinNT://#{domain}/#{group},group")
283
+ adsi.Add("WinNT://#{domain}/#{user}")
284
+ rescue WIN32OLERuntimeError => err
285
+ raise Error, err
286
+ end
287
+
288
+ # Removes +user+ from +group+ on the specified +domain+, or the localhost
289
+ # if no domain is specified.
290
+ #
291
+ def self.remove_group_member(user, group, domain=nil)
292
+ domain ||= Socket.gethostname
293
+ adsi = WIN32OLE.connect("WinNT://#{domain}/#{group},group")
294
+ adsi.Remove("WinNT://#{domain}/#{user}")
295
+ rescue WIN32OLERuntimeError => err
296
+ raise Error, err
297
+ end
298
+
299
+ # Configures the +group+ using +options+. If no domain option is
300
+ # specified then your local host is used, i.e. you are configuring
301
+ # a local group.
302
+ #
303
+ # See http://tinyurl.com/cjkzl for a list of valid options.
304
+ #
305
+ # Examples:
306
+ #
307
+ # # Configure a local group.
308
+ # Sys::Admin.configure_group(:name => 'Abba', :description => 'Swedish')
309
+ #
310
+ # # Configure a group on a specific domain.
311
+ # Sys::Admin.configure_group(
312
+ # :name => 'Web Team',
313
+ # :domain => 'Foo',
314
+ # :description => 'Web programming cowboys'
315
+ # )
316
+ #
317
+ def self.configure_group(options = {})
318
+ options = munge_options(options)
319
+
320
+ group = options.delete(:name) or raise ArgumentError, 'No name given'
321
+ domain = options[:domain] || Socket.gethostname
322
+
323
+ begin
324
+ adsi = WIN32OLE.connect("WinNT://#{domain}/#{group},group")
325
+ options.each{ |option, value| adsi.put(option.to_s, value) }
326
+ adsi.setinfo
327
+ rescue WIN32OLERuntimeError => err
328
+ raise Error, err
329
+ end
330
+ end
331
+
332
+ # Delete the +group+ from +domain+. If no domain is specified, then
333
+ # you are deleting a local group.
334
+ #
335
+ def self.delete_group(group, domain = nil)
336
+ if domain.nil?
337
+ domain = Socket.gethostname
338
+ moniker = "WinNT://#{domain},Computer"
339
+ else
340
+ moniker = "WinNT://#{domain}"
341
+ end
342
+
343
+ begin
344
+ adsi = WIN32OLE.connect(moniker)
345
+ adsi.delete('group', group)
346
+ rescue WIN32OLERuntimeError => err
347
+ raise Error, err
348
+ end
349
+ end
350
+
351
+ # Returns the user name (only) of the current login.
352
+ #
353
+ def self.get_login
354
+ buffer = FFI::MemoryPointer.new(:char, 256)
355
+ nsize = FFI::MemoryPointer.new(:ulong)
356
+ nsize.write_ulong(buffer.size)
357
+
358
+ unless GetUserNameW(buffer, nsize)
359
+ raise Error, 'GetUserName() call failed in get_login'
360
+ end
361
+
362
+ buffer.read_string(nsize.read_ulong * 2).tr(0.chr, '')
363
+ end
364
+
365
+ # Returns a User object based on either +name+ or +uid+.
366
+ #
367
+ # call-seq:
368
+ # Sys::Admin.get_user(name, options = {})
369
+ # Sys::Admin.get_user(uid, options = {})
370
+ #
371
+ # Looks for +usr+ information based on the options you specify, where
372
+ # the +usr+ argument can be either a user name or a RID.
373
+ #
374
+ # If a 'host' option is specified, information is retrieved from that
375
+ # host. Otherwise, the local host is used.
376
+ #
377
+ # All other options are converted to WQL statements against the
378
+ # Win32_UserAccount WMI object. See http://tinyurl.com/by9nvn for a
379
+ # list of possible options.
380
+ #
381
+ # Examples:
382
+ #
383
+ # # Get a user by name
384
+ # Admin.get_user('djberge')
385
+ #
386
+ # # Get a user by uid
387
+ # Admin.get_user(100)
388
+ #
389
+ # # Get a user on a specific domain
390
+ # Admin.get_user('djberge', :domain => 'TEST')
391
+ #--
392
+ # The reason for keeping the +usr+ variable as a separate argument
393
+ # instead of rolling it into the options hash was to keep a unified
394
+ # API between the Windows and UNIX versions.
395
+ #
396
+ def self.get_user(usr, options = {})
397
+ options = munge_options(options)
398
+
399
+ host = options.delete(:host) || Socket.gethostname
400
+ cs = "winmgmts:{impersonationLevel=impersonate}!"
401
+ cs << "//#{host}/root/cimv2"
402
+
403
+ begin
404
+ wmi = WIN32OLE.connect(cs)
405
+ rescue WIN32OLERuntimeError => err
406
+ raise Error, err
407
+ end
408
+
409
+ query = "select * from win32_useraccount"
410
+
411
+ i = 0
412
+
413
+ options.each{ |opt, val|
414
+ if i == 0
415
+ query << " where #{opt} = '#{val}'"
416
+ i += 1
417
+ else
418
+ query << " and #{opt} = '#{val}'"
419
+ end
420
+ }
421
+
422
+ if usr.kind_of?(Numeric)
423
+ if i == 0
424
+ query << " where sid like '%-#{usr}'"
425
+ else
426
+ query << " and sid like '%-#{usr}'"
427
+ end
428
+ else
429
+ if i == 0
430
+ query << " where name = '#{usr}'"
431
+ else
432
+ query << " and name = '#{usr}'"
433
+ end
434
+ end
435
+
436
+ domain = options[:domain] || host
437
+
438
+ wmi.execquery(query).each{ |user|
439
+ uid = user.sid.split('-').last.to_i
440
+
441
+ # Because our 'like' query isn't fulproof, let's parse
442
+ # the SID again to make sure
443
+ if usr.kind_of?(Numeric)
444
+ next if usr != uid
445
+ end
446
+
447
+ groups, primary_group = *get_groups(domain, user.name)
448
+
449
+ user_object = User.new do |u|
450
+ u.account_type = user.accounttype
451
+ u.caption = user.caption
452
+ u.description = user.description
453
+ u.disabled = user.disabled
454
+ u.domain = user.domain
455
+ u.full_name = user.fullname
456
+ u.install_date = user.installdate
457
+ u.local = user.localaccount
458
+ u.lockout = user.lockout
459
+ u.name = user.name
460
+ u.password_changeable = user.passwordchangeable
461
+ u.password_expires = user.passwordexpires
462
+ u.password_required = user.passwordrequired
463
+ u.sid = user.sid
464
+ u.sid_type = user.sidtype
465
+ u.status = user.status
466
+ u.uid = uid
467
+ u.gid = primary_group
468
+ u.groups = groups
469
+ u.dir = get_home_dir(user.name, options[:localaccount], domain)
470
+ end
471
+
472
+ return user_object
473
+ }
474
+
475
+ # If we're here, it means it wasn't found.
476
+ raise Error, "no user found for '#{usr}'"
477
+ end
478
+
479
+ # Returns an array of User objects for each user on the system.
480
+ #
481
+ # You may specify a host from which information is retrieved. The
482
+ # default is the local host.
483
+ #
484
+ # All other arguments are passed as WQL query parameters against
485
+ # the Win32_UserAccont WMI object.
486
+ #
487
+ # Examples:
488
+ #
489
+ # # Get all local account users
490
+ # Sys::Admin.users(:localaccount => true)
491
+ #
492
+ # # Get all user accounts on a specific domain
493
+ # Sys::Admin.users(:domain => 'FOO')
494
+ #
495
+ # # Get a single user from a domain
496
+ # Sys::Admin.users(:name => 'djberge', :domain => 'FOO')
497
+ #
498
+ def self.users(options = {})
499
+ options = munge_options(options)
500
+
501
+ host = options.delete(:host) || Socket.gethostname
502
+ cs = "winmgmts:{impersonationLevel=impersonate}!"
503
+ cs << "//#{host}/root/cimv2"
504
+
505
+ begin
506
+ wmi = WIN32OLE.connect(cs)
507
+ rescue WIN32OLERuntimeError => e
508
+ raise Error, e
509
+ end
510
+
511
+ query = "select * from win32_useraccount"
512
+
513
+ i = 0
514
+
515
+ options.each{ |opt, val|
516
+ if i == 0
517
+ query << " where #{opt} = '#{val}'"
518
+ i += 1
519
+ else
520
+ query << " and #{opt} = '#{val}'"
521
+ end
522
+ }
523
+
524
+ array = []
525
+ domain = options[:domain] || host
526
+
527
+ wmi.execquery(query).each{ |user|
528
+ uid = user.sid.split('-').last.to_i
529
+
530
+ usr = User.new do |u|
531
+ u.account_type = user.accounttype
532
+ u.caption = user.caption
533
+ u.description = user.description
534
+ u.disabled = user.disabled
535
+ u.domain = user.domain
536
+ u.full_name = user.fullname
537
+ u.install_date = user.installdate
538
+ u.local = user.localaccount
539
+ u.lockout = user.lockout
540
+ u.name = user.name
541
+ u.password_changeable = user.passwordchangeable
542
+ u.password_expires = user.passwordexpires
543
+ u.password_required = user.passwordrequired
544
+ u.sid = user.sid
545
+ u.sid_type = user.sidtype
546
+ u.status = user.status
547
+ u.groups = get_groups(domain, user.name)
548
+ u.uid = uid
549
+ u.dir = get_home_dir(user.name, options[:localaccount], host)
550
+ end
551
+
552
+ array.push(usr)
553
+ }
554
+
555
+ array
556
+ end
557
+
558
+ # Returns a Group object based on either +name+ or +gid+.
559
+ #
560
+ # call-seq:
561
+ # Sys::Admin.get_group(name, options = {})
562
+ # Sys::Admin.get_group(gid, options = {})
563
+ #
564
+ # If a numeric value is sent as the first parameter, it is treated
565
+ # as a RID and is checked against the SID for a match.
566
+ #
567
+ # You may specify a host as an option from which information is
568
+ # retrieved. The default is the local host.
569
+ #
570
+ # All other options are passed as WQL parameters to the Win32_Group
571
+ # WMI object. See http://tinyurl.com/bngc8s for a list of possible
572
+ # options.
573
+ #
574
+ # Examples:
575
+ #
576
+ # # Find a group by name
577
+ # Sys::Admin.get_group('Web Team')
578
+ #
579
+ # # Find a group by id
580
+ # Sys::Admin.get_group(31667)
581
+ #
582
+ # # Find a group on a specific domain
583
+ # Sys::Admin.get_group('Web Team', :domain => 'FOO')
584
+ #
585
+ def self.get_group(grp, options = {})
586
+ options = munge_options(options)
587
+
588
+ host = options.delete(:host) || Socket.gethostname
589
+ cs = "winmgmts:{impersonationLevel=impersonate}!"
590
+ cs << "//#{host}/root/cimv2"
591
+
592
+ begin
593
+ wmi = WIN32OLE.connect(cs)
594
+ rescue WIN32OLERuntimeError => err
595
+ raise Error, err
596
+ end
597
+
598
+ query = "select * from win32_group"
599
+
600
+ i = 0
601
+
602
+ options.each{ |opt, val|
603
+ if i == 0
604
+ query << " where #{opt} = '#{val}'"
605
+ i += 1
606
+ else
607
+ query << " and #{opt} = '#{val}'"
608
+ end
609
+ }
610
+
611
+ if grp.kind_of?(Integer)
612
+ if i == 0
613
+ query << " where sid like '%-#{grp}'"
614
+ else
615
+ query << " and sid like '%-#{grp}'"
616
+ end
617
+ else
618
+ if i == 0
619
+ query << " where name = '#{grp}'"
620
+ else
621
+ query << " and name = '#{grp}'"
622
+ end
623
+ end
624
+
625
+ domain = options[:domain] || host
626
+
627
+ wmi.execquery(query).each{ |group|
628
+ gid = group.sid.split("-").last.to_i
629
+
630
+ # Because our 'like' query isn't fulproof, let's parse
631
+ # the SID again to make sure
632
+ if grp.kind_of?(Integer)
633
+ next if grp != gid
634
+ end
635
+
636
+ group_object = Group.new do |g|
637
+ g.caption = group.caption
638
+ g.description = group.description
639
+ g.domain = group.domain
640
+ g.gid = gid
641
+ g.install_date = group.installdate
642
+ g.local = group.localaccount
643
+ g.name = group.name
644
+ g.sid = group.sid
645
+ g.sid_type = group.sidtype
646
+ g.status = group.status
647
+ g.members = get_members(domain, group.name)
648
+ end
649
+
650
+ return group_object
651
+ }
652
+
653
+ # If we're here, it means it wasn't found.
654
+ raise Error, "no group found for '#{grp}'"
655
+ end
656
+
657
+ # Returns an array of Group objects for each user on the system.
658
+ #
659
+ # You may specify a host option from which information is retrieved.
660
+ # The default is the local host.
661
+ #
662
+ # All other options are passed as WQL parameters to the Win32_Group
663
+ # WMI object. See http://tinyurl.com/bngc8s for a list of possible
664
+ # options.
665
+ #
666
+ # Examples:
667
+ #
668
+ # # Get local group information
669
+ # Sys::Admin.groups(:localaccount => true)
670
+ #
671
+ # # Get all groups on a specific domain
672
+ # Sys::Admin.groups(:domain => 'FOO')
673
+ #
674
+ # # Get a specific group on a domain
675
+ # Sys::Admin.groups(:name => 'Some Group', :domain => 'FOO')
676
+ #
677
+ def self.groups(options = {})
678
+ options = munge_options(options)
679
+
680
+ host = options.delete(:host) || Socket.gethostname
681
+ cs = "winmgmts:{impersonationLevel=impersonate}!"
682
+ cs << "//#{host}/root/cimv2"
683
+
684
+ begin
685
+ wmi = WIN32OLE.connect(cs)
686
+ rescue WIN32OLERuntimeError => err
687
+ raise Error, err
688
+ end
689
+
690
+ query = "select * from win32_group"
691
+
692
+ i = 0
693
+
694
+ options.each{ |opt, val|
695
+ if i == 0
696
+ query << " where #{opt} = '#{val}'"
697
+ i += 1
698
+ else
699
+ query << " and #{opt} = '#{val}'"
700
+ end
701
+ }
702
+
703
+ array = []
704
+ domain = options[:domain] || host
705
+
706
+ wmi.execquery(query).each{ |group|
707
+ grp = Group.new do |g|
708
+ g.caption = group.caption
709
+ g.description = group.description
710
+ g.domain = group.domain
711
+ g.gid = group.sid.split("-").last.to_i
712
+ g.install_date = group.installdate
713
+ g.local = group.localaccount
714
+ g.name = group.name
715
+ g.sid = group.sid
716
+ g.sid_type = group.sidtype
717
+ g.status = group.status
718
+ g.members = get_members(domain, group.name)
719
+ end
720
+
721
+ array.push(grp)
722
+ }
723
+
724
+ array
725
+ end
726
+
727
+ class User
728
+ # An account for users whose primary account is in another domain.
729
+ TEMP_DUPLICATE = 0x0100
730
+
731
+ # Default account type that represents a typical user.
732
+ NORMAL = 0x0200
733
+
734
+ # A permit to trust account for a domain that trusts other domains.
735
+ INTERDOMAIN_TRUST = 0x0800
736
+
737
+ # An account for a Windows NT/2000 workstation or server that is a
738
+ # member of this domain.
739
+ WORKSTATION_TRUST = 0x1000
740
+
741
+ # A computer account for a backup domain controller that is a member
742
+ # of this domain.
743
+ SERVER_TRUST = 0x2000
744
+
745
+ # Domain and username of the account.
746
+ attr_accessor :caption
747
+
748
+ # Description of the account.
749
+ attr_accessor :description
750
+
751
+ # Name of the Windows domain to which a user account belongs.
752
+ attr_accessor :domain
753
+
754
+ # The user's password.
755
+ attr_accessor :password
756
+
757
+ # Full name of a local user.
758
+ attr_accessor :full_name
759
+
760
+ # An array of groups to which the user belongs.
761
+ attr_accessor :groups
762
+
763
+ # Date the user account was created.
764
+ attr_accessor :install_date
765
+
766
+ # Name of the Windows user account on the domain that the User#domain
767
+ # property specifies.
768
+ attr_accessor :name
769
+
770
+ # The user's security identifier.
771
+ attr_accessor :sid
772
+
773
+ # Current status for the user, such as "ok", "error", etc.
774
+ attr_accessor :status
775
+
776
+ # The user's id (RID).
777
+ attr_accessor :uid
778
+
779
+ # The user's primary group ID.
780
+ attr_accessor :gid
781
+
782
+ # The user's home directory
783
+ attr_accessor :dir
784
+
785
+ # Used to set whether or not the account is disabled.
786
+ attr_writer :disabled
787
+
788
+ # Sets whether or not the account is defined on the local computer.
789
+ attr_writer :local
790
+
791
+ # Sets whether or not the account is locked out of the OS.
792
+ attr_writer :lockout
793
+
794
+ # Sets whether or not the password for the account can be changed.
795
+ attr_writer :password_changeable
796
+
797
+ # Sets whether or not the password for the account expires.
798
+ attr_writer :password_expires
799
+
800
+ # Sets whether or not a password is required for the account.
801
+ attr_writer :password_required
802
+
803
+ # Returns the account type as a human readable string.
804
+ attr_reader :account_type
805
+
806
+ # Creates an returns a new User object. A User object encapsulates a
807
+ # user account on the operating system.
808
+ #
809
+ # Yields +self+ if a block is provided.
810
+ #
811
+ def initialize
812
+ yield self if block_given?
813
+ end
814
+
815
+ # Sets the account type for the account. Possible values are:
816
+ #
817
+ # * User::TEMP_DUPLICATE
818
+ # * User::NORMAL
819
+ # * User::INTERDOMAIN_TRUST
820
+ # * User::WORKSTATION_TRUST
821
+ # * User::SERVER_TRUST
822
+ #
823
+ def account_type=(type)
824
+ case type
825
+ when TEMP_DUPLICATE
826
+ @account_type = 'duplicate'
827
+ when NORMAL
828
+ @account_type = 'normal'
829
+ when INTERDOMAIN_TRUST
830
+ @account_type = 'interdomain_trust'
831
+ when WORKSTATION_TRUST
832
+ @account_type = 'workstation_trust'
833
+ when SERVER_TRUST
834
+ @account_type = 'server_trust'
835
+ else
836
+ @account_type = 'unknown'
837
+ end
838
+ end
839
+
840
+ # Returns the SID type as a human readable string.
841
+ #
842
+ def sid_type
843
+ @sid_type
844
+ end
845
+
846
+ # Sets the SID (Security Identifier) type to +stype+, which can be
847
+ # one of the following constant values:
848
+ #
849
+ # * Admin::SidTypeUser
850
+ # * Admin::SidTypeGroup
851
+ # * Admin::SidTypeDomain
852
+ # * Admin::SidTypeAlias
853
+ # * Admin::SidTypeWellKnownGroup
854
+ # * Admin::SidTypeDeletedAccount
855
+ # * Admin::SidTypeInvalid
856
+ # * Admin::SidTypeUnknown
857
+ # * Admin::SidTypeComputer
858
+ #
859
+ def sid_type=(stype)
860
+ case stype
861
+ when Admin::SidTypeUser
862
+ @sid_type = 'user'
863
+ when Admin::SidTypeGroup
864
+ @sid_type = 'group'
865
+ when Admin::SidTypeDomain
866
+ @sid_type = 'domain'
867
+ when Admin::SidTypeAlias
868
+ @sid_type = 'alias'
869
+ when Admin::SidTypeWellKnownGroup
870
+ @sid_type = 'well_known_group'
871
+ when Admin::SidTypeDeletedAccount
872
+ @sid_type = 'deleted_account'
873
+ when Admin::SidTypeInvalid
874
+ @sid_type = 'invalid'
875
+ when Admin::SidTypeUnknown
876
+ @sid_type = 'unknown'
877
+ when Admin::SidTypeComputer
878
+ @sid_type = 'computer'
879
+ else
880
+ @sid_type = 'unknown'
881
+ end
882
+ end
883
+
884
+ # Returns whether or not the account is disabled.
885
+ #
886
+ def disabled?
887
+ @disabled
888
+ end
889
+
890
+ # Returns whether or not the account is local.
891
+ #
892
+ def local?
893
+ @local
894
+ end
895
+
896
+ # Returns whether or not the account is locked out.
897
+ #
898
+ def lockout?
899
+ @lockout
900
+ end
901
+
902
+ # Returns whether or not the password for the account is changeable.
903
+ #
904
+ def password_changeable?
905
+ @password_changeable
906
+ end
907
+
908
+ # Returns whether or not the password for the account is changeable.
909
+ #
910
+ def password_expires?
911
+ @password_expires
912
+ end
913
+
914
+ # Returns whether or not the a password is required for the account.
915
+ #
916
+ def password_required?
917
+ @password_required
918
+ end
919
+ end
920
+
921
+ class Group
922
+ # Short description of the object.
923
+ attr_accessor :caption
924
+
925
+ # Description of the group.
926
+ attr_accessor :description
927
+
928
+ # Name of the Windows domain to which the group account belongs.
929
+ attr_accessor :domain
930
+
931
+ # Date the group was added.
932
+ attr_accessor :install_date
933
+
934
+ # Name of the Windows group account on the Group#domain specified.
935
+ attr_accessor :name
936
+
937
+ # Security identifier for this group.
938
+ attr_accessor :sid
939
+
940
+ # Current status for the group, such as "ok", "error", etc.
941
+ attr_accessor :status
942
+
943
+ # The group ID.
944
+ attr_accessor :gid
945
+
946
+ # Sets whether or not the group is local (as opposed to global).
947
+ attr_writer :local
948
+
949
+ # An array of members for that group. May contain SID's.
950
+ attr_accessor :members
951
+
952
+ # Creates and returns a new Group object. This class encapsulates
953
+ # the information for a group account, whether it be global or local.
954
+ #
955
+ # Yields +self+ if a block is given.
956
+ #
957
+ def initialize
958
+ yield self if block_given?
959
+ end
960
+
961
+ # Returns whether or not the group is a local group.
962
+ #
963
+ def local?
964
+ @local
965
+ end
966
+
967
+ # Returns the type of SID (Security Identifier) as a stringified value.
968
+ #
969
+ def sid_type
970
+ @sid_type
971
+ end
972
+
973
+ # Sets the SID (Security Identifier) type to +stype+, which can be
974
+ # one of the following constant values:
975
+ #
976
+ # * Admin::SidTypeUser
977
+ # * Admin::SidTypeGroup
978
+ # * Admin::SidTypeDomain
979
+ # * Admin::SidTypeAlias
980
+ # * Admin::SidTypeWellKnownGroup
981
+ # * Admin::SidTypeDeletedAccount
982
+ # * Admin::SidTypeInvalid
983
+ # * Admin::SidTypeUnknown
984
+ # * Admin::SidTypeComputer
985
+ #
986
+ def sid_type=(stype)
987
+ if stype.kind_of?(String)
988
+ @sid_type = stype.downcase
989
+ else
990
+ case stype
991
+ when Admin::SidTypeUser
992
+ @sid_type = "user"
993
+ when Admin::SidTypeGroup
994
+ @sid_type = "group"
995
+ when Admin::SidTypeDomain
996
+ @sid_type = "domain"
997
+ when Admin::SidTypeAlias
998
+ @sid_type = "alias"
999
+ when Admin::SidTypeWellKnownGroup
1000
+ @sid_type = "well_known_group"
1001
+ when Admin::SidTypeDeletedAccount
1002
+ @sid_type = "deleted_account"
1003
+ when Admin::SidTypeInvalid
1004
+ @sid_type = "invalid"
1005
+ when Admin::SidTypeUnknown
1006
+ @sid_type = "unknown"
1007
+ when Admin::SidTypeComputer
1008
+ @sid_type = "computer"
1009
+ else
1010
+ @sid_type = "unknown"
1011
+ end
1012
+ end
1013
+
1014
+ @sid_type
1015
+ end
1016
+ end
1017
+ end
1018
+ end