postfix_admin 0.3.0 → 0.3.2
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/.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
|