class Sys::Admin

The Admin class provides a unified, cross platform replacement for the Etc module.

Constants

BUF_MAX

:no-doc:

HKEY
SidTypeAlias
SidTypeComputer
SidTypeDeletedAccount
SidTypeDomain
SidTypeGroup
SidTypeInvalid
SidTypeUnknown
SidTypeUser
SidTypeWellKnownGroup
VERSION

The version of the sys-admin library.

Public Class Methods

add_group(options = {}) click to toggle source

Create a new group using options. If no domain option is specified then a local group is created instead.

Examples:

# Create a local group with no options
Sys::Admin.add_group(:name => 'Dudes')

# Create a local group with options
Sys::Admin.add_group(:name => 'Dudes', :description => 'Boys')

# Create a group on a specific domain
Sys::Admin.add_group(
   :name        => 'Ladies',
   :domain      => 'XYZ',
   :description => 'Girls'
)
# File lib/windows/sys/admin.rb, line 540
def self.add_group(options = {})
  options = munge_options(options)

  group  = options.delete(:name) or raise ArgumentError, 'No name given'
  domain = options[:domain]

  if domain.nil?
    domain  = Socket.gethostname
    moniker = "WinNT://#{domain},Computer"
  else
    moniker = "WinNT://#{domain}"
  end

  begin
    adsi = WIN32OLE.connect(moniker)
    group = adsi.create('group', group)
    group.setinfo
    configure_group(options) unless options.empty?
  rescue WIN32OLERuntimeError => err
    raise Error, err
  end
end
add_group_member(user, group, domain=nil) click to toggle source

Adds user to group on the specified domain, or the localhost if no domain is specified.

# File lib/windows/sys/admin.rb, line 566
def self.add_group_member(user, group, domain=nil)
  domain ||= Socket.gethostname
  adsi = WIN32OLE.connect("WinNT://#{domain}/#{group},group")
  adsi.Add("WinNT://#{domain}/#{user}")
rescue WIN32OLERuntimeError => err
  raise Error, err
end
add_user(options = {}) click to toggle source

Creates the given user. If no domain option is specified, then it defaults to your local host, i.e. a local account is created.

Any options provided are treated as IADsUser interface methods and are called before SetInfo is finally called.

Examples:

# Create a local user with no options
Sys::Admin.add_user(:name => 'asmith')

# Create a local user with options
Sys::Admin.add_user(
   :name        => 'asmith',
   :description => 'Really cool guy',
   :password    => 'abc123'
)

# Create a user on a specific domain
Sys::Admin.add_user(
   :name     => 'asmith',
   :domain   => 'XX',
   :fullname => 'Al Smith'
)
# File lib/windows/sys/admin.rb, line 418
def self.add_user(options = {})
  options = munge_options(options)

  name   = options.delete(:name) or raise ArgumentError, 'No user given'
  domain = options[:domain]

  if domain.nil?
    domain  = Socket.gethostname
    moniker = "WinNT://#{domain},Computer"
  else
    moniker = "WinNT://#{domain}"
  end

  begin
    adsi = WIN32OLE.connect(moniker)
    user = adsi.create('user', name)

    options.each{ |option, value|
      if option.to_s == 'password'
        user.setpassword(value)
      else
        user.put(option.to_s, value)
      end
    }

    user.setinfo
  rescue WIN32OLERuntimeError => err
    raise Error, err
  end
end
configure_group(options = {}) click to toggle source

Configures the group using options. If no domain option is specified then your local host is used, i.e. you are configuring a local group.

See tinyurl.com/cjkzl for a list of valid options.

Examples:

# Configure a local group.
Sys::Admin.configure_group(:name => 'Abba', :description => 'Swedish')

# Configure a group on a specific domain.
Sys::Admin.configure_group(
   :name        => 'Web Team',
   :domain      => 'Foo',
   :description => 'Web programming cowboys'
)
# File lib/windows/sys/admin.rb, line 603
def self.configure_group(options = {})
  options = munge_options(options)

  group  = options.delete(:name) or raise ArgumentError, 'No name given'
  domain = options[:domain] || Socket.gethostname

  begin
    adsi = WIN32OLE.connect("WinNT://#{domain}/#{group},group")
    options.each{ |option, value| adsi.put(option.to_s, value) }
    adsi.setinfo
  rescue WIN32OLERuntimeError => err
    raise Error, err
  end
end
configure_user(options = {}) click to toggle source

Configures the user using options. If no domain option is specified then your local host is used, i.e. you are configuring a local user account.

See tinyurl.com/3hjv9 for a list of valid options.

In the case of a password change, pass a two element array as the old value and new value.

Examples:

# Configure a local user
Sys::Admin.configure_user(
   :name        => 'djberge',
   :description => 'Awesome'
)

# Change the password
Sys::Admin.configure_user(
   :name     => 'asmith',
   :password => 'new_password'
)

# Configure a user on a specific domain
Sys::Admin.configure_user(
   :name      => 'jsmrz',
   :domain    => 'XX',
   :firstname => 'Jo'
)
# File lib/windows/sys/admin.rb, line 479
def self.configure_user(options = {})
  options = munge_options(options)

  name   = options.delete(:name) or raise ArgumentError, 'No name given'
  domain = options[:domain] || Socket.gethostname

  begin
    adsi = WIN32OLE.connect("WinNT://#{domain}/#{name},user")

    options.each{ |option, value|
      if option.to_s == 'password'
        adsi.setpassword(value)
      else
        adsi.put(option.to_s, value)
      end
    }

    adsi.setinfo
  rescue WIN32OLERuntimeError => err
    raise Error, err
  end
end
delete_group(group, domain = nil) click to toggle source

Delete the group from domain. If no domain is specified, then you are deleting a local group.

# File lib/windows/sys/admin.rb, line 621
def self.delete_group(group, domain = nil)
  if domain.nil?
    domain = Socket.gethostname
    moniker = "WinNT://#{domain},Computer"
  else
    moniker = "WinNT://#{domain}"
  end

  begin
    adsi = WIN32OLE.connect(moniker)
    adsi.delete('group', group)
  rescue WIN32OLERuntimeError => err
    raise Error, err
  end
end
delete_user(user, domain = nil) click to toggle source

Deletes the given user on domain. If no domain is specified, then it defaults to your local host, i.e. a local account is deleted.

# File lib/windows/sys/admin.rb, line 506
def self.delete_user(user, domain = nil)
  if domain.nil?
    domain  = Socket.gethostname
    moniker = "WinNT://#{domain},Computer"
  else
    moniker = "WinNT://#{domain}"
  end

  begin
    adsi = WIN32OLE.connect(moniker)
    adsi.delete('user', user)
  rescue WIN32OLERuntimeError => err
    raise Error, err
  end
end
get_group(gid) click to toggle source

Returns a Group object for the given name or uid. Raises an error if a group cannot be found.

Examples:

Sys::Admin.get_group('admin')
Sys::Admin.get_group(101)
# File lib/bsd/sys/admin.rb, line 125
def self.get_group(gid)
  size = 1024
  buf  = FFI::MemoryPointer.new(:char, size)
  pbuf = FFI::MemoryPointer.new(PasswdStruct)
  temp = GroupStruct.new

  begin
    if gid.is_a?(String)
      val = getgrnam_r(gid, temp, buf, buf.size, pbuf)
      fun = 'getgrnam_r'
    else
      val = getgrgid_r(gid, temp, buf, buf.size, pbuf)
      fun = 'getgrgid_r'
    end
    raise SystemCallError.new(fun, val) if val != 0
  rescue Errno::ERANGE
    size += 1024
    raise if size > BUF_MAX
    buf = FFI::MemoryPointer.new(:char, size)
    retry
  end

  ptr = pbuf.read_pointer

  if ptr.null?
    raise Error, "no group found for '#{gid}'"
  end

  grp = GroupStruct.new(ptr)
  get_group_from_struct(grp)
end
get_login() click to toggle source

Returns the login for the current process.

# File lib/bsd/sys/admin.rb, line 74
def self.get_login
  buf = FFI::MemoryPointer.new(:char, 256)

  if getlogin_r(buf, buf.size) != 0
    raise Error, "getlogin_r function failed: " + strerror(FFI.errno)
  end

  buf.read_string
end
get_user(uid) click to toggle source

Returns a User object for the given name or uid. Raises an error if a user cannot be found.

Examples:

Sys::Admin.get_user('joe')
Sys::Admin.get_user(501)
# File lib/bsd/sys/admin.rb, line 92
def self.get_user(uid)
  buf  = FFI::MemoryPointer.new(:char, 1024)
  pbuf = FFI::MemoryPointer.new(PasswdStruct)
  temp = PasswdStruct.new

  if uid.is_a?(String)
    if getpwnam_r(uid, temp, buf, buf.size, pbuf) != 0
      raise Error, "getpwnam_r function failed: " + strerror(FFI.errno)
    end
  else
    if getpwuid_r(uid, temp, buf, buf.size, pbuf) != 0
      raise Error, "getpwuid_r function failed: " + strerror(FFI.errno)
    end
  end

  ptr = pbuf.read_pointer

  if ptr.null?
    raise Error, "no user found for #{uid}"
  end

  pwd = PasswdStruct.new(ptr)
  get_user_from_struct(pwd)
end
groups() click to toggle source

Returns an array of Group objects for each user on the system.

# File lib/bsd/sys/admin.rb, line 178
def self.groups
  groups = []

  begin
    setgrent()

    until (ptr = getgrent()).null?
      grp = GroupStruct.new(ptr)
      groups << get_group_from_struct(grp)
    end
  ensure
    endgrent()
  end

  groups
end
remove_group_member(user, group, domain=nil) click to toggle source

Removes user from group on the specified domain, or the localhost if no domain is specified.

# File lib/windows/sys/admin.rb, line 577
def self.remove_group_member(user, group, domain=nil)
  domain ||= Socket.gethostname
  adsi = WIN32OLE.connect("WinNT://#{domain}/#{group},group")
  adsi.Remove("WinNT://#{domain}/#{user}")
rescue WIN32OLERuntimeError => err
  raise Error, err
end
users() click to toggle source

Returns an array of User objects for each user on the system.

# File lib/bsd/sys/admin.rb, line 159
def self.users
  users = []

  begin
    setpwent()

    until (ptr = getpwent()).null?
      pwd = PasswdStruct.new(ptr)
      users << get_user_from_struct(pwd)
    end
  ensure
    endpwent()
  end

  users
end

Private Class Methods

get_group_from_struct(grp) click to toggle source

Takes a GroupStruct and converts it to a Group object.

# File lib/bsd/sys/admin.rb, line 198
def self.get_group_from_struct(grp)
  Group.new do |g|
    g.name    = grp[:gr_name]
    g.passwd  = grp[:gr_passwd]
    g.gid     = grp[:gr_gid]
    g.members = grp[:gr_mem].read_array_of_string
  end
end
get_groups(domain, user) click to toggle source

An internal, private method for getting a list of groups for a particular user. The first member is a list of group names, the second member is the primary group ID.

# File lib/windows/sys/admin.rb, line 364
def self.get_groups(domain, user)
  array = []
  adsi = WIN32OLE.connect("WinNT://#{domain}/#{user},user")
  adsi.groups.each{ |g| array << g.name }
  [array, adsi.PrimaryGroupId]
end
get_home_dir(user, local = false, domain = nil) click to toggle source

Retrieves the user's home directory. For local accounts query the registry. For domain accounts use ADSI and use the HomeDirectory.

# File lib/windows/sys/admin.rb, line 324
def self.get_home_dir(user, local = false, domain = nil)
  if local
    sec = Win32::Security::SID.open(user)
    key = HKEY + sec.to_s
    dir = nil

    begin
      Win32::Registry::HKEY_LOCAL_MACHINE.open(key) do |reg|
        dir = reg['ProfileImagePath']
      end
    rescue Win32::Registry::Error
      # Not every local user has a home directory
    end
  else
    domain ||= Socket.gethostname
    adsi = WIN32OLE.connect("WinNT://#{domain}/#{user},user")
    dir = adsi.get('HomeDirectory')
  end

  dir
end
get_lastlog_info(uid) click to toggle source

Get lastlog information for the given user.

# File lib/bsd/sys/admin.rb, line 234
def self.get_lastlog_info(uid)
  logfile = '/var/log/lastlog'
  lastlog = LastlogStruct.new

  begin
    fd = open_c(logfile, File::RDONLY)

    if fd != -1
      bytes = pread_c(fd, lastlog, lastlog.size, uid * lastlog.size)
      if bytes < 0
        raise Error, "pread function failed: " + strerror(FFI.errno)
      end
    else
      nil # Ignore, improper permissions
    end
  ensure
    close_c(fd) if fd && fd >= 0
  end

  lastlog
end
get_members(domain, group) click to toggle source

An internal, private method for getting a list of members for any particular group.

# File lib/windows/sys/admin.rb, line 374
def self.get_members(domain, group)
  array = []
  adsi = WIN32OLE.connect("WinNT://#{domain}/#{group}")
  adsi.members.each{ |g| array << g.name }
  array
end
get_user_from_struct(pwd) click to toggle source

Takes a UserStruct and converts it to a User object.

# File lib/bsd/sys/admin.rb, line 208
def self.get_user_from_struct(pwd)
  user = User.new do |u|
    u.name         = pwd[:pw_name]
    u.passwd       = pwd[:pw_passwd]
    u.uid          = pwd[:pw_uid]
    u.gid          = pwd[:pw_gid]
    u.change       = Time.at(pwd[:pw_change])
    u.access_class = pwd[:pw_class]
    u.gecos        = pwd[:pw_gecos]
    u.dir          = pwd[:pw_dir]
    u.shell        = pwd[:pw_shell]
    u.expire       = Time.at(pwd[:pw_expire])
  end

  log = get_lastlog_info(user.uid)

  if log
    user.login_time = Time.at(log[:ll_time])
    user.login_device = log[:ll_line].to_s
    user.login_host = log[:ll_host].to_s
  end

  user
end
munge_options(opts) click to toggle source

A private method that lower cases all keys, and converts them all to symbols.

# File lib/windows/sys/admin.rb, line 349
def self.munge_options(opts)
  rhash = {}

  opts.each{ |k, v|
    k = k.to_s.downcase.to_sym
    rhash[k] = v
  }

  rhash
end