postfix_admin 0.3.0 → 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +2 -4
- data/CHANGELOG.md +24 -14
- data/README.md +14 -0
- data/Rakefile +29 -0
- data/db/reset.rb +7 -0
- data/db/seeds.rb +26 -0
- data/docker-admin/config.local.php +3 -1
- data/docker-compose.yml +3 -8
- data/lib/postfix_admin/base.rb +11 -22
- data/lib/postfix_admin/cli.rb +140 -56
- data/lib/postfix_admin/doveadm.rb +31 -15
- data/lib/postfix_admin/{admin.rb → models/admin.rb} +21 -2
- data/lib/postfix_admin/models/alias.rb +63 -0
- data/lib/postfix_admin/{application_record.rb → models/application_record.rb} +2 -2
- data/lib/postfix_admin/{concerns → models/concerns}/existing_timestamp.rb +1 -2
- data/lib/postfix_admin/models/concerns/has_password.rb +16 -0
- data/lib/postfix_admin/{domain.rb → models/domain.rb} +66 -2
- data/lib/postfix_admin/models/domain_admin.rb +21 -0
- data/lib/postfix_admin/models/log.rb +22 -0
- data/lib/postfix_admin/models/mailbox.rb +143 -0
- data/lib/postfix_admin/models/quota2.rb +20 -0
- data/lib/postfix_admin/models.rb +8 -9
- data/lib/postfix_admin/runner.rb +68 -14
- data/lib/postfix_admin/version.rb +1 -1
- metadata +15 -14
- data/lib/postfix_admin/alias.rb +0 -46
- data/lib/postfix_admin/concerns/dovecot_cram_md5_password.rb +0 -29
- data/lib/postfix_admin/domain_admin.rb +0 -8
- data/lib/postfix_admin/log.rb +0 -5
- data/lib/postfix_admin/mail_domain.rb +0 -9
- data/lib/postfix_admin/mailbox.rb +0 -97
- data/lib/postfix_admin/quota.rb +0 -6
- /data/lib/postfix_admin/{concerns → models/concerns}/.keep +0 -0
data/lib/postfix_admin/cli.rb
CHANGED
@@ -57,9 +57,11 @@ module PostfixAdmin
|
|
57
57
|
show_domain_details(name)
|
58
58
|
else
|
59
59
|
# no argument: show all domains and admins
|
60
|
-
|
60
|
+
show_domains
|
61
61
|
puts
|
62
|
-
|
62
|
+
show_admins
|
63
|
+
puts
|
64
|
+
show_recent_logs
|
63
65
|
end
|
64
66
|
end
|
65
67
|
|
@@ -71,15 +73,35 @@ module PostfixAdmin
|
|
71
73
|
end
|
72
74
|
end
|
73
75
|
|
76
|
+
def show_recent_logs
|
77
|
+
if Log.count.zero?
|
78
|
+
puts "No logs"
|
79
|
+
return
|
80
|
+
end
|
81
|
+
|
82
|
+
logs = Log.last(10)
|
83
|
+
puts_title("Recent Logs")
|
84
|
+
puts_log_table(logs)
|
85
|
+
end
|
86
|
+
|
74
87
|
# Set up a domain
|
75
88
|
# Add a domain, add an admin, and grant the admin access to the domain
|
76
|
-
def setup_domain(domain_name, password
|
89
|
+
def setup_domain(domain_name, password, description: nil,
|
90
|
+
scheme: nil, rounds: nil)
|
77
91
|
admin = "admin@#{domain_name}"
|
78
|
-
add_domain(domain_name)
|
79
|
-
add_admin(admin, password)
|
92
|
+
add_domain(domain_name, description: description)
|
93
|
+
add_admin(admin, password, scheme: scheme, rounds: rounds)
|
80
94
|
add_admin_domain(admin, domain_name)
|
81
95
|
end
|
82
96
|
|
97
|
+
# Tear down a domain
|
98
|
+
# Delete a domain and delete an admin user for it
|
99
|
+
def teardown_domain(domain_name)
|
100
|
+
admin = "admin@#{domain_name}"
|
101
|
+
delete_domain(domain_name)
|
102
|
+
delete_admin(admin)
|
103
|
+
end
|
104
|
+
|
83
105
|
def show_account_details(user_name, display_password: false)
|
84
106
|
account_check(user_name)
|
85
107
|
mailbox = Mailbox.find(user_name)
|
@@ -90,7 +112,7 @@ module PostfixAdmin
|
|
90
112
|
rows << ["Address", mailbox.username]
|
91
113
|
rows << ["Name", mailbox.name]
|
92
114
|
rows << ["Password", mailbox.password] if display_password
|
93
|
-
rows << ["Quota (MB)", mailbox.
|
115
|
+
rows << ["Quota (MB)", mailbox.quota_display_str(format: "%.1f")]
|
94
116
|
rows << ["Go to", mail_alias.goto]
|
95
117
|
rows << ["Active", mailbox.active_str]
|
96
118
|
|
@@ -125,12 +147,13 @@ module PostfixAdmin
|
|
125
147
|
puts_table(rows: rows)
|
126
148
|
end
|
127
149
|
|
128
|
-
def
|
150
|
+
def show_domains
|
129
151
|
rows = []
|
130
152
|
headings = ["No.", "Domain", "Aliases", "Mailboxes","Max Quota (MB)",
|
131
153
|
"Active", "Description"]
|
132
154
|
|
133
155
|
puts_title("Domains")
|
156
|
+
|
134
157
|
if Domain.without_all.empty?
|
135
158
|
puts "No domains"
|
136
159
|
return
|
@@ -138,9 +161,7 @@ module PostfixAdmin
|
|
138
161
|
|
139
162
|
Domain.without_all.each_with_index do |d, i|
|
140
163
|
no = i + 1
|
141
|
-
|
142
|
-
mailboxes_str = "%4d / %4s" % [d.rel_mailboxes.count, d.mailboxes_str]
|
143
|
-
rows << [no.to_s, d.domain, aliases_str, mailboxes_str,
|
164
|
+
rows << [no.to_s, d.domain, d.alias_usage_display_str, d.mailbox_usage_display_str,
|
144
165
|
d.maxquota_str, d.active_str, d.description]
|
145
166
|
end
|
146
167
|
|
@@ -152,12 +173,12 @@ module PostfixAdmin
|
|
152
173
|
puts_registered(domain_name, "a domain")
|
153
174
|
end
|
154
175
|
|
155
|
-
def change_admin_password(user_name, password)
|
156
|
-
change_password(Admin, user_name, password)
|
176
|
+
def change_admin_password(user_name, password, scheme: nil, rounds: nil)
|
177
|
+
change_password(Admin, user_name, password, scheme: scheme, rounds: rounds)
|
157
178
|
end
|
158
179
|
|
159
|
-
def change_account_password(user_name, password)
|
160
|
-
change_password(Mailbox, user_name, password)
|
180
|
+
def change_account_password(user_name, password, scheme: nil, rounds: nil)
|
181
|
+
change_password(Mailbox, user_name, password, scheme: scheme, rounds: rounds)
|
161
182
|
end
|
162
183
|
|
163
184
|
def edit_admin(admin_name, options)
|
@@ -171,7 +192,7 @@ module PostfixAdmin
|
|
171
192
|
admin.active = options[:active] unless options[:active].nil?
|
172
193
|
admin.save!
|
173
194
|
|
174
|
-
puts "
|
195
|
+
puts "successfully updated #{admin_name}"
|
175
196
|
show_admin_details(admin_name)
|
176
197
|
end
|
177
198
|
|
@@ -185,7 +206,7 @@ module PostfixAdmin
|
|
185
206
|
domain.description = options[:description] if options[:description]
|
186
207
|
domain.save!
|
187
208
|
|
188
|
-
puts "
|
209
|
+
puts "successfully updated #{domain_name}"
|
189
210
|
show_summary(domain_name)
|
190
211
|
end
|
191
212
|
|
@@ -194,11 +215,12 @@ module PostfixAdmin
|
|
194
215
|
puts_deleted(domain_name)
|
195
216
|
end
|
196
217
|
|
197
|
-
def
|
218
|
+
def show_admins(domain_name = nil)
|
198
219
|
admins = domain_name ? Admin.select { |a| a.rel_domains.exists?(domain_name) } : Admin.all
|
199
|
-
headings =
|
220
|
+
headings = ["No.", "Admin", "Domains", "Active", "Scheme Prefix"]
|
200
221
|
|
201
222
|
puts_title("Admins")
|
223
|
+
|
202
224
|
if admins.empty?
|
203
225
|
puts "No admins"
|
204
226
|
return
|
@@ -208,51 +230,68 @@ module PostfixAdmin
|
|
208
230
|
admins.each_with_index do |a, i|
|
209
231
|
no = i + 1
|
210
232
|
domains = a.super_admin? ? 'Super Admin' : a.rel_domains.count
|
211
|
-
rows << [no.to_s, a.username, domains.to_s, a.active_str]
|
233
|
+
rows << [no.to_s, a.username, domains.to_s, a.active_str, a.scheme_prefix]
|
212
234
|
end
|
213
235
|
|
214
236
|
puts_table(headings: headings, rows: rows)
|
215
237
|
end
|
216
238
|
|
217
|
-
def
|
218
|
-
domain_check(domain_name)
|
239
|
+
def show_accounts(domain_name=nil)
|
240
|
+
domain_check(domain_name) if domain_name
|
219
241
|
|
220
242
|
rows = []
|
221
|
-
mailboxes =
|
222
|
-
|
243
|
+
mailboxes = if domain_name
|
244
|
+
Domain.find(domain_name).rel_mailboxes
|
245
|
+
else
|
246
|
+
Mailbox.all
|
247
|
+
end
|
248
|
+
headings = ["No.", "Email", "Name", "Quota (MB)", "Active",
|
249
|
+
"Scheme Prefix", "Maildir"]
|
250
|
+
|
251
|
+
puts_title("Accounts")
|
223
252
|
|
224
|
-
puts_title("Addresses")
|
225
253
|
if mailboxes.empty?
|
226
|
-
puts "No
|
254
|
+
puts "No accounts"
|
227
255
|
return
|
228
256
|
end
|
229
257
|
|
230
258
|
mailboxes.each_with_index do |m, i|
|
231
259
|
no = i + 1
|
232
|
-
rows << [no.to_s, m.username, m.name, m.
|
233
|
-
m.active_str, m.maildir]
|
260
|
+
rows << [no.to_s, m.username, m.name, m.quota_display_str,
|
261
|
+
m.active_str, m.scheme_prefix, m.maildir]
|
234
262
|
end
|
235
263
|
|
236
264
|
puts_table(headings: headings, rows: rows)
|
237
265
|
end
|
238
266
|
|
239
|
-
def
|
240
|
-
domain_check(domain_name)
|
267
|
+
def show_forwards(domain_name=nil)
|
268
|
+
domain_check(domain_name) if domain_name
|
241
269
|
|
242
|
-
forwards
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
270
|
+
forwards = if domain_name
|
271
|
+
Domain.find(domain_name).rel_aliases.forward
|
272
|
+
else
|
273
|
+
Alias.forward
|
274
|
+
end
|
247
275
|
|
248
276
|
show_alias_base("Forwards", forwards)
|
249
|
-
|
277
|
+
end
|
278
|
+
|
279
|
+
def show_aliases(domain_name=nil)
|
280
|
+
domain_check(domain_name) if domain_name
|
281
|
+
|
282
|
+
aliases = if domain_name
|
283
|
+
Domain.find(domain_name).rel_aliases.pure
|
284
|
+
else
|
285
|
+
db_aliases = Alias.pure
|
286
|
+
end
|
287
|
+
|
250
288
|
show_alias_base("Aliases", aliases)
|
251
289
|
end
|
252
290
|
|
253
291
|
def show_admin_domain(user_name)
|
254
292
|
admin = Admin.find(user_name)
|
255
293
|
puts_title("Admin Domains (#{user_name})")
|
294
|
+
|
256
295
|
if admin.rel_domains.empty?
|
257
296
|
puts "\nNo domains for #{user_name}"
|
258
297
|
return
|
@@ -263,13 +302,18 @@ module PostfixAdmin
|
|
263
302
|
no = i + 1
|
264
303
|
rows << [no.to_s, d.domain]
|
265
304
|
end
|
305
|
+
|
266
306
|
puts_table(rows: rows, headings: %w[No. Domain])
|
267
307
|
end
|
268
308
|
|
269
|
-
def add_admin(user_name, password, super_admin
|
309
|
+
def add_admin(user_name, password, super_admin: false,
|
310
|
+
scheme: nil, rounds: nil)
|
270
311
|
validate_password(password)
|
271
312
|
|
272
|
-
|
313
|
+
h_password = hashed_password(password, user_name: user_name,
|
314
|
+
scheme: scheme, rounds: rounds)
|
315
|
+
@base.add_admin(user_name, h_password)
|
316
|
+
|
273
317
|
if super_admin
|
274
318
|
Admin.find(user_name).super_admin = true
|
275
319
|
puts_registered(user_name, "a super admin")
|
@@ -288,10 +332,12 @@ module PostfixAdmin
|
|
288
332
|
puts "#{domain_name} was successfully deleted from #{user_name}"
|
289
333
|
end
|
290
334
|
|
291
|
-
def add_account(address, password, scheme
|
335
|
+
def add_account(address, password, name: nil, scheme: nil, rounds: nil)
|
292
336
|
validate_password(password)
|
293
337
|
|
294
|
-
|
338
|
+
h_password = hashed_password(password, user_name: address,
|
339
|
+
scheme: scheme, rounds: rounds)
|
340
|
+
@base.add_account(address, h_password, name: name)
|
295
341
|
puts_registered(address, "an account")
|
296
342
|
end
|
297
343
|
|
@@ -301,10 +347,13 @@ module PostfixAdmin
|
|
301
347
|
end
|
302
348
|
|
303
349
|
def edit_account(address, options)
|
350
|
+
quota = options[:quota]
|
351
|
+
raise "Invalid Quota value: #{quota}" if quota && quota < 0
|
352
|
+
|
304
353
|
mailbox_check(address)
|
305
354
|
mailbox = Mailbox.find(address)
|
306
355
|
mailbox.name = options[:name] if options[:name]
|
307
|
-
mailbox.
|
356
|
+
mailbox.quota_mb = quota if quota
|
308
357
|
mailbox.active = options[:active] unless options[:active].nil?
|
309
358
|
mailbox.save!
|
310
359
|
|
@@ -314,7 +363,7 @@ module PostfixAdmin
|
|
314
363
|
mail_alias.save!
|
315
364
|
end
|
316
365
|
|
317
|
-
puts "
|
366
|
+
puts "successfully updated #{address}"
|
318
367
|
show_account_details(address)
|
319
368
|
end
|
320
369
|
|
@@ -325,7 +374,7 @@ module PostfixAdmin
|
|
325
374
|
mail_alias.active = options[:active] unless options[:active].nil?
|
326
375
|
mail_alias.save or raise "Could not save Alias"
|
327
376
|
|
328
|
-
puts "
|
377
|
+
puts "successfully updated #{address}"
|
329
378
|
show_alias_details(address)
|
330
379
|
end
|
331
380
|
|
@@ -345,7 +394,7 @@ module PostfixAdmin
|
|
345
394
|
end
|
346
395
|
|
347
396
|
def log(domain: nil, last: nil)
|
348
|
-
headings = %w[Timestamp Admin Domain Action Data]
|
397
|
+
headings = %w[No. Timestamp Admin Domain Action Data]
|
349
398
|
rows = []
|
350
399
|
|
351
400
|
logs = if domain
|
@@ -356,10 +405,17 @@ module PostfixAdmin
|
|
356
405
|
|
357
406
|
logs = logs.last(last) if last
|
358
407
|
|
359
|
-
logs
|
408
|
+
puts_log_table(logs)
|
409
|
+
end
|
410
|
+
|
411
|
+
def puts_log_table(logs)
|
412
|
+
headings = %w[No. Timestamp Admin Domain Action Data]
|
413
|
+
rows = []
|
414
|
+
logs.each_with_index do |l, i|
|
415
|
+
no = i + 1
|
360
416
|
# TODO: Consider if zone should be included ('%Z').
|
361
417
|
time = l.timestamp.strftime("%Y-%m-%d %X")
|
362
|
-
rows << [time, l.username, l.domain, l.action, l.data]
|
418
|
+
rows << [no, time, l.username, l.domain, l.action, l.data]
|
363
419
|
end
|
364
420
|
|
365
421
|
puts_table(headings: headings, rows: rows)
|
@@ -372,24 +428,28 @@ module PostfixAdmin
|
|
372
428
|
puts [a.username, %Q!"#{a.password}"!, a.super_admin?, a.active].join(',')
|
373
429
|
end
|
374
430
|
puts
|
431
|
+
|
375
432
|
puts "Domains"
|
376
433
|
puts "Domain Name,Max Quota,Active"
|
377
434
|
Domain.without_all.each do |d|
|
378
435
|
puts [d.domain, d.maxquota, d.active].join(',')
|
379
436
|
end
|
380
437
|
puts
|
438
|
+
|
381
439
|
puts "Mailboxes"
|
382
440
|
puts "User Name,Name,Password,Quota,Maildir,Active"
|
383
441
|
Mailbox.all.each do |m|
|
384
442
|
puts [m.username, %Q!"#{m.name}"!, %Q!"#{m.password}"!, m.quota, %Q!"#{m.maildir}"!, m.active].join(',')
|
385
443
|
end
|
386
444
|
puts
|
445
|
+
|
387
446
|
puts "Aliases"
|
388
447
|
puts "Address,Go to,Active"
|
389
448
|
Alias.all.select { |a| !a.mailbox? }.each do |a|
|
390
449
|
puts [a.address, %Q!"#{a.goto}"!, a.active].join(',')
|
391
450
|
end
|
392
451
|
puts
|
452
|
+
|
393
453
|
puts "Forwards"
|
394
454
|
puts "Address,Go to,Active"
|
395
455
|
Alias.all.select { |a| a.mailbox? && a.goto != a.address }.each do |a|
|
@@ -406,6 +466,7 @@ module PostfixAdmin
|
|
406
466
|
rows << ["Admins", Admin.count]
|
407
467
|
rows << ["Mailboxes", Mailbox.count]
|
408
468
|
rows << ["Aliases", Alias.pure.count]
|
469
|
+
rows << ["Logs", Log.count]
|
409
470
|
|
410
471
|
puts_title(title)
|
411
472
|
puts_table(rows: rows)
|
@@ -417,8 +478,8 @@ module PostfixAdmin
|
|
417
478
|
|
418
479
|
rows = []
|
419
480
|
domain = Domain.find(domain_name)
|
420
|
-
rows << ["Mailboxes",
|
421
|
-
rows << ["Aliases",
|
481
|
+
rows << ["Mailboxes", domain.mailbox_usage_display_str]
|
482
|
+
rows << ["Aliases", domain.alias_usage_display_str]
|
422
483
|
rows << ["Max Quota (MB)", domain.maxquota_str]
|
423
484
|
rows << ["Active", domain.active_str]
|
424
485
|
rows << ["Description", domain.description]
|
@@ -428,11 +489,13 @@ module PostfixAdmin
|
|
428
489
|
end
|
429
490
|
|
430
491
|
def show_domain_details(domain_name)
|
431
|
-
|
492
|
+
show_admins(domain_name)
|
493
|
+
puts
|
494
|
+
show_accounts(domain_name)
|
432
495
|
puts
|
433
|
-
|
496
|
+
show_forwards(domain_name)
|
434
497
|
puts
|
435
|
-
|
498
|
+
show_aliases(domain_name)
|
436
499
|
end
|
437
500
|
|
438
501
|
def show_alias_base(title, addresses)
|
@@ -471,6 +534,7 @@ module PostfixAdmin
|
|
471
534
|
puts "configure file: #{config_file} was generated.\nPlease execute after edit it."
|
472
535
|
exit
|
473
536
|
end
|
537
|
+
|
474
538
|
open(config_file) do |f|
|
475
539
|
YAML.load(f.read)
|
476
540
|
end
|
@@ -524,24 +588,44 @@ module PostfixAdmin
|
|
524
588
|
end
|
525
589
|
end
|
526
590
|
|
527
|
-
def change_password(klass, user_name, password)
|
591
|
+
def change_password(klass, user_name, password, scheme: nil, rounds: nil)
|
528
592
|
raise Error, "Could not find #{user_name}" unless klass.exists?(user_name)
|
529
593
|
|
530
594
|
validate_password(password)
|
531
595
|
|
532
596
|
obj = klass.find(user_name)
|
597
|
+
h_password = hashed_password(password, scheme: scheme, rounds: rounds,
|
598
|
+
user_name: user_name)
|
533
599
|
|
534
|
-
if obj.update(password:
|
535
|
-
puts "the password of #{user_name} was successfully
|
600
|
+
if obj.update(password: h_password)
|
601
|
+
puts "the password of #{user_name} was successfully updated."
|
536
602
|
else
|
537
603
|
raise "Could not change password of #{klass.name}"
|
538
604
|
end
|
539
605
|
end
|
540
606
|
|
541
|
-
|
607
|
+
# The default number of rounds for BLF-CRYPT in `doveadm pw` is 5.
|
608
|
+
# However, this method uses 10 rounds by default, similar to
|
609
|
+
# the password_hash() function in PHP.
|
610
|
+
#
|
611
|
+
# https://www.php.net/manual/en/function.password-hash.php
|
612
|
+
# <?php
|
613
|
+
# echo password_hash("password", PASSWORD_BCRYPT);
|
614
|
+
#
|
615
|
+
# $2y$10$qzRgjWZWfH4VsNQGvp/DNObFSaMiZxXJSzgXqOOS/qtF68qIhhwFe
|
616
|
+
DEFAULT_BLF_CRYPT_ROUNDS = 10
|
617
|
+
|
618
|
+
# Generate a hashed password
|
619
|
+
def hashed_password(password, scheme: nil, rounds: nil, user_name: nil)
|
542
620
|
prefix = @base.config[:passwordhash_prefix]
|
543
|
-
|
544
|
-
|
621
|
+
new_scheme = scheme || @base.config[:scheme]
|
622
|
+
new_rounds = if rounds
|
623
|
+
rounds
|
624
|
+
elsif new_scheme == "BLF-CRYPT"
|
625
|
+
DEFAULT_BLF_CRYPT_ROUNDS
|
626
|
+
end
|
627
|
+
PostfixAdmin::Doveadm.password(password, new_scheme, rounds: new_rounds,
|
628
|
+
user_name: user_name, prefix: prefix)
|
545
629
|
end
|
546
630
|
end
|
547
631
|
end
|
@@ -1,33 +1,49 @@
|
|
1
|
-
|
2
1
|
require 'open3'
|
3
2
|
require 'shellwords'
|
4
3
|
|
5
4
|
module PostfixAdmin
|
6
5
|
class Doveadm
|
6
|
+
# doveadm-pw: https://doc.dovecot.org/3.0/man/doveadm-pw.1/
|
7
|
+
CMD_DOVEADM_PW = "doveadm pw"
|
8
|
+
|
9
|
+
# List all supported password schemes
|
7
10
|
def self.schemes
|
8
|
-
result = `#{
|
11
|
+
result = `#{CMD_DOVEADM_PW} -l`
|
9
12
|
result.split
|
10
13
|
end
|
11
14
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
15
|
+
# Generate a password hash using `doveadm pw` command
|
16
|
+
def self.password(password, scheme, rounds: nil, user_name: nil,
|
17
|
+
prefix: true)
|
18
|
+
escaped_password = Shellwords.escape(password)
|
19
|
+
escaped_scheme = Shellwords.escape(scheme)
|
16
20
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
+
cmd = "#{CMD_DOVEADM_PW} -s #{escaped_scheme} -p #{escaped_password}"
|
22
|
+
|
23
|
+
# DIGEST-MD5 requires -u option (user name)
|
24
|
+
if scheme == "DIGEST-MD5"
|
25
|
+
escaped_user_name = Shellwords.escape(user_name)
|
26
|
+
cmd << " -u #{escaped_user_name}"
|
27
|
+
end
|
28
|
+
|
29
|
+
if rounds
|
30
|
+
escaped_rounds = Shellwords.escape(rounds.to_s)
|
31
|
+
cmd << " -r #{rounds}"
|
32
|
+
end
|
33
|
+
|
34
|
+
output, error, status = Open3.capture3(cmd)
|
35
|
+
|
36
|
+
if status.success?
|
37
|
+
res = output.chomp
|
21
38
|
if prefix
|
22
39
|
res
|
23
40
|
else
|
24
|
-
|
41
|
+
# Remove the prefix
|
42
|
+
res.gsub("{#{escaped_scheme}}", "")
|
25
43
|
end
|
44
|
+
else
|
45
|
+
raise Error, "#{CMD_DOVEADM_PW}: #{error}"
|
26
46
|
end
|
27
47
|
end
|
28
|
-
|
29
|
-
def self.command_name
|
30
|
-
"doveadm pw"
|
31
|
-
end
|
32
48
|
end
|
33
49
|
end
|
@@ -1,16 +1,35 @@
|
|
1
|
-
require
|
1
|
+
require "postfix_admin/models/application_record"
|
2
|
+
require "postfix_admin/models/concerns/has_password"
|
2
3
|
|
3
4
|
module PostfixAdmin
|
4
5
|
class Admin < ApplicationRecord
|
6
|
+
# version: 1841
|
7
|
+
# > describe admin;
|
8
|
+
# +----------------+--------------+------+-----+---------------------+-------+
|
9
|
+
# | Field | Type | Null | Key | Default | Extra |
|
10
|
+
# +----------------+--------------+------+-----+---------------------+-------+
|
11
|
+
# | username | varchar(255) | NO | PRI | NULL | |
|
12
|
+
# | password | varchar(255) | NO | | NULL | |
|
13
|
+
# | created | datetime | NO | | 2000-01-01 00:00:00 | |
|
14
|
+
# | modified | datetime | NO | | 2000-01-01 00:00:00 | |
|
15
|
+
# | active | tinyint(1) | NO | | 1 | |
|
16
|
+
# | superadmin | tinyint(1) | NO | | 0 | |
|
17
|
+
# | phone | varchar(30) | NO | | | |
|
18
|
+
# | email_other | varchar(255) | NO | | | |
|
19
|
+
# | token | varchar(255) | NO | | | |
|
20
|
+
# | token_validity | datetime | NO | | 2000-01-01 00:00:00 | |
|
21
|
+
# +----------------+--------------+------+-----+---------------------+-------+
|
22
|
+
|
5
23
|
self.table_name = :admin
|
6
24
|
self.primary_key = :username
|
7
25
|
|
8
|
-
include
|
26
|
+
include HasPassword
|
9
27
|
|
10
28
|
validates :username, presence: true, uniqueness: { case_sensitive: false },
|
11
29
|
format: { with: RE_EMAIL_LIKE_WITH_ANCHORS,
|
12
30
|
message: "must be a valid email address" }
|
13
31
|
|
32
|
+
# Admin <-> DomainAdmin <-> Domain
|
14
33
|
has_many :domain_admins, foreign_key: :username, dependent: :delete_all
|
15
34
|
has_many :rel_domains, through: :domain_admins
|
16
35
|
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require "postfix_admin/models/application_record"
|
2
|
+
|
3
|
+
module PostfixAdmin
|
4
|
+
class Alias < ApplicationRecord
|
5
|
+
# version: 1841
|
6
|
+
# > describe alias;
|
7
|
+
# +----------+--------------+------+-----+---------------------+-------+
|
8
|
+
# | Field | Type | Null | Key | Default | Extra |
|
9
|
+
# +----------+--------------+------+-----+---------------------+-------+
|
10
|
+
# | address | varchar(255) | NO | PRI | NULL | |
|
11
|
+
# | goto | text | NO | | NULL | |
|
12
|
+
# | domain | varchar(255) | NO | MUL | NULL | |
|
13
|
+
# | created | datetime | NO | | 2000-01-01 00:00:00 | |
|
14
|
+
# | modified | datetime | NO | | 2000-01-01 00:00:00 | |
|
15
|
+
# | active | tinyint(1) | NO | | 1 | |
|
16
|
+
# +----------+--------------+------+-----+---------------------+-------+
|
17
|
+
|
18
|
+
self.table_name = :alias
|
19
|
+
self.primary_key = :address
|
20
|
+
|
21
|
+
validate on: :create do |a|
|
22
|
+
domain = a.rel_domain
|
23
|
+
|
24
|
+
if domain.alias_unlimited?
|
25
|
+
# unlimited: do nothing
|
26
|
+
elsif domain.alias_disabled?
|
27
|
+
# disabled
|
28
|
+
a.errors.add(:domain, "has a disabled status for aliases")
|
29
|
+
elsif domain.pure_alias_count >= domain.aliases
|
30
|
+
# exceeding alias limit
|
31
|
+
message = "has already reached the maximum number of aliases " \
|
32
|
+
"(maximum: #{domain.aliases})"
|
33
|
+
a.errors.add(:domain, message)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
validates :address, presence: true, uniqueness: { case_sensitive: false },
|
38
|
+
format: { with: RE_EMAIL_LIKE_WITH_ANCHORS,
|
39
|
+
message: "must be a valid email address" }
|
40
|
+
validates :goto, presence: true
|
41
|
+
|
42
|
+
belongs_to :rel_domain, class_name: "Domain", foreign_key: :domain
|
43
|
+
belongs_to :mailbox, foreign_key: :address, optional: true
|
44
|
+
|
45
|
+
# aliases which do not belong to any mailbox
|
46
|
+
scope :pure, -> { joins("LEFT OUTER JOIN mailbox ON alias.address = mailbox.username").where("mailbox.username" => nil) }
|
47
|
+
|
48
|
+
# aliases which belong to a mailbox and have forwardings to other addresses
|
49
|
+
scope :forward, -> { joins("LEFT OUTER JOIN mailbox ON alias.address = mailbox.username").where("mailbox.username <> alias.goto") }
|
50
|
+
|
51
|
+
def mailbox?
|
52
|
+
!!mailbox
|
53
|
+
end
|
54
|
+
|
55
|
+
def pure_alias?
|
56
|
+
!mailbox
|
57
|
+
end
|
58
|
+
|
59
|
+
def gotos
|
60
|
+
goto.split(",")
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module HasPassword
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
# example: {CRAM-MD5}, {BLF-CRYPT}, {PLAIN}
|
7
|
+
# return nil if no scheme prefix
|
8
|
+
def scheme_prefix
|
9
|
+
res = password&.match(/^\{.*?\}/)
|
10
|
+
if res
|
11
|
+
res[0]
|
12
|
+
else
|
13
|
+
nil
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|