postfix_admin 0.1.4 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|