From 483d14dc8228a81d12fb109d3ed6510e2d2b03c1 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Sun, 6 Oct 2013 15:35:18 -0400 Subject: [PATCH] Move domain removal into the plugins. --- doc/TODO | 13 ------- lib/common/agendav_plugin.rb | 6 +-- lib/common/davical_plugin.rb | 4 +- lib/common/dovecot_mailstore_plugin.rb | 38 +++++++++++++++++- lib/common/mailstore.rb | 20 ---------- lib/common/plugin.rb | 26 +++++++++++++ lib/common/postfixadmin_db_plugin.rb | 32 ++++++++++++++- lib/common/roundcube_db_plugin.rb | 6 +-- lib/common/runner.rb | 1 + lib/prune/plugins/dovecot_mailstore.rb | 8 ++-- lib/rm/plugins/agendav.rb | 14 ------- lib/rm/plugins/davical.rb | 15 ------- lib/rm/plugins/dovecot_mailstore.rb | 54 +------------------------- lib/rm/plugins/postfixadmin_db.rb | 24 ++++++++---- lib/rm/plugins/roundcube_db.rb | 15 ------- lib/rm/rm_plugin.rb | 19 ++++----- lib/rm/rm_runner.rb | 7 ---- 17 files changed, 132 insertions(+), 170 deletions(-) delete mode 100644 lib/common/mailstore.rb diff --git a/doc/TODO b/doc/TODO index 8d77398..6aa317b 100644 --- a/doc/TODO +++ b/doc/TODO @@ -1,16 +1,3 @@ -* When we delete a domain, do we delete all of the accounts, too? Or - just the domain? We can do this in either the runner or the plugin. - - At the moment, it's mixed. The RmRunner does it manually, but e.g. - the PostfixadminDbRm plugin also deletes the mailboxes when the - domain is deleted. - - One doesn't seem any better than the other, but maybe if you - consider that some of the plugins have no-ops for - delete_domain(). Those could be made to actually do something - (i.e. delete all matching account names). Then we could get rid of - the get_domain_accounts() or-whatever-it's-called methods. - * PostfixadminDb can be made a plugin -- prune just won't work without it. diff --git a/lib/common/agendav_plugin.rb b/lib/common/agendav_plugin.rb index fad869f..cb445be 100644 --- a/lib/common/agendav_plugin.rb +++ b/lib/common/agendav_plugin.rb @@ -20,7 +20,7 @@ module AgendavPlugin def describe_domain(domain) # AgenDAV doesn't have a concept of domains. - return 'N/A' + return domain end @@ -36,11 +36,11 @@ module AgendavPlugin protected; def user_exists(account) - ad_users = get_agendav_usernames() + ad_users = list_users() return ad_users.include?(account) end - def get_agendav_usernames() + def list_users() usernames = [] # Just assume PostgreSQL for now. diff --git a/lib/common/davical_plugin.rb b/lib/common/davical_plugin.rb index cdde3d1..1cc06ce 100644 --- a/lib/common/davical_plugin.rb +++ b/lib/common/davical_plugin.rb @@ -19,7 +19,7 @@ module DavicalPlugin def describe_domain(domain) # DAViCal doesn't have a concept of domains. - return 'N/A' + return domain end @@ -70,7 +70,7 @@ module DavicalPlugin end - def get_davical_usernames() + def list_users() usernames = [] begin diff --git a/lib/common/dovecot_mailstore_plugin.rb b/lib/common/dovecot_mailstore_plugin.rb index d0f8dd1..90aa02c 100644 --- a/lib/common/dovecot_mailstore_plugin.rb +++ b/lib/common/dovecot_mailstore_plugin.rb @@ -1,4 +1,5 @@ require 'common/plugin' +require 'common/filesystem' module DovecotMailstorePlugin # Code that all DovecotMailstore plugins (Prune, Rm, Mv...) will @@ -49,7 +50,8 @@ module DovecotMailstorePlugin # Return the filesystem path of this account's mailbox. # Only works if the account exists! if not account.include?('@') - raise InvalidAccountError.new("#{account}: Accounts must contain an '@' symbol.") + msg = "#{account}: Accounts must contain an '@' symbol." + raise InvalidAccountError.new(msg) end account_parts = account.split('@') @@ -71,4 +73,38 @@ module DovecotMailstorePlugin end end + + def list_domains() + return Filesystem.get_subdirs(@domain_root) + end + + def list_domains_users(domains) + accounts = [] + + domains.each do |domain| + begin + # Throws a NonexistentDomainError if the domain's path + # doesn't exist on the filesystem. In this case, we want + # to report zero accounts. + domain_path = get_domain_path(domain) + usernames = Filesystem.get_subdirs(domain_path) + + usernames.each do |username| + accounts << "#{username}@#{domain}" + end + rescue NonexistentDomainError + # Party hard. + end + end + + return accounts + end + + + def list_users() + domains = list_domains() + users = list_domains_users(domains) + return users + end + end diff --git a/lib/common/mailstore.rb b/lib/common/mailstore.rb deleted file mode 100644 index 19772d3..0000000 --- a/lib/common/mailstore.rb +++ /dev/null @@ -1,20 +0,0 @@ -class Mailstore - - attr_accessor :domain_root - - def def get_domains_from_filesystem - raise NotImplementedError - end - - def get_accounts_from_filesystem(domains) - raise NotImplementedError - end - - def get_domain_path(domain) - raise NotImplementedError - end - - def get_account_path(account) - raise NotImplementedError - end -end diff --git a/lib/common/plugin.rb b/lib/common/plugin.rb index 0687989..633c8fd 100644 --- a/lib/common/plugin.rb +++ b/lib/common/plugin.rb @@ -28,4 +28,30 @@ module Plugin raise NotImplementedError end + def list_users() + # Return a list of all users managed by this plugin. + raise NotImplementedError + end + + def list_domains_users(domains) + # Get all usernames belonging to the given domains. If a username + # ends in @example.com, it belongs to the domain example.com + # + # This uses a naive loop, but relies only on the existence of a + # list_users() method which is guaranteed above. More efficient + # implementations can usually be made within the plugin. + domains_users = [] + + usernames = list_users(); + domains.each do |d| + matches = usernames.select do |username| + username =~ /@#{d}$/ + end + + domains_users += matches + end + + return domains_users + end + end diff --git a/lib/common/postfixadmin_db_plugin.rb b/lib/common/postfixadmin_db_plugin.rb index 936672e..da40879 100644 --- a/lib/common/postfixadmin_db_plugin.rb +++ b/lib/common/postfixadmin_db_plugin.rb @@ -31,7 +31,7 @@ module PostfixadminDbPlugin end - def get_domains_from_db() + def list_domains() domains = [] # Just assume PostgreSQL for now. @@ -59,7 +59,7 @@ module PostfixadminDbPlugin end - def get_accounts_from_db() + def list_users() accounts = [] # Just assume PostgreSQL for now. @@ -88,4 +88,32 @@ module PostfixadminDbPlugin end + def list_domains_users(domains) + 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 IN $1;' + + connection.query(sql_query, [domains]) 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/roundcube_db_plugin.rb b/lib/common/roundcube_db_plugin.rb index e4e9e7f..df5413f 100644 --- a/lib/common/roundcube_db_plugin.rb +++ b/lib/common/roundcube_db_plugin.rb @@ -19,7 +19,7 @@ module RoundcubeDbPlugin def describe_domain(domain) # Roundcube doesn't have a concept of domains. - return 'N/A' + return domain end def describe_account(account) @@ -66,8 +66,8 @@ module RoundcubeDbPlugin end - # Uses in both prune/rm. - def get_roundcube_usernames() + # Used in both prune/rm. + def list_users() usernames = [] # Just assume PostgreSQL for now. diff --git a/lib/common/runner.rb b/lib/common/runner.rb index b9bb8c9..32f2b3e 100644 --- a/lib/common/runner.rb +++ b/lib/common/runner.rb @@ -1,4 +1,5 @@ module Runner + def run(plugin, targets) raise NotImplementedError end diff --git a/lib/prune/plugins/dovecot_mailstore.rb b/lib/prune/plugins/dovecot_mailstore.rb index f504d9c..eddd822 100644 --- a/lib/prune/plugins/dovecot_mailstore.rb +++ b/lib/prune/plugins/dovecot_mailstore.rb @@ -1,5 +1,3 @@ -require 'common/filesystem' -require 'common/mailstore' require 'common/dovecot_mailstore_plugin' require 'prune/prune_plugin' require 'rm/plugins/dovecot_mailstore' @@ -12,7 +10,7 @@ class DovecotMailstorePrune < DovecotMailstoreRm def get_leftover_domains(db_domains) # Get the list of domains according to the filesystem. - fs_domains = self.get_domains_from_filesystem() + fs_domains = self.list_domains() # Return the list of domains on the filesystem that aren't in the DB. return (fs_domains - db_domains) @@ -20,8 +18,8 @@ class DovecotMailstorePrune < DovecotMailstoreRm def get_leftover_accounts(db_accounts) # Get the list of accounts according to the filesystem. - fs_domains = self.get_domains_from_filesystem() - fs_accounts = self.get_accounts_from_filesystem(fs_domains) + fs_domains = self.list_domains() + fs_accounts = self.list_domains_users(fs_domains) # And return the accounts on the filesystem that aren't in the DB. return (fs_accounts - db_accounts) diff --git a/lib/rm/plugins/agendav.rb b/lib/rm/plugins/agendav.rb index 6aa6d91..1366bd6 100644 --- a/lib/rm/plugins/agendav.rb +++ b/lib/rm/plugins/agendav.rb @@ -9,10 +9,6 @@ class AgendavRm include RmPlugin - def delete_domain(domain) - # AgenDAV doesn't have a concept of domains. - end - def delete_account(account) # Delete the given username and any records in other tables # belonging to it. @@ -42,14 +38,4 @@ 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 ce5828a..02e8894 100644 --- a/lib/rm/plugins/davical.rb +++ b/lib/rm/plugins/davical.rb @@ -12,11 +12,6 @@ class DavicalRm include RmPlugin - def delete_domain(domain) - # DAViCal doesn't have a concept of domains. - end - - def delete_account(account) # Delete the given username. DAViCal uses foreign keys properly # and only supports postgres, so we let the ON DELETE CASCADE @@ -46,14 +41,4 @@ 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 8ab507d..72c80c8 100644 --- a/lib/rm/plugins/dovecot_mailstore.rb +++ b/lib/rm/plugins/dovecot_mailstore.rb @@ -1,12 +1,10 @@ # Needed for rm_r. require 'fileutils' -require 'common/filesystem' -require 'common/mailstore' require 'common/dovecot_mailstore_plugin' require 'rm/rm_plugin' -class DovecotMailstoreRm < Mailstore +class DovecotMailstoreRm include DovecotMailstorePlugin include RmPlugin @@ -22,54 +20,4 @@ class DovecotMailstoreRm < Mailstore FileUtils.rm_r(account_path) end - def get_leftover_domains(db_domains) - # Get the list of domains according to the filesystem. - fs_domains = self.get_domains_from_filesystem() - - # Return the list of domains on the filesystem that aren't in the DB. - return (fs_domains - db_domains) - end - - def get_leftover_accounts(db_accounts) - # Get the list of accounts according to the filesystem. - fs_domains = self.get_domains_from_filesystem() - fs_accounts = self.get_accounts_from_filesystem(fs_domains) - - # And return the accounts on the filesystem that aren't in the DB. - return (fs_accounts - db_accounts) - end - - - def get_domain_usernames(domain) - return get_accounts_from_filesystem([domain]) - end - - protected; - - def get_domains_from_filesystem() - return Filesystem.get_subdirs(@domain_root) - end - - def get_accounts_from_filesystem(domains) - accounts = [] - - domains.each do |domain| - begin - # Throws a NonexistentDomainError if the domain's path - # doesn't exist on the filesystem. In this case, we want - # to report zero accounts. - domain_path = get_domain_path(domain) - usernames = Filesystem.get_subdirs(domain_path) - - usernames.each do |username| - accounts << "#{username}@#{domain}" - end - rescue NonexistentDomainError - # Party hard. - end - end - - return accounts - end - end diff --git a/lib/rm/plugins/postfixadmin_db.rb b/lib/rm/plugins/postfixadmin_db.rb index cd59928..1928202 100644 --- a/lib/rm/plugins/postfixadmin_db.rb +++ b/lib/rm/plugins/postfixadmin_db.rb @@ -51,12 +51,15 @@ class PostfixadminDbRm def delete_domain(domain) + raise NonexistentDomainError.new(domain) if not domain_exists(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;' + sql_queries << 'DELETE FROM domain WHERE domain = $1;' begin connection = PGconn.connect(@db_host, @@ -81,9 +84,10 @@ class PostfixadminDbRm end + protected; - def get_domain_usernames(domain) - usernames = [] + def domain_exists(domain) + count = 0 # Just assume PostgreSQL for now. begin @@ -95,19 +99,23 @@ class PostfixadminDbRm @db_user, @db_pass) - sql_query = 'SELECT username FROM mailbox WHERE domain = $1;' - + sql_query = 'SELECT COUNT(domain) as count FROM domain WHERE domain = $1;' connection.query(sql_query, [domain]) do |result| - usernames = result.field_values('username') + return false if result.ntuples() < 1 + begin + count = result.getvalue(0,0).to_i() + return false if count.nil? + rescue StandardError + return false + end end - connection.close() rescue PGError => e - # Pretend like we're database-agnostic in case we ever are. + # But pretend like we're database-agnostic in case we ever are. raise DatabaseError.new(e) end - return usernames + return (count > 0) end end diff --git a/lib/rm/plugins/roundcube_db.rb b/lib/rm/plugins/roundcube_db.rb index 252ce32..5687ce4 100644 --- a/lib/rm/plugins/roundcube_db.rb +++ b/lib/rm/plugins/roundcube_db.rb @@ -8,10 +8,6 @@ class RoundcubeDbRm include RoundcubeDbPlugin include RmPlugin - def delete_domain(domain) - # Roundcube doesn't have a concept of domains. - end - def delete_account(account) # Delete the given username and any records in other tables # belonging to it. @@ -50,15 +46,4 @@ class RoundcubeDbRm end - - - def get_domain_usernames(domain) - usernames = get_roundcube_usernames(); - matches = usernames.select do |username| - username =~ /@#{domain}$/ - end - - return matches - end - end diff --git a/lib/rm/rm_plugin.rb b/lib/rm/rm_plugin.rb index cad493d..49c340f 100644 --- a/lib/rm/rm_plugin.rb +++ b/lib/rm/rm_plugin.rb @@ -16,8 +16,16 @@ module RmPlugin end def delete_domain(domain) - # Delete the given domain. - raise NotImplementedError + # Delete the given domain. Some plugins don't have a concept of + # domains, so just delete all users with a username that looks + # like it's in the given domain. + usernames = list_domains_users([domain]) + + raise NonexistentDomainError.new(domain) if usernames.empty? + + usernames.each do |u| + delete_account(u) + end end def delete_account(account) @@ -25,11 +33,4 @@ 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 0392d11..114aabc 100644 --- a/lib/rm/rm_runner.rb +++ b/lib/rm/rm_runner.rb @@ -20,13 +20,6 @@ class RmRunner end else begin - # 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) report(plugin, "Removed domain: #{target} (#{domain_description})") -- 2.44.2