require 'mailshears'
# Since we removed both the executable name and the mode name (if it
-# existed) from ARGV, what remains should be the required
-# arguments.
-if (mode == :prune and (ARGV.length() != 0)) or
- (mode == :rm and (ARGV.length() < 1)) or
- (mode == :mv and (ARGV.length() != 2)) then
- puts "ERROR: missing (or extra) command-line arguments."
+# existed) from ARGV, what remains should be the required arguments.
+# Figure out if we have the wrong number of arguments, and store the
+# associated error message in args_error_message if we do.
+args_error_message = nil
+if mode == :prune and ARGV.length() != 0 then
+ args_error_message = "ERROR: prune mode takes no additional arguments."
+elsif mode == :rm and ARGV.length() < 1 then
+ args_error_message = "ERROR: rm mode takes two or more user arguments."
+elsif mode == :mv and ARGV.length() != 2 then
+ args_error_message = "ERROR: mv mode takes exactly two user arguments."
+end
+
+# If we got the wrong number of arguments, we'll have some error
+# message. Report it and quit.
+if not args_error_message.nil? then
+ STDERR.puts args_error_message
puts "Usage: #{usage}"
Kernel.exit(ExitCodes::BAD_COMMAND_LINE)
end
+
cfg = Configuration.new()
# Load each of the plugins that we'll need.
plugin_module = PrunePlugin
end
-
# Buffer the output so that we can avoid printing the informational
# header when no plugins produce output.
require 'stringio'
output_buffer = StringIO.new()
$stdout = output_buffer
+# Parse the remaining arguments as User/Domain objects. If we get some
+# other argument that isn't one of those, it's an error.
+parsed_args = []
+ARGV.each do |arg|
+ begin
+ u = User.new(arg)
+ parsed_args << u
+ rescue InvalidUserError
+ begin
+ d = Domain.new(arg)
+ parsed_args << d
+ rescue InvalidDomainError
+ STDERR.puts "ERROR: invalid user/domain argument #{arg}"
+ Kernel.exit(ExitCodes::BAD_COMMAND_LINE)
+ end
+ end
+end
+
begin
- plugin_module.run(cfg, *ARGV)
+ plugin_module.run(cfg, *parsed_args)
ensure
# Now restore stdout, and print the header plus whatever the plugins
# produced if they produced anything. If they didn't produce any
-* Error reporting sucks, and when a domain or user doesn't exist we
- should be able to say so. The describe_domain/user functions
- should also work better. For plugins that don't implement domains,
- we can return a (count of?) list of users, or fall back to the
- user deletion descriptions.
-
-* Error reporting is inconsistent. We know what goes wrong, and then
- errors bubble up, but where do they get reported? Are they fatal or
- informational? Do we want later plugins to run of earlier ones
- failed?
-
-* Implement "mv".
-
- Design:
-
- This should only work from a domain that exists in postfixadmin to a
- domain that already exists in postfixadmin. If the target domain
- does not exist, we should error out as soon as possible.
-
- Once we're sure that the target domain does exist, we can hand off
- the "mv" operation to the plugins. Some of them might have nothing
- to do -- that's fine.
-
- MAKE SURE WE DON'T OVERWRITE AN EXISTING USER!
-
-* Add convenience methods (e.g. user_exists, domain_exists) that we
- can use in the tests to check results.
-
-* Potentially add OpenDKIM support.
+* There is essentially no error handling. We report errors, but we
+ don't fail when we see one. The main reason for this is that we
+ don't know when each plugin will be run. If the first plugin
+ encounters an error, we could quit right there. But what if the
+ third one fails after the first two succeed? We would need some kind
+ of rollback mechanism.
+
+ For "mv", a rollback is conceivable. But with "rm", there's no going
+ back. Maybe relying on the user to interpret the output and go
+ fix stuff himself is the best we can do?
+
+* Add OpenDKIM support.
require 'common/plugin'
+require 'common/user'
module AgendavPlugin
# Code that all Agendav plugins (Prune, Rm, Mv...) will
def describe_domain(domain)
- # AgenDAV doesn't have a concept of domains.
- return domain
+ return domain.to_s()
end
-
def describe_user(user)
- if self.user_exists(user)
- return "Username: #{user}"
- else
- return 'User not found'
- end
+ return user.to_s()
end
# Produce a list of AgenDAV users. This is public because it's
# useful in testing.
#
- usernames = []
+ users = []
# Just assume PostgreSQL for now.
begin
sql_query += '(SELECT user_from FROM shared);'
connection.query(sql_query) do |result|
- usernames = result.field_values('username')
+ users = result.field_values('username')
end
connection.close()
raise DatabaseError.new(e)
end
- return usernames
+ return users.map{ |u| User.new(u) }
end
end
require 'common/plugin'
+require 'common/user'
module DavicalPlugin
# Code that all Davical plugins (Prune, Rm, Mv...) will share. That
def describe_domain(domain)
# DAViCal doesn't have a concept of domains.
- return domain
+ return domain.to_s()
end
raise DatabaseError.new(e)
end
- return usernames
+ return usernames.map{ |u| User.new(u) }
end
sql_query += " ON principal.user_no = usr.user_no) "
sql_query += "WHERE usr.username = $1;"
- connection.query(sql_query, [user]) do |result|
+ connection.query(sql_query, [user.to_s()]) do |result|
if result.num_tuples > 0
principal_id = result[0]['principal_id']
end
--- /dev/null
+require 'common/errors'
+
+class Domain
+
+ @domain = nil
+
+ def initialize(domain)
+ if domain.empty? then
+ msg = "domain cannot be empty"
+ raise InvalidDomainError.new(msg)
+ end
+
+ @domain = domain
+ end
+
+ def to_s()
+ return @domain
+ end
+
+ def ==(other)
+ return self.to_s() == other.to_s()
+ end
+end
-require 'common/plugin'
+require 'common/domain'
require 'common/filesystem'
+require 'common/plugin'
+require 'common/user'
+
module DovecotPlugin
# Code that all Dovecot plugins (Prune, Rm, Mv...) will
end
+ def domain_exists(domain)
+ domains = list_domains()
+ return domains.include?(domain)
+ end
+
+
protected;
def get_domain_path(domain)
# Return the filesystem path for the given domain.
# That is, the directory where its mail is stored.
# Only works if the domain directory exists!
- domain_path = File.join(@domain_root, domain)
-
- if File.directory?(domain_path)
- return domain_path
- else
- raise NonexistentDomainError.new(domain)
- end
+ return File.join(@domain_root, domain.to_s())
end
- def get_user_path(user, existence_check = true)
- # Return the filesystem path of this user's mailbox. With
- # existence_check = true, it only works if the user exists!
- # We always require that the domain exists.
- if not user.include?('@')
- msg = "#{user}: Users must contain an '@' symbol."
- raise InvalidUserError.new(msg)
- end
-
- user_parts = user.split('@')
- local_part = user_parts[0]
- domain_part = user_parts[1]
-
- begin
- domain_path = get_domain_path(domain_part)
- rescue NonexistentDomainError
- raise NonexistentUserError.new(user)
- end
-
- user_path = File.join(domain_path, local_part)
-
- return user_path if not existence_check
-
- if File.directory?(user_path)
- return user_path
- else
- raise NonexistentUserError.new(user)
- end
+ def get_user_path(user)
+ # Return the filesystem path of this user's mailbox.
+ domain_path = get_domain_path(user.domain())
+ return File.join(domain_path, user.localpart())
end
def list_domains()
- return Filesystem.get_subdirs(@domain_root)
+ return Filesystem.get_subdirs(@domain_root).map{ |d| Domain.new(d) }
end
def list_domains_users(domains)
usernames = Filesystem.get_subdirs(domain_path)
usernames.each do |username|
- users << "#{username}@#{domain}"
+ users << User.new("#{username}@#{domain}")
end
rescue NonexistentDomainError
# Party hard.
class InvalidUserError < StandardError
end
+# Domain is syntactically invalid.
+class InvalidDomainError < StandardError
+end
+
# Used to indicate that an user does not exist.
class NonexistentUserError < StandardError
end
-
# Used to indicate that a domain does not exist.
class NonexistentDomainError < StandardError
end
+
+
+# When you try to rename a user on top of one that already exists.
+class UserAlreadyExistsError < StandardError
+end
+require 'common/domain'
+require 'common/user'
+
# All plugins should include this module. It defines the basic
# operations that all plugins are supposed to support.
module Plugin
end
def dummy_runner()
- # The RummyRunner associated with this plugin.
+ # The DummyRunner associated with this plugin.
raise NotImplementedError
end
end
end
+ def describe(target)
+ # A generic version of describe_user/describe_domain that
+ # dispatches base on the class of the target.
+ if target.is_a?(User)
+ return describe_user(target)
+ elsif target.is_a?(Domain)
+ return describe_domain(target)
+ else
+ raise NotImplementedError
+ end
+ end
+
def describe_domain(domain)
# Provide a "description" of the domain. This is output along
# with the domain name and can be anything of use to the system
raise NotImplementedError
end
- def user_exists(username)
+ def user_exists(user)
# Does the given username exist for this plugin? We use a naive
# implementation here based on list_users() which is required to
# exist above. Plugins can override this with something fast.
users = list_users()
- return users.include?(username)
+ return users.include?(user)
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
+ # Get all users belonging to the given domains. If a user has
+ # domainpart "example.com" then 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();
+ users = list_users();
domains.each do |d|
- matches = usernames.select do |username|
- username =~ /@#{d}$/
+ matches = users.select do |user|
+ user.domainpart() == d.to_s()
end
domains_users += matches
+require 'common/domain'
require 'common/plugin'
+require 'common/user'
require 'pg'
module PostfixadminPlugin
def describe_user(user)
# There's no other unique identifier in PostfixAdmin
- return user
+ return user.to_s()
end
def describe_domain(domain)
# There's no other unique identifier in PostfixAdmin
- return domain
+ return domain.to_s()
end
raise DatabaseError.new(e)
end
- return domains
+ return domains.map{ |d| Domain.new(d) }
end
raise DatabaseError.new(e)
end
- return users
+ return users.map{ |u| User.new(u) }
end
sql_query = 'SELECT username FROM mailbox WHERE domain IN $1;'
- connection.query(sql_query, [domains]) do |result|
+ connection.query(sql_query, domains.map{|d| d.to_s()}) do |result|
usernames = result.field_values('username')
end
raise DatabaseError.new(e)
end
- return usernames
+ return usernames.map{ |u| User.new(u) }
end
require 'common/plugin'
+require 'common/user'
module RoundcubePlugin
# Code that all Roundcube plugins (Prune, Rm, Mv...) will share.
def describe_domain(domain)
# Roundcube doesn't have a concept of domains.
- return domain
+ return domain.to_s()
end
def describe_user(user)
raise DatabaseError.new(e)
end
- return usernames
+ return usernames.map{ |u| User.new(u) }
end
protected;
sql_query = "SELECT user_id FROM users WHERE username = $1;"
- connection.query(sql_query, [user]) do |result|
+ connection.query(sql_query, [user.to_s()]) do |result|
if result.num_tuples > 0
user_id = result[0]['user_id']
end
--- /dev/null
+require 'common/domain'
+require 'common/errors'
+
+class User
+ # A class representing a syntactically valid user; that is, an email
+ # address. Once constructed, you can be sure that it's valid.
+
+ @localpart = nil
+ @domain = nil
+
+ def domain()
+ return @domain
+ end
+
+ def domainpart()
+ return @domain.to_s()
+ end
+
+ def initialize(username)
+ # Initialize this user object, but throw an error if either the
+ # localpart or domainpart are invalid. The one argument is an
+ # email address string.
+ if not username.is_a?(String)
+ msg = 'username must be a String '
+ msg += "but a #{username.class.to_s()} was given"
+ raise InvalidUserError.new(msg)
+ end
+
+ parts = username.split('@')
+
+ if parts.length() < 2 then
+ msg = "the username #{username} does not contain an '@' symbol"
+ raise InvalidUserError.new(msg)
+ end
+
+ localpart = parts[0]
+
+ if localpart.length() > 64 then
+ msg = "the local part of #{username} cannot have more than 64 characters"
+ raise InvalidUserError(msg)
+ end
+
+ if localpart.empty? then
+ msg = "the local part of #{username} cannot be empty"
+ raise InvalidUserError.new(msg)
+ end
+
+ @localpart = localpart
+ @domain = Domain.new(parts[1])
+ end
+
+ def localpart()
+ return @localpart
+ end
+
+ def to_s()
+ return @localpart + '@' + @domain.to_s()
+ end
+
+ def ==(other)
+ return self.to_s() == other.to_s()
+ end
+end
include Runner
def run(plugin, src, dst)
- if src.include?('@') then
- puts "Would move user: #{src} to #{dst}"
- else
- raise NotImplementedError.new('Only users can be moved.')
+
+ if src.is_a?(Domain) or dst.is_a?(Domain) then
+ msg = 'only users can be moved'
+ raise NotImplementedError.new(msg)
end
+
+ puts "Would move user #{src.to_s()} to #{dst.to_s()}."
end
end
return MvDummyRunner
end
- def mv_user(from, to)
+ def mv_user(src, dst)
# Rename the given user.
raise NotImplementedError
end
+require 'common/domain'
require 'common/errors'
require 'common/runner'
include Runner
def run(plugin, src, dst)
- # Why think too hard? An user has an @, a domain doesn't.
- if not src.include?('@') then
- # We only support moving users right now, and the destination
- # domain must already exist.
- raise NotImplementedError.new('Only users can be moved.')
- end
- # Handle this once so we don't have to do it in every plugin.
- if not dst.include?('@') then
- msg = "the destination user #{dst} is not valid"
- raise InvalidUserError.new(msg)
+ if src.is_a?(Domain) or dst.is_a?(Domain) then
+ msg = 'only users can be moved'
+ raise NotImplementedError.new(msg)
end
begin
- src_description = plugin.describe_user(src)
+ src_description = plugin.describe(src)
plugin.mv_user(src, dst)
- msg = "Moved user #{src} (#{src_description}) "
- msg += "to #{dst}."
+ dst_description = plugin.describe(dst)
+
+ msg = "Moved user #{src} "
+
+ # Only append the extra description if it's useful.
+ if not src_description.nil? and
+ not src_description.empty? and
+ not src_description == src.to_s() then
+ msg += "(#{src_description}) "
+ end
+
+ msg += "to #{dst}"
+
+ # Only append the extra description if it's useful.
+ if not dst_description.nil? and
+ not dst_description.empty? and
+ not dst_description == dst.to_s() then
+ msg += " (#{dst_description})"
+ end
+
+ msg += "."
report(plugin, msg)
- rescue InvalidUserError => e
- report(plugin, e.message)
+
rescue NonexistentUserError => e
- report(plugin, e.message)
+ # This means that the SOURCE user didn't exist, since a
+ # nonexistent destination user is perfectly expected.
+ report(plugin, "Source user #{src.to_s()} not found.")
rescue NonexistentDomainError => e
- report(plugin, e.message)
- rescue StandardError => e
- msg = "There was an error (#{e.to_s}) moving the user: #{e.message}"
- report(plugin, msg)
- Kernel.exit(ExitCodes::DATABASE_ERROR)
+ # This could mean that the source domain doesn't exist, but in
+ # that case, we just report that the source user doesn't
+ # exist. So a nonexistent domain refers to a nonexistent
+ # DESTINATION domain.
+ report(plugin, "Destination domain #{dst.domainpart()} not found.")
+ rescue UserAlreadyExistsError => e
+ report(plugin, "Destination user #{dst.to_s()} already exists.")
end
end
include AgendavPlugin
include MvPlugin
+ def mv_user(src, dst)
+ # It's obviously an error if the source user does not exist. It
+ # would also be an error if the destination domain didn't exist;
+ # however, Agendav doesn't know about domains, so we let that slide.
+ raise NonexistentUserError.new(src.to_s()) if not user_exists(src)
- def mv_domain(from, to)
- # AgenDAV doesn't have a concept of domains.
- end
+ # And it's an error if the destination user exists already.
+ raise UserAlreadyExistsError.new(dst.to_s()) if user_exists(dst)
- def mv_user(from, to)
sql_queries = ['UPDATE prefs SET username = $1 WHERE username = $2;']
sql_queries << 'UPDATE shared SET user_from = $1 WHERE user_from = $2;'
sql_queries << 'UPDATE shared SET user_which = $1 WHERE user_which = $2;'
@db_pass)
sql_queries.each do |sql_query|
- connection.query(sql_query, [to, from])
+ connection.query(sql_query, [dst.to_s(), src.to_s()])
end
connection.close()
include MvPlugin
- def mv_domain(from, to)
- # DAViCal doesn't have a concept of domains.
- end
-
-
- def mv_user(from, to)
+ def mv_user(src, dst)
# Switch the given usernames. DAViCal uses foreign keys properly
# and only supports postgres, so we let the ON UPDATE CASCADE
# trigger handle most of the work.
+
+ # It's obviously an error if the source user does not exist. It
+ # would also be an error if the destination domain didn't exist;
+ # however, DAViCal doesn't know about domains, so we let that slide.
+ raise NonexistentUserError.new(src.to_s()) if not user_exists(src)
+
+ # And it's an error if the destination user exists already.
+ raise UserAlreadyExistsError.new(dst.to_s()) if user_exists(dst)
+
sql_queries = ['UPDATE usr SET username = $1 WHERE username = $2']
begin
@db_pass)
sql_queries.each do |sql_query|
- connection.query(sql_query, [to, from])
+ connection.query(sql_query, [dst.to_s(), src.to_s()])
end
connection.close()
include DovecotPlugin
include MvPlugin
- def mv_user(user_from, user_to)
- # Move the user's mail directory from one domain (that should
- # exist) to another domain (that should exist).
+ def mv_user(src, dst)
+ # It's obviously an error if the source user does not exist.
+ raise NonexistentUserError.new(src.to_s()) if not user_exists(src)
+
+ # And it's an error if the destination user exists already.
+ raise UserAlreadyExistsError.new(dst.to_s()) if user_exists(dst)
+
+ # But is it an error if the target domain does not exist? That's a
+ # bit subtle... The domain may exist in the database, but if it
+ # has not received any mail yet, then its directory won't exist
+ # on-disk.
+ #
+ # There are two possible "oops" scenarios resulting from the fact
+ # that we may run either the Postfixadmin move first or the
+ # Dovecot move first. If we move the user in the database, we
+ # definitely want to move him on disk (that is, we should create
+ # the directory here). But if we move him on disk first, then we
+ # don't know if the database move will fail! We don't want to move
+ # his mail files if he won't get moved in the database.
#
- # This is actually a little subtle, because none of the
- # directories in question have to exist. If I create two new
- # domains in postfixadmin and one mailbox but never deliver any
- # mail, it should be possible to move the mailbox to the other
- # domain. No directories will exist until mail is delivered,
- # though.
-
- # get_user_path() will fail if there's no mailbox to move. It will
- # also fail on an invalid user -- oh well!
- begin
- from_path = self.get_user_path(user_from)
- rescue NonexistentUserError
- # Do nothing, there's no source mailbox...
- return
+ # Faced with two equally-bad (but easy-to-fix) options, we do the
+ # simplest thing and fail if the destination domain directory
+ # doesn't exist. If nothing else, this is at least consistent.
+ if not self.domain_exists(dst.domain()) then
+ raise NonexistentDomainError.new(dst.domainpart())
end
# We may need to create the target domain directory, even if the
# domain exists in the database.
- domain_to = user_to.split('@')[1]
- domain_dir = self.get_domain_path(domain_to)
- FileUtils.mkdir_p(domain_dir)
-
- # The from_path exists or else we would have returned earlier. The
- # parent of to_path exists because we just created it.
- to_path = self.get_user_path(user_to, false)
- FileUtils.mv(from_path, to_path)
+ FileUtils.mkdir_p(self.get_domain_path(dst.domain()))
+
+ # The parent of dst_path exists because we just created it.The
+ # source path should exist too, because the "source user" does,
+ # and, well, how did we determine that?
+ src_path = self.get_user_path(src)
+ dst_path = self.get_user_path(dst)
+ FileUtils.mv(src_path, dst_path)
end
end
include PostfixadminPlugin
include MvPlugin
- def mv_user(user_from, user_to)
- if not user_exists(user_from)
- raise NonexistentUserError.new("source user #{user_from} does not exist")
- end
-
- user_to_parts = user_to.split('@')
- localpart_to = user_to_parts[0]
- domain_to = user_to_parts[1]
+ def mv_user(src, dst)
+ # It's obviously an error if the source user does not exist.
+ raise NonexistentUserError.new(src.to_s()) if not user_exists(src)
- if not domain_exists(domain_to)
- msg = "destination domain #{domain_to} does not exist"
- raise NonexistentDomainError.new(msg)
+ # And if the destination domain does not exist.
+ if not domain_exists(dst.domain())
+ raise NonexistentDomainError.new(dst.domain.to_s())
end
+ # But the destination *user* should *not* exist.
+ # And it's an error if the destination user exists already.
+ raise UserAlreadyExistsError.new(dst.to_s()) if user_exists(dst)
+
mailbox_query = 'UPDATE mailbox SET '
mailbox_query += ' username=$1,'
mailbox_query += ' domain=$2,'
sql_queries.each do |sql_query|
varchar = 1043 # from pg_type.h
- params = [{:value => user_to, :type => varchar},
- {:value => domain_to, :type => varchar},
- {:value => localpart_to, :type => varchar},
- {:value => user_from, :type => varchar}]
+ params = [{:value => dst.to_s(), :type => varchar},
+ {:value => dst.domainpart(), :type => varchar},
+ {:value => dst.localpart(), :type => varchar},
+ {:value => src.to_s(), :type => varchar}]
connection.query(sql_query, params)
end
include MvPlugin
- def mv_domain(from, to)
- # Roundcube doesn't have a concept of domains.
- end
+ def mv_user(src, dst)
+ # It's obviously an error if the source user does not exist. It
+ # would also be an error if the destination domain didn't exist;
+ # however, Roundcube doesn't know about domains, so we let that slide.
+ raise NonexistentUserError.new(src.to_s()) if not user_exists(src)
+
+ # And it's an error if the destination user exists already.
+ raise UserAlreadyExistsError.new(dst.to_s()) if user_exists(dst)
- def mv_user(from, to)
sql_queries = ['UPDATE users SET username = $1 WHERE username = $2;']
begin
@db_pass)
sql_queries.each do |sql_query|
- connection.query(sql_query, [to, from])
+ connection.query(sql_query, [dst.to_s(), src.to_s()])
end
connection.close()
require 'common/runner'
-
-# This is always needed, regardless of which plugin is running.
require 'prune/plugins/postfixadmin'
+require 'rm/rm_dummy_runner'
class PruneDummyRunner
include Runner
db_users = pfa.list_users()
db_domains = pfa.list_domains()
- leftover_users = plugin.get_leftover_users(db_users)
- leftover_domains = plugin.get_leftover_domains(db_domains)
-
- leftover_users.each do |user|
- user_description = plugin.describe_user(user)
- report(plugin, "Would remove user: #{user} (#{user_description})")
- end
+ leftovers = plugin.get_leftover_users(db_users)
+ leftovers += plugin.get_leftover_domains(db_domains)
- leftover_domains.each do |domain|
- domain_description = plugin.describe_domain(domain)
- report(plugin, "Would remove domain: #{domain} (#{domain_description})")
- end
+ rm_dummy_runner = RmDummyRunner.new()
+ rm_dummy_runner.run(plugin, *leftovers)
end
end
require 'common/runner'
-
-# This is always needed, regardless of which plugin is running.
require 'prune/plugins/postfixadmin'
+require 'rm/rm_runner'
class PruneRunner
include Runner
db_users = pfa.list_users()
db_domains = pfa.list_domains()
- leftover_users = plugin.get_leftover_users(db_users)
- leftover_domains = plugin.get_leftover_domains(db_domains)
-
- leftover_users.each do |user|
- user_description = plugin.describe_user(user)
- plugin.delete_user(user)
- report(plugin, "Removed user: #{user} (#{user_description})")
- end
-
- leftover_domains.each do |domain|
- domain_description = plugin.describe_domain(domain)
- plugin.delete_domain(domain)
- report(plugin, "Removed domain: #{domain} (#{domain_description})")
- end
+ leftovers = plugin.get_leftover_users(db_users)
+ leftovers += plugin.get_leftover_domains(db_domains)
+ rm_runner = RmRunner.new()
+ rm_runner.run(plugin, *leftovers)
end
end
def delete_user(user)
# Delete the given username and any records in other tables
# belonging to it.
- raise NonexistentUserError.new(user) if not user_exists(user)
+ raise NonexistentUserError.new(user.to_s()) if not user_exists(user)
sql_queries = ['DELETE FROM prefs WHERE username = $1;']
sql_queries << 'DELETE FROM shared WHERE user_from = $1;'
@db_pass)
sql_queries.each do |sql_query|
- connection.query(sql_query, [user])
+ connection.query(sql_query, [user.to_s()])
end
connection.close()
# Delete the given username. DAViCal uses foreign keys properly
# and only supports postgres, so we let the ON DELETE CASCADE
# trigger handle most of the work.
- raise NonexistentUserError.new(user) if not user_exists(user)
+ raise NonexistentUserError.new(user.to_s()) if not user_exists(user)
sql_queries = ['DELETE FROM usr WHERE username = $1']
@db_pass)
sql_queries.each do |sql_query|
- connection.query(sql_query, [user])
+ connection.query(sql_query, [user.to_s()])
end
connection.close()
def delete_domain(domain)
- # Will raise an exception if the path doesn't exist.
domain_path = self.get_domain_path(domain)
+
+ if not File.directory?(domain_path)
+ raise NonexistentDomainError.new(domain.to_s())
+ end
+
FileUtils.rm_r(domain_path)
end
def delete_user(user)
- # Will raise an exception if the path doesn't exist.
user_path = self.get_user_path(user)
+
+ if not File.directory?(user_path)
+ raise NonexistentUserError.new(user.to_s())
+ end
+
FileUtils.rm_r(user_path)
end
def delete_user(user)
- raise NonexistentUserError.new(user) if not user_exists(user)
+ raise NonexistentUserError.new(user.to_s()) if not user_exists(user)
sql_queries = ['DELETE FROM alias WHERE address = $1;']
# Wipe out any aliases pointed at our user.
@db_pass)
sql_queries.each do |sql_query|
- connection.query(sql_query, [user])
+ connection.query(sql_query, [user.to_s()])
end
# The earlier alias update query will leave things like
def delete_domain(domain)
- raise NonexistentDomainError.new(domain) if not domain_exists(domain)
+ raise NonexistentDomainError.new(domain.to_s()) if not domain_exists(domain)
sql_queries = ['DELETE FROM domain_admins WHERE domain = $1;']
sql_queries << 'DELETE FROM alias WHERE domain = $1;'
@db_pass)
sql_queries.each do |sql_query|
- connection.query(sql_query, [domain])
+ connection.query(sql_query, [domain.to_s()])
end
connection.close()
@db_pass)
sql_query = 'SELECT COUNT(domain) as count FROM domain WHERE domain = $1;'
- connection.query(sql_query, [domain]) do |result|
+ connection.query(sql_query, [domain.to_s()]) do |result|
return false if result.ntuples() < 1
begin
count = result.getvalue(0,0).to_i()
def delete_user(user)
# Delete the given username and any records in other tables
# belonging to it.
- raise NonexistentUserError.new(user) if not user_exists(user)
+ raise NonexistentUserError.new(user.to_s()) if not user_exists(user)
user_id = self.get_user_id(user)
def run(plugin, *targets)
targets.each do |target|
- if target.include?('@') then
- user_description = plugin.describe_user(target)
- report(plugin, "Would remove user: #{target} (#{user_description})")
- else
- domain_description = plugin.describe_domain(target)
- report(plugin, "Would remove domain: #{target} (#{domain_description})")
+ target_description = plugin.describe(target)
+ msg = "Would remove #{target.class.to_s().downcase()} #{target}"
+
+ # Only append the extra description if it's useful.
+ if not target_description.nil? and
+ not target_description.empty? and
+ not target_description == target.to_s() then
+ msg += " (#{target_description})"
end
+ msg += '.'
+
+ report(plugin, msg)
end
end
return RmDummyRunner
end
+ def delete(target)
+ # A generic version of delete_user/delete_domain that
+ # dispatches base on the class of the target.
+ if target.is_a?(User)
+ return delete_user(target)
+ elsif target.is_a?(Domain)
+ return delete_domain(target)
+ else
+ raise NotImplementedError
+ end
+ end
+
def delete_domain(domain)
# 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])
+ users = list_domains_users([domain])
- raise NonexistentDomainError.new(domain) if usernames.empty?
+ raise NonexistentDomainError.new(domain.to_s()) if users.empty?
- usernames.each do |u|
+ users.each do |u|
delete_user(u)
end
end
def run(plugin, *targets)
targets.each do |target|
- # Why think too hard? An user has an @, a domain doesn't.
- if target.include?('@') then
- begin
- user_description = plugin.describe_user(target)
- plugin.delete_user(target)
- report(plugin, "Removed user: #{target} (#{user_description})")
+ remove_target(plugin, target)
+ end
+ end
+
+ private;
+
+ def remove_target(plugin, target)
+ # Wrap the "remove this thing" operation so that it can be reused
+ # in the prine plugin.
+ target_description = plugin.describe(target)
- rescue NonexistentUserError => e
- report(plugin, "User not found: #{e.to_s}")
- rescue StandardError => e
- report(plugin, "There was an error removing the user: #{e.to_s}")
- Kernel.exit(ExitCodes::DATABASE_ERROR)
- end
- else
- begin
- domain_description = plugin.describe_domain(target)
- plugin.delete_domain(target)
- report(plugin, "Removed domain: #{target} (#{domain_description})")
+ begin
+ plugin.delete(target)
+ msg = "Removed #{target.class.to_s().downcase()} #{target}"
- rescue NonexistentUserError => e
- # Can happen in the usernames.each... block.
- report(plugin, "User not found: #{e.to_s}")
- rescue NonexistentDomainError => e
- report(plugin, "Domain not found: #{e.to_s}")
- rescue StandardError => e
- report(plugin, "There was an error removing the domain: #{e.to_s}")
- Kernel.exit(ExitCodes::DATABASE_ERROR)
- end
+ # Only append the extra description if it's useful.
+ if not target_description.nil? and
+ not target_description.empty? and
+ not target_description == target.to_s() then
+ msg += " (#{target_description})"
end
- end
+ msg += '.'
+ report(plugin, msg)
+ rescue NonexistentDomainError, NonexistentUserError => e
+ report(plugin, "#{target.class.to_s()} #{e.to_s} not found.")
+ end
end
-
end