From: Michael Orlitzky Date: Sat, 20 Apr 2024 23:47:09 +0000 (-0400) Subject: mailshears.gemspec: bump version to 0.1.0 X-Git-Tag: 0.1.0^0 X-Git-Url: http://gitweb.michael.orlitzky.com/?p=mailshears.git;a=commitdiff_plain;h=HEAD;hp=adaab0ea0fb8ae690ac7c7d5a69a079467252767 mailshears.gemspec: bump version to 0.1.0 (Since I changed the config file location.) --- diff --git a/Rakefile b/Rakefile index 72eff7b..0dc4d9e 100644 --- a/Rakefile +++ b/Rakefile @@ -20,7 +20,7 @@ end desc 'Install the gem locally (user install)' task :install => :build do - sh 'gem install --user-install *.gem' + sh 'gem install mailshears-*.gem' end Rake::TestTask.new do |t| diff --git a/bin/mailshears b/bin/mailshears index d9d1625..1793112 100755 --- a/bin/mailshears +++ b/bin/mailshears @@ -13,6 +13,14 @@ program_name = File.basename($PROGRAM_NAME) mode_name = 'prune' mode = :prune +# Before doing anything else, check for "-h" and "--help" in the args, +# because those should cause us to dump usage info and bail out. +if ARGV.include?('-h') or ARGV.include?('--help') then + puts "Usage: #{UserInterface.usage(program_name)}" + Kernel.exit(ExitCodes::SUCCESS) +end + + # If a mode was supplied, it should be in ARGV[0]. if ARGV.length() > 0 mode_names = ['prune', 'rm', 'mv'] diff --git a/doc/TODO b/doc/TODO index 240adab..d806357 100644 --- a/doc/TODO +++ b/doc/TODO @@ -15,26 +15,11 @@ * Implement moving of domains. -* The AgenDAV "user exists" test is wonky, because there's no real - users in AgenDAV. Right now we check the "username" column in the - "prefs" table, but all of the shares (and principals?) have URLs - instead of usernames. We don't parse the URLs, and instead rely - on doing find/replace of substrings in e.g. AgendavMv. - - In particular, this means that AgenDAV pruning does not work! If - a user with default preferences is deleted, we don't notice. - -* mailshears --help crashes before doing what it should just do: - - $ mailshears --help - ERROR: prune mode takes no additional arguments. - * The "pretend mode" output is missing the destination information: # mailshears mv "test1@example.com" "test2@example.com" mailshears, 2020-01-30 14:40:09 -0500 (Plugin: MvPlugin) -------------------------------------------------------- - AgendavMv - Would move user test1@example.com (User not found) to . DavicalMv - Would move user test1@example.com (User not found) to . DovecotMv - Would move user test1@example.com (/var/spool/mail/vhosts/example.com/test1) to . PostfixadminMv - Would move user test1@example.com to . diff --git a/doc/mailshears.example.conf.yml b/doc/mailshears.example.conf.yml index 0764153..5c304ec 100644 --- a/doc/mailshears.example.conf.yml +++ b/doc/mailshears.example.conf.yml @@ -1,18 +1,9 @@ i_mean_business: false -plugins: [agendav, davical, dovecot, postfixadmin, roundcube] - -agendav_dbhost: localhost -agendav_dbport: 5432 -agendav_dbopts: -agendav_dbtty: -agendav_dbuser: postgres -agendav_dbpass: -agendav_dbname: agendav +plugins: [davical, dovecot, postfixadmin, roundcube] davical_dbhost: localhost davical_dbport: 5432 davical_dbopts: -davical_dbtty: davical_dbuser: postgres davical_dbpass: davical_dbname: davical @@ -22,7 +13,6 @@ dovecot_mail_root: /var/spool/mail/vhosts postfixadmin_dbhost: localhost postfixadmin_dbport: 5432 postfixadmin_dbopts: -postfixadmin_dbtty: postfixadmin_dbuser: postgres postfixadmin_dbpass: postfixadmin_dbname: postfixadmin @@ -30,7 +20,6 @@ postfixadmin_dbname: postfixadmin roundcube_dbhost: localhost roundcube_dbport: 5432 roundcube_dbopts: -roundcube_dbtty: roundcube_dbuser: postgres roundcube_dbpass: roundcube_dbname: roundcube diff --git a/doc/man1/mailshears.1 b/doc/man1/mailshears.1 index 286e5e0..ee9bbd5 100644 --- a/doc/man1/mailshears.1 +++ b/doc/man1/mailshears.1 @@ -4,7 +4,7 @@ mailshears \- mangle your mail garden .SH SYNOPSIS -\fBmailshears\fR [ [\fBprune\fR] | [\fBrm\fR <\fItargets\fR>] | [\fBmv\fR <\fIsrc\fR> <\fIdst\fR>] ] +\fBmailshears\fR [--help] [ [\fBprune\fR] | [\fBrm\fR <\fItargets\fR>] | [\fBmv\fR <\fIsrc\fR> <\fIdst\fR>] ] .SH DESCRIPTION @@ -28,8 +28,6 @@ It is assumed that you use the \fBPostfixAdmin schema\fI with functionality is provided by plugins. The following are supported at the moment: \# -.IP \(bu 2 -Agendav (database, v2.1.x) .IP \(bu DAViCal (database, v1.1.x) .IP \(bu @@ -77,7 +75,6 @@ Pruning leftover users and domains: .I $ mailshears mailshears, 2015-11-08 17:01:47 -0500 (Plugin: PrunePlugin) ----------------------------------------------------------- -AgendavPrune - Removed user booger@example.com. DavicalPrune - Removed user booger@example.com (Principal ID: 2). DovecotPrune - Removed user booger@example.com (/tmp/mailshears-test/example.com/booger). DovecotPrune - Removed user jeremy@example.com (/tmp/mailshears-test/example.com/jeremy). @@ -90,7 +87,6 @@ Removing a specific user: .I $ mailshears rm adam@example.net mailshears, 2015-11-08 17:04:42 -0500 (Plugin: RmPlugin) -------------------------------------------------------- -AgendavRm - Removed user adam@example.net. DavicalRm - User adam@example.net not found. DovecotRm - Removed user adam@example.net (/tmp/mailshears-test/example.net/adam). PostfixadminRm - Removed user adam@example.net. @@ -103,7 +99,6 @@ Removing a domain: .I $ mailshears rm example.net mailshears, 2015-11-08 17:05:42 -0500 (Plugin: RmPlugin) -------------------------------------------------------- -AgendavRm - Removed domain example.net. DavicalRm - Domain example.net not found. DovecotRm - Removed domain example.net (/tmp/mailshears-test/example.net). PostfixadminRm - Removed domain example.net. @@ -116,7 +111,6 @@ Renaming an existing user: .I $ mailshears mv alice@example.com alice@example.net mailshears, 2015-11-08 17:06:29 -0500 (Plugin: MvPlugin) -------------------------------------------------------- -AgendavMv - Source user alice@example.com not found. DavicalMv - Moved user alice@example.com (Principal ID: 1) to alice@example.net (Principal ID: 1). DovecotMv - Moved user alice@example.com (/tmp/mailshears-test/example.com/alice) to alice@example.net (/tmp/mailshears-test/example.net/alice). PostfixadminMv - Moved user alice@example.com to alice@example.net. @@ -127,7 +121,8 @@ RoundcubeMv - Moved user alice@example.com (User ID: 1) to alice@example.net (Us Mailshears is configured with a YAML file containing all of the database settings and plugin information. This file should be located -at ~/.mailshears.conf.yml, in your home directory. +at \fI$XDG_CONFIG_HOME\fR/mailshears/mailshears.conf.yml, where +\fI$XDG_CONFIG_HOME\fR defaults to \fI$HOME\fR/.config. The following two settings are global: \# @@ -138,9 +133,8 @@ will output some \(dqwhat if\(dq information but won't actually \# .IP \(bu \fIplugins\fR (default: ['postfixadmin']) -A list of enabled plugins. Valid values are \(dqagendav\(dq, -\(dqdavical\(dq, \(dqdovecot\(dq, \(dqpostfixadmin\(dq, and -\(dqroundcube\(dq. +A list of enabled plugins. Valid values are \(dqdavical\(dq, +\(dqdovecot\(dq, \(dqpostfixadmin\(dq, and \(dqroundcube\(dq. .P The \(dqdovecot\(dq plugin supports the following: \# @@ -152,8 +146,8 @@ located one level beneath dovecot_mail_root. .P The database plugins all have the same configutation options: connections settings preceded by the plugin name. So in what follows, - would be replaced by \(dqagendav\(dq, \(dqdavical\(dq, or so -on. Their meanings should be self-explanatory. + would be replaced by \(dqdavical\(dq, for example. Their +meanings should be self-explanatory. \# .IP \(bu 2 \fI_dbhost\fR (default: localhost) @@ -165,9 +159,6 @@ on. Their meanings should be self-explanatory. \fI_dbopts\fR (default: empty) \# .IP \(bu -\fI_dbtty\fR (default: empty) -\# -.IP \(bu \fI_dbuser\fR (default: 'postgres') \# .IP \(bu diff --git a/lib/common/agendav_plugin.rb b/lib/common/agendav_plugin.rb deleted file mode 100644 index d8ef6d6..0000000 --- a/lib/common/agendav_plugin.rb +++ /dev/null @@ -1,79 +0,0 @@ -require 'common/plugin' -require 'common/user' - -# Code that all Agendav plugins ({AgendavPrune}, {AgendavRm}, -# {AgendavMv}) share. -module AgendavPlugin - - # We implement the Plugin "interface." - include Plugin - - - # Initialize this Agendav {Plugin} with values in *cfg*. - # - # @param cfg [Configuration] the configuration for this plugin. - # - def initialize(cfg) - @db_hash = { - :host => cfg.agendav_dbhost, - :port => cfg.agendav_dbport, - :options => cfg.agendav_dbopts, - :tty => cfg.agendav_dbtty, - :dbname => cfg.agendav_dbname, - :user => cfg.agendav_dbuser, - :password => cfg.agendav_dbpass } - end - - - # Return a list of Agendav users. - # - # @return [Array] a list of users contained in the - # Agendav database. - # - def list_users() - users = [] - - connection = PG::Connection.new(@db_hash) - - # There are also "owner" and "with" fields in the "shares" table, - # but they contains principal URLs and not a bare username. Thus - # their format depends on the CalDAV server configuration, and - # isn't predictable. - sql_query = 'SELECT username FROM prefs;' - - begin - connection.query(sql_query) do |result| - users = result.field_values('username') - end - ensure - # Make sure the connection gets closed even if the query explodes. - connection.close() - end - - return users.map{ |u| User.new(u) } - end - - - # Count the number of rows in the "shares" table. Used only for - # testing. - # - # @return [Fixnum] the number of rows in the "shares" table. - # - def count_shares() - count = nil - connection = PG::Connection.new(@db_hash) - - sql_query = 'SELECT count(*) FROM shares;' - begin - connection.query(sql_query) do |result| - count = result.getvalue(0,0).to_i() - end - ensure - # Make sure the connection gets closed even if the query explodes. - connection.close() - end - - return count - end - -end diff --git a/lib/common/configuration.rb b/lib/common/configuration.rb index 02c30d2..78f5c04 100644 --- a/lib/common/configuration.rb +++ b/lib/common/configuration.rb @@ -1,9 +1,9 @@ require 'yaml' # A configuration object that knows how to read options out of a file -# in ~/.mailshears.conf.yml. The configuration options can be -# accessed via methods even though the internal representation is a -# hash. +# in $XDG_CONFIG_HOME/mailshears/mailshears.conf.yml. The +# configuration options can be accessed via methods even though the +# internal representation is a hash. # # === Examples # @@ -13,21 +13,26 @@ require 'yaml' # class Configuration - # The default path to the user's configuration file. - USERCONF_PATH = ENV['HOME'] + '/.mailshears.conf.yml' - # The hash structure in which we store our configuration options # internally. @dict = {} - # Initialize a {Configuration} object with the config file at *path*. # # @param path [String] the path to the configuration file to - # load. We check for a file named ".mailshears.conf.yml" in the - # user's home directory by default. + # load. We check for a file named "mailshears.conf.yml" in the + # user's XDG configuration directory by default. # - def initialize(path = USERCONF_PATH) + def initialize(path = nil) + if path.nil? then + # The default path to the user's configuration file. + path = ENV['HOME'] + '/.config' + if ENV.has_key?('XDG_CONFIG_HOME') then + path = ENV['XDG_CONFIG_HOME'] + end + path += '/mailshears/mailshears.conf.yml' + end + cfg = default_configuration() # Now, load the user configuration which will override the @@ -76,18 +81,9 @@ class Configuration d['i_mean_business'] = false d['plugins'] = ['postfixadmin'] - d['agendav_dbhost'] = 'localhost' - d['agendav_dbport'] = 5432 - d['agendav_dbopts'] = '' - d['agendav_dbtty'] = '' - d['agendav_dbuser'] = 'postgres' - d['agendav_dbpass'] = '' - d['agendav_dbname'] = 'agendav' - d['davical_dbhost'] = 'localhost' d['davical_dbport'] = 5432 d['davical_dbopts'] = '' - d['davical_dbtty'] = '' d['davical_dbuser'] = 'postgres' d['davical_dbpass'] = '' d['davical_dbname'] = 'davical' @@ -97,7 +93,6 @@ class Configuration d['postfixadmin_dbhost'] = 'localhost' d['postfixadmin_dbport'] = 5432 d['postfixadmin_dbopts'] = '' - d['postfixadmin_dbtty'] = '' d['postfixadmin_dbuser'] = 'postgres' d['postfixadmin_dbpass'] = '' d['postfixadmin_dbname'] = 'postfixadmin' @@ -105,7 +100,6 @@ class Configuration d['roundcube_dbhost'] = 'localhost' d['roundcube_dbport'] = 5432 d['roundcube_dbopts'] = '' - d['roundcube_dbtty'] = '' d['roundcube_dbuser'] = 'postgres' d['roundcube_dbpass'] = '' d['roundcube_dbname'] = 'roundcube' diff --git a/lib/common/davical_plugin.rb b/lib/common/davical_plugin.rb index ba09770..2298a8e 100644 --- a/lib/common/davical_plugin.rb +++ b/lib/common/davical_plugin.rb @@ -18,7 +18,6 @@ module DavicalPlugin :host => cfg.davical_dbhost, :port => cfg.davical_dbport, :options => cfg.davical_dbopts, - :tty => cfg.davical_dbtty, :dbname => cfg.davical_dbname, :user => cfg.davical_dbuser, :password => cfg.davical_dbpass } @@ -55,7 +54,7 @@ module DavicalPlugin sql_query = 'SELECT username FROM usr WHERE user_no > 1;' begin - connection.query(sql_query) do |result| + connection.sync_exec(sql_query) do |result| usernames = result.field_values('username') end ensure @@ -88,7 +87,7 @@ module DavicalPlugin sql_query += 'WHERE usr.username = $1;' begin - connection.query(sql_query, [user.to_s()]) do |result| + connection.sync_exec_params(sql_query, [user.to_s()]) do |result| if result.num_tuples > 0 principal_id = result[0]['principal_id'] end diff --git a/lib/common/postfixadmin_plugin.rb b/lib/common/postfixadmin_plugin.rb index 08fca7d..b4c9030 100644 --- a/lib/common/postfixadmin_plugin.rb +++ b/lib/common/postfixadmin_plugin.rb @@ -20,7 +20,6 @@ module PostfixadminPlugin :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 } @@ -43,7 +42,7 @@ module PostfixadminPlugin sql_query = "SELECT domain FROM domain WHERE domain <> 'ALL';" begin - connection.query(sql_query) do |result| + connection.sync_exec(sql_query) do |result| domains = result.field_values('domain') end ensure @@ -68,7 +67,7 @@ module PostfixadminPlugin sql_query = 'SELECT username FROM mailbox;' begin - connection.query(sql_query) do |result| + connection.sync_exec(sql_query) do |result| users = result.field_values('username') end ensure @@ -104,7 +103,7 @@ module PostfixadminPlugin 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| + connection.sync_exec_params(sql_query, domains.map{ |d| d.to_s() }) do |result| usernames = result.field_values('username') end ensure @@ -131,7 +130,7 @@ module PostfixadminPlugin sql_query = 'SELECT address,goto FROM alias;' begin - results = connection.query(sql_query) + results = connection.sync_exec(sql_query) results.each do |row| # row should be a hash aliases << row @@ -163,7 +162,7 @@ module PostfixadminPlugin sql_query = 'SELECT COUNT(domain) as count FROM domain WHERE domain = $1;' begin - connection.query(sql_query, [domain.to_s()]) do |result| + connection.sync_exec_params(sql_query, [domain.to_s()]) do |result| return false if result.ntuples() < 1 count = result.getvalue(0,0).to_i() diff --git a/lib/common/roundcube_plugin.rb b/lib/common/roundcube_plugin.rb index b66c9f6..2ba9a98 100644 --- a/lib/common/roundcube_plugin.rb +++ b/lib/common/roundcube_plugin.rb @@ -19,7 +19,6 @@ module RoundcubePlugin :host => cfg.roundcube_dbhost, :port => cfg.roundcube_dbport, :options => cfg.roundcube_dbopts, - :tty => cfg.roundcube_dbtty, :dbname => cfg.roundcube_dbname, :user => cfg.roundcube_dbuser, :password => cfg.roundcube_dbpass } @@ -52,7 +51,7 @@ module RoundcubePlugin sql_query = 'SELECT username FROM users;' begin - connection.query(sql_query) do |result| + connection.sync_exec(sql_query) do |result| usernames = result.field_values('username') end ensure @@ -79,7 +78,7 @@ module RoundcubePlugin sql_query = 'SELECT user_id FROM users WHERE username = $1;' begin - connection.query(sql_query, [user.to_s()]) do |result| + connection.sync_exec_params(sql_query, [user.to_s()]) do |result| if result.num_tuples > 0 user_id = result[0]['user_id'] end diff --git a/lib/common/user_interface.rb b/lib/common/user_interface.rb index 1bcf67e..92ed74b 100644 --- a/lib/common/user_interface.rb +++ b/lib/common/user_interface.rb @@ -12,7 +12,7 @@ class UserInterface # invocation. # def self.usage(program_name) - return "#{program_name} [prune | rm | mv ]" + return "#{program_name} [--help] [prune | rm | mv ]" end diff --git a/lib/mv/plugins/agendav.rb b/lib/mv/plugins/agendav.rb deleted file mode 100644 index 2177009..0000000 --- a/lib/mv/plugins/agendav.rb +++ /dev/null @@ -1,69 +0,0 @@ -require 'pg' - -require 'common/agendav_plugin' -require 'mv/mv_plugin' - - -# Handle moving (renaming) Agendav users in its database. Agendav has -# no concept of domains. -# -class AgendavMv - - include AgendavPlugin - include MvPlugin - - # Move the user *src* to *dst* within the Agendav database. This - # should "rename" him in _every_ table where he is referenced. - # - # This can fail if *dst* already exists before the move. It should - # also be an error if the destination domain doesn't exist. But - # Agendav doesn't know about domains, so we let that slide. - # - # If the source user doesn't exist, we do our best. AgenDAV has a - # "shares" table that isn't keyed on the username, but rather the - # principal URL. And its "prefs" table doesn't contain entries for - # users who have default preferences. As a result, we may need to - # perform some find/replaces in the "shares" table even if no - # corresponding user exists in the "prefs" table (which is how we - # tell if a user exists in AgenDAV). Thus it's not a fatal error if - # the *src* user doesn't exist. - # - # @param src [User] the source user to be moved. - # - # @param dst [User] the destination user being moved to. - # - def mv_user(src, dst) - raise UserAlreadyExistsError.new(dst.to_s()) if user_exists(dst) - - connection = PG::Connection.new(@db_hash) - begin - # The "prefs" table uses the normal username as a key... - # This should be harmless if the source user does not exist. - sql_query0 = 'UPDATE prefs SET username = $1 WHERE username = $2;' - connection.query(sql_query0, [dst.to_s(), src.to_s()]) - - # But the "shares" table uses encoded principal URLs. For the - # "shares" table, we need to do a find/replace on the username - # with its "@" symbol translated to a "%40". - encoded_src = src.to_s()['@'] = '%40' - encoded_dst = dst.to_s()['@'] = '%40' - - # Unlike in the "rm" plugin, we do modify the "calendar" field - # here. That's because in the usual legitimate use case, the - # calendar URL will change when a user moves. This will ALSO - # affect people who name their calendars something like - # "user%40example.com", but screw those people. - sql_queries = ['UPDATE shares SET owner=REPLACE(owner, $2, $1);'] - sql_queries << 'UPDATE shares SET calendar=REPLACE(calendar, $2, $1);' - sql_queries << 'UPDATE shares SET "with"=REPLACE("with", $2, $1);' - - sql_queries.each do |sql_query| - connection.query(sql_query, [encoded_dst, encoded_src]) - end - ensure - # Make sure the connection gets closed even if a query explodes. - connection.close() - end - end - -end diff --git a/lib/mv/plugins/davical.rb b/lib/mv/plugins/davical.rb index e7aa050..1b8a858 100644 --- a/lib/mv/plugins/davical.rb +++ b/lib/mv/plugins/davical.rb @@ -1,7 +1,7 @@ require 'pg' require 'common/davical_plugin' -require 'rm/rm_plugin' +require 'mv/mv_plugin' # Handle moving (renaming) DAViCal users in its database. DAViCal has # no concept of domains. @@ -33,7 +33,7 @@ class DavicalMv connection = PG::Connection.new(@db_hash) begin - connection.query(sql_query, [dst.to_s(), src.to_s()]) + connection.sync_exec_params(sql_query, [dst.to_s(), src.to_s()]) ensure # Make sure the connection gets closed even if the query explodes. connection.close() diff --git a/lib/mv/plugins/postfixadmin.rb b/lib/mv/plugins/postfixadmin.rb index f8f86c6..1623118 100644 --- a/lib/mv/plugins/postfixadmin.rb +++ b/lib/mv/plugins/postfixadmin.rb @@ -59,7 +59,7 @@ class PostfixadminMv {:value => dst.domainpart(), :type => varchar}, {:value => dst.localpart(), :type => varchar}, {:value => src.to_s(), :type => varchar}] - connection.query(sql_query, params) + connection.sync_exec_params(sql_query, params) end ensure # Make sure the connection gets closed even if a query explodes. diff --git a/lib/mv/plugins/roundcube.rb b/lib/mv/plugins/roundcube.rb index 5cd20c7..e2126f7 100644 --- a/lib/mv/plugins/roundcube.rb +++ b/lib/mv/plugins/roundcube.rb @@ -34,7 +34,7 @@ class RoundcubeMv connection = PG::Connection.new(@db_hash) begin - connection.query(sql_query, [dst.to_s(), src.to_s()]) + connection.sync_exec_params(sql_query, [dst.to_s(), src.to_s()]) ensure # Make sure the connection gets closed even if the query explodes. connection.close() diff --git a/lib/prune/plugins/agendav.rb b/lib/prune/plugins/agendav.rb deleted file mode 100644 index f0f0183..0000000 --- a/lib/prune/plugins/agendav.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'pg' - -require 'prune/prune_plugin' -require 'rm/plugins/agendav' - -# Handle the pruning of Agendav users from its database. This class -# doesn't need to do anything; by inheriting from {AgendavRm}, we get -# its {AgendavRm#remove_user} method and that's all we need to prune. -# -class AgendavPrune < AgendavRm - # Needed for the magic includers run() method. - include PrunePlugin -end diff --git a/lib/rm/plugins/agendav.rb b/lib/rm/plugins/agendav.rb deleted file mode 100644 index 921bfe0..0000000 --- a/lib/rm/plugins/agendav.rb +++ /dev/null @@ -1,58 +0,0 @@ -require 'pg' - -require 'common/agendav_plugin' -require 'rm/rm_plugin' - - -# Handle the removal of Agendav users from its database. Agendav has -# no concept of domains. -# -class AgendavRm - - include AgendavPlugin - include RmPlugin - - - # Remove *user* from the Agendav database. This should remove him - # from _every_ table in which he is referenced. - # - # We do not raise an error if the user doesn't exist. This is due to - # an unfortunate problem with the "user exists" check in AgenDAV. - # The AgenDAV "shares" table is not tied directly to a username, so - # we are forced to use a regexp match to decide what rows to delete - # from that table. We do so regardless of whether or not the username - # exists in the "prefs" table, because that table stores only non- - # default preferences -- not all users' preferences. - # - # @param user [User] the user to remove. - # - def remove_user(user) - sql_queries = ['DELETE FROM prefs WHERE username = $1;'] - - # The "shares" table contains principal URLs, and the "@" symbol - # is usually encoded to "%40". These queries do a regex match on - # the username after replacing the "%40" with a "@". - # - # As a precaution, I haven chosen not to delete based on the - # "calendar" field here. Nobody should have a calendar named - # "user%40example.com", but it's not impossible -- and we don't - # want to delete that calendar when the not-necessarily-related - # "user@example.com" account is removed. And the usual appearance - # of the user's email address in the "calendar" field happens when - # he is also the owner, so the calendar does get deleted in the - # normal situation. - sql_queries << "DELETE FROM shares WHERE REPLACE(owner,'%40','@') ~ $1;" - sql_queries << "DELETE FROM shares WHERE REPLACE(\"with\",'%40','@') ~ $1;" - - connection = PG::Connection.new(@db_hash) - begin - sql_queries.each do |sql_query| - connection.query(sql_query, [user.to_s()]) - end - ensure - # Make sure the connection gets closed even if a query explodes. - connection.close() - end - end - -end diff --git a/lib/rm/plugins/davical.rb b/lib/rm/plugins/davical.rb index 1029994..1978e80 100644 --- a/lib/rm/plugins/davical.rb +++ b/lib/rm/plugins/davical.rb @@ -27,7 +27,7 @@ class DavicalRm connection = PG::Connection.new(@db_hash) begin - connection.query(sql_query, [user.to_s()]) + connection.sync_exec_params(sql_query, [user.to_s()]) ensure # Make sure the connection gets closed even if the query explodes. connection.close() diff --git a/lib/rm/plugins/postfixadmin.rb b/lib/rm/plugins/postfixadmin.rb index 46eda47..958d42c 100644 --- a/lib/rm/plugins/postfixadmin.rb +++ b/lib/rm/plugins/postfixadmin.rb @@ -56,7 +56,7 @@ class PostfixadminRm sql_queries.each do |sql_query| varchar = 1043 # from pg_type.h params = [{:value => user.to_s(), :type => varchar}] - connection.query(sql_query, params) + connection.sync_exec_params(sql_query, params) end ensure # Make sure the connection gets closed even if a query explodes. @@ -103,7 +103,7 @@ class PostfixadminRm begin sql_queries.each do |sql_query| - connection.query(sql_query, [domain.to_s()]) + connection.sync_exec_params(sql_query, [domain.to_s()]) end ensure # Make sure the connection gets closed even if a query explodes. diff --git a/lib/rm/plugins/roundcube.rb b/lib/rm/plugins/roundcube.rb index cb55af7..56c3108 100644 --- a/lib/rm/plugins/roundcube.rb +++ b/lib/rm/plugins/roundcube.rb @@ -32,7 +32,7 @@ class RoundcubeRm connection = PG::Connection.new(@db_hash) begin - connection.query(sql_query, [user_id]) + connection.sync_exec_params(sql_query, [user_id]) ensure # Make sure the connection gets closed even if the query explodes. connection.close() diff --git a/mailshears.gemspec b/mailshears.gemspec index e0d9b80..a22b2cf 100644 --- a/mailshears.gemspec +++ b/mailshears.gemspec @@ -1,11 +1,11 @@ Gem::Specification.new do |s| s.name = 'mailshears' - s.version = '0.0.4' + s.version = '0.1.0' s.platform = Gem::Platform::RUBY s.authors = ['Michael Orlitzky'] s.email = ['michael@orlitzky.com'] - s.homepage = 'http://michael.orlitzky.com/code/mailshears.xhtml' + s.homepage = 'https://michael.orlitzky.com/code/mailshears.xhtml' s.summary = 'Prune unused mail directories.' s.description = <<-EOF Managing a mail system with virtual users is annoying. The @@ -24,7 +24,7 @@ Gem::Specification.new do |s| as well. Since these two tasks are related, mailshears does them both. EOF - s.license = 'AGPL-3.0' + s.license = 'AGPL-3.0-or-later' s.required_rubygems_version = '>= 1.3.6' # If you have runtime dependencies, add them here diff --git a/test/bin/install-fixtures.sh b/test/bin/install-fixtures.sh index 88e4441..9116cc9 100755 --- a/test/bin/install-fixtures.sh +++ b/test/bin/install-fixtures.sh @@ -10,11 +10,6 @@ mkdir -p /tmp/mailshears-test/example.com/booger mkdir -p /tmp/mailshears-test/example.com/jeremy mkdir -p /tmp/mailshears-test/example.net/adam -dropdb --if-exists -U postgres agendav -createdb -U postgres agendav -psql -U postgres -d agendav < test/sql/agendav.sql -psql -U postgres -d agendav < test/sql/agendav-fixtures.sql - dropdb --if-exists -U postgres davical createdb -U postgres davical psql -U postgres -d davical < test/sql/davical.sql diff --git a/test/mailshears.test.conf.yml b/test/mailshears.test.conf.yml index 0bfbd7f..3ae99b5 100644 --- a/test/mailshears.test.conf.yml +++ b/test/mailshears.test.conf.yml @@ -1,28 +1,20 @@ i_mean_business: true -plugins: [agendav, davical, dovecot, postfixadmin, roundcube] - -agendav_dbhost: localhost -agendav_dbport: 5432 -agendav_dbopts: -agendav_dbtty: -agendav_dbuser: postgres -agendav_dbpass: -agendav_dbname: agendav_test +plugins: [davical, dovecot, postfixadmin, roundcube] davical_dbhost: localhost davical_dbport: 5432 davical_dbopts: -davical_dbtty: davical_dbuser: postgres davical_dbpass: davical_dbname: davical_test +# Warning: unsafe! Fixed paths under /tmp are asking to be exploited +# by other users on the system. dovecot_mail_root: /tmp/mailshears-test postfixadmin_dbhost: localhost postfixadmin_dbport: 5432 postfixadmin_dbopts: -postfixadmin_dbtty: postfixadmin_dbuser: postgres postfixadmin_dbpass: postfixadmin_dbname: postfixadmin_test @@ -30,7 +22,6 @@ postfixadmin_dbname: postfixadmin_test roundcube_dbhost: localhost roundcube_dbport: 5432 roundcube_dbopts: -roundcube_dbtty: roundcube_dbuser: postgres roundcube_dbpass: roundcube_dbname: roundcube_test diff --git a/test/mailshears_test.rb b/test/mailshears_test.rb index 2b653ed..215d05f 100644 --- a/test/mailshears_test.rb +++ b/test/mailshears_test.rb @@ -11,7 +11,7 @@ require 'common/configuration' require 'fileutils' require 'pg' -class MailshearsTest < MiniTest::Test +class MailshearsTest < Minitest::Test # This is that class that most (if not all) of our test cases will # inherit. It provides the automatic setup and teardown of the # filesystem and database that the test cases will exercise. @@ -32,16 +32,15 @@ class MailshearsTest < MiniTest::Test # Connect to the database (specified in the test configuration) as # the superuser. Your local configuration is expected to be such # that this "just works." - db_host = 'localhost' - db_port = 5432 - db_opts = nil - db_tty = nil - db_name = 'postgres' - db_user = 'postgres' - db_pass = nil - - connection = PG::Connection.new(db_host, db_port, db_opts, db_tty, - db_name, db_user, db_pass) + db_hash = { + :host => 'localhost', + :port => 5432, + :options => nil, + :dbname => 'postgres', + :user => 'postgres', + :password => nil + } + connection = PG::Connection.new(db_hash) return connection end @@ -66,31 +65,7 @@ class MailshearsTest < MiniTest::Test # # == Databases == # - # 1. agendav_test - # - # +------------------------------+ - # | prefs | - # +--------------------+---------+ - # | username | options | - # +--------------------+---------+ - # | adam@example.net | herp | - # +--------------------+---------+ - # | booger@example.com | herp | - # +------------------ +---------+ - # - # - # +---------------------------------------------------------------------------------------------------------------------+ - # | shares | - # +-----+-----------------------------------+---------------------------------------------------+-----------------------+ - # | sid | owner | calendar | with | - # +-----+-----------------------------------+---------------------------------------------------+-----------------------+ - # | 1 | /caldav.php/adam%40example.net/ | /caldav.php/adam%40example.net/calendar-default | /beth%40example.net/ | - # +-----+-----------------------------------+---------------------------------------------------+-----------------------+ - # | 2 | /caldav.php/booger%40example.com/ | /caldav.php/booger%40example.com/calendar-default | /carol%40example.net/ | - # +-----+-----------------------------------+---------------------------------------------------+-----------------------+ - # - # - # 2. davical_test + # 1. davical_test # # +--------------------------------------------------------+ # | usr | @@ -114,7 +89,7 @@ class MailshearsTest < MiniTest::Test # +---------+--------------+----------------+ # # - # 3. postfixadmin_test + # 2. postfixadmin_test # # +-------------+ # | domain | @@ -176,7 +151,7 @@ class MailshearsTest < MiniTest::Test # | admin@example.com | example.net | # +-------------------+-------------+ # - # 4. roundcube_test + # 3. roundcube_test # # # +---------+--------------------+ @@ -208,24 +183,23 @@ class MailshearsTest < MiniTest::Test plugin_dbname = cfg.send("#{plugin}_dbname") next if plugin_dbname.nil? # Skip the dovecot plugin query = "CREATE DATABASE #{plugin_dbname};" - connection.query(query) + connection.sync_exec(query) - plugin_dbhost = cfg.send("#{plugin}_dbhost") - plugin_dbport = cfg.send("#{plugin}_dbport") - plugin_dbopts = cfg.send("#{plugin}_dbopts") - plugin_dbtty = cfg.send("#{plugin}_dbtty") - plugin_dbuser = cfg.send("#{plugin}_dbuser") - plugin_dbpass = cfg.send("#{plugin}_dbpass") + plugin_dbhash = { + :host => cfg.send("#{plugin}_dbhost"), + :port => cfg.send("#{plugin}_dbport"), + :options => cfg.send("#{plugin}_dbopts"), + :dbname => plugin_dbname, + :user => cfg.send("#{plugin}_dbuser"), + :password => cfg.send("#{plugin}_dbpass") + } - plugin_conn = PG::Connection.new(plugin_dbhost, plugin_dbport, - plugin_dbopts, plugin_dbtty, - plugin_dbname, plugin_dbuser, - plugin_dbpass) + plugin_conn = PG::Connection.new(plugin_dbhash) sql = File.open("test/sql/#{plugin}.sql").read() - plugin_conn.query(sql) + plugin_conn.sync_exec(sql) sql = File.open("test/sql/#{plugin}-fixtures.sql").read() - plugin_conn.query(sql) + plugin_conn.sync_exec(sql) plugin_conn.close() end @@ -246,7 +220,7 @@ class MailshearsTest < MiniTest::Test plugin_dbname = cfg.send("#{plugin}_dbname") next if plugin_dbname.nil? # Skip the dovecot plugin query = "DROP DATABASE IF EXISTS #{plugin_dbname};" - connection.query(query) + connection.sync_exec(query) end connection.close() diff --git a/test/sql/agendav-fixtures.sql b/test/sql/agendav-fixtures.sql deleted file mode 100644 index 535f023..0000000 --- a/test/sql/agendav-fixtures.sql +++ /dev/null @@ -1,34 +0,0 @@ -/* Add an AgenDAV record for one user only. */ -INSERT INTO prefs (username, options) VALUES ('adam@example.net', 'herp'); -INSERT INTO shares (owner, calendar, "with", options, rw) - VALUES ('/caldav.php/adam%40example.net/', - '/caldav.php/adam%40example.net/calendar-default', - '/beth%40example.net/', - 'a:0:{}', - false); - -/* Just kidding, here's another one! */ -INSERT INTO prefs (username, options) VALUES ('booger@example.com', 'herp'); -INSERT INTO shares (owner, calendar, "with", options, rw) - VALUES ('/caldav.php/booger%40example.com/', - '/caldav.php/booger%40example.com/calendar-default', - '/carol%40example.net/', - 'a:0:{}', - true); - -/* These two are missing a prefs entry to test the removal of "shares" - * entries in that situation. -*/ -INSERT INTO shares (owner, calendar, "with", options, rw) - VALUES ('/caldav.php/stinky%40example.com/', - '/caldav.php/stinky%40example.com/calendar-default', - '/herp%40example.net/', - 'a:0:{}', - true); - -INSERT INTO shares (owner, calendar, "with", options, rw) - VALUES ('/caldav.php/goober%40example.com/', - '/caldav.php/goober%40example.com/calendar-default', - '/derp%40example.net/', - 'a:0:{}', - true); diff --git a/test/sql/agendav.sql b/test/sql/agendav.sql deleted file mode 100644 index 9b84ac6..0000000 --- a/test/sql/agendav.sql +++ /dev/null @@ -1,194 +0,0 @@ --- --- PostgreSQL database dump --- - -SET statement_timeout = 0; -SET lock_timeout = 0; -SET client_encoding = 'UTF8'; -SET standard_conforming_strings = on; -SET check_function_bodies = false; -SET client_min_messages = warning; -SET row_security = off; - --- --- Name: plpgsql; Type: EXTENSION; Schema: -; Owner: --- - -CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog; - - --- --- Name: EXTENSION plpgsql; Type: COMMENT; Schema: -; Owner: --- - -COMMENT ON EXTENSION plpgsql IS 'PL/pgSQL procedural language'; - - -SET search_path = public, pg_catalog; - -SET default_tablespace = ''; - -SET default_with_oids = false; - --- --- Name: prefs; Type: TABLE; Schema: public; Owner: agendav --- - -CREATE TABLE prefs ( - username character varying(255) NOT NULL, - options text NOT NULL -); - - -ALTER TABLE prefs OWNER to postgres; - --- --- Name: principals; Type: TABLE; Schema: public; Owner: agendav --- - -CREATE TABLE principals ( - url character varying(255) NOT NULL, - displayname character varying(255) NOT NULL, - email character varying(255) NOT NULL -); - - -ALTER TABLE principals OWNER to postgres; - --- --- Name: schema_versions; Type: TABLE; Schema: public; Owner: agendav --- - -CREATE TABLE schema_versions ( - version character varying(255) NOT NULL -); - - -ALTER TABLE schema_versions OWNER to postgres; - --- --- Name: sessions; Type: TABLE; Schema: public; Owner: agendav --- - -CREATE TABLE sessions ( - sess_id character varying(128) NOT NULL, - sess_data bytea NOT NULL, - sess_lifetime integer NOT NULL, - sess_time integer NOT NULL -); - - -ALTER TABLE sessions OWNER to postgres; - --- --- Name: shares; Type: TABLE; Schema: public; Owner: agendav --- - -CREATE TABLE shares ( - sid integer NOT NULL, - owner character varying(255) NOT NULL, - calendar character varying(255) NOT NULL, - "with" character varying(255) NOT NULL, - options text NOT NULL, - rw boolean NOT NULL -); - - -ALTER TABLE shares OWNER to postgres; - --- --- Name: shares_sid_seq; Type: SEQUENCE; Schema: public; Owner: agendav --- - -CREATE SEQUENCE shares_sid_seq - START WITH 1 - INCREMENT BY 1 - NO MINVALUE - NO MAXVALUE - CACHE 1; - - -ALTER TABLE shares_sid_seq OWNER to postgres; - --- --- Name: shares_sid_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: agendav --- - -ALTER SEQUENCE shares_sid_seq OWNED BY shares.sid; - - --- --- Name: sid; Type: DEFAULT; Schema: public; Owner: agendav --- - -ALTER TABLE ONLY shares ALTER COLUMN sid SET DEFAULT nextval('shares_sid_seq'::regclass); - - --- --- Name: prefs_pkey; Type: CONSTRAINT; Schema: public; Owner: agendav --- - -ALTER TABLE ONLY prefs - ADD CONSTRAINT prefs_pkey PRIMARY KEY (username); - - --- --- Name: principals_pkey; Type: CONSTRAINT; Schema: public; Owner: agendav --- - -ALTER TABLE ONLY principals - ADD CONSTRAINT principals_pkey PRIMARY KEY (url); - - --- --- Name: schema_versions_pkey; Type: CONSTRAINT; Schema: public; Owner: agendav --- - -ALTER TABLE ONLY schema_versions - ADD CONSTRAINT schema_versions_pkey PRIMARY KEY (version); - - --- --- Name: sessions_pkey; Type: CONSTRAINT; Schema: public; Owner: agendav --- - -ALTER TABLE ONLY sessions - ADD CONSTRAINT sessions_pkey PRIMARY KEY (sess_id); - - --- --- Name: shares_pkey; Type: CONSTRAINT; Schema: public; Owner: agendav --- - -ALTER TABLE ONLY shares - ADD CONSTRAINT shares_pkey PRIMARY KEY (sid); - - --- --- Name: idx_905f717c9890e20e; Type: INDEX; Schema: public; Owner: agendav --- - -CREATE INDEX idx_905f717c9890e20e ON shares USING btree ("with"); - - --- --- Name: idx_905f717ccf60e67c6ea9a146; Type: INDEX; Schema: public; Owner: agendav --- - -CREATE INDEX idx_905f717ccf60e67c6ea9a146 ON shares USING btree (owner, calendar); - - --- --- Name: public; Type: ACL; Schema: -; Owner: postgres --- - -REVOKE ALL ON SCHEMA public FROM PUBLIC; -REVOKE ALL ON SCHEMA public FROM postgres; -GRANT ALL ON SCHEMA public TO postgres; -GRANT ALL ON SCHEMA public TO PUBLIC; - - --- --- PostgreSQL database dump complete --- - diff --git a/test/test_mv.rb b/test/test_mv.rb index a409304..f007cdc 100644 --- a/test/test_mv.rb +++ b/test/test_mv.rb @@ -3,7 +3,6 @@ require 'common/domain' require 'common/user' require 'mailshears_test' -require 'mv/plugins/agendav' require 'mv/plugins/davical' require 'mv/plugins/dovecot' require 'mv/plugins/postfixadmin' @@ -27,8 +26,6 @@ class TestMv < MailshearsTest actual = output_buffer.string() expected = - "AgendavMv - Moved user alice@example.com (User not found) " + - "to alice@example.net (User not found).\n" + "DavicalMv - Moved user alice@example.com (Principal ID: 1) " + "to alice@example.net (Principal ID: 1).\n" + "DovecotMv - Moved user alice@example.com " + @@ -42,11 +39,6 @@ class TestMv < MailshearsTest # Now check the database. - amv = AgendavMv.new(cfg) - actual = amv.list_users() - expected = [User.new('adam@example.net'),User.new('booger@example.com')] - assert_equal(expected.sort(), actual.sort()) - dmv = DavicalMv.new(cfg) actual = dmv.list_users() expected = [User.new('alice@example.net'), User.new('booger@example.com')] @@ -117,11 +109,6 @@ class TestMv < MailshearsTest # Skip output verification, it's ugly. But make sure the database # has what we expect. - amv = AgendavMv.new(cfg) - actual = amv.list_users() - expected = [User.new('adam@example.net'),User.new('booger@example.com')] - assert_equal(expected.sort(), actual.sort()) - dmv = DavicalMv.new(cfg) actual = dmv.list_users() expected = [User.new('alice@example.com'), User.new('booger@example.com')] diff --git a/test/test_prune.rb b/test/test_prune.rb index 059de52..4904fd1 100644 --- a/test/test_prune.rb +++ b/test/test_prune.rb @@ -3,7 +3,6 @@ require 'common/domain' require 'common/user' require 'mailshears_test' -require 'prune/plugins/agendav' require 'prune/plugins/davical' require 'prune/plugins/dovecot' require 'prune/plugins/postfixadmin' @@ -19,7 +18,6 @@ class TestPrune < MailshearsTest # Both of our tests have the same expected output / results, so # check them both using the same function. expected = - "AgendavPrune - Removed user booger@example.com.\n" + "DavicalPrune - Removed user booger@example.com (Principal ID: 2).\n" + "DovecotPrune - Removed user booger@example.com " + "(#{cfg.dovecot_mail_root()}/example.com/booger).\n" + @@ -31,11 +29,6 @@ class TestPrune < MailshearsTest # Now make sure the database has what we expect. - apr = AgendavPrune.new(cfg) - actual = apr.list_users() - expected = [User.new('adam@example.net')] - assert_equal(expected, actual) - dpr = DavicalPrune.new(cfg) actual = dpr.list_users() expected = [User.new('alice@example.com')] diff --git a/test/test_rm.rb b/test/test_rm.rb index 02de53d..002feda 100644 --- a/test/test_rm.rb +++ b/test/test_rm.rb @@ -3,7 +3,6 @@ require 'common/domain' require 'common/user' require 'mailshears_test' -require 'rm/plugins/agendav' require 'rm/plugins/davical' require 'rm/plugins/dovecot' require 'rm/plugins/postfixadmin' @@ -26,7 +25,6 @@ class TestRm < MailshearsTest actual = output_buffer.string() expected = - "AgendavRm - Removed user adam@example.net.\n" + "DavicalRm - User adam@example.net not found.\n" + "DovecotRm - Removed user adam@example.net " + "(#{cfg.dovecot_mail_root}/example.net/adam).\n" + @@ -37,18 +35,6 @@ class TestRm < MailshearsTest # Now make sure the database has what we expect. - arm = AgendavRm.new(cfg) - actual = arm.list_users() - expected = [User.new('booger@example.com')] - assert_equal(expected, actual) - - # Only try to remove this guy from the agendav database, to ensure - # that "nonexistent" users have their shares removed. - arm.remove_user('stinky@example.com') - expected = 2 - actual = arm.count_shares() - assert_equal(expected, actual) - drm = DavicalRm.new(cfg) actual = drm.list_users() expected = [User.new('alice@example.com'), User.new('booger@example.com')] @@ -106,7 +92,6 @@ class TestRm < MailshearsTest actual = output_buffer.string() expected = - "AgendavRm - Removed domain example.net.\n" + "DavicalRm - Domain example.net not found.\n" + "DovecotRm - Removed domain example.net " + "(#{cfg.dovecot_mail_root}/example.net).\n" + @@ -117,11 +102,6 @@ class TestRm < MailshearsTest # Now make sure the database has what we expect. - arm = AgendavRm.new(cfg) - actual = arm.list_users() - expected = [User.new('booger@example.com')] - assert_equal(expected, actual) - drm = DavicalRm.new(cfg) actual = drm.list_users() expected = [User.new('alice@example.com'), User.new('booger@example.com')]