Module: RailsPwnerer::Base

Included in:
App::Assets, App::ClusterConfig, App::Config, App::Database, App::Files, App::Gems, App::Git, App::NginxConfig, App::Perforce, App::Scripts, App::Svn, Config, DevExecutor, Scaffolds::Config, Scaffolds::DirPermissions, Scaffolds::Dirs, Scaffolds::Gems, Scaffolds::HookDaemon, Scaffolds::HookDyndns, Scaffolds::MysqlConfig, Scaffolds::Packages, Scaffolds::RubyGems, Scaffolds::Sshd, Util
Defined in:
lib/rails_pwnerer/base/gems.rb,
lib/rails_pwnerer/base.rb,
lib/rails_pwnerer/base/cpus.rb,
lib/rails_pwnerer/base/dirs.rb,
lib/rails_pwnerer/base/gems.rb,
lib/rails_pwnerer/base/input.rb,
lib/rails_pwnerer/base/rails.rb,
lib/rails_pwnerer/base/atomics.rb,
lib/rails_pwnerer/base/process.rb,
lib/rails_pwnerer/base/startup.rb,
lib/rails_pwnerer/base/hostname.rb,
lib/rails_pwnerer/base/packages.rb,
lib/rails_pwnerer/base/packages.rb

Overview

extends Base with Rails-related functions

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

._setup_unixObject

initializes the module in UNIX mode



3
4
5
# File 'lib/rails_pwnerer/base.rb', line 3

def self._setup_unix
  #SUDO_PREFIX = 'sudo '
end

._setup_windowsObject

initializes the module in Windows mode



8
9
10
# File 'lib/rails_pwnerer/base.rb', line 8

def self._setup_windows
  #SUDO_PREFIX = ''
end

.all_packagesObject



220
221
222
# File 'lib/rails_pwnerer/base/packages.rb', line 220

def self.all_packages
  @packages ||= all_packages_without_caching
end

.all_packages_without_cachingObject

A hash of all the packages in the system, associated with their versions.

This method is slow as hell, so it’s memoized in all_packages.



227
228
229
230
231
232
233
234
# File 'lib/rails_pwnerer/base/packages.rb', line 227

def self.all_packages_without_caching
  output = Kernel.` "apt-cache search --full ."
  versions = output.split("\n\n").map(&:strip).reject(&:empty?).map { |info|
    info_hash = package_info_hash info
    [info_hash['Package'], info_hash['Version']]
  }
  Hash[*(versions.flatten)]    
end

.package_info_hash(output) ⇒ Object



236
237
238
239
# File 'lib/rails_pwnerer/base/packages.rb', line 236

def self.package_info_hash(output)
  Hash[output.split(/\n(?=\w)/m).map { |s| s.split(': ', 2) }.
              select { |kvp| kvp.length == 2 }]
end

Instance Method Details

#atomic_erase(path, name) ⇒ Object

erases a repository



58
59
60
61
62
63
64
# File 'lib/rails_pwnerer/base/atomics.rb', line 58

def atomic_erase(path, name)
  main_file = File.join(path, name) + '.yml'
  dup_file = File.join(path, name) + '.yml2'
  [main_file, dup_file].each do |file|
    File.delete file if File.exists? file      
  end
end

#atomic_read(path, name) ⇒ Object

reads the data in a repository



24
25
26
27
28
29
30
31
32
33
# File 'lib/rails_pwnerer/base/atomics.rb', line 24

def atomic_read(path, name)
  main_file = File.join(path, name) + '.yml'
  dup_file = File.join(path, name) + '.yml2'
  
  # choose the single good file or, if both are good, use the latest timestamp
  # this works as long as the time on a box doesn't go back (it's ok to have it skewed)
  results = [main_file, dup_file].map { |file| atomic_read_internal file }
  results.sort { |a, b| b[1] <=> a[1] } 
  return results.first[0]
end

#atomic_write(data, path, name, options = {}) ⇒ Object

writes data to a repository



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/rails_pwnerer/base/atomics.rb', line 36

def atomic_write(data, path, name, options = {})
  main_file = File.join(path, name) + '.yml'
  dup_file = File.join(path, name) + '.yml2'
  
  # append verification info at the end of the file to guard from incomplete writes
  ts = Time.now
  ts_checksum = Digest::MD5.hexdigest("#{ts.tv_sec}.#{ts.tv_usec}")
  if options[:owner]
    # secure the file
    File.open(dup_file, 'w').close
    uid = uid_for_username options[:owner]
    gid = gid_for_username options[:owner]
    File.chown uid, gid, dup_file
    File.chmod options[:permissions] || 0660, dup_file
  end
  File.open(dup_file, 'w') { |f| YAML::dump [data, ts.tv_sec, ts.tv_usec, ts_checksum], f }

  # move the file atomically to the main copy
  FileUtils.mv(dup_file, main_file)
end

#best_package_matching(patterns) ⇒ Object

Package info for the best package matching a pattern or set of patterns.

Args:

patterns:: a String or Regexp, or an array of such Strings or Regexps

Returns a hash with the following keys:

:name:: the package name
:version:: the package version

Each pattern is searched for in turn. Once there are packages matching a pattern, the



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/rails_pwnerer/base/packages.rb', line 177

def best_package_matching(patterns)
  patterns = [patterns] unless patterns.kind_of?(Enumerable)
  patterns.each do |pattern|
    packages = search_packages(pattern)
    next if packages.empty?      
    best = packages.sort_by { |key, value|
      [
        pattern.kind_of?(Regexp) ? ((key.index(pattern) == 0) ? 1 : 0) :
            ((key == pattern) ? 1 : 0),
        value.split(/[.-]/)
      ]
    }.last      
    return { :name => best.first, :version => best.last }
  end
  nil
end

#check_rails_root(app_path = '.') ⇒ Object

check if the given path is the root of a Rails application



5
6
7
8
9
10
11
12
# File 'lib/rails_pwnerer/base/rails.rb', line 5

def check_rails_root(app_path = '.')
  ['app', 'config', 'public', 'Rakefile'].all? do |path|
    File.exists? File.join(app_path, path)
  end
  ['script/rails', 'config/database.yml'].any? do |path|
    File.exists? File.join(app_path, path)
  end
end

#control_boot_script(script_name, action = :restart) ⇒ Object



35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/rails_pwnerer/base/startup.rb', line 35

def control_boot_script(script_name, action = :restart)
  path_to_script = "/etc/init.d/#{script_name}"
  case action
  when :stop
    system "#{path_to_script} stop"
  when :start
    system "#{path_to_script} start"
  when :restart
    system "#{path_to_script} restart"
  when :reload
    system "#{path_to_script} reload"
  end
end

#cpu_coresObject

returns information for each core in the system



49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/rails_pwnerer/base/cpus.rb', line 49

def cpu_cores
  cpus = []
  Sys::CPU.processors do |p|
    cpus << {
        :freq => p.cpu_mhz.to_f,
        :id => p.processor.to_i,
        :cpu => (p.respond_to?(:physical_id) ? p.physical_id.to_i : 0),
        :core => (p.respond_to?(:core_id) ? p.core_id.to_i : 0),
        :num_cores => (p.respond_to?(:cpu_cores) ? p.cpu_cores.to_i : 1)
    }
  end
  return cpus
end

#current_userObject

gets the currently logged on user



28
29
30
# File 'lib/rails_pwnerer/base/dirs.rb', line 28

def current_user
  Etc.getpwuid.name
end

#gem_exists?(gem_name) ⇒ Boolean

checks if a gem exists

Returns:

  • (Boolean)


20
21
22
23
24
25
26
27
28
# File 'lib/rails_pwnerer/base/gems.rb', line 20

def gem_exists?(gem_name)
  begin
    output = `gem specification --local #{gem_name} 2> /dev/null`
    return output =~ /^\-\-\- \!ruby\/object\:Gem\:\:Specification/
  rescue
    # we get here if gem exits with an error code
    return false
  end
end

#gid_for_username(name) ⇒ Object

gets the GID associated with the username



14
15
16
17
# File 'lib/rails_pwnerer/base/dirs.rb', line 14

def gid_for_username(name)
  passwd_entry = Etc.getpwnam(name)
  return (passwd_entry.nil?) ? nil : passwd_entry.gid    
end

#group_for_username(name) ⇒ Object

gets the main group of the given user



20
21
22
23
24
25
# File 'lib/rails_pwnerer/base/dirs.rb', line 20

def group_for_username(name)
  gid = gid_for_username(name)
  return nil if gid.nil?
  group_entry = Etc.getgrgid(gid)
  return (group_entry.nil?) ? nil : group_entry.name 
end

#hook_boot_script(script_location, script_name = File.basename(script_location), options = {}) ⇒ Object

hooks a script into the boot sequence



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/rails_pwnerer/base/startup.rb', line 19

def hook_boot_script(script_location, script_name = File.basename(script_location), options = {})
  # copy the script to /etc/init.d and chmod +x
  target_script = path_to_boot_script script_name
  if options[:symlink]
    FileUtils.ln_s script_location, target_script, :force => true
    exec_file = script_location
  else
    FileUtils.cp script_location, target_script
    exec_file = target_script
  end
  File.chmod 0755, exec_file
  
  # add to boot sequence
  system "update-rc.d #{script_name} defaults"
end

#install_gem(gem_name) ⇒ Object

TODO: use the Gem API instead of the command line



6
7
8
# File 'lib/rails_pwnerer/base/gems.rb', line 6

def install_gem(gem_name)
  system "gem install #{gem_name}"
end

#install_gems(gem_names) ⇒ Object



39
40
41
# File 'lib/rails_pwnerer/base/gems.rb', line 39

def install_gems(gem_names)
  unroll_collection(gem_names) { |n| install_gem(n) }
end

#install_package(package_name, options = {}) ⇒ Object

Installs a package.

Args:

package_name:: the exact name of the package to be installed
options:: accepts the following:
  :source:: if true, a source package is installed and built
  :skip_proxy:: if true, apt is instructed to bypass any proxy that might
                be

Returns true for success, false if something went wrong.



32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/rails_pwnerer/base/packages.rb', line 32

def install_package(package_name, options = {})
  return true if install_package_impl(package_name, options)
  if options[:source]
    if options[:no_proxy]
      install_package package_name, options.merge(:source => false)
    else
      install_package package_name, options.merge(:no_proxy => true)
    end
  else
    return false unless options[:no_proxy]
    install_package package_name, options.merge(:no_proxy => true)
  end
end

#install_package_impl(package_name, options) ⇒ Object

Internals for install_package.



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/rails_pwnerer/base/packages.rb', line 59

def install_package_impl(package_name, options)
  prefix, params = apt_params_for options
  if options[:source]
    with_temp_dir(:root => true) do
      dep_cmd = "#{prefix} apt-get build-dep #{params} #{package_name}"
      return false unless Kernel.system(dep_cmd)
      fetch_cmd = "#{prefix} apt-get source -b #{params} #{package_name}"
      return false unless Kernel.system(fetch_cmd)
      deb_files = Dir.glob '*.deb', File::FNM_DOTMATCH
      build_cmd = "#{prefix} dpkg -i #{deb_files.join(' ')}"
      return false unless Kernel.system(build_cmd)
    end
  else
    install_cmd = "#{prefix} apt-get install #{params} #{package_name}"
    return false unless Kernel.system(install_cmd)
  end
  return true
end

#install_package_matching(patterns, options = {}) ⇒ Object

Installs a package matching a pattern or list of patterns.

Args:

patterns:: same as for best_package_matching
options:: same as for install_package

Returns true for success, false if something went wrong.



17
18
19
20
# File 'lib/rails_pwnerer/base/packages.rb', line 17

def install_package_matching(patterns, options = {})
  package = best_package_matching patterns    
  package ? install_package(package[:name], options) : false
end

#install_packages(package_names, options = {}) ⇒ Object



275
276
277
# File 'lib/rails_pwnerer/base/packages.rb', line 275

def install_packages(package_names, options = {})
  unroll_collection(package_names) { |n| install_package(n, options) }
end

#kill_tree(pid) ⇒ Object



24
25
26
# File 'lib/rails_pwnerer/base/process.rb', line 24

def kill_tree(pid)
  Zerg::Support::Process.kill_tree pid
end

#os_distroObject

the distribution of the OS



46
47
48
49
50
51
52
# File 'lib/rails_pwnerer/base/dirs.rb', line 46

def os_distro
  if RUBY_ARCH =~ /win/
    return "Windows"
  else
    File.open('/etc/issue', 'r') { |f| f.read }.split(/(\r|\n)+/, 2)[0]
  end     
end

#path_to_boot_script(script_name) ⇒ Object

returns the filesystem path to a boot script



9
10
11
# File 'lib/rails_pwnerer/base/startup.rb', line 9

def path_to_boot_script(script_name)
  File.join '', 'etc', 'init.d', script_name
end

#path_to_boot_script_defaults(script_name) ⇒ Object

returns the filesystem path to the defaults used by a boot script (or nil if unsupported)



14
15
16
# File 'lib/rails_pwnerer/base/startup.rb', line 14

def path_to_boot_script_defaults(script_name)
  File.join '', 'etc', 'default', script_name
end

#path_to_gemdirObject

locates the main file in a gem (used to locate the gem)



31
32
33
34
35
# File 'lib/rails_pwnerer/base/gems.rb', line 31

def path_to_gemdir
  # TODO: use the rubygems API instead of this hack

  `gem environment gemdir`.strip
end

#process_info(pid = nil) ⇒ Object

returns information about a process



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# File 'lib/rails_pwnerer/base/process.rb', line 6

def process_info(pid = nil)
  info = Hash.new
  Zerg::Support::ProcTable.ps.each do |process|
    item = { :cmdline => process.cmdline, :pid => process.pid.to_s }

    if pid.nil?
      info[process.pid.to_s] = item
    else
      return item if item.pid.to_s == pid.to_s
    end
  end
  if pid.nil?
    return info
  else
    return nil
  end
end

#prompt_user_for_password(prompt, fail_prompt) ⇒ Object



11
12
13
14
15
16
17
18
# File 'lib/rails_pwnerer/base/input.rb', line 11

def prompt_user_for_password(prompt, fail_prompt)
  unless defined?(HighLine)
    print "#{fail_prompt}\n"
    return nil
  end
  
  HighLine.new.ask(prompt) { |question| question.echo = '' }
end

#remove_package(package_name, options = {}) ⇒ Object

Removes a package.

Args:

package_name:: the exact name of the package to be installed

Returns true for success, false if something went wrong.



52
53
54
55
56
# File 'lib/rails_pwnerer/base/packages.rb', line 52

def remove_package(package_name, options = {})
  prefix, params = apt_params_for options
  del_cmd = "#{prefix } apt-get remove #{params} #{package_name}"
  Kernel.system(del_cmd) ? true : false
end

#remove_packages(package_names) ⇒ Object



283
284
285
# File 'lib/rails_pwnerer/base/packages.rb', line 283

def remove_packages(package_names)
  unroll_collection(package_names) { |n| remove_package(n) }
end

#search_packages(pattern) ⇒ Object

Searches for packages matching a name.

Args:

pattern:: a String or Regexp containing a pattern that should be matched
          by the package names

Returns a hash where the keys are matching package names, and the values are version numbers.



202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/rails_pwnerer/base/packages.rb', line 202

def search_packages(pattern)
  packages = RailsPwnerer::Base.all_packages
  Hash[packages.select { |key, value|
    pattern.kind_of?(Regexp) ? (pattern =~ key) : key.index(pattern)
  }.map { |key, value|
    # apt-cache search sometimes leaves version numbers out
    # Update the cache with version numbers.
    if value.nil?
      info = RailsPwnerer::Base.package_info_hash(
          Kernel.`("apt-cache show #{key}"))
      packages[key] = value = info['Version']
    end
    [key, value]
  }]
end

#uid_for_username(name) ⇒ Object

gets the UID associated with the username



8
9
10
11
# File 'lib/rails_pwnerer/base/dirs.rb', line 8

def uid_for_username(name)
  passwd_entry = Etc.getpwnam(name)
  return (passwd_entry.nil?) ? nil : passwd_entry.uid
end

#unroll_collection(arg, &proc) ⇒ Object

unrolls a collection



20
21
22
23
24
25
26
# File 'lib/rails_pwnerer/base.rb', line 20

def unroll_collection(arg, &proc)
  if arg.kind_of? String
    yield arg
  else
    arg.each { |i| unroll_collection(i, &proc) }
  end
end

#update_all_packages(options = {}) ⇒ Object

Upgrades all the packages on the system to the latest version.



259
260
261
262
263
264
# File 'lib/rails_pwnerer/base/packages.rb', line 259

def update_all_packages(options = {})
  return true if update_all_packages_impl(options)
  
  return false if options[:no_proxy]
  update_all_packages options.merge(:no_proxy => true)
end

#update_all_packages_impl(options) ⇒ Object

Internals for upgrade_all_packages.



267
268
269
270
271
# File 'lib/rails_pwnerer/base/packages.rb', line 267

def update_all_packages_impl(options)
  prefix, params = apt_params_for options    
  success = Kernel.system "#{prefix} apt-get upgrade #{params}"
  success ? true : false
end

#update_gemsObject

update the metadata for all the gems



15
16
17
# File 'lib/rails_pwnerer/base/gems.rb', line 15

def update_gems()
  system "gem update --system"
end

#update_package_metadata(options = {}) ⇒ Object

Updates the metadata for all the packages.

Options:

:skip_proxy:: if true, apt is instructed to bypass the proxy

Returns true for success, false if something went wrong.



128
129
130
131
132
133
134
135
136
137
# File 'lib/rails_pwnerer/base/packages.rb', line 128

def (options = {})
  if (options)
    # Reset the metadata cache.
    RailsPwnerer::Base.instance_variable_set :@packages, nil
    return true
  end
  
  return false if options[:skip_proxy]
   options.merge(:skip_proxy => true)
end

#upgrade_gem(gem_name) ⇒ Object



10
11
12
# File 'lib/rails_pwnerer/base/gems.rb', line 10

def upgrade_gem(gem_name)
  system "gem update #{gem_name.nil ? '' : gem_name}"
end

#upgrade_gems(gem_names) ⇒ Object



43
44
45
# File 'lib/rails_pwnerer/base/gems.rb', line 43

def upgrade_gems(gem_names)
  unroll_collection(gem_names) { |n| upgrade_gem(n) }
end

#upgrade_package(package_name, options = {}) ⇒ Object

Upgrades a package to the latest version.



242
243
244
245
246
247
248
249
# File 'lib/rails_pwnerer/base/packages.rb', line 242

def upgrade_package(package_name, options = {})
  return install_package(package_name, options) if options[:source]
  
  return true if upgrade_package_impl(package_name, options)
    
  return false if options[:no_proxy]
  upgrade_package package_name, options.merge(:no_proxy => true)
end

#upgrade_package_impl(package_name, options) ⇒ Object

Internals for upgrade_package.



252
253
254
255
256
# File 'lib/rails_pwnerer/base/packages.rb', line 252

def upgrade_package_impl(package_name, options)
  prefix, params = apt_params_for options
  update_cmd = "#{prefix} apt-get upgrade #{params} #{package_name}"
  Kernel.system(update_cmd) ? true : false
end

#upgrade_packages(package_names, options = {}) ⇒ Object



279
280
281
# File 'lib/rails_pwnerer/base/packages.rb', line 279

def upgrade_packages(package_names, options = {})
  unroll_collection(package_names) { |n| upgrade_package(n, options) }
end

#with_package_source(source_url, source_repos = [], options = {}) ⇒ Object

Executes the given block in the context of having new package sources.

Args:

source_url:: the source URL, e.g. http://security.ubuntu.com/ubuntu
repositories:: the package repositories to use, e.g. ['main', 'universe']
options:: supports the following keys:
          :source:: if true, will use source-form packages from the new
                    sources; by default, binary packages will be used

Returns the block’s return value.

After adding the new package source, the package metadata is refreshed, so the block can focus on installing new packages.

If the package source already exists, the given block is yielded without making any changes to the package configuration.



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/rails_pwnerer/base/packages.rb', line 94

def with_package_source(source_url, source_repos = [], options = {})
  source_prefix = options[:source] ? 'deb-src' : 'deb'
  source_patterns = [source_prefix, source_url] + source_repos    
  
  source_contents = File.read '/etc/apt/sources.list'
  sources = source_contents.split(/(\r|\n)+/)
  source_exists = sources.any? do |source_line|
    source_frags = source_line.split(' ')
    source_patterns.all? { |pattern| source_frags.any? { |frag| frag == pattern } }
  end

  unless source_exists
    File.open('/etc/apt/sources.list', 'a') do |f|
      f.write "#{source_prefix} #{source_url} #{source_repos.join(' ')}\n"
    end
    
  end
  
  begin
    yield
  ensure
    unless source_exists
      File.open('/etc/apt/sources.list', 'w') { |f| f.write source_contents }
              
    end
  end
end

#with_temp_dir(options = {}) ⇒ Object

executes a block in a temporary directory



33
34
35
36
37
38
39
40
41
42
43
# File 'lib/rails_pwnerer/base/dirs.rb', line 33

def with_temp_dir(options = {})
  base_dir = if options[:root]
    File.exists?('/tmp') ? '/tmp/' : '/'
  else
    './'
  end
  temp_dir = base_dir + "rbpwn_#{(Time.now.to_f * 1000).to_i}"
  Dir.mkdir temp_dir
  Dir.chdir(temp_dir) { yield }  
  FileUtils.rm_r temp_dir
end