+require 'common/domain'
require 'common/plugin'
+require 'common/user'
require 'pg'
+# Code that all Postfixadmin plugins ({PostfixadminPrune},
+# {PostfixadminRm}, {PostfixadminMv}) share.
+#
module PostfixadminPlugin
- # Code that all Postfixadmin plugins (Prune, Rm, Mv...) will
- # share. That is, we implement the Plugin interface.
- include Plugin
-
- def initialize()
-
- cfg = Configuration.new()
- @db_host = cfg.postfixadmin_dbhost
- @db_port = cfg.postfixadmin_dbport
- @db_opts = cfg.postfixadmin_dbopts
- @db_tty = cfg.postfixadmin_dbtty
- @db_name = cfg.postfixadmin_dbname
- @db_user = cfg.postfixadmin_dbuser
- @db_pass = cfg.postfixadmin_dbpass
- end
-
-
- def describe_account(account)
- # There's no other unique identifier in PostfixAdmin
- return account
- end
+ # We implement the Plugin "interface."
+ include Plugin
- def describe_domain(domain)
- # There's no other unique identifier in PostfixAdmin
- return domain
+ # Initialize this Postfixadmin {Plugin} with values in *cfg*.
+ #
+ # @param cfg [Configuration] the configuration for this plugin.
+ #
+ def initialize(cfg)
+ @db_hash = {
+ :host => cfg.postfixadmin_dbhost,
+ :port => cfg.postfixadmin_dbport,
+ :options => cfg.postfixadmin_dbopts,
+ :tty => cfg.postfixadmin_dbtty,
+ :dbname => cfg.postfixadmin_dbname,
+ :user => cfg.postfixadmin_dbuser,
+ :password => cfg.postfixadmin_dbpass }
end
+ # Obtain a list of domains from Postfixadmin. This is more efficient
+ # than the {Plugin} default implementation because domains have
+ # their own table in the database and we can easily select them
+ # rather than filtering the list of users.
+ #
+ # @return [Array<Domain>] a list of the domains in Postfixadmin.
+ #
def list_domains()
domains = []
- # Just assume PostgreSQL for now.
+ connection = PG::Connection.new(@db_hash)
+
+ # 'ALL' is a magic domain, and we don't want it.
+ sql_query = "SELECT domain FROM domain WHERE domain <> 'ALL';"
+
begin
- connection = PGconn.connect(@db_host,
- @db_port,
- @db_opts,
- @db_tty,
- @db_name,
- @db_user,
- @db_pass)
-
- # 'ALL' is a magic domain, and we don't want it.
- sql_query = "SELECT domain FROM domain WHERE domain <> 'ALL';"
connection.query(sql_query) do |result|
domains = result.field_values('domain')
end
+ ensure
+ # Make sure the connection gets closed even if the query explodes.
connection.close()
- rescue PGError => e
- # But pretend like we're database-agnostic in case we ever are.
- raise DatabaseError.new(e)
end
- return domains
+ return domains.map{ |d| Domain.new(d) }
end
+ # Return a list of Postfixadmin users.
+ #
+ # @return [Array<User>] a list of users contained in the
+ # Postfixadmin database.
+ #
def list_users()
- accounts = []
+ users = []
+
+ connection = PG::Connection.new(@db_hash)
+
+ sql_query = 'SELECT username FROM mailbox;'
- # Just assume PostgreSQL for now.
begin
- connection = PGconn.connect(@db_host,
- @db_port,
- @db_opts,
- @db_tty,
- @db_name,
- @db_user,
- @db_pass)
-
- # If address = goto, then the alias basically says, "really
- # deliver to that address; it's not an alias."
- sql_query = 'SELECT username FROM mailbox;'
connection.query(sql_query) do |result|
- accounts = result.field_values('username')
+ users = result.field_values('username')
end
+ ensure
+ # Make sure the connection gets closed even if the query explodes.
connection.close()
- rescue PGError => e
- # But pretend like we're database-agnostic in case we ever are.
- raise DatabaseError.new(e)
end
- return accounts
+ return users.map{ |u| User.new(u) }
end
+
+ # Efficiently list all Postfixadmin users belonging to the given
+ # Postfixadmin *domains*.
+ #
+ # @param domains [Array<Domain>] the domains whose users we want.
+ #
+ # @return [Array<User>] a list of {User} objects belonging to
+ # *domains* for this plugin.
+ #
def list_domains_users(domains)
usernames = []
+ return usernames if domains.length() == 0
- # Just assume PostgreSQL for now.
- begin
- connection = PGconn.connect(@db_host,
- @db_port,
- @db_opts,
- @db_tty,
- @db_name,
- @db_user,
- @db_pass)
+ connection = PG::Connection.new(@db_hash)
- sql_query = 'SELECT username FROM mailbox WHERE domain IN $1;'
+ # The number of parameters that we'll pass into our prepared query
+ # is the number of domains that we're given. It's important that
+ # we have at least one domain here.
+ params = 1.upto(domains.length()).map{ |i| '$' + i.to_s() }.join(',')
+ sql_query = "SELECT username FROM mailbox WHERE domain IN (#{params});"
- connection.query(sql_query, [domains]) do |result|
+ begin
+ # Now replace each Domain with its string representation and pass
+ # those in as our individual parameters.
+ connection.query(sql_query, domains.map{ |d| d.to_s() }) do |result|
usernames = result.field_values('username')
end
+ ensure
+ # Make sure the connection gets closed even if the query explodes.
+ connection.close()
+ end
+
+ return usernames.map{ |u| User.new(u) }
+ end
+
+ # Get a list of all Postfixadmin aliases as a <tt>from => to</tt>
+ # hash. This is useful for testing, since aliases should be removed
+ # when either the "from user" or "to user" are removed.
+ #
+ # @return [Hash] all aliases known to Postfixadmin in the form of a
+ # <tt>from => to</tt> hash.
+ #
+ def list_aliases()
+ aliases = []
+
+ connection = PG::Connection.new(@db_hash)
+
+ sql_query = 'SELECT address,goto FROM alias;'
+
+ begin
+ results = connection.query(sql_query)
+ results.each do |row|
+ # row should be a hash
+ aliases << row
+ end
+ ensure
+ # Make sure the connection gets closed even if the query explodes.
+ connection.close()
+ end
+
+ return aliases
+ end
+
+
+ # A fast implementation of the "does this domain exist?"
+ # operation. It only queries the database for the existence of
+ # *domain* rather than a list of all domains (which is the default
+ # implementation).
+ #
+ # @param domain [Domain] the domain whose existence is in question.
+ #
+ # @return [Boolean] true if *domain* exists in the Postfixadmin
+ # database and false otherwise.
+ #
+ def domain_exists(domain)
+ count = 0
+
+ connection = PG::Connection.new(@db_hash)
+
+ sql_query = 'SELECT COUNT(domain) as count FROM domain WHERE domain = $1;'
+
+ begin
+ connection.query(sql_query, [domain.to_s()]) do |result|
+ return false if result.ntuples() < 1
+ count = result.getvalue(0,0).to_i()
+
+ return false if count.nil?
+ end
+ ensure
+ # Make sure the connection gets closed even if the query explodes.
connection.close()
- rescue PGError => e
- # Pretend like we're database-agnostic in case we ever are.
- raise DatabaseError.new(e)
end
- return usernames
+ return (count > 0)
end
end