postfix_admin 0.1.4 → 0.2.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 +4 -4
- data/.rubocop.yml +2 -0
- data/CHANGELOG.md +7 -5
- data/Dockerfile +24 -0
- data/README.md +22 -15
- data/Rakefile +5 -0
- data/bin/console +18 -0
- data/docker-compose.yml +24 -0
- data/docker-entrypoint.sh +5 -0
- data/{bin → exe}/postfix_admin +1 -0
- data/lib/postfix_admin.rb +1 -1
- data/lib/postfix_admin/admin.rb +52 -0
- data/lib/postfix_admin/alias.rb +65 -0
- data/lib/postfix_admin/application_record.rb +44 -0
- data/lib/postfix_admin/base.rb +98 -88
- data/lib/postfix_admin/cli.rb +50 -46
- data/lib/postfix_admin/concerns/.keep +0 -0
- data/lib/postfix_admin/concerns/dovecot_cram_md5_password.rb +30 -0
- data/lib/postfix_admin/concerns/existing_timestamp.rb +18 -0
- data/lib/postfix_admin/domain.rb +98 -0
- data/lib/postfix_admin/domain_admin.rb +8 -0
- data/lib/postfix_admin/doveadm.rb +1 -1
- data/lib/postfix_admin/log.rb +5 -0
- data/lib/postfix_admin/mail_domain.rb +9 -0
- data/lib/postfix_admin/mailbox.rb +89 -0
- data/lib/postfix_admin/models.rb +10 -213
- data/lib/postfix_admin/quota.rb +6 -0
- data/lib/postfix_admin/runner.rb +39 -36
- data/lib/postfix_admin/version.rb +1 -1
- data/postfix_admin.gemspec +20 -13
- metadata +49 -50
- data/spec/base_spec.rb +0 -253
- data/spec/cli_spec.rb +0 -300
- data/spec/doveadm_spec.rb +0 -35
- data/spec/models_spec.rb +0 -195
- data/spec/postfix_admin.conf +0 -5
- data/spec/postfix_test.sql +0 -250
- data/spec/runner_spec.rb +0 -370
- data/spec/spec_helper.rb +0 -201
data/lib/postfix_admin/cli.rb
CHANGED
@@ -20,17 +20,21 @@ module PostfixAdmin
|
|
20
20
|
@config_file = value
|
21
21
|
end
|
22
22
|
|
23
|
+
def db_setup
|
24
|
+
@base.db_setup
|
25
|
+
end
|
26
|
+
|
23
27
|
def show(name)
|
24
28
|
name = name.downcase if name
|
25
29
|
|
26
30
|
if name =~ /@/
|
27
|
-
if Admin.
|
31
|
+
if Admin.exists?(name)
|
28
32
|
show_admin_details(name)
|
29
33
|
end
|
30
34
|
|
31
|
-
if Mailbox.
|
35
|
+
if Mailbox.exists?(name)
|
32
36
|
show_account_details(name)
|
33
|
-
elsif Alias.
|
37
|
+
elsif Alias.exists?(name)
|
34
38
|
show_alias_details(name)
|
35
39
|
end
|
36
40
|
|
@@ -49,7 +53,7 @@ module PostfixAdmin
|
|
49
53
|
end
|
50
54
|
end
|
51
55
|
|
52
|
-
def show_summary(domain_name=nil)
|
56
|
+
def show_summary(domain_name = nil)
|
53
57
|
title = "Summary"
|
54
58
|
if domain_name
|
55
59
|
domain_name = domain_name.downcase
|
@@ -60,15 +64,15 @@ module PostfixAdmin
|
|
60
64
|
report(title) do
|
61
65
|
if domain_name
|
62
66
|
domain = Domain.find(domain_name)
|
63
|
-
puts "Mailboxes : %4d / %4s" % [domain.
|
64
|
-
puts "Aliases : %4d / %4s" % [domain.
|
67
|
+
puts "Mailboxes : %4d / %4s" % [domain.rel_mailboxes.count, max_str(domain.mailboxes)]
|
68
|
+
puts "Aliases : %4d / %4s" % [domain.pure_aliases.count, max_str(domain.aliases)]
|
65
69
|
puts "Max Quota : %4d MB" % domain.maxquota
|
66
70
|
puts "Active : %3s" % domain.active_str
|
67
71
|
else
|
68
|
-
puts "Domains : %4d" % Domain.
|
72
|
+
puts "Domains : %4d" % Domain.without_all.count
|
69
73
|
puts "Admins : %4d" % Admin.count
|
70
74
|
puts "Mailboxes : %4d" % Mailbox.count
|
71
|
-
puts "Aliases : %4d" %
|
75
|
+
puts "Aliases : %4d" % Alias.pure.count
|
72
76
|
end
|
73
77
|
end
|
74
78
|
end
|
@@ -102,7 +106,7 @@ module PostfixAdmin
|
|
102
106
|
report("Admin") do
|
103
107
|
puts "Name : %s" % admin.username
|
104
108
|
puts "Password : %s" % admin.password
|
105
|
-
puts "Domains : %s" % (admin.super_admin? ? "ALL" : admin.
|
109
|
+
puts "Domains : %s" % (admin.super_admin? ? "ALL" : admin.rel_domains.count)
|
106
110
|
puts "Role : %s" % (admin.super_admin? ? "Super admin" : "Admin")
|
107
111
|
puts "Active : %s" % admin.active_str
|
108
112
|
end
|
@@ -121,18 +125,17 @@ module PostfixAdmin
|
|
121
125
|
def show_domain
|
122
126
|
index = " No. Domain Aliases Mailboxes Quota (MB) Active"
|
123
127
|
report('Domains', index) do
|
124
|
-
if Domain.
|
128
|
+
if Domain.without_all.empty?
|
125
129
|
puts " No domains"
|
126
130
|
next
|
127
131
|
end
|
128
132
|
|
129
|
-
Domain.
|
133
|
+
Domain.without_all.each_with_index do |d, i|
|
130
134
|
puts "%4d %-30s %3d /%3s %3d /%3s %10d %-3s" %
|
131
|
-
[i+1, d.
|
132
|
-
d.
|
135
|
+
[i+1, d.domain, d.pure_aliases.count, max_str(d.aliases),
|
136
|
+
d.rel_mailboxes.count, max_str(d.mailboxes), d.maxquota, d.active_str]
|
133
137
|
end
|
134
138
|
end
|
135
|
-
|
136
139
|
end
|
137
140
|
|
138
141
|
def add_domain(domain_name)
|
@@ -151,9 +154,13 @@ module PostfixAdmin
|
|
151
154
|
def edit_admin(admin_name, options)
|
152
155
|
admin_check(admin_name)
|
153
156
|
admin = Admin.find(admin_name)
|
154
|
-
|
157
|
+
|
158
|
+
unless options[:super].nil?
|
159
|
+
admin.super_admin = options[:super]
|
160
|
+
end
|
161
|
+
|
155
162
|
admin.active = options[:active] unless options[:active].nil?
|
156
|
-
admin.save
|
163
|
+
admin.save!
|
157
164
|
|
158
165
|
puts "Successfully updated #{admin_name}"
|
159
166
|
show_admin_details(admin_name)
|
@@ -162,11 +169,11 @@ module PostfixAdmin
|
|
162
169
|
def edit_domain(domain_name, options)
|
163
170
|
domain_check(domain_name)
|
164
171
|
domain = Domain.find(domain_name)
|
165
|
-
domain.
|
166
|
-
domain.
|
172
|
+
domain.aliases = options[:aliases] if options[:aliases]
|
173
|
+
domain.mailboxes = options[:mailboxes] if options[:mailboxes]
|
167
174
|
domain.maxquota = options[:maxquota] if options[:maxquota]
|
168
175
|
domain.active = options[:active] unless options[:active].nil?
|
169
|
-
domain.save
|
176
|
+
domain.save!
|
170
177
|
|
171
178
|
puts "Successfully updated #{domain_name}"
|
172
179
|
show_summary(domain_name)
|
@@ -177,8 +184,8 @@ module PostfixAdmin
|
|
177
184
|
puts_deleted(domain_name)
|
178
185
|
end
|
179
186
|
|
180
|
-
def show_admin(domain_name=nil)
|
181
|
-
admins = domain_name ? Admin.select{|a| a.
|
187
|
+
def show_admin(domain_name = nil)
|
188
|
+
admins = domain_name ? Admin.select { |a| a.rel_domains.exists?(domain_name) } : Admin.all
|
182
189
|
index = " No. Admin Domains Active"
|
183
190
|
report("Admins", index) do
|
184
191
|
if admins.empty?
|
@@ -187,17 +194,16 @@ module PostfixAdmin
|
|
187
194
|
end
|
188
195
|
|
189
196
|
admins.each_with_index do |a, i|
|
190
|
-
domains = a.super_admin? ? 'Super admin' : a.
|
197
|
+
domains = a.super_admin? ? 'Super admin' : a.rel_domains.count
|
191
198
|
puts "%4d %-40s %11s %-3s" % [i+1, a.username, domains, a.active_str]
|
192
199
|
end
|
193
200
|
end
|
194
|
-
|
195
201
|
end
|
196
202
|
|
197
203
|
def show_address(domain_name)
|
198
204
|
domain_check(domain_name)
|
199
205
|
|
200
|
-
mailboxes = Domain.find(domain_name).
|
206
|
+
mailboxes = Domain.find(domain_name).rel_mailboxes
|
201
207
|
index = " No. Email Name Quota (MB) Active Maildir"
|
202
208
|
report("Addresses", index) do
|
203
209
|
if mailboxes.empty?
|
@@ -210,13 +216,12 @@ module PostfixAdmin
|
|
210
216
|
puts "%4d %-30s %-20s %10s %-3s %s" % [i+1, m.username, m.name, max_str(quota.to_i), m.active_str, m.maildir]
|
211
217
|
end
|
212
218
|
end
|
213
|
-
|
214
219
|
end
|
215
220
|
|
216
221
|
def show_alias(domain_name)
|
217
222
|
domain_check(domain_name)
|
218
223
|
|
219
|
-
forwards, aliases = Domain.find(domain_name).
|
224
|
+
forwards, aliases = Domain.find(domain_name).rel_aliases.partition { |a| a.mailbox? }
|
220
225
|
|
221
226
|
forwards.delete_if do |f|
|
222
227
|
f.address == f.goto
|
@@ -228,19 +233,19 @@ module PostfixAdmin
|
|
228
233
|
|
229
234
|
def show_admin_domain(user_name)
|
230
235
|
admin = Admin.find(user_name)
|
231
|
-
if admin.
|
236
|
+
if admin.rel_domains.empty?
|
232
237
|
puts "\nNo domain in database"
|
233
238
|
return
|
234
239
|
end
|
235
240
|
|
236
241
|
report("Domains (#{user_name})", " No. Domain") do
|
237
|
-
admin.
|
238
|
-
puts "%4d %-30s" % [i+1, d.
|
242
|
+
admin.rel_domains.each_with_index do |d, i|
|
243
|
+
puts "%4d %-30s" % [i + 1, d.domain]
|
239
244
|
end
|
240
245
|
end
|
241
246
|
end
|
242
247
|
|
243
|
-
def add_admin(user_name, password, super_admin=false, scheme=nil)
|
248
|
+
def add_admin(user_name, password, super_admin = false, scheme = nil)
|
244
249
|
validate_password(password)
|
245
250
|
|
246
251
|
@base.add_admin(user_name, hashed_password(password, scheme))
|
@@ -262,7 +267,7 @@ module PostfixAdmin
|
|
262
267
|
puts "#{domain_name} was successfully deleted from #{user_name}"
|
263
268
|
end
|
264
269
|
|
265
|
-
def add_account(address, password, scheme=nil, name=nil)
|
270
|
+
def add_account(address, password, scheme = nil, name = nil)
|
266
271
|
validate_password(password)
|
267
272
|
|
268
273
|
@base.add_account(address, hashed_password(password, scheme), name)
|
@@ -281,12 +286,12 @@ module PostfixAdmin
|
|
281
286
|
mailbox.name = options[:name] if options[:name]
|
282
287
|
mailbox.quota = options[:quota] * KB_TO_MB if options[:quota]
|
283
288
|
mailbox.active = options[:active] unless options[:active].nil?
|
284
|
-
mailbox.save
|
289
|
+
mailbox.save!
|
285
290
|
|
286
291
|
if options[:goto]
|
287
292
|
mail_alias = Alias.find(address)
|
288
293
|
mail_alias.goto = options[:goto]
|
289
|
-
mail_alias.save
|
294
|
+
mail_alias.save!
|
290
295
|
end
|
291
296
|
|
292
297
|
puts "Successfully updated #{address}"
|
@@ -322,7 +327,7 @@ module PostfixAdmin
|
|
322
327
|
def log
|
323
328
|
Log.all.each do |l|
|
324
329
|
time = l.timestamp.strftime("%Y-%m-%d %X %Z")
|
325
|
-
puts "#{time} #{l.username} #{l.
|
330
|
+
puts "#{time} #{l.username} #{l.domain} #{l.action} #{l.data}"
|
326
331
|
end
|
327
332
|
end
|
328
333
|
|
@@ -335,8 +340,8 @@ module PostfixAdmin
|
|
335
340
|
puts
|
336
341
|
puts "Domains"
|
337
342
|
puts "Domain Name,Max Quota,Active"
|
338
|
-
Domain.
|
339
|
-
puts [d.
|
343
|
+
Domain.without_all.each do |d|
|
344
|
+
puts [d.domain, d.maxquota, d.active].join(',')
|
340
345
|
end
|
341
346
|
puts
|
342
347
|
puts "Mailboxes"
|
@@ -347,13 +352,13 @@ module PostfixAdmin
|
|
347
352
|
puts
|
348
353
|
puts "Aliases"
|
349
354
|
puts "Address,Go to,Active"
|
350
|
-
Alias.all.select{|a| !a.mailbox? }.each do |a|
|
355
|
+
Alias.all.select { |a| !a.mailbox? }.each do |a|
|
351
356
|
puts [a.address, %Q!"#{a.goto}"!, a.active].join(',')
|
352
357
|
end
|
353
358
|
puts
|
354
359
|
puts "Forwards"
|
355
360
|
puts "Address,Go to,Active"
|
356
|
-
Alias.all.select{|a| a.mailbox? && a.goto != a.address }.each do |a|
|
361
|
+
Alias.all.select { |a| a.mailbox? && a.goto != a.address }.each do |a|
|
357
362
|
puts [a.address, %Q!"#{a.goto}"!, a.active].join(',')
|
358
363
|
end
|
359
364
|
end
|
@@ -407,7 +412,7 @@ module PostfixAdmin
|
|
407
412
|
puts "-"*120
|
408
413
|
end
|
409
414
|
|
410
|
-
def report(title, index=nil)
|
415
|
+
def report(title, index = nil)
|
411
416
|
puts "\n[#{title}]"
|
412
417
|
print_line if index
|
413
418
|
puts index if index
|
@@ -417,7 +422,7 @@ module PostfixAdmin
|
|
417
422
|
end
|
418
423
|
|
419
424
|
def account_check(user_name)
|
420
|
-
unless Mailbox.
|
425
|
+
unless Mailbox.exists?(user_name) && Alias.exists?(user_name)
|
421
426
|
raise Error, %Q!Could not find account "#{user_name}"!
|
422
427
|
end
|
423
428
|
end
|
@@ -440,7 +445,7 @@ module PostfixAdmin
|
|
440
445
|
|
441
446
|
def klass_check(klass, name)
|
442
447
|
object_name = klass.name.gsub(/PostfixAdmin::/, '').downcase
|
443
|
-
raise Error, %Q!Could not find #{object_name} "#{name}"! unless klass.
|
448
|
+
raise Error, %Q!Could not find #{object_name} "#{name}"! unless klass.exists?(name)
|
444
449
|
end
|
445
450
|
|
446
451
|
def validate_password(password)
|
@@ -450,13 +455,13 @@ module PostfixAdmin
|
|
450
455
|
end
|
451
456
|
|
452
457
|
def change_password(klass, user_name, password)
|
453
|
-
raise Error, "Could not find #{user_name}" unless klass.
|
458
|
+
raise Error, "Could not find #{user_name}" unless klass.exists?(user_name)
|
454
459
|
|
455
460
|
validate_password(password)
|
456
461
|
|
457
462
|
obj = klass.find(user_name)
|
458
|
-
|
459
|
-
if obj.
|
463
|
+
|
464
|
+
if obj.update(password: hashed_password(password))
|
460
465
|
puts "the password of #{user_name} was successfully changed."
|
461
466
|
else
|
462
467
|
raise "Could not change password of #{klass.name}"
|
@@ -476,11 +481,10 @@ module PostfixAdmin
|
|
476
481
|
|
477
482
|
private
|
478
483
|
|
479
|
-
def hashed_password(password, in_scheme=nil)
|
484
|
+
def hashed_password(password, in_scheme = nil)
|
480
485
|
scheme = in_scheme || @base.config[:scheme]
|
481
486
|
puts "scheme: #{scheme}"
|
482
487
|
PostfixAdmin::Doveadm.password(password, scheme)
|
483
488
|
end
|
484
|
-
|
485
489
|
end
|
486
490
|
end
|
File without changes
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module DovecotCramMD5Password
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
validates :password_unencrypted, length: { minimum: 5 }, allow_blank: true
|
8
|
+
validates_confirmation_of :password_unencrypted, allow_blank: true
|
9
|
+
|
10
|
+
validate do |record|
|
11
|
+
record.errors.add(:password_unencrypted, :blank) unless record.password.present?
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :password_unencrypted
|
15
|
+
attr_accessor :password_unencrypted_confirmation
|
16
|
+
end
|
17
|
+
|
18
|
+
def password_unencrypted=(unencrypted_password)
|
19
|
+
if unencrypted_password.nil?
|
20
|
+
self.password = nil
|
21
|
+
elsif !unencrypted_password.empty?
|
22
|
+
@password_unencrypted = unencrypted_password
|
23
|
+
self.password = DovecotCrammd5.calc(unencrypted_password)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def authenticate(unencrypted_password)
|
28
|
+
password == DovecotCrammd5.calc(unencrypted_password) && self
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module ExistingTimestamp
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
class_methods do
|
7
|
+
private
|
8
|
+
|
9
|
+
def timestamp_attributes_for_create
|
10
|
+
["created"]
|
11
|
+
end
|
12
|
+
|
13
|
+
def timestamp_attributes_for_update
|
14
|
+
["modified"]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module PostfixAdmin
|
2
|
+
class Domain < ApplicationRecord
|
3
|
+
self.table_name = :domain
|
4
|
+
self.primary_key = :domain
|
5
|
+
|
6
|
+
validates :domain, presence: true, uniqueness: { case_sensitive: false },
|
7
|
+
format: { with: RE_DOMAIN_NAME_LIKE_WITH_ANCHORS,
|
8
|
+
message: "must be a valid domain name" }
|
9
|
+
validates :transport, presence: true
|
10
|
+
|
11
|
+
validates :aliases, presence: true,
|
12
|
+
numericality: { only_integer: true,
|
13
|
+
greater_than_or_equal_to: 0 }
|
14
|
+
validates :mailboxes, presence: true,
|
15
|
+
numericality: { only_integer: true,
|
16
|
+
greater_than_or_equal_to: 0 }
|
17
|
+
validates :maxquota, presence: true,
|
18
|
+
numericality: { only_integer: true,
|
19
|
+
greater_than_or_equal_to: 0 }
|
20
|
+
|
21
|
+
has_many :rel_mailboxes, class_name: "Mailbox", foreign_key: :domain,
|
22
|
+
dependent: :destroy
|
23
|
+
has_many :rel_aliases, class_name: "Alias", foreign_key: :domain,
|
24
|
+
dependent: :destroy
|
25
|
+
|
26
|
+
has_many :domain_admins, foreign_key: :domain, dependent: :delete_all
|
27
|
+
has_many :admins, through: :domain_admins
|
28
|
+
|
29
|
+
before_validation do |domain|
|
30
|
+
domain.domain = domain.domain.downcase unless domain.domain.empty?
|
31
|
+
domain.transport = "virtual"
|
32
|
+
end
|
33
|
+
|
34
|
+
scope :without_all, -> { where.not(domain: "ALL") }
|
35
|
+
|
36
|
+
def pure_aliases
|
37
|
+
rel_aliases.pure
|
38
|
+
end
|
39
|
+
|
40
|
+
def aliases_unlimited?
|
41
|
+
aliases.zero?
|
42
|
+
end
|
43
|
+
|
44
|
+
def mailboxes_unlimited?
|
45
|
+
mailboxes.zero?
|
46
|
+
end
|
47
|
+
|
48
|
+
def aliases_str
|
49
|
+
num_str(aliases)
|
50
|
+
end
|
51
|
+
|
52
|
+
def mailboxes_str
|
53
|
+
num_str(mailboxes)
|
54
|
+
end
|
55
|
+
|
56
|
+
def aliases_short_str
|
57
|
+
num_short_str(aliases)
|
58
|
+
end
|
59
|
+
|
60
|
+
def mailboxes_short_str
|
61
|
+
num_short_str(mailboxes)
|
62
|
+
end
|
63
|
+
|
64
|
+
def maxquota_str
|
65
|
+
if maxquota.zero?
|
66
|
+
"Unlimited"
|
67
|
+
else
|
68
|
+
"#{maxquota} MB"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def maxquota_short_str
|
73
|
+
if maxquota.zero?
|
74
|
+
"--"
|
75
|
+
else
|
76
|
+
"#{maxquota} MB"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def num_str(num)
|
83
|
+
if num.zero?
|
84
|
+
"Unlimited"
|
85
|
+
else
|
86
|
+
num.to_s
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def num_short_str(num)
|
91
|
+
if num.zero?
|
92
|
+
"--"
|
93
|
+
else
|
94
|
+
num.to_s
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|