module ExecSandbox::Users

Manages sandbox users.

@see Users#temp @see Users#destroy

Public Class Methods

create(user_name, primary_group_name = nil) click to toggle source

Creates a user for unprivileged operations.

@param [String] user_name the user’s short (UNIX) name (should be unique) @param [String] primary_group_name; if no name is supplied, the user’s

primary group will be set to a new group

@return [Fixnum] the new user’s UID

# File lib/exec_sandbox/users.rb, line 37
def self.create(user_name, primary_group_name = nil)
  group_id = primary_group_name && Etc.getgrnam(primary_group_name).gid 
  
  if RUBY_PLATFORM =~ /darwin/  # OSX
    home_dir = "/Users/#{user_name}" 
    unless group_id
      # Create a group with the same name as the user.
      group_id = `dscl . -list /Groups`.split.
          map { |g| `dscl . -read /Groups/#{g} PrimaryGroupID`.split.last.
          to_i }.sort.last + 1

      # Simulate adduser's group creation.
      command_prefix = ['dscl', '.', '-create', "/Groups/#{user_name}"]
      [
        [],
        ['PrimaryGroupID', group_id.to_s],
      ].each do |command_suffix|
        command = command_prefix + command_suffix
        unless Kernel.system(*command)
          raise RuntimeError, "User creation failed at #{command.inspect}!"
        end
      end
    end
    
    # Find an available UID.
    user_id = `dscl . -list /Users`.split.
        map { |u| `dscl . -read /Users/#{u} UniqueID`.split.last.to_i }.
        sort.last + 1
  
    # Simulate adduser.
    command_prefix = ['dscl', '.', '-create', "/Users/#{user_name}"]
    [
      [],
      ['UserShell', '/bin/bash'],
      ['UniqueID', user_id.to_s],
      ['PrimaryGroupID', group_id.to_s],
      ['NFSHomeDirectory', home_dir],
    ].each do |command_suffix|
      command = command_prefix + command_suffix
      unless Kernel.system(*command)
        raise RuntimeError, "User creation failed at #{command.inspect}!"
      end
    end
    
  elsif RUBY_PLATFORM =~ /win/  # Windows
    raise 'Windows is not supported; patches welcome!'
    
  else  # Linux
    if group_id
      command = ['useradd', '--gid', group_id.to_s, '--no-user-group',
                 '--no-create-home', '--system',
                 '--comment', 'exec_sandbox.rb temporary user', user_name]
    else
      command = ['useradd', '--user-group', '--no-create-home', '--system',
                 '--comment', 'exec_sandbox.rb temporary user', user_name]
    end
    unless Kernel.system(*command)
      raise RuntimeError, "User creation failed at #{command.inspect}!"
    end
  
    home_dir = File.join '/home', user_name
    user_id = Etc.getpwnam(user_name).uid
    group_id = Etc.getpwnam(user_name).gid
  end  # RUBY_PLATFORM

  FileUtils.mkdir_p home_dir
  FileUtils.chown_R user_id, group_id, home_dir
  FileUtils.chmod_R 0750, home_dir
  
  user_id
end
destroy(user_name) click to toggle source

Removes a user that was previously created by create.

@param [String] user_name the user’s short (UNIX) name

# File lib/exec_sandbox/users.rb, line 112
def self.destroy(user_name)
  user_pw = Etc.getpwnam(user_name)
  home_dir = user_pw.dir
  FileUtils.rm_rf home_dir
  
  user_gid = user_pw.gid
  group_name = Etc.getgrgid(user_gid).name
  # If the group name matches the user name, the group is a temp.
  destroy_group = user_name == group_name
  
  if RUBY_PLATFORM =~ /darwin/  # OSX
    command = ['dscl', '.', '-delete', "/Users/#{user_name}"]
    unless Kernel.system(*command)
      raise RuntimeError, "User removal failed at #{command.inspect}!"
    end

    if destroy_group
      command = ['dscl', '.', '-delete', "/Groups/#{group_name}"]
      unless Kernel.system(*command)
        raise RuntimeError, "User removal failed at #{command.inspect}!"
      end
    end
  elsif RUBY_PLATFORM =~ /win/  # Windows
    raise 'Windows is not supported; patches welcome!'
    ['userdel', git_user]
  else  # Linux
    command = ['userdel', user_name]
    unless Kernel.system(*command)
      raise RuntimeError, "User removal failed at #{command.inspect}!"
    end
    if destroy_group
      # Make sure that the group exists. userdel might remove it.
      begin
        Etc.getgrnam(group_name)
      rescue ArgumentError
        destroy_group = false
      end
    end
    if destroy_group
      command = ['groupdel', group_name]
      unless Kernel.system(*command)
        raise RuntimeError, "User removal failed at #{command.inspect}!"
      end
    end
  end  # RUBY_PLATFORM
end
destroy_temps(prefix = 'xsbx.rb') click to toggle source

Destroys users created by temp.

@param [String] prefix match the prefix given to temp @return [Array<String>] names of users that were deleted

# File lib/exec_sandbox/users.rb, line 175
def self.destroy_temps(prefix = 'xsbx.rb')
  # Matches the names created by temp.
  pattern = /^#{prefix}\-[0-9a-f]+\.[0-9a-f]+\.[0-9a-f]+$/
  # Each returns the collection that it iterates over.
  self.named(pattern).each { |name| destroy name }
end
named(pattern) click to toggle source

Finds users whose names match a String or Regexp pattern.

@param [String] pattern matched against user names using === @return [Array<String>] names of users

# File lib/exec_sandbox/users.rb, line 163
def self.named(pattern)
  users = []
  Etc.passwd do |user|
    users << user.name if pattern === user.name
  end
  users
end
temp(prefix = 'xsbx.rb') click to toggle source

Creates an unprivileged user.

@param [String] prefix used at the beginning of temporary user names to

help identify the application that created the users

@return [String] the user’s name

# File lib/exec_sandbox/users.rb, line 14
def self.temp(prefix = 'xsbx.rb')
  loop do
    user_name = prefix + '-%x.%x.%x' % [$PID, Time.now.to_i, rand(65536)]
    etc = nil
    begin
      etc = Etc.getpwnam(name)
    rescue ArgumentError
      # User not found: good!
    end
    next if etc
    
    create user_name
    return user_name
  end
end