smailr 0.6.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 137f8c287a816f8de1cc332745f1b7a7e7c6248d
4
- data.tar.gz: 20e72c156dc8fdc41c17778c0436eadbf8187d0b
3
+ metadata.gz: dd1155588b6637a588d5284aefb168421f2a5325
4
+ data.tar.gz: d750025813678d2d6aadff17b3ebf3efc729fd7e
5
5
  SHA512:
6
- metadata.gz: ce459c937af2283a0411329fc04f5369d2894557fb288b3a501d2c20d10ffebf9fbbd382a4d12fd06163f34e25daa395f5789c4b101a4c26b9dd1b8be84ae56e
7
- data.tar.gz: 3166aba8a9629e697c316c64e72a938c3c784b150911b784fb2ec39ab28dbf6fcad23ac8979befddd2f48cf1022567d9bdc0b7b3ef71e10a0fcbffd89e495c81
6
+ metadata.gz: a1d9da463d534d61cc9e1e7942d11a7d7756b18b74b150c85e31498cc2b3e1f12778c00be5fcbe9c444b9b0434cd22e2f32e070c0f9845d344602bff906ce034
7
+ data.tar.gz: 2297af6c3b3ce95dd82f9cc6279487f2de6cf37c8faaeb8141e768a0b6596890024c21c1f175ed8d969bb295b054350fada39601d2ba36f3c84cbc592919965d
data/README.md CHANGED
@@ -157,9 +157,9 @@ automatically. Open mutt for the specified mailbox:
157
157
  ### Verify
158
158
 
159
159
  Smailr generates a report via the Port25 SMTP Verifier. It generates a test,
160
- sends it to check-auth-user=eaxmple.comt@verifier.port25.com, which will in
161
- return generate a echo message with a report about a results from many SMTP
162
- combonents: SPF, SenderID, DomainKeys, DKIM and Spamassassin.
160
+ sends it to check-auth-user=eaxmple.comt@verifier.port25.com, which will in
161
+ return generate a echo message with a report about results of many SMTP
162
+ components: SPF, SenderID, DomainKeys, DKIM and Spamassassin.
163
163
 
164
164
  To generate a message, sent from user@example.com and return the report to the
165
165
  same address simply call the following command:
data/bin/smailr CHANGED
@@ -1,241 +1,15 @@
1
1
  #!/usr/bin/env ruby
2
- $: << File.expand_path('../../lib', __FILE__)
3
-
4
- require 'smailr'
5
-
2
+ #$: << File.expand_path('../../lib', __FILE__)
6
3
  #
7
- # CLI Helpers
4
+ #require 'pathname'
5
+ #ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
6
+ # Pathname.new(__FILE__).realpath)
8
7
  #
9
- def determine_object(string)
10
- return :domain if string =~ /^[^@][A-Z0-9.-]+\.[A-Z]{2,6}$/i
11
- return :address if string =~ /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,6}$/i
12
- end
13
-
14
- def ask_password
15
- min_password_length = Smailr.config["password_policy"]["length"]
16
-
17
- password = ask("Password: ") { |q| q.echo = "*" }
18
- confirm = ask("Confirm: ") { |q| q.echo = "*" }
19
-
20
- if password != confirm
21
- say("Mismatch; try again.")
22
- ask_password
23
- end
24
-
25
- if password.length < min_password_length.to_i
26
- say("Too short; try again.")
27
- ask_password
28
- end
29
-
30
- password
31
- end
32
-
33
- program :version, Smailr::VERSION
34
- program :description, 'Simple MAIL manageR - Virtual mail hosting management from the CLI'
35
-
36
-
37
- #
38
- # Commands
39
- #
40
- command :add do |c|
41
- c.syntax = 'smailr add domain | mailbox | alias [options]'
42
- c.summary = 'Add a new domain, mailbox or alias to the mail system.'
43
- c.example 'Add a domain', 'smailr add example.com'
44
- c.example 'Add a mailbox', 'smailr add user@example.com'
45
- c.example 'Add an alias', 'smailr add alias@localdom.com --alias user@example.com,user1@example.com'
46
- c.example 'Setup DKIM for a domain', 'smailr add ono.at --dkim'
47
- c.option '--alias DESTINATION', String, 'Specify the alias destination.'
48
- c.option '--password PASSWORD', String, 'The password for a new mailbox. If you omit this option, it prompts for one.'
49
- c.option '--dkim SELECTOR', String, 'Add a DKIM Key with the specified selector for domain.'
50
- c.action do |args, options|
51
- address = args[0]
52
- type = determine_object(address)
53
-
54
- case type
55
- when :domain
56
- if options.dkim
57
- selector = options.dkim
58
- key = Smailr::Dkim.add(address, selector)
59
-
60
- puts "public-key " + key.to_a[1..-2].join.gsub(/\n/, '')
61
- else
62
- Smailr::Domain.add(address)
63
- end
64
-
65
- when :address
66
- if options.alias
67
- source = args[0]
68
- destinations = options.alias.split(',')
69
- Smailr::Alias.add(source, destinations)
70
- else
71
- options.password ||= ask_password
72
- Smailr::Mailbox.add(address, options.password)
73
- end
74
-
75
- end
76
- end
77
- end
78
-
79
- command :ls do |c|
80
- c.syntax = 'smailr ls [domain]'
81
- c.summary = 'List domains or mailboxes of a specific domain.'
82
- c.action do |args, options|
83
- case args[0]
84
- when /^[^@][A-Z0-9.-]+\.[A-Z]{2,6}$/i then
85
- domain = Smailr::Model::Domain[:fqdn => args[0]]
86
- domain.mailboxes.each do |mbox|
87
- puts "m: #{mbox.localpart}@#{args[0]}"
88
- end
89
- domain.aliases.each do |aliass|
90
- puts "a: #{aliass.localpart}@#{args[0]} > #{aliass.dstlocalpart}@#{aliass.dstdomain}"
91
- end
92
- when nil
93
- domains = Smailr::DB[:domains]
94
- domains.all.each do |d|
95
- domain = Smailr::Model::Domain[:fqdn => d[:fqdn]]
96
- puts d[:fqdn]
97
- end
98
- else
99
- error "You can either list a domains or a domains addresses."
100
- exit 1
101
- end
102
- end
103
- end
104
-
105
- command :rm do |c|
106
- c.syntax = 'smailr rm domain | mailbox [options]'
107
- c.summary = 'Remove a domain, mailbox or alias known to the mail system.'
108
- c.example 'Remove a domain', 'smailr rm example.com'
109
- c.option '--force', 'Force the operation, do not ask for confirmation.'
110
- c.option '--dkim SELECTOR', String, 'Remove a dkim key.'
111
- c.option '--alias DESTINATION', String, 'Specify the destination you want to remove from the alias.'
112
- c.action do |args, options|
113
- address = args[0]
114
- type = determine_object(address)
115
- case type
116
- when :domain
117
- if options.dkim
118
- selecotr = options.dkim
119
- Smailr::Dkim.rm(address, selector)
120
- else
121
- Smailr::Domain.rm(address, options.force)
122
- end
123
-
124
- when :address
125
- if options.alias
126
- source = args[0]
127
- destinations = options.alias.split(',')
8
+ #require 'rubygems'
9
+ #require 'bundler/setup'
128
10
 
129
- Smailr::Alias.rm(source, destinations)
130
- else
131
- Smailr::Mailbox.rm(address, options)
132
- end
133
- end
134
- end
135
- end
136
-
137
- command :passwd do |c|
138
- c.syntax = 'smailr passwd mailbox'
139
- c.summary = 'Update a users password.'
140
- c.action do |args,options|
141
- address = args[0]
142
- password = ask_password
143
- Smailr::Mailbox.update_password(address, password)
144
- end
145
- end
146
-
147
-
148
- command :setup do |c|
149
- c.syntax = 'smailr setup'
150
- c.summary = 'Install all required components on a mailserver'
151
- c.action do |args,options|
152
- Smailr::Setup.run
153
- end
154
- end
155
-
156
-
157
- command :migrate do |c|
158
- c.syntax = 'smailr migrate [options]'
159
- c.summary = 'Create database and run migrations'
160
- c.option '--to VERSION', String, 'Migrate the database to a specifict version.'
161
- c.action do |args,options|
162
- require 'sequel/extensions/migration'
163
- raise "Database not configured" unless Smailr::DB
164
-
165
- if options.version.nil?
166
- Sequel::Migrator.apply(Smailr::DB, Smailr.migrations_directory)
167
- else
168
- Sequel::Migrator.apply(Smailr::DB, Smailr.migrations_directory, :target => options.version.to_i)
169
- end
170
- end
171
- end
172
-
173
-
174
- command :mutt do |c|
175
- base = Smailr.config["mail_spool_path"]
176
-
177
- c.syntax = "smailr mutt address"
178
- c.summary = "View the mailbox of the specified address in mutt."
179
- c.description = "Open the mailbox of the specified address in mutt.\n\n " +
180
- "Requires that mutt is installed and tries to find a suitable maildir in: " + base
181
- c.example 'Open test@example.com', 'smailr mutt test@example.com'
182
- c.action do |args,options|
183
- localpart, fqdn = args[0].split('@')
184
-
185
- mutt = `command -v mutt || { echo "Please install mutt first. Aborting." >&2; exit 1; }`
186
- if $?
187
-
188
- possibilities = [
189
- "#{base}/#{fqdn}/#{localpart}/Maildir",
190
- "#{base}/users/#{fqdn}/#{localpart}/Maildir",
191
- "#{base}/users/#{fqdn}/#{localpart}/.maildir",
192
- "#{base}/users/#{fqdn}/#{localpart}"
193
- ]
194
-
195
- possibilities.each do |path|
196
- if File.readable?(path)
197
- puts "Opening maildir #{path} with mutt."
198
- exec "MAIL=#{path} MAILDIR=#{path} #{mutt} -mMaildir"
199
- end
200
- end
201
- end
202
- end
203
- end
204
-
205
- command :verify do |c|
206
- c.syntax = "smailr verify address"
207
- c.summary = "Send out a test message to verify a domains configuration via verifier.port25.com"
208
- c.description = "A reply email will be sent back to you with an analysis of the message’s authentication" +
209
- "status. The report will perform the following checks: SPF, SenderID, DomainKeys, DKIM " +
210
- "and Spamassassin.\n\n"
211
- c.example 'Verify test@example.com (report will be sent to test@example.com)', 'smailr verify test@example.com'
212
- c.example 'Verify test@example.com, send report to root@example.com', 'smailr verify test@example.com --report-to root@example.com'
213
-
214
- c.option '-r DESTINATION', '--report-to DESTINATION', String, 'Send the report to the specified address instead.'
215
-
216
- c.action do |args,options|
217
- from = args[0]
218
- options.default :report_to => from
219
-
220
- dstlocalpart, dstfqdn = options.report_to.split('@')
221
-
222
- require 'socket'
223
- require 'date'
224
- require 'net/smtp'
225
- Net::SMTP.start('localhost', 25) do |smtp|
226
- to = "check-auth-#{dstlocalpart}=#{dstfqdn}@verifier.port25.com"
227
-
228
- message = [
229
- "From: #{from}",
230
- "To: #{to}",
231
- "Subject: Port25 Mail Verification Test",
232
- "Date: #{DateTime.now.strftime("%a, %d %b %Y %H:%M:%S %z")}",
233
- "",
234
- "This is a test message for the port25 mail verification test, it was",
235
- "sent from the following server: #{Socket.gethostname}."
236
- ].join("\r\n")
11
+ require 'smailr'
12
+ require 'smailr/cli'
13
+ require 'smailr/setup'
237
14
 
238
- smtp.send_message(message, from, to)
239
- end
240
- end
241
- end
15
+ Smailr::Cli.new.run
@@ -1,47 +1,73 @@
1
1
  require 'rubygems'
2
- require 'yaml'
3
- require 'sqlite3'
4
- require 'sequel'
5
- require 'commander/import'
2
+
6
3
  require 'fileutils'
4
+ require 'logger'
5
+ require 'sequel'
6
+ require 'sqlite3'
7
+ require 'yaml'
8
+
9
+ # dkim
10
+ require 'date'
11
+ require 'openssl'
12
+
13
+ require 'smailr/alias'
14
+ require 'smailr/dkim'
15
+ require 'smailr/domain'
16
+ require 'smailr/mailbox'
7
17
 
8
18
  module Smailr
9
- VERSION = '0.6.2'
10
-
11
- autoload :Model, 'smailr/model'
12
- autoload :Domain, 'smailr/domain'
13
- autoload :Mailbox, 'smailr/mailbox'
14
- autoload :Alias, 'smailr/alias'
15
- autoload :Dkim, 'smailr/dkim'
16
- autoload :Setup, 'smailr/setup'
17
-
18
- class << self;
19
- attr_accessor :config
20
- attr_accessor :config_files
21
- attr_accessor :load_config
22
- attr_accessor :contrib_directory
23
- attr_accessor :migrations_directory
24
- end
25
19
 
26
- def self.load_config
27
- config = {}
28
- config_files.each do |f|
29
- if File.readable?(f)
30
- config.merge!(YAML.load_file(f))
31
- end
32
- end
33
- self.config = config
34
- end
20
+ # Exception Classes
21
+ class MissingDomain < StandardError ; end
22
+
23
+ VERSION = '0.7.0'
35
24
 
36
- def self.db_connect
37
- Sequel.connect(self.config['database'])
25
+ class << self;
26
+ attr_accessor :config
27
+ attr_accessor :config_files
28
+ attr_accessor :load_config
29
+ attr_accessor :contrib_directory
30
+ attr_accessor :migrations_directory
31
+ end
32
+
33
+ def self.load_config
34
+ config = {}
35
+ config_files.each do |f|
36
+ if File.readable?(f)
37
+ config.merge!(YAML.load_file(f))
38
+ end
38
39
  end
40
+ self.config = config
41
+ end
42
+
43
+ def self.db_connect
44
+ Sequel.connect(self.config['database'])
45
+ end
39
46
 
47
+ def self.logger
48
+ unless @logger
49
+ @logger = Logger.new(STDOUT)
50
+ @logger.level = Logger::Severity::DEBUG
51
+ @logger.formatter = proc do |severity, datetime, progname, msg|
52
+ if severity == "ERROR"
53
+ "ERROR: #{msg}\n"
54
+ else
55
+ "#{msg}\n"
56
+ end
57
+ end
58
+ end
59
+ @logger
60
+ end
40
61
 
62
+ def self.logger=(logger)
63
+ @logger = logger
64
+ end
41
65
  end
42
66
 
43
- Smailr.contrib_directory ||= File.expand_path('../../contrib', __FILE__)
44
- Smailr.migrations_directory ||= File.expand_path('../../migrations', __FILE__)
45
- Smailr.config_files ||= [ File.expand_path('../../smailr.yml', __FILE__), '/etc/smailr.yml']
67
+ Smailr.contrib_directory ||= File.expand_path('../../contrib', __FILE__)
68
+ Smailr.migrations_directory ||= File.expand_path('../../migrations', __FILE__)
69
+ Smailr.config_files ||= [ File.expand_path('../../smailr.yml', __FILE__), '/etc/smailr.yml']
46
70
  Smailr.load_config
47
71
  Smailr::DB = Smailr::db_connect
72
+ require 'smailr/model'
73
+ Smailr::DB.sql_log_level = :debug
@@ -1,40 +1,39 @@
1
1
  module Smailr
2
- module Alias
3
- def self.add(source, destinations)
4
- srclocalpart, srcdomain = source.split('@')
2
+ class Alias
3
+ def self.add(source, destinations)
4
+ srclocalpart, srcdomain = source.split('@')
5
5
 
6
- # We don't want aliases for non-local domains, since the
7
- # exim router won't accept it.
8
- if not Model::Domain[:fqdn => srcdomain].exists?
9
- say_error "You are trying to add an alias for a non-local domain: #{source}"
10
- exit 1
11
- end
6
+ # We don't want aliases for non-local domains, since the
7
+ # exim router won't accept it.
8
+ if not Model::Domain[:fqdn => srcdomain].exists?
9
+ raise MissingDomain, "You are trying to add an alias for a non existing domain: #{source}"
10
+ end
12
11
 
13
- destinations.each do |dst|
14
- dstlocalpart, dstdomain = dst.split('@')
12
+ destinations.each do |dst|
13
+ dstlocalpart, dstdomain = dst.split('@')
15
14
 
16
- puts "Adding alias: #{source} -> #{dst}"
15
+ Smailr::logger.warn("Adding alias: #{source} -> #{dst}")
17
16
 
18
- Model::Alias.find_or_create(:domain => Model::Domain[:fqdn => srcdomain],
19
- :localpart => srclocalpart,
20
- :dstdomain => dstdomain,
21
- :dstlocalpart => dstlocalpart)
22
- end
23
- end
17
+ Model::Alias.find_or_create(:domain => Model::Domain[:fqdn => srcdomain],
18
+ :localpart => srclocalpart,
19
+ :dstdomain => dstdomain,
20
+ :dstlocalpart => dstlocalpart)
21
+ end
22
+ end
24
23
 
25
- def self.rm(source, destinations)
26
- srclocalpart, srcdomain = source.split('@')
24
+ def self.rm(source, destinations)
25
+ srclocalpart, srcdomain = source.split('@')
27
26
 
28
- destinations.each do |dst|
29
- puts "Removing alias: #{source} -> #{dst}"
27
+ destinations.each do |dst|
28
+ Smailr::logger.warn("Removing alias: #{source} -> #{dst}")
30
29
 
31
- dstlocalpart, dstdomain = dst.split('@')
30
+ dstlocalpart, dstdomain = dst.split('@')
32
31
 
33
- Model::Alias.filter(:domain => Model::Domain[:fqdn => srcdomain],
34
- :localpart => srclocalpart,
35
- :dstdomain => dstdomain,
36
- :dstlocalpart => dstlocalpart).delete
37
- end
38
- end
32
+ Model::Alias.filter(:domain => Model::Domain[:fqdn => srcdomain],
33
+ :localpart => srclocalpart,
34
+ :dstdomain => dstdomain,
35
+ :dstlocalpart => dstlocalpart).delete
36
+ end
39
37
  end
38
+ end
40
39
  end
@@ -0,0 +1,262 @@
1
+ require 'commander'
2
+
3
+ module Smailr
4
+ class Cli
5
+
6
+ include Commander::Methods
7
+
8
+ ## Our own CLI Helpers
9
+
10
+ # Determine whether we the passed string is a domain or a mail
11
+ # address.
12
+ #
13
+ # Returns either :domain or :address
14
+ def determine_object(string)
15
+ return :domain if string =~ /^[^@][A-Z0-9.-]+\.[A-Z]{2,6}$/i
16
+ return :address if string =~ /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,6}$/i
17
+ end
18
+
19
+ # Run an interactive cli dialog to enter and confirm a password.
20
+ #
21
+ # Returns a string containing the entered password if password and
22
+ # confirmation match.
23
+ def ask_password
24
+ min_password_length = Smailr.config["password_policy"]["length"]
25
+
26
+ password = ask("Password: ") { |q| q.echo = "*" }
27
+ confirm = ask("Confirm: ") { |q| q.echo = "*" }
28
+
29
+ if password != confirm
30
+ say("Mismatch; try again.")
31
+ ask_password
32
+ end
33
+
34
+ if password.length < min_password_length.to_i
35
+ say("Too short; try again.")
36
+ ask_password
37
+ end
38
+
39
+ password
40
+ end
41
+
42
+ # Initialize the Cli
43
+ def run
44
+ program :description, 'smailr - Virtual Mail Hosting Management CLI'
45
+ program :version, Smailr::VERSION
46
+
47
+ ### Commands
48
+
49
+ command :add do |c|
50
+ c.syntax = 'smailr add domain | mailbox | alias [options]'
51
+ c.summary = 'Add a new domain, mailbox or alias to the mail system.'
52
+ c.example 'Add a domain', 'smailr add example.com'
53
+ c.example 'Add a mailbox', 'smailr add user@example.com'
54
+ c.example 'Add an alias', 'smailr add alias@localdom.com --alias user@example.com,user1@example.com'
55
+ c.example 'Setup DKIM for a domain', 'smailr add ono.at --dkim'
56
+ c.option '--alias DESTINATION', String, 'Specify the alias destination.'
57
+ c.option '--password PASSWORD', String, 'The password for a new mailbox. If you omit this option, it prompts for one.'
58
+ c.option '--dkim SELECTOR', String, 'Add a DKIM Key with the specified selector for domain.'
59
+ c.action do |args, options|
60
+ address = args[0]
61
+ type = determine_object(address)
62
+
63
+ case type
64
+ when :domain
65
+ if options.dkim
66
+ selector = options.dkim
67
+ key = Smailr::Dkim.add(address, selector)
68
+
69
+ puts "public-key " + key.split("\n").slice(1..-2).join
70
+ else
71
+ Smailr::Domain.add(address)
72
+ end
73
+
74
+ when :address
75
+ if options.alias
76
+ source = args[0]
77
+ destinations = options.alias.split(',')
78
+ Smailr::Alias.add(source, destinations)
79
+ else
80
+ options.password ||= ask_password
81
+ Smailr::Mailbox.add(address, options.password)
82
+ end
83
+
84
+ end
85
+ end
86
+ end
87
+
88
+ command :ls do |c|
89
+ c.syntax = 'smailr ls [domain]'
90
+ c.summary = 'List domains or mailboxes and aliases of a specific domain.'
91
+ c.action do |args, options|
92
+ case args[0]
93
+ when /^[^@][A-Z0-9.-]+\.[A-Z]{2,6}$/i then
94
+ domain = Smailr::Model::Domain[:fqdn => args[0]]
95
+ domain.mailboxes.each do |mbox|
96
+ puts "m: #{mbox.localpart}@#{args[0]}"
97
+ end
98
+ domain.aliases.each do |aliass|
99
+ puts "a: #{aliass.localpart}@#{args[0]} > #{aliass.dstlocalpart}@#{aliass.dstdomain}"
100
+ end
101
+ when nil
102
+ domains = Smailr::DB[:domains]
103
+ domains.all.each do |d|
104
+ domain = Smailr::Model::Domain[:fqdn => d[:fqdn]]
105
+ puts d[:fqdn]
106
+ end
107
+ else
108
+ error "You can either list a domains or a domains addresses."
109
+ exit 1
110
+ end
111
+ end
112
+ end
113
+
114
+ command :rm do |c|
115
+ c.syntax = 'smailr rm domain | mailbox [options]'
116
+ c.summary = 'Remove a domain, mailbox or alias known to the mail system.'
117
+ c.example 'Remove a domain', 'smailr rm example.com'
118
+ c.option '--force', 'Force the operation, do not ask for confirmation.'
119
+ c.option '--dkim SELECTOR', String, 'Remove a dkim key.'
120
+ c.option '--alias DESTINATION', String, 'Specify the destination you want to remove from the alias.'
121
+ c.action do |args, options|
122
+ address = args[0]
123
+ type = determine_object(address)
124
+ case type
125
+ when :domain
126
+ if options.dkim
127
+ selector = options.dkim
128
+ Smailr::Dkim.rm(address, selector)
129
+ else
130
+ Smailr::Domain.rm(address, options.force)
131
+ end
132
+
133
+ when :address
134
+ if options.alias
135
+ source = args[0]
136
+ destinations = options.alias.split(',')
137
+
138
+ Smailr::Alias.rm(source, destinations)
139
+ else
140
+ Smailr::Mailbox.rm(address, options)
141
+ end
142
+ end
143
+ end
144
+ end
145
+
146
+ command :passwd do |c|
147
+ c.syntax = 'smailr passwd mailbox'
148
+ c.summary = 'Update a users password.'
149
+ c.action do |args,options|
150
+ address = args[0]
151
+ password = ask_password
152
+ Smailr::Mailbox.update_password(address, password)
153
+ end
154
+ end
155
+
156
+
157
+ command :setup do |c|
158
+ c.syntax = 'smailr setup'
159
+ c.summary = 'Install all required components on a mailserver'
160
+ c.action do |args,options|
161
+ Smailr::Setup.new.run
162
+ end
163
+ end
164
+
165
+
166
+ command :migrate do |c|
167
+ c.syntax = 'smailr migrate [options]'
168
+ c.summary = 'Create database and run migrations'
169
+ c.option '--to VERSION', String, 'Migrate the database to a specifict version.'
170
+ c.action do |args,options|
171
+ require 'sequel/extensions/migration'
172
+ raise "Database not configured" unless Smailr::DB
173
+
174
+ if options.to.nil?
175
+ if Sequel::Migrator.is_current?(Smailr::DB, Smailr.migrations_directory)
176
+ puts "Database schema already up to date. Exiting"
177
+ exit 0
178
+ end
179
+
180
+ puts "Running database migrations to latest version."
181
+ Sequel::Migrator.apply(Smailr::DB, Smailr.migrations_directory)
182
+ else
183
+ puts "Running database migrations to version: #{options.to}"
184
+ Sequel::Migrator.apply(Smailr::DB, Smailr.migrations_directory, options.to.to_i)
185
+ end
186
+ end
187
+ end
188
+
189
+
190
+ command :mutt do |c|
191
+ base = Smailr.config["mail_spool_path"]
192
+
193
+ c.syntax = "smailr mutt address"
194
+ c.summary = "View the mailbox of the specified address in mutt."
195
+ c.description = "Open the mailbox of the specified address in mutt.\n\n " +
196
+ "Requires that mutt is installed and tries to find a suitable maildir in: " + base
197
+ c.example 'Open test@example.com', 'smailr mutt test@example.com'
198
+ c.action do |args,options|
199
+ localpart, fqdn = args[0].split('@')
200
+
201
+ mutt = `command -v mutt || { echo "Please install mutt first. Aborting." >&2; exit 1; }`
202
+ if $?
203
+
204
+ possibilities = [
205
+ "#{base}/#{fqdn}/#{localpart}/Maildir",
206
+ "#{base}/users/#{fqdn}/#{localpart}/Maildir",
207
+ "#{base}/users/#{fqdn}/#{localpart}/.maildir",
208
+ "#{base}/users/#{fqdn}/#{localpart}"
209
+ ]
210
+
211
+ possibilities.each do |path|
212
+ if File.readable?(path)
213
+ puts "Opening maildir #{path} with mutt."
214
+ exec "MAIL=#{path} MAILDIR=#{path} #{mutt} -mMaildir"
215
+ end
216
+ end
217
+ end
218
+ end
219
+ end
220
+
221
+ command :verify do |c|
222
+ c.syntax = "smailr verify address"
223
+ c.summary = "Send out a test message to verify a domains configuration via verifier.port25.com"
224
+ c.description = "A reply email will be sent back to you with an analysis of the message’s authentication" +
225
+ "status. The report will perform the following checks: SPF, SenderID, DomainKeys, DKIM " +
226
+ "and Spamassassin.\n\n"
227
+ c.example 'Verify test@example.com (report will be sent to test@example.com)', 'smailr verify test@example.com'
228
+ c.example 'Verify test@example.com, send report to root@example.com', 'smailr verify test@example.com --report-to root@example.com'
229
+
230
+ c.option '-r DESTINATION', '--report-to DESTINATION', String, 'Send the report to the specified address instead.'
231
+
232
+ c.action do |args,options|
233
+ from = args[0]
234
+ options.default :report_to => from
235
+
236
+ dstlocalpart, dstfqdn = options.report_to.split('@')
237
+
238
+ require 'socket'
239
+ require 'date'
240
+ require 'net/smtp'
241
+ Net::SMTP.start('localhost', 25) do |smtp|
242
+ to = "check-auth-#{dstlocalpart}=#{dstfqdn}@verifier.port25.com"
243
+
244
+ message = [
245
+ "From: #{from}",
246
+ "To: #{to}",
247
+ "Subject: Port25 Mail Verification Test",
248
+ "Date: #{DateTime.now.strftime("%a, %d %b %Y %H:%M:%S %z")}",
249
+ "",
250
+ "This is a test message for the port25 mail verification test, it was",
251
+ "sent from the following server: #{Socket.gethostname}."
252
+ ].join("\r\n")
253
+
254
+ smtp.send_message(message, from, to)
255
+ end
256
+ end
257
+ end
258
+
259
+ run!
260
+ end
261
+ end
262
+ end
@@ -1,37 +1,33 @@
1
- require 'date'
2
- require 'openssl'
3
-
4
1
  module Smailr
5
- module Dkim
6
- def self.add(fqdn, selector)
7
- if not Model::Domain[:fqdn => fqdn]
8
- say_error "You trying to add a DKIM key for a non existing domain: #{fqdn}"
9
- exit 1
10
- end
2
+ class Dkim
3
+ def self.add(fqdn, selector)
4
+ unless Model::Domain[:fqdn => fqdn]
5
+ raise MissingDomain, "You trying to add a DKIM key for a non existing domain: #{fqdn}"
6
+ end
11
7
 
12
- private_key, public_key = generate_rsa_key
8
+ private_key, public_key = generate_rsa_key
13
9
 
14
- dkim = Model::Dkim.for_domain!(fqdn, selector)
15
- dkim.private_key = private_key
16
- dkim.public_key = public_key
17
- dkim.selector = selector
18
- dkim.save
10
+ dkim = Model::Dkim.for_domain!(fqdn, selector)
11
+ dkim.private_key = private_key
12
+ dkim.public_key = public_key
13
+ dkim.selector = selector
14
+ dkim.save
19
15
 
20
- # Return the key so it can be used for automation
21
- dkim.public_key
22
- end
16
+ # Return the key so it can be used for automation
17
+ dkim.public_key
18
+ end
23
19
 
24
- def self.rm(fqdn, selector)
25
- dkim = Model::Dkim.for_domain(fqdn, selector)
26
- dkim.destroy
27
- end
20
+ def self.rm(fqdn, selector)
21
+ dkim = Model::Dkim.for_domain(fqdn, selector)
22
+ dkim.destroy
23
+ end
28
24
 
29
- private
25
+ private
30
26
 
31
- def self.generate_rsa_key(length = 1024)
32
- rsa_key = OpenSSL::PKey::RSA.new(length)
33
- [ rsa_key.to_pem,
34
- rsa_key.public_key.to_pem ]
35
- end
27
+ def self.generate_rsa_key(length = 1024)
28
+ rsa_key = OpenSSL::PKey::RSA.new(length)
29
+ [ rsa_key.to_pem,
30
+ rsa_key.public_key.to_pem ]
36
31
  end
32
+ end
37
33
  end
@@ -1,18 +1,17 @@
1
1
  module Smailr
2
- module Domain
3
- def self.add(fqdn)
4
- puts "Adding domain: #{fqdn}"
5
- Model::Domain.create(:fqdn => fqdn)
6
- end
7
-
8
- def self.rm(fqdn, force = false)
9
- if force or
10
- agree("Do you want to remove the domain #{fqdn} and all related items? (yes/no) ")
2
+ class Domain
3
+ def self.add(fqdn)
4
+ Smailr::logger.warn("Adding domain: #{fqdn}")
5
+ Model::Domain.create(:fqdn => fqdn)
6
+ end
11
7
 
12
- domain = Model::Domain[:fqdn => fqdn]
13
- domain.rm_related
14
- domain.destroy
15
- end
16
- end
8
+ def self.rm(fqdn, force = false)
9
+ # TODO - only require force, if related entries exist
10
+ if force
11
+ domain = Model::Domain[:fqdn => fqdn]
12
+ domain.rm_related
13
+ domain.destroy
14
+ end
17
15
  end
16
+ end
18
17
  end
@@ -1,32 +1,31 @@
1
1
  module Smailr
2
- module Mailbox
3
- def self.add(address, password)
4
- puts "Adding mailbox: #{address}"
2
+ class Mailbox
3
+ def self.add(address, password)
4
+ Smailr::logger.warn("Adding mailbox: #{address}")
5
5
 
6
- fqdn = address.split('@')[1]
6
+ fqdn = address.split('@')[1]
7
7
 
8
- if not Model::Domain[:fqdn => fqdn]
9
- say_error "Trying to add a mailbox for a non existing domain: #{fqdn}"
10
- exit 1
11
- end
8
+ if not Model::Domain[:fqdn => fqdn]
9
+ raise MissingDomain, "Trying to add a mailbox for a non existing domain: #{fqdn}"
10
+ end
12
11
 
13
- mbox = Model::Mailbox.for_address!(address)
14
- mbox.password = password
15
- mbox.save
16
- end
12
+ mbox = Model::Mailbox.for_address!(address)
13
+ mbox.password = password
14
+ mbox.save
15
+ end
17
16
 
18
- def self.update_password(address, password)
19
- mbox = Model::Mailbox.for_address(address)
20
- mbox.password = password
21
- mbox.save
22
- end
17
+ def self.update_password(address, password)
18
+ mbox = Model::Mailbox.for_address(address)
19
+ mbox.password = password
20
+ mbox.save
21
+ end
23
22
 
24
- def self.rm(address, options)
25
- puts "Removing mailbox (from database): #{address}"
23
+ def self.rm(address, options)
24
+ Smailr::logger.warn("Removing mailbox (from database): #{address}")
26
25
 
27
- mbox = Model::Mailbox.for_address(address)
28
- mbox.rm_related
29
- mbox.destroy
30
- end
26
+ mbox = Model::Mailbox.for_address(address)
27
+ mbox.rm_related
28
+ mbox.destroy
31
29
  end
30
+ end
32
31
  end
@@ -1,68 +1,68 @@
1
1
  require 'digest/sha1'
2
2
 
3
3
  module Smailr
4
- module Model
5
- class Domain < Sequel::Model
6
- one_to_many :mailboxes
7
- one_to_many :aliases
8
- one_to_many :dkims
4
+ module Model
5
+ class Domain < Sequel::Model
6
+ one_to_many :mailboxes
7
+ one_to_many :aliases
8
+ one_to_many :dkims
9
9
 
10
- def rm_related
11
- self.remove_all_mailboxes
12
- self.remove_all_aliases
13
- self.remove_all_dkims
14
- end
15
- end
10
+ def rm_related
11
+ self.remove_all_mailboxes
12
+ self.remove_all_aliases
13
+ self.remove_all_dkims
14
+ end
15
+ end
16
16
 
17
- class Dkim < Sequel::Model
18
- many_to_one :domain
17
+ class Dkim < Sequel::Model
18
+ many_to_one :domain
19
19
 
20
- def self.for_domain(fqdn, selector)
21
- self[:domain => Domain[:fqdn => fqdn], :selector => selector]
22
- end
20
+ def self.for_domain(fqdn, selector)
21
+ self[:domain => Domain[:fqdn => fqdn], :selector => selector]
22
+ end
23
23
 
24
- def self.for_domain!(fqdn, selector)
25
- find_or_create(:domain => Domain[:fqdn => fqdn], :selector => selector)
26
- end
27
- end
24
+ def self.for_domain!(fqdn, selector)
25
+ find_or_create(:domain => Domain[:fqdn => fqdn], :selector => selector)
26
+ end
27
+ end
28
28
 
29
- class Mailbox < Sequel::Model
30
- many_to_one :domain
29
+ class Mailbox < Sequel::Model
30
+ many_to_one :domain
31
31
 
32
- def password=(clear)
33
- self[:password] = Digest::SHA1.hexdigest(clear)
34
- self[:password_scheme] = "{SHA}"
35
- end
32
+ def password=(clear)
33
+ self[:password_scheme] = '{SHA}'
34
+ self[:password] = Digest::SHA1.hexdigest(clear)
35
+ end
36
36
 
37
- def rm_related
38
- self.aliases.destroy
39
- end
37
+ def rm_related
38
+ self.aliases.destroy
39
+ end
40
40
 
41
- def aliases
42
- Model::Alias.where(
43
- :dstlocalpart => self.localpart,
44
- :dstdomain => self.domain.fqdn
45
- )
46
- end
41
+ def aliases
42
+ Model::Alias.where(
43
+ :dstlocalpart => self.localpart,
44
+ :dstdomain => self.domain.fqdn
45
+ )
46
+ end
47
47
 
48
- def self.domain(fqdn)
49
- Domain[:fqdn => fqdn]
50
- end
48
+ def self.domain(fqdn)
49
+ Domain[:fqdn => fqdn]
50
+ end
51
51
 
52
- def self.for_address(address)
53
- localpart, fqdn = address.split('@')
54
- self[:localpart => localpart, :domain => domain(fqdn)]
55
- end
52
+ def self.for_address(address)
53
+ localpart, fqdn = address.split('@')
54
+ self[:localpart => localpart, :domain => domain(fqdn)]
55
+ end
56
56
 
57
- def self.for_address!(address)
58
- localpart, fqdn = address.split('@')
59
- find_or_create(:localpart => localpart, :domain => domain(fqdn))
60
- end
57
+ def self.for_address!(address)
58
+ localpart, fqdn = address.split('@')
59
+ find_or_create(:localpart => localpart, :domain => domain(fqdn))
60
+ end
61
61
 
62
- end
62
+ end
63
63
 
64
- class Alias < Sequel::Model
65
- many_to_one :domain
66
- end
64
+ class Alias < Sequel::Model
65
+ many_to_one :domain
67
66
  end
67
+ end
68
68
  end
@@ -1,6 +1,10 @@
1
+ require 'commander'
2
+
1
3
  module Smailr
2
- module Setup
3
- def self.run
4
+ class Setup
5
+ include Commander::Methods
6
+
7
+ def run
4
8
 
5
9
  if Process.euid != 0
6
10
  say "ERROR: YOU ARE NOT RUNNING THIS SCRIPT WITH ROOT PRIVILEGES, EXITING."
@@ -1,27 +1,27 @@
1
1
  Sequel.migration do
2
- up do
3
- puts <<-MESSAGE
2
+ up do
3
+ puts <<-MESSAGE
4
4
 
5
- WARNING ---------------------------------------------------------------------------------
5
+ WARNING ---------------------------------------------------------------------------------
6
6
 
7
- You need to adapt your mailserver configuration with this version of smailr, as
8
- passwords are now stored including the hash scheme.
7
+ You need to adapt your mailserver configuration with this version of smailr, as
8
+ passwords are now stored including the hash scheme.
9
9
 
10
- Select the hash from `mailboxes.password` and the scheme from `mailboxes.password_scheme`
10
+ Select the hash from `mailboxes.password` and the scheme from `mailboxes.password_scheme`
11
11
 
12
- --------------------------------------------------------------------------------- WARNING
12
+ --------------------------------------------------------------------------------- WARNING
13
13
 
14
- MESSAGE
14
+ MESSAGE
15
15
 
16
- add_column :mailboxes, :password_scheme, String
16
+ add_column :mailboxes, :password_scheme, String
17
17
 
18
- from(:mailboxes).update(password_scheme: "{SHA}")
18
+ from(:mailboxes).update(password_scheme: "{SHA}")
19
19
 
20
- alter_table(:mailboxes) do
21
- set_column_not_null(:password_scheme)
22
- end
20
+ alter_table(:mailboxes) do
21
+ set_column_not_null(:password_scheme)
23
22
  end
23
+ end
24
24
 
25
- # Downgrade is not supported, as we would drop necessary information do do effective hashing
26
- # in case this feature was already used to add various hashes to the database
25
+ # Downgrade is not supported, as we would drop necessary information do do effective hashing
26
+ # in case this feature was already used to add various hashes to the database
27
27
  end
@@ -0,0 +1,12 @@
1
+ Sequel.migration do
2
+ up do
3
+ # Remove this until we figured out whats wrong with error handling
4
+ # and we can do it for all fields
5
+ alter_table(:mailboxes) do
6
+ set_column_allow_null(:password_scheme)
7
+ end
8
+ end
9
+
10
+ # Downgrade is not supported, as we would drop necessary information do do effective hashing
11
+ # in case this feature was already used to add various hashes to the database
12
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smailr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.2
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stefan Schlesinger
@@ -14,30 +14,30 @@ dependencies:
14
14
  name: commander
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: '4.3'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: '4.3'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: sequel
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: '4.26'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ">="
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: '4.26'
41
41
  description: |-
42
42
  Smailr is a CLI tool which lets you manage your virtual mailhosting setup
43
43
  from the shell. It currently uses SQLite as a backend, samples for Dovecot/Exim provided.
@@ -48,14 +48,13 @@ extensions: []
48
48
  extra_rdoc_files: []
49
49
  files:
50
50
  - README.md
51
- - bin/commander
52
- - bin/sequel
53
51
  - bin/smailr
54
52
  - contrib/dovecot-sql.conf
55
53
  - contrib/dovecot.conf
56
54
  - contrib/exim4.conf
57
55
  - lib/smailr.rb
58
56
  - lib/smailr/alias.rb
57
+ - lib/smailr/cli.rb
59
58
  - lib/smailr/dkim.rb
60
59
  - lib/smailr/domain.rb
61
60
  - lib/smailr/mailbox.rb
@@ -66,9 +65,11 @@ files:
66
65
  - migrations/003_aliases.rb
67
66
  - migrations/004_dkims.rb
68
67
  - migrations/005_mailboxes_add_digest_identifier_to_passwords.rb
68
+ - migrations/006_mailboxes_password_schema_allow_null.rb
69
69
  - smailr.yml
70
70
  homepage: http://github.com/sts/smailr
71
- licenses: []
71
+ licenses:
72
+ - Apache-2.0
72
73
  metadata: {}
73
74
  post_install_message: |2+
74
75
 
@@ -81,7 +82,7 @@ post_install_message: |2+
81
82
 
82
83
  * Install Dovecot with SQlite support
83
84
 
84
- * run export PATH="/var/lib/gems/1.8/bin:${PATH}"
85
+ * run ln -s `gem contents smailr|grep bin/smailr` /usr/local/sbin
85
86
 
86
87
  * run "smailr setup" to create exim, dovecot and smailr configuration (you
87
88
  can edit the configuration in an editor window before everyting is
@@ -107,7 +108,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
107
108
  requirements:
108
109
  - Exim
109
110
  - Dovecot
110
- - Debian
111
111
  rubyforge_project:
112
112
  rubygems_version: 2.4.6
113
113
  signing_key:
@@ -1,16 +0,0 @@
1
- #!/usr/bin/env ruby
2
- #
3
- # This file was generated by Bundler.
4
- #
5
- # The application 'commander' is installed as part of a gem, and
6
- # this file is here to facilitate running it.
7
- #
8
-
9
- require 'pathname'
10
- ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
- Pathname.new(__FILE__).realpath)
12
-
13
- require 'rubygems'
14
- require 'bundler/setup'
15
-
16
- load Gem.bin_path('commander', 'commander')
data/bin/sequel DELETED
@@ -1,16 +0,0 @@
1
- #!/usr/bin/env ruby
2
- #
3
- # This file was generated by Bundler.
4
- #
5
- # The application 'sequel' is installed as part of a gem, and
6
- # this file is here to facilitate running it.
7
- #
8
-
9
- require 'pathname'
10
- ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
- Pathname.new(__FILE__).realpath)
12
-
13
- require 'rubygems'
14
- require 'bundler/setup'
15
-
16
- load Gem.bin_path('sequel', 'sequel')