sys-admin 1.8.0-universal-mingw32

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.
@@ -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