Way too many changes to mention. The 'rm' mode works now.
authorMichael Orlitzky <michael@orlitzky.com>
Mon, 30 Sep 2013 01:27:24 +0000 (21:27 -0400)
committerMichael Orlitzky <michael@orlitzky.com>
Mon, 30 Sep 2013 01:27:24 +0000 (21:27 -0400)
19 files changed:
bin/mailshears
lib/common/davical_plugin.rb
lib/common/dovecot_mailstore_plugin.rb
lib/common/postfixadmin_db_plugin.rb [moved from lib/common/postfixadmin_db.rb with 72% similarity]
lib/common/roundcube_db_plugin.rb
lib/common/runner.rb
lib/mailshears.rb
lib/mv/mv_dummy_runner.rb
lib/mv/mv_runner.rb
lib/prune/prune_dummy_runner.rb
lib/prune/prune_runner.rb
lib/rm/plugins/agendav.rb
lib/rm/plugins/davical.rb
lib/rm/plugins/dovecot_mailstore.rb
lib/rm/plugins/postfixadmin_db.rb [new file with mode: 0644]
lib/rm/plugins/roundcube_db.rb
lib/rm/rm_dummy_runner.rb
lib/rm/rm_plugin.rb
lib/rm/rm_runner.rb

index 09735ba26ba7087711a8b81605483a0b00d9ea4b..37d660175dff6b1f055c73d3320b3031a04a2e06 100755 (executable)
@@ -57,6 +57,9 @@ cfg.plugins.each do |plugin_file|
   require "#{mode_name}/plugins/#{plugin_file}"
 end
 
   require "#{mode_name}/plugins/#{plugin_file}"
 end
 
+# Always enabled, for now.
+require "#{mode_name}/plugins/postfixadmin_db"
+
 # And the runners.
 require "#{mode_name}/#{mode_name}_runner"
 require "#{mode_name}/#{mode_name}_dummy_runner"
 # And the runners.
 require "#{mode_name}/#{mode_name}_runner"
 require "#{mode_name}/#{mode_name}_dummy_runner"
@@ -102,10 +105,10 @@ else
   dummy_runner_class = PruneDummyRunner
 end
 
   dummy_runner_class = PruneDummyRunner
 end
 
+puts make_header(plugin_class.to_s())
 
 plugin_class.includers.each do |plugin_class_includer|
   plugin = plugin_class_includer.new()
 
 plugin_class.includers.each do |plugin_class_includer|
   plugin = plugin_class_includer.new()
-  puts make_header(plugin_class.to_s())
 
   if cfg.i_mean_business then
     runner = runner_class.new()
 
   if cfg.i_mean_business then
     runner = runner_class.new()
@@ -124,15 +127,6 @@ end
 
 
 Kernel.exit(0)
 
 
 Kernel.exit(0)
-pgadb = PostfixadminDb.new(cfg.dbhost,
-                           cfg.dbport,
-                           cfg.dbopts,
-                           cfg.dbtty,
-                           cfg.dbname,
-                           cfg.dbuser,
-                           cfg.dbpass)
-
-
 
 
 begin
 
 
 begin
index e4bef48ac41239f49b7aa3656adbce782f25bd00..cdde3d1e068301fbd13a3eb05783fed7f878d8bc 100644 (file)
@@ -70,4 +70,31 @@ module DavicalPlugin
   end
 
 
   end
 
 
+  def get_davical_usernames()
+    usernames = []
+
+    begin
+      connection = PGconn.connect(@db_host,
+                                  @db_port,
+                                  @db_opts,
+                                  @db_tty,
+                                  @db_name,
+                                  @db_user,
+                                  @db_pass)
+
+      sql_query =  "SELECT username FROM usr"
+
+      connection.query(sql_query) do |result|
+        usernames = result.field_values('username')
+      end
+
+      connection.close()
+    rescue PGError => e
+      # Pretend like we're database-agnostic in case we ever are.
+      raise DatabaseError.new(e)
+    end
+
+    return usernames
+  end
+
 end
 end
index ff901ee43417e8e7d66562763792aeb75e13eb14..d0f8dd11f7f76c10b8a53e7e83fa87989fe0b7b2 100644 (file)
@@ -67,7 +67,7 @@ module DovecotMailstorePlugin
     if File.directory?(account_path)
       return account_path
     else
     if File.directory?(account_path)
       return account_path
     else
-      raise NonexistentAccountError(account)
+      raise NonexistentAccountError.new(account)
     end
   end
 
     end
   end
 
similarity index 72%
rename from lib/common/postfixadmin_db.rb
rename to lib/common/postfixadmin_db_plugin.rb
index 0249ea07949b384a5438f34c9c5f7c27e582d7e0..936672e27a331d60f1c05bead0fb14a96efb72e1 100644 (file)
@@ -1,22 +1,33 @@
+require 'common/plugin'
 require 'pg'
 
 require 'pg'
 
-class PostfixadminDb
-
-  def initialize(db_host,
-                 db_port,
-                 db_opts,
-                 db_tty,
-                 db_name,
-                 db_user,
-                 db_pass)
-
-    @db_host = db_host
-    @db_port = db_port
-    @db_opts = db_opts
-    @db_tty  = db_tty
-    @db_name = db_name
-    @db_user = db_user
-    @db_pass = db_pass
+module PostfixadminDbPlugin
+  # Code that all PostfixadminDb plugins (Prune, Rm, Mv...) will
+  # share.  That is, we implement the Plugin interface.
+  include Plugin
+
+  def initialize()
+
+    cfg = Configuration.new()
+    @db_host = cfg.dbhost
+    @db_port = cfg.dbport
+    @db_opts = cfg.dbopts
+    @db_tty  = cfg.dbtty
+    @db_name = cfg.dbname
+    @db_user = cfg.dbuser
+    @db_pass = cfg.dbpass
+  end
+
+
+  def describe_account(account)
+    # There's no other unique identifier in PostfixAdmin
+    return account
+  end
+
+
+  def describe_domain(domain)
+    # There's no other unique identifier in PostfixAdmin
+    return domain
   end
 
 
   end
 
 
@@ -76,4 +87,5 @@ class PostfixadminDb
     return accounts
   end
 
     return accounts
   end
 
+
 end
 end
index 364b8199e23f992c2a4744ac3ec76239a98b7714..e4e9e7fb73471d4115cd6694527a43bb139cc293 100644 (file)
@@ -65,4 +65,33 @@ module RoundcubeDbPlugin
     return user_id
   end
 
     return user_id
   end
 
+
+  # Uses in both prune/rm.
+  def get_roundcube_usernames()
+    usernames = []
+
+    # Just assume PostgreSQL for now.
+    begin
+      connection = PGconn.connect(@db_host,
+                                  @db_port,
+                                  @db_opts,
+                                  @db_tty,
+                                  @db_name,
+                                  @db_user,
+                                  @db_pass)
+
+      sql_query = "SELECT username FROM users;"
+      connection.query(sql_query) do |result|
+        usernames = result.field_values('username')
+      end
+
+      connection.close()
+    rescue PGError => e
+      # Pretend like we're database-agnostic in case we ever are.
+      raise DatabaseError.new(e)
+    end
+
+    return usernames
+  end
+
 end
 end
index c48f89a4f629168d657142871bc19f46ed781c27..b9bb8c9323af87df7d097f2879f481f7ba7cd039 100644 (file)
@@ -2,4 +2,10 @@ module Runner
   def run(plugin, targets)
     raise NotImplementedError
   end
   def run(plugin, targets)
     raise NotImplementedError
   end
+
+  def report(plugin, msg)
+    print "#{plugin.class.to_s()} - "
+    puts msg
+  end
+
 end
 end
index 5eff52169061579975d6cb0ab0b3e4754f8ff37c..bda9f0dd46b0dd20ac5d8b3d3ca243f234b88038 100644 (file)
@@ -5,5 +5,4 @@
 require 'common/configuration'
 require 'common/errors'
 require 'common/exit_codes'
 require 'common/configuration'
 require 'common/errors'
 require 'common/exit_codes'
-require 'common/postfixadmin_db'
-require 'common/runner'
+
index 0e3beeed4c73ad30545258e34a4ed79da29ab5ca..0d06fc180820397016c5e6c62a1e7c2953dddcc3 100644 (file)
@@ -1,3 +1,5 @@
+require 'common/runner'
+
 class MvDummyRunner
   include Runner
 
 class MvDummyRunner
   include Runner
 
index a0fa8e7ae6089e9899a7910b0355b3b12c7a14df..c7dbc5e7497147c90f40dbfb696474e11f5f8619 100644 (file)
@@ -1,3 +1,5 @@
+require 'common/runner'
+
 class MvRunner
   include Runner
 
 class MvRunner
   include Runner
 
index f9bde5e17c667b086f037a0232b054644d7ce292..8e9fcd9f14280e43af55c7fe10a43940e61efeb1 100644 (file)
@@ -1,3 +1,5 @@
+require 'common/runner'
+
 class PruneDummyRunner
   include Runner
 
 class PruneDummyRunner
   include Runner
 
index 0f1dfeb3832a70c7baf77b59d3b8d0bfbd493e07..789ed573623ef3b72cd0e5d983c45e9f1bbc68c1 100644 (file)
@@ -1,3 +1,5 @@
+require 'common/runner'
+
 class PruneRunner
   include Runner
 
 class PruneRunner
   include Runner
 
index 4cd179c655323070d71b01348a975a4a214047c0..6aa6d9191216d59f635238836bfe7e05ad68d49e 100644 (file)
@@ -42,4 +42,14 @@ class AgendavRm
 
   end
 
 
   end
 
+
+  def get_domain_usernames(domain)
+    usernames = get_agendav_usernames();
+    matches = usernames.select do |username|
+      username =~ /@#{domain}$/
+    end
+
+    return matches
+  end
+
 end
 end
index 86273cba6e5d2a31f0fb617b24a88f9088d523cc..ce5828a627b3cf72a29069815feab17660a07267 100644 (file)
@@ -45,4 +45,15 @@ class DavicalRm
 
   end
 
 
   end
 
+
+
+  def get_domain_usernames(domain)
+    usernames = get_davical_usernames();
+    matches = usernames.select do |username|
+      username =~ /@#{domain}$/
+    end
+
+    return matches
+  end
+
 end
 end
index e1386512138680f4fa1d120505d61ed74cd63bf0..8ab507d674c3c77739d854ad9d95439b702531ee 100644 (file)
@@ -40,6 +40,10 @@ class DovecotMailstoreRm < Mailstore
   end
 
 
   end
 
 
+  def get_domain_usernames(domain)
+    return get_accounts_from_filesystem([domain])
+  end
+
   protected;
 
   def get_domains_from_filesystem()
   protected;
 
   def get_domains_from_filesystem()
@@ -68,5 +72,4 @@ class DovecotMailstoreRm < Mailstore
     return accounts
   end
 
     return accounts
   end
 
-
 end
 end
diff --git a/lib/rm/plugins/postfixadmin_db.rb b/lib/rm/plugins/postfixadmin_db.rb
new file mode 100644 (file)
index 0000000..cd59928
--- /dev/null
@@ -0,0 +1,113 @@
+require 'pg'
+
+require 'common/postfixadmin_db_plugin'
+require 'rm/rm_plugin'
+
+class PostfixadminDbRm
+
+  include PostfixadminDbPlugin
+  include RmPlugin
+
+
+  def delete_account(account)
+    sql_queries = ['DELETE FROM alias WHERE address = $1;']
+    # Wipe out any aliases pointed at our account.
+    sql_queries << "UPDATE alias SET goto=REPLACE(goto, $1, '');"
+    sql_queries << 'DELETE FROM mailbox WHERE username = $1;'
+    sql_queries << 'DELETE FROM quota WHERE username = $1;'
+    sql_queries << 'DELETE FROM quota2 WHERE username = $1;'
+    sql_queries << 'DELETE FROM vacation WHERE email = $1;'
+
+    # Should be handled by a trigger, according to PostfixAdmin code.
+    sql_queries << 'DELETE FROM vacation_notification WHERE on_vacation = $1;'
+
+    begin
+      connection = PGconn.connect(@db_host,
+                                  @db_port,
+                                  @db_opts,
+                                  @db_tty,
+                                  @db_name,
+                                  @db_user,
+                                  @db_pass)
+
+      sql_queries.each do |sql_query|
+        connection.query(sql_query, [account])
+      end
+
+      # The earlier alias update query will leave things like
+      # "foo@example.com,,bar@example.com" in the "goto" column. Now
+      # we fix it. We don't do it in the loop because query() craps
+      # out on the superfluous parameter.
+      sql_query = "UPDATE alias SET goto=REPLACE(goto, ',,', ',');"
+      connection.query(sql_query)
+
+      connection.close()
+
+    rescue PGError => e
+      # Pretend like we're database-agnostic in case we ever are.
+      raise DatabaseError.new(e)
+    end
+  end
+
+
+  def delete_domain(domain)
+    sql_queries = ['DELETE FROM domain_admins WHERE domain = $1;']
+    sql_queries << 'DELETE FROM alias WHERE domain = $1;'
+    sql_queries << 'DELETE FROM mailbox WHERE domain = $1;'
+    sql_queries << 'DELETE FROM alias_domain WHERE alias_domain = $1;'
+    sql_queries << 'DELETE FROM log WHERE domain = $1;'
+    sql_queries << 'DELETE FROM vacation WHERE domain = $1;'
+
+    begin
+      connection = PGconn.connect(@db_host,
+                                  @db_port,
+                                  @db_opts,
+                                  @db_tty,
+                                  @db_name,
+                                  @db_user,
+                                  @db_pass)
+
+      sql_queries.each do |sql_query|
+        connection.query(sql_query, [domain])
+      end
+
+      connection.close()
+
+    rescue PGError => e
+      # Pretend like we're database-agnostic in case we ever are.
+      raise DatabaseError.new(e)
+    end
+
+  end
+
+
+
+  def get_domain_usernames(domain)
+    usernames = []
+
+    # Just assume PostgreSQL for now.
+    begin
+      connection = PGconn.connect(@db_host,
+                                  @db_port,
+                                  @db_opts,
+                                  @db_tty,
+                                  @db_name,
+                                  @db_user,
+                                  @db_pass)
+
+      sql_query  = 'SELECT username FROM mailbox WHERE domain = $1;'
+
+      connection.query(sql_query, [domain]) do |result|
+        usernames = result.field_values('username')
+      end
+
+      connection.close()
+    rescue PGError => e
+      # Pretend like we're database-agnostic in case we ever are.
+      raise DatabaseError.new(e)
+    end
+
+    return usernames
+  end
+
+end
index 3e18e8e4b8a5c2bbb8db7555ba8745773ed8f5b5..252ce32688cbb82faa81f42b5522ac8abf73cc7b 100644 (file)
@@ -51,46 +51,14 @@ class RoundcubeDbRm
   end
 
 
   end
 
 
-  def get_leftover_domains(db_domains)
-    # Roundcube doesn't have a concept of domains.
-    return []
-  end
-
-
-  def get_leftover_accounts(db_accounts)
-    # Get a list of all users who have logged in to Roundcube.
-    rc_accounts = self.get_roundcube_usernames()
-    return rc_accounts - db_accounts
-  end
-
 
 
-  protected;
-
-  def get_roundcube_usernames()
-    usernames = []
-
-    # Just assume PostgreSQL for now.
-    begin
-      connection = PGconn.connect(@db_host,
-                                  @db_port,
-                                  @db_opts,
-                                  @db_tty,
-                                  @db_name,
-                                  @db_user,
-                                  @db_pass)
-
-      sql_query = "SELECT username FROM users;"
-      connection.query(sql_query) do |result|
-        usernames = result.field_values('username')
-      end
-
-      connection.close()
-    rescue PGError => e
-      # Pretend like we're database-agnostic in case we ever are.
-      raise DatabaseError.new(e)
+  def get_domain_usernames(domain)
+    usernames = get_roundcube_usernames();
+    matches = usernames.select do |username|
+      username =~ /@#{domain}$/
     end
 
     end
 
-    return usernames
+    return matches
   end
 
 end
   end
 
 end
index d6c2c5f85223a80ce93c6d9a2f4b126954c3b20a..9bdd13f5f47fb2dc1b71e7c012545c47c08e7e4a 100644 (file)
@@ -1,3 +1,5 @@
+require 'common/runner'
+
 class RmDummyRunner
   include Runner
 
 class RmDummyRunner
   include Runner
 
@@ -5,12 +7,16 @@ class RmDummyRunner
     targets.each do |target|
       if target.include?('@') then
         puts "Would remove account: #{target}"
     targets.each do |target|
       if target.include?('@') then
         puts "Would remove account: #{target}"
+        # TODO: remove from postfixadmin as well.
       else
       else
+        usernames = plugin.get_domain_usernames(target)
+        usernames.each { |u| run(plugin, u) }
+
         puts "Would remove domain: #{target}"
         puts "Would remove domain: #{target}"
+        # TODO: remove from postfixadmin as well.
       end
     end
 
       end
     end
 
-    # TODO: remove from postfixadmin as well.
   end
 
 end
   end
 
 end
index f8cdd175be5587c0f98e032727a10bcf003a363a..cad493d9f85c95ae8a3c79bee121e59c508a8efb 100644 (file)
@@ -25,4 +25,11 @@ module RmPlugin
     raise NotImplementedError
   end
 
     raise NotImplementedError
   end
 
+  def get_domain_usernames(domain)
+    # Get a list of usernames for a given domain,
+    # needed to delete accounts before removing
+    # a domain.
+    raise NotImplementedError
+  end
+
 end
 end
index 5fe2eaf933bf7d073a200f23381718ac60529b31..0392d1192acbac86218c0813218e37b5b5c56929 100644 (file)
@@ -1,3 +1,5 @@
+require 'common/runner'
+
 class RmRunner
   include Runner
 
 class RmRunner
   include Runner
 
@@ -8,25 +10,39 @@ class RmRunner
         begin
           account_description = plugin.describe_account(target)
           plugin.delete_account(target)
         begin
           account_description = plugin.describe_account(target)
           plugin.delete_account(target)
-          puts "Removed account: #{target} (#{account_description})"
+          report(plugin, "Removed account: #{target} (#{account_description})")
+
+        rescue NonexistentAccountError => e
+          report(plugin, "Account not found: #{e.to_s}")
         rescue StandardError => e
         rescue StandardError => e
-          puts "There was an error removing the account: #{e.to_s}"
+          report(plugin, "There was an error removing the account: #{e.to_s}")
           Kernel.exit(ExitCodes::DATABASE_ERROR)
         end
       else
         begin
           Kernel.exit(ExitCodes::DATABASE_ERROR)
         end
       else
         begin
-          # TODO: Delete all accounts first.
+          # We must delete all accounts belonging to the domain first.
+          # This prevents us from leaving behind accounts. Not a
+          # problem with the mailstore, since we'll delete the domain
+          # directory anyway, but it is for the database plugins.
+          usernames = plugin.get_domain_usernames(target)
+          usernames.each { |u| run(plugin, u) }
+
           domain_description = plugin.describe_domain(target)
           plugin.delete_domain(target)
           domain_description = plugin.describe_domain(target)
           plugin.delete_domain(target)
-          puts "Removed domain: #{target} (#{domain_description})"
+          report(plugin, "Removed domain: #{target} (#{domain_description})")
+
+        rescue NonexistentAccountError => e
+          # Can happen in the usernames.each... block.
+          report(plugin, "Account not found: #{e.to_s}")
+        rescue NonexistentDomainError => e
+          report(plugin, "Domain not found: #{e.to_s}")
         rescue StandardError => e
         rescue StandardError => e
-          puts "There was an error removing the domain: #{e.to_s}"
+          report(plugin, "There was an error removing the domain: #{e.to_s}")
           Kernel.exit(ExitCodes::DATABASE_ERROR)
         end
       end
     end
 
           Kernel.exit(ExitCodes::DATABASE_ERROR)
         end
       end
     end
 
-    # TODO: remove from postfixadmin as well.
   end
 
 end
   end
 
 end