From 045253c917b6f6387d63bf31828d8487aafaf6e2 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Tue, 18 Aug 2009 16:07:18 -0400 Subject: [PATCH] Initial commit. --- bin/janitize.rb | 56 ++++++++++++++++ makefile | 4 ++ src/apache_conf_file.rb | 69 ++++++++++++++++++++ src/janitor.rb | 77 ++++++++++++++++++++++ test/apache_conf_file_test.rb | 82 ++++++++++++++++++++++++ test/fixtures/example.com-boring.conf | 5 ++ test/fixtures/example.com-different.conf | 27 ++++++++ test/fixtures/example.com-multi.conf | 55 ++++++++++++++++ test/fixtures/example.com-typical.conf | 27 ++++++++ test/janitor_test.rb | 57 ++++++++++++++++ test/test_suite.rb | 2 + 11 files changed, 461 insertions(+) create mode 100644 bin/janitize.rb create mode 100644 makefile create mode 100644 src/apache_conf_file.rb create mode 100644 src/janitor.rb create mode 100644 test/apache_conf_file_test.rb create mode 100644 test/fixtures/example.com-boring.conf create mode 100644 test/fixtures/example.com-different.conf create mode 100644 test/fixtures/example.com-multi.conf create mode 100644 test/fixtures/example.com-typical.conf create mode 100644 test/janitor_test.rb create mode 100644 test/test_suite.rb diff --git a/bin/janitize.rb b/bin/janitize.rb new file mode 100644 index 0000000..4e8c039 --- /dev/null +++ b/bin/janitize.rb @@ -0,0 +1,56 @@ +# Actually run the thing. + +require 'src/janitor' +require 'optparse' + +# Set up the default options +options = { :apache_vhost_directory => '/etc/apache2/vhosts.d', + :maximum_file_age => 7 } + +# And parse any that were given on the command line. +OptionParser.new do |opts| + opts.banner = "Usage: #{$0} [options]" + + opts.on('-a', '--apache_vhosts_directory VHOSTS_DIR', + 'Apache vhost conf file directory') do |vhost_dir| + options[:apache_vhost_directory] = vhost_dir + end + + opts.on('-m', '--maximum_file_age MAX_DAYS_OLD', Integer, + 'Maximum file age (in days)') do |max_age| + options[:maximum_file_age] = max_age + end + + opts.on_tail("-h", "--help", "Show this message") do + puts opts + exit + end + +end.parse! + + +# Print an informational header every time the program is run. +puts "Command line: #{$0 + ' ' + ARGV.join(' ')}" + +current_time = Time.now() +if current_time.respond_to?(:iso8601) + # Somehow this method is missing on some machines. + puts "Time: #{current_time.iso8601}" +else + # Fall back to whatever this looks like. + puts "Time: #{current_time}" +end + +puts "Apache vhost directory: #{options[:apache_vhost_directory]}" +puts "Max File Age: #{options[:maximum_file_age]}" + +j = Janitor.new() +j.apache_vhosts_directory = options[:apache_vhost_directory] + +temp_dirs = j.get_temporary_directories() + +puts "Temporary directories: #{temp_dirs.join(', ')}\n\n" + +temp_dirs.each do |dir| + j.clean_directory(dir, options[:maximum_file_age]) +end diff --git a/makefile b/makefile new file mode 100644 index 0000000..e0eb009 --- /dev/null +++ b/makefile @@ -0,0 +1,4 @@ +.PHONY : test + +test: + ruby test/test_suite.rb diff --git a/src/apache_conf_file.rb b/src/apache_conf_file.rb new file mode 100644 index 0000000..72b1f95 --- /dev/null +++ b/src/apache_conf_file.rb @@ -0,0 +1,69 @@ +class ApacheConfFile + + def initialize(conf_file_path) + if conf_file_path.nil? or not File.file?(conf_file_path) + raise(ArgumentError.new('How about passing in a real file?')) + end + + # Read each line of the conf file into an array. + # Apache config directives are all one-line, right? + @file_lines = [] + + File::open(conf_file_path) do |f| + @file_lines = f.readlines() + end + + if @file_lines.length < 1 + raise(ArgumentError.new('Your file sucks.')) + end + end + + + + def get_php_admin_values(directive_name) + # First, we have to regex-ize the directive name, + # since they can contain periods (at the least!). + directive_name.sub!('.', '\.') + + # Our return variable, an array of matching directive values. + values = [] + + # Loop through each line in the conf file looking a + # matching pattern. + @file_lines.each do |line| + matches = line.match(/php_admin_value[[:space:]]+#{directive_name}[[:space:]](.*)$/) + + if not matches.nil? + # If there's a match, there should be only one + # (in addition to [0], the entire matched string). + if matches.length > 2 + raise(StandardError.new('Matched the Regex more than once?')) + end + + # If there's only one, add it to our list. + # These are probably likely to have some extra + # whitespace around them one way or another. + values << matches[1].strip + end + end + + return values + end + + + def get_php_temporary_directory_list(remove_duplicates = true) + # Return a list of all PHP "temporary" directories. + # I.e. upload_tmp_dir, session.save_path + temp_dirs = [] + temp_dirs += self.get_php_admin_values('upload_tmp_dir') + temp_dirs += self.get_php_admin_values('session.save_path') + + # We expect these to have been strip()ed already + if remove_duplicates + return temp_dirs.uniq + else + return temp_dirs + end + end + +end diff --git a/src/janitor.rb b/src/janitor.rb new file mode 100644 index 0000000..24217ad --- /dev/null +++ b/src/janitor.rb @@ -0,0 +1,77 @@ +require('src/apache_conf_file') + +class Janitor + + attr_accessor :apache_vhosts_directory + + SECONDS_PER_MINUTE = 60 + MINUTES_PER_HOUR = 60 + HOURS_PER_DAY = 24 + SECONDS_PER_DAY = SECONDS_PER_MINUTE * MINUTES_PER_HOUR * HOURS_PER_DAY + + + def initialize() + @apache_vhosts_directory = nil + end + + + protected; + + def seconds_to_whole_days(seconds) + # Given some number of seconds, computer how many + # entire days are contained in that number of seconds. + # Fractions of days don't count! + + if (seconds < 0) + raise(ArgumentException.new('Negative seconds?')) + end + + return (seconds / SECONDS_PER_DAY).floor + end + + public; + + # Get a list of all the temporary directories that + # we'd like to clean out. + def get_temporary_directories(remove_duplicates = true) + temp_dirs = [] + + # Get the PHP temp dirs if we've been given a vhosts path + if !@apache_vhosts_directory.nil? && File.directory?(@apache_vhosts_directory) + Dir.glob(@apache_vhosts_directory + '/*.conf').each do |conf_file| + acf = ApacheConfFile.new(conf_file) + + # Don't remove the duplicates here since we have + # to do it afterwards anyway. + temp_dirs += acf.get_php_temporary_directory_list(false) + end + end + + # We expect these to have been strip()ed already + if remove_duplicates + return temp_dirs.uniq + else + return temp_dirs + end + end + + + def clean_directory(directory, max_age, file_pattern = '*') + glob_pattern = File.join(directory, file_pattern) + + Dir.glob(glob_pattern).each do |f| + # Obviously, we should bail if it's not a file. + next if not File.file?(f) + + # Calculate how old it is + days_old = seconds_to_whole_days(Time.now - File.mtime(f)) + + # And then delete the file (with a notice) if it's too old. + if days_old > max_age + puts "Deleting file: #{f} (#{days_old} days old)" + File.delete(f) + end + end + + end +end diff --git a/test/apache_conf_file_test.rb b/test/apache_conf_file_test.rb new file mode 100644 index 0000000..c3af6ae --- /dev/null +++ b/test/apache_conf_file_test.rb @@ -0,0 +1,82 @@ +require('src/apache_conf_file') +require('test/unit') + +class ApacheConfFileTest < Test::Unit::TestCase + + def test_bad_conf_file_path_raises_error + assert_raise(ArgumentError) do + acf = ApacheConfFile.new(nil) + end + + assert_raise(ArgumentError) do + acf = ApacheConfFile.new('/does/not/exist') + end + + assert_raise(ArgumentError) do + # This raises a "different" error than the one above, + # but quit giving me empty files! + acf = ApacheConfFile.new('test/fixtures/empty_file.txt') + end + end + + + def test_good_conf_file_loads + acf = ApacheConfFile.new('test/fixtures/example.com-boring.conf') + # That's all.. + end + + + def test_conf_file_with_no_directives + acf = ApacheConfFile.new('test/fixtures/example.com-boring.conf') + temp_files = acf.get_php_temporary_directory_list() + assert_kind_of(Array, temp_files) + assert_equal(0, temp_files.length) + end + + + def test_get_php_temporary_dirs_non_unique + acf = ApacheConfFile.new('test/fixtures/example.com-typical.conf') + # There should be two (identical) directories. + temp_files = acf.get_php_temporary_directory_list(false) + assert_equal(2, temp_files.length) + assert_equal(temp_files[0], temp_files[1]) + end + + + def test_get_php_temporary_dirs_unique + acf = ApacheConfFile.new('test/fixtures/example.com-typical.conf') + # There should be just one directory, since the + # upload_tmp and session.save paths are identical. + temp_files = acf.get_php_temporary_directory_list() + assert_equal(1, temp_files.length) + assert_not_equal(0, temp_files[0].length) + end + + + def test_get_php_temporary_dirs_different_unique + acf = ApacheConfFile.new('test/fixtures/example.com-different.conf') + temp_files = acf.get_php_temporary_directory_list() + assert_equal(2, temp_files.length) + assert_not_equal(temp_files[0], temp_files[1]) + end + + + def test_get_php_temporary_dirs_multi_unique + # Two different vhosts (one SSL), both configured + # with the same two (different) tmp directories. + acf = ApacheConfFile.new('test/fixtures/example.com-multi.conf') + temp_files = acf.get_php_temporary_directory_list() + assert_equal(2, temp_files.length) + assert_not_equal(temp_files[0], temp_files[1]) + end + + + def test_get_php_temporary_dirs_multi_non_unique + # Two different vhosts (one SSL), both configured + # with the same two (different) tmp directories. + acf = ApacheConfFile.new('test/fixtures/example.com-multi.conf') + temp_files = acf.get_php_temporary_directory_list(false) + assert_equal(4, temp_files.length) + end + +end diff --git a/test/fixtures/example.com-boring.conf b/test/fixtures/example.com-boring.conf new file mode 100644 index 0000000..dcdb4bb --- /dev/null +++ b/test/fixtures/example.com-boring.conf @@ -0,0 +1,5 @@ + + ServerName www.example.com + ServerAlias example.com + DocumentRoot "/var/www/example.com/www/public" + diff --git a/test/fixtures/example.com-different.conf b/test/fixtures/example.com-different.conf new file mode 100644 index 0000000..7705693 --- /dev/null +++ b/test/fixtures/example.com-different.conf @@ -0,0 +1,27 @@ + + ServerName www.example.com + ServerAlias example.com + DocumentRoot "/var/www/example.com/www/public" + + php_admin_value open_basedir /var/www/example.com/www/:/usr/share/php/ + php_admin_value upload_tmp_dir /tmp + php_admin_value session.save_path /var/www/example.com/www/tmp + + LogLevel warn + ErrorLog /var/log/apache2/example.com/www/error.log + CustomLog /var/log/apache2/example.com/www/access.log combined + + + Options IncludesNoExec + AllowOverride None + Order allow,deny + Allow from all + + + DirectoryIndex default.shtml + + Include /etc/apache2/vhosts.d/include/php.conf + + DirectoryIndex default.php index.php + + diff --git a/test/fixtures/example.com-multi.conf b/test/fixtures/example.com-multi.conf new file mode 100644 index 0000000..f907b8d --- /dev/null +++ b/test/fixtures/example.com-multi.conf @@ -0,0 +1,55 @@ + + ServerName www.example.com + ServerAlias example.com + DocumentRoot "/var/www/example.com/www/public" + + php_admin_value open_basedir /var/www/example.com/www/:/usr/share/php/ + php_admin_value upload_tmp_dir /tmp + php_admin_value session.save_path /var/www/example.com/www/tmp + + LogLevel warn + ErrorLog /var/log/apache2/example.com/www/error.log + CustomLog /var/log/apache2/example.com/www/access.log combined + + + Options IncludesNoExec + AllowOverride None + Order allow,deny + Allow from all + + + DirectoryIndex default.shtml + + Include /etc/apache2/vhosts.d/include/php.conf + + DirectoryIndex default.php index.php + + + + + + ServerName secure.example.com + DocumentRoot "/var/www/example.com/www/secure" + + php_admin_value open_basedir /var/www/example.com/www/:/usr/share/php/ + php_admin_value upload_tmp_dir /tmp + php_admin_value session.save_path /var/www/example.com/www/tmp + + LogLevel warn + ErrorLog /var/log/apache2/example.com/www/error.log + CustomLog /var/log/apache2/example.com/www/access.log combined + + + Options IncludesNoExec + AllowOverride None + Order allow,deny + Allow from all + + + DirectoryIndex default.shtml + + Include /etc/apache2/vhosts.d/include/php.conf + + DirectoryIndex default.php index.php + + diff --git a/test/fixtures/example.com-typical.conf b/test/fixtures/example.com-typical.conf new file mode 100644 index 0000000..d6e76c3 --- /dev/null +++ b/test/fixtures/example.com-typical.conf @@ -0,0 +1,27 @@ + + ServerName www.example.com + ServerAlias example.com + DocumentRoot "/var/www/example.com/www/public" + + php_admin_value open_basedir /var/www/example.com/www/:/usr/share/php/ + php_admin_value upload_tmp_dir /var/www/example.com/www/tmp + php_admin_value session.save_path /var/www/example.com/www/tmp + + LogLevel warn + ErrorLog /var/log/apache2/example.com/www/error.log + CustomLog /var/log/apache2/example.com/www/access.log combined + + + Options IncludesNoExec + AllowOverride None + Order allow,deny + Allow from all + + + DirectoryIndex default.shtml + + Include /etc/apache2/vhosts.d/include/php.conf + + DirectoryIndex default.php index.php + + diff --git a/test/janitor_test.rb b/test/janitor_test.rb new file mode 100644 index 0000000..f3831ac --- /dev/null +++ b/test/janitor_test.rb @@ -0,0 +1,57 @@ +require('src/janitor') +require('test/unit') + +class JanitorTest < Test::Unit::TestCase + + def test_new_files_dont_get_cleaned + j = Janitor.new() + + File.open('test/fixtures/dummy/dummy-1.txt', 'w') do |f| + f << 'dfhdsgfhsdywerhdf' + end + + File.open('test/fixtures/dummy/dummy-2.txt', 'w') do |f| + f << 'gjkh983yfhufg' + end + + assert(true, File.file?('test/fixtures/dummy/dummy-1.txt')) + assert(true, File.file?('test/fixtures/dummy/dummy-2.txt')) + + j.clean_directory('test/fixtures/dummy', 1) + + assert(true, File.file?('test/fixtures/dummy/dummy-1.txt')) + assert(true, File.file?('test/fixtures/dummy/dummy-2.txt')) + + File.delete('test/fixtures/dummy/dummy-1.txt') + File.delete('test/fixtures/dummy/dummy-2.txt') + end + + + def test_get_temporary_directories_unique + j = Janitor.new() + j.apache_vhosts_directory = 'test/fixtures/' + tmp_dirs = j.get_temporary_directories() + assert(true, tmp_dirs.include?('/tmp')) + assert(true, tmp_dirs.include?('/var/www/example.com/www/tmp')) + assert_equal(2, tmp_dirs.length) + end + + + def test_get_temporary_directories_non_unique + j = Janitor.new() + j.apache_vhosts_directory = 'test/fixtures/' + tmp_dirs = j.get_temporary_directories(false) + assert(true, tmp_dirs.include?('/tmp')) + assert(true, tmp_dirs.include?('/var/www/example.com/www/tmp')) + assert_equal(8, tmp_dirs.length) + end + + + def test_non_directory_returns_no_paths + j = Janitor.new() + j.apache_vhosts_directory = 'test/fixtures/WHARRGHARBL' + tmp_dirs = j.get_temporary_directories() + assert_equal(0, tmp_dirs.length) + end + +end diff --git a/test/test_suite.rb b/test/test_suite.rb new file mode 100644 index 0000000..af3eb2b --- /dev/null +++ b/test/test_suite.rb @@ -0,0 +1,2 @@ +require 'test/apache_conf_file_test' +require 'test/janitor_test' -- 2.43.2