-* 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.
def describe_domain(domain)
# AgenDAV doesn't have a concept of domains.
- return 'N/A'
+ return domain
end
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.
def describe_domain(domain)
# DAViCal doesn't have a concept of domains.
- return 'N/A'
+ return domain
end
end
- def get_davical_usernames()
+ def list_users()
usernames = []
begin
require 'common/plugin'
+require 'common/filesystem'
module DovecotMailstorePlugin
# Code that all DovecotMailstore plugins (Prune, Rm, Mv...) will
# 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('@')
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
+++ /dev/null
-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
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
end
- def get_domains_from_db()
+ def list_domains()
domains = []
# Just assume PostgreSQL for now.
end
- def get_accounts_from_db()
+ def list_users()
accounts = []
# Just assume PostgreSQL for now.
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
def describe_domain(domain)
# Roundcube doesn't have a concept of domains.
- return 'N/A'
+ return domain
end
def describe_account(account)
end
- # Uses in both prune/rm.
- def get_roundcube_usernames()
+ # Used in both prune/rm.
+ def list_users()
usernames = []
# Just assume PostgreSQL for now.
module Runner
+
def run(plugin, targets)
raise NotImplementedError
end
-require 'common/filesystem'
-require 'common/mailstore'
require 'common/dovecot_mailstore_plugin'
require 'prune/prune_plugin'
require 'rm/plugins/dovecot_mailstore'
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)
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)
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.
end
-
- def get_domain_usernames(domain)
- usernames = get_agendav_usernames();
- matches = usernames.select do |username|
- username =~ /@#{domain}$/
- end
-
- return matches
- end
-
end
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
end
-
- def get_domain_usernames(domain)
- usernames = get_davical_usernames();
- matches = usernames.select do |username|
- username =~ /@#{domain}$/
- end
-
- return matches
- end
-
end
# 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
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
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,
end
+ protected;
- def get_domain_usernames(domain)
- usernames = []
+ def domain_exists(domain)
+ count = 0
# Just assume PostgreSQL for now.
begin
@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
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.
end
-
-
- def get_domain_usernames(domain)
- usernames = get_roundcube_usernames();
- matches = usernames.select do |username|
- username =~ /@#{domain}$/
- end
-
- return matches
- end
-
end
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)
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
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})")