From: Michael Orlitzky <michael@orlitzky.com>
Date: Mon, 30 Sep 2013 01:27:24 +0000 (-0400)
Subject: Way too many changes to mention. The 'rm' mode works now.
X-Git-Tag: 0.0.1~86
X-Git-Url: https://gitweb.michael.orlitzky.com/?a=commitdiff_plain;h=51f027b01e242737956c3ab5aecdd322d6ceeeed;p=mailshears.git

Way too many changes to mention. The 'rm' mode works now.
---

diff --git a/bin/mailshears b/bin/mailshears
index 09735ba..37d6601 100755
--- a/bin/mailshears
+++ b/bin/mailshears
@@ -57,6 +57,9 @@ cfg.plugins.each do |plugin_file|
   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"
@@ -102,10 +105,10 @@ else
   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()
-  puts make_header(plugin_class.to_s())
 
   if cfg.i_mean_business then
     runner = runner_class.new()
@@ -124,15 +127,6 @@ end
 
 
 Kernel.exit(0)
-pgadb = PostfixadminDb.new(cfg.dbhost,
-                           cfg.dbport,
-                           cfg.dbopts,
-                           cfg.dbtty,
-                           cfg.dbname,
-                           cfg.dbuser,
-                           cfg.dbpass)
-
-
 
 
 begin
diff --git a/lib/common/davical_plugin.rb b/lib/common/davical_plugin.rb
index e4bef48..cdde3d1 100644
--- a/lib/common/davical_plugin.rb
+++ b/lib/common/davical_plugin.rb
@@ -70,4 +70,31 @@ module DavicalPlugin
   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
diff --git a/lib/common/dovecot_mailstore_plugin.rb b/lib/common/dovecot_mailstore_plugin.rb
index ff901ee..d0f8dd1 100644
--- a/lib/common/dovecot_mailstore_plugin.rb
+++ b/lib/common/dovecot_mailstore_plugin.rb
@@ -67,7 +67,7 @@ module DovecotMailstorePlugin
     if File.directory?(account_path)
       return account_path
     else
-      raise NonexistentAccountError(account)
+      raise NonexistentAccountError.new(account)
     end
   end
 
diff --git a/lib/common/postfixadmin_db.rb b/lib/common/postfixadmin_db_plugin.rb
similarity index 72%
rename from lib/common/postfixadmin_db.rb
rename to lib/common/postfixadmin_db_plugin.rb
index 0249ea0..936672e 100644
--- a/lib/common/postfixadmin_db.rb
+++ b/lib/common/postfixadmin_db_plugin.rb
@@ -1,22 +1,33 @@
+require 'common/plugin'
 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
 
 
@@ -76,4 +87,5 @@ class PostfixadminDb
     return accounts
   end
 
+
 end
diff --git a/lib/common/roundcube_db_plugin.rb b/lib/common/roundcube_db_plugin.rb
index 364b819..e4e9e7f 100644
--- a/lib/common/roundcube_db_plugin.rb
+++ b/lib/common/roundcube_db_plugin.rb
@@ -65,4 +65,33 @@ module RoundcubeDbPlugin
     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
diff --git a/lib/common/runner.rb b/lib/common/runner.rb
index c48f89a..b9bb8c9 100644
--- a/lib/common/runner.rb
+++ b/lib/common/runner.rb
@@ -2,4 +2,10 @@ module Runner
   def run(plugin, targets)
     raise NotImplementedError
   end
+
+  def report(plugin, msg)
+    print "#{plugin.class.to_s()} - "
+    puts msg
+  end
+
 end
diff --git a/lib/mailshears.rb b/lib/mailshears.rb
index 5eff521..bda9f0d 100644
--- a/lib/mailshears.rb
+++ b/lib/mailshears.rb
@@ -5,5 +5,4 @@
 require 'common/configuration'
 require 'common/errors'
 require 'common/exit_codes'
-require 'common/postfixadmin_db'
-require 'common/runner'
+
diff --git a/lib/mv/mv_dummy_runner.rb b/lib/mv/mv_dummy_runner.rb
index 0e3beee..0d06fc1 100644
--- a/lib/mv/mv_dummy_runner.rb
+++ b/lib/mv/mv_dummy_runner.rb
@@ -1,3 +1,5 @@
+require 'common/runner'
+
 class MvDummyRunner
   include Runner
 
diff --git a/lib/mv/mv_runner.rb b/lib/mv/mv_runner.rb
index a0fa8e7..c7dbc5e 100644
--- a/lib/mv/mv_runner.rb
+++ b/lib/mv/mv_runner.rb
@@ -1,3 +1,5 @@
+require 'common/runner'
+
 class MvRunner
   include Runner
 
diff --git a/lib/prune/prune_dummy_runner.rb b/lib/prune/prune_dummy_runner.rb
index f9bde5e..8e9fcd9 100644
--- a/lib/prune/prune_dummy_runner.rb
+++ b/lib/prune/prune_dummy_runner.rb
@@ -1,3 +1,5 @@
+require 'common/runner'
+
 class PruneDummyRunner
   include Runner
 
diff --git a/lib/prune/prune_runner.rb b/lib/prune/prune_runner.rb
index 0f1dfeb..789ed57 100644
--- a/lib/prune/prune_runner.rb
+++ b/lib/prune/prune_runner.rb
@@ -1,3 +1,5 @@
+require 'common/runner'
+
 class PruneRunner
   include Runner
 
diff --git a/lib/rm/plugins/agendav.rb b/lib/rm/plugins/agendav.rb
index 4cd179c..6aa6d91 100644
--- a/lib/rm/plugins/agendav.rb
+++ b/lib/rm/plugins/agendav.rb
@@ -42,4 +42,14 @@ class AgendavRm
 
   end
 
+
+  def get_domain_usernames(domain)
+    usernames = get_agendav_usernames();
+    matches = usernames.select do |username|
+      username =~ /@#{domain}$/
+    end
+
+    return matches
+  end
+
 end
diff --git a/lib/rm/plugins/davical.rb b/lib/rm/plugins/davical.rb
index 86273cb..ce5828a 100644
--- a/lib/rm/plugins/davical.rb
+++ b/lib/rm/plugins/davical.rb
@@ -45,4 +45,15 @@ class DavicalRm
 
   end
 
+
+
+  def get_domain_usernames(domain)
+    usernames = get_davical_usernames();
+    matches = usernames.select do |username|
+      username =~ /@#{domain}$/
+    end
+
+    return matches
+  end
+
 end
diff --git a/lib/rm/plugins/dovecot_mailstore.rb b/lib/rm/plugins/dovecot_mailstore.rb
index e138651..8ab507d 100644
--- a/lib/rm/plugins/dovecot_mailstore.rb
+++ b/lib/rm/plugins/dovecot_mailstore.rb
@@ -40,6 +40,10 @@ class DovecotMailstoreRm < Mailstore
   end
 
 
+  def get_domain_usernames(domain)
+    return get_accounts_from_filesystem([domain])
+  end
+
   protected;
 
   def get_domains_from_filesystem()
@@ -68,5 +72,4 @@ class DovecotMailstoreRm < Mailstore
     return accounts
   end
 
-
 end
diff --git a/lib/rm/plugins/postfixadmin_db.rb b/lib/rm/plugins/postfixadmin_db.rb
new file mode 100644
index 0000000..cd59928
--- /dev/null
+++ b/lib/rm/plugins/postfixadmin_db.rb
@@ -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
diff --git a/lib/rm/plugins/roundcube_db.rb b/lib/rm/plugins/roundcube_db.rb
index 3e18e8e..252ce32 100644
--- a/lib/rm/plugins/roundcube_db.rb
+++ b/lib/rm/plugins/roundcube_db.rb
@@ -51,46 +51,14 @@ class RoundcubeDbRm
   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
 
-    return usernames
+    return matches
   end
 
 end
diff --git a/lib/rm/rm_dummy_runner.rb b/lib/rm/rm_dummy_runner.rb
index d6c2c5f..9bdd13f 100644
--- a/lib/rm/rm_dummy_runner.rb
+++ b/lib/rm/rm_dummy_runner.rb
@@ -1,3 +1,5 @@
+require 'common/runner'
+
 class RmDummyRunner
   include Runner
 
@@ -5,12 +7,16 @@ class RmDummyRunner
     targets.each do |target|
       if target.include?('@') then
         puts "Would remove account: #{target}"
+        # TODO: remove from postfixadmin as well.
       else
+        usernames = plugin.get_domain_usernames(target)
+        usernames.each { |u| run(plugin, u) }
+
         puts "Would remove domain: #{target}"
+        # TODO: remove from postfixadmin as well.
       end
     end
 
-    # TODO: remove from postfixadmin as well.
   end
 
 end
diff --git a/lib/rm/rm_plugin.rb b/lib/rm/rm_plugin.rb
index f8cdd17..cad493d 100644
--- a/lib/rm/rm_plugin.rb
+++ b/lib/rm/rm_plugin.rb
@@ -25,4 +25,11 @@ module RmPlugin
     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
diff --git a/lib/rm/rm_runner.rb b/lib/rm/rm_runner.rb
index 5fe2eaf..0392d11 100644
--- a/lib/rm/rm_runner.rb
+++ b/lib/rm/rm_runner.rb
@@ -1,3 +1,5 @@
+require 'common/runner'
+
 class RmRunner
   include Runner
 
@@ -8,25 +10,39 @@ class RmRunner
         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
-          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
-          # 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)
-          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
-          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
 
-    # TODO: remove from postfixadmin as well.
   end
 
 end