Convert DovecotMailstore to a plugin, and generalize the main mailshears script to...
[mailshears.git] / bin / mailshears
1 #!/usr/bin/ruby -wKU
2 #
3 # mailshears, to prune unused mail directories.
4 #
5 # Mail accounts for virtual hosts are stored in SQL, and managed by
6 # Postfixadmin. However, the physical directories are handled by
7 # Postfix/Dovecot and are left untouched by Postfixadmin. This is good
8 # for security, but comes at a cost: Postfixadmin can't remove a
9 # user's mail directory when his or her account is deleted.
10 #
11 # This program compares the list of filesystem accounts with the ones
12 # in the database. It outputs any accounts that exist in the
13 # filesystem, but not the database.
14 #
15
16 # We need Pathname to get the real filesystem path
17 # of this script (and not, for example, the path of
18 # a symlink which points to it).
19 require 'pathname'
20
21 # This bit of magic adds the parent directory (the
22 # project root) to the list of ruby load paths.
23 # Thus, our require statements will work regardless of
24 # how or from where the script was run.
25 executable = Pathname.new(__FILE__).realpath.to_s
26 $: << File.dirname(executable) + '/../'
27
28 # Needed for rm_rf.
29 require 'fileutils'
30
31 # Load our config file.
32 require 'bin/configuration'
33
34 # And the necessary classes.
35 require 'src/errors.rb'
36 require 'src/exit_codes.rb'
37 require 'src/dovecot_mailstore'
38 require 'src/postfixadmin_db'
39
40 pgadb = PostfixadminDb.new(Configuration::DBHOST,
41 Configuration::DBPORT,
42 Configuration::DBOPTS,
43 Configuration::DBTTY,
44 Configuration::DBNAME,
45 Configuration::DBUSER,
46 Configuration::DBPASS)
47
48
49 begin
50 # Get a list of domains from the Postfixadmin database.
51 db_domains = pgadb.get_domains_from_db()
52 rescue DatabaseError => e
53 puts "There was an error connecting to the database: #{e.to_s}"
54 Kernel.exit(ExitCodes::DATABASE_ERROR)
55 end
56
57 begin
58 # And the accounts.
59 db_accounts = pgadb.get_accounts_from_db()
60 rescue DatabaseError => e
61 puts "There was an error connecting to the database: #{e.to_s}"
62 Kernel.exit(ExitCodes::DATABASE_ERROR)
63 end
64
65
66 Configuration::PLUGINS.each do |plugin_name|
67 # Convert a string into a class.
68 plugin_class = Kernel.const_get(plugin_name)
69 plugin = plugin_class.new()
70
71 begin
72 leftover_domains = plugin.get_leftover_domains(db_domains)
73 rescue StandardError => e
74 puts "There was an error retrieving domains from the filesystem: #{e.to_s}"
75 Kernel.exit(ExitCodes::FILESYSTEM_ERROR)
76 end
77
78 begin
79 leftover_accounts = plugin.get_leftover_accounts(db_accounts)
80 rescue StandardError => e
81 puts "There was an error retrieving accounts from the filesystem: #{e.to_s}"
82 Kernel.exit(ExitCodes::FILESYSTEM_ERROR)
83 end
84
85 if leftover_domains.size > 0 or leftover_accounts.size > 0
86 # The header that we output before the list of domains/accounts.
87 # Just the path of this script, the current time, and the plugin name.
88 header = "#{$0}, "
89
90 current_time = Time.now()
91 if current_time.respond_to?(:iso8601)
92 # Somehow this method is missing on some machines.
93 header += current_time.iso8601.to_s
94 else
95 # Fall back to whatever this looks like.
96 header += current_time.to_s + "\n"
97 end
98
99 header += 'Plugin: ' + plugin_name + "\n"
100 puts header
101 puts '-' * header.size # Underline the header.
102
103 leftover_domains.each do |domain|
104 puts "Found: #{domain} (#{plugin.describe_domain(domain)})"
105 end
106
107 leftover_accounts.each do |account|
108 puts "Found: #{account} (#{plugin.describe_account(account)})"
109 end
110
111 if Configuration::I_MEAN_BUSINESS
112 leftover_domains.each do |domain|
113 plugin.delete_domain(domain)
114 puts "Removed: #{domain} (#{plugin.describe_domain(domain)})"
115 end
116
117 leftover_accounts.each do |account|
118 plugin.delete_account(account)
119 puts "Removed: #{account} (#{plugin.describe_account(account)})"
120 end
121 end
122
123 puts ""
124 end
125
126 end