smailr 0.6.2 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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')