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"
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()
Kernel.exit(0)
-pgadb = PostfixadminDb.new(cfg.dbhost,
- cfg.dbport,
- cfg.dbopts,
- cfg.dbtty,
- cfg.dbname,
- cfg.dbuser,
- cfg.dbpass)
-
-
begin
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
if File.directory?(account_path)
return account_path
else
- raise NonexistentAccountError(account)
+ raise NonexistentAccountError.new(account)
end
end
+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
return accounts
end
+
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
def run(plugin, targets)
raise NotImplementedError
end
+
+ def report(plugin, msg)
+ print "#{plugin.class.to_s()} - "
+ puts msg
+ end
+
end
require 'common/configuration'
require 'common/errors'
require 'common/exit_codes'
-require 'common/postfixadmin_db'
-require 'common/runner'
+
+require 'common/runner'
+
class MvDummyRunner
include Runner
+require 'common/runner'
+
class MvRunner
include Runner
+require 'common/runner'
+
class PruneDummyRunner
include Runner
+require 'common/runner'
+
class PruneRunner
include Runner
end
+
+ def get_domain_usernames(domain)
+ usernames = get_agendav_usernames();
+ matches = usernames.select do |username|
+ username =~ /@#{domain}$/
+ end
+
+ return matches
+ end
+
end
end
+
+
+ def get_domain_usernames(domain)
+ usernames = get_davical_usernames();
+ matches = usernames.select do |username|
+ username =~ /@#{domain}$/
+ end
+
+ return matches
+ end
+
end
end
+ def get_domain_usernames(domain)
+ return get_accounts_from_filesystem([domain])
+ end
+
protected;
def get_domains_from_filesystem()
return accounts
end
-
end
--- /dev/null
+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
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
+require 'common/runner'
+
class RmDummyRunner
include Runner
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
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
+require 'common/runner'
+
class RmRunner
include Runner
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