postfix_admin 0.2.1 → 0.3.1

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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +52 -0
  3. data/.gitignore +2 -0
  4. data/.rubocop.yml +20 -0
  5. data/CHANGELOG.md +26 -12
  6. data/Gemfile +1 -1
  7. data/LICENSE +1 -1
  8. data/README.md +26 -20
  9. data/Rakefile +48 -1
  10. data/bin/console +6 -2
  11. data/db/reset.rb +7 -0
  12. data/db/seeds.rb +26 -0
  13. data/docker-admin/config.local.php +6 -1
  14. data/docker-app/Dockerfile +2 -6
  15. data/docker-app/my.cnf +2 -2
  16. data/docker-compose.yml +20 -20
  17. data/lib/postfix_admin/base.rb +119 -112
  18. data/lib/postfix_admin/cli.rb +267 -158
  19. data/lib/postfix_admin/doveadm.rb +32 -20
  20. data/lib/postfix_admin/{admin.rb → models/admin.rb} +25 -2
  21. data/lib/postfix_admin/{alias.rb → models/alias.rb} +16 -26
  22. data/lib/postfix_admin/{application_record.rb → models/application_record.rb} +1 -1
  23. data/lib/postfix_admin/{concerns → models/concerns}/existing_timestamp.rb +1 -2
  24. data/lib/postfix_admin/models/concerns/has_password.rb +16 -0
  25. data/lib/postfix_admin/models/domain.rb +112 -0
  26. data/lib/postfix_admin/models/domain_admin.rb +19 -0
  27. data/lib/postfix_admin/models/log.rb +20 -0
  28. data/lib/postfix_admin/models/mailbox.rb +126 -0
  29. data/lib/postfix_admin/models/quota2.rb +18 -0
  30. data/lib/postfix_admin/models.rb +8 -9
  31. data/lib/postfix_admin/runner.rb +91 -28
  32. data/lib/postfix_admin/version.rb +1 -1
  33. data/postfix_admin.gemspec +12 -10
  34. metadata +61 -34
  35. data/.github/workflows/ruby.yml +0 -37
  36. data/docker-app/docker-entrypoint.sh +0 -5
  37. data/docker-app-2.5/Dockerfile +0 -15
  38. data/lib/postfix_admin/concerns/dovecot_cram_md5_password.rb +0 -30
  39. data/lib/postfix_admin/domain.rb +0 -98
  40. data/lib/postfix_admin/domain_admin.rb +0 -8
  41. data/lib/postfix_admin/log.rb +0 -5
  42. data/lib/postfix_admin/mail_domain.rb +0 -9
  43. data/lib/postfix_admin/mailbox.rb +0 -89
  44. data/lib/postfix_admin/quota.rb +0 -6
  45. /data/lib/postfix_admin/{concerns → models/concerns}/.keep +0 -0
@@ -1,6 +1,7 @@
1
1
  require 'yaml'
2
2
  require 'postfix_admin'
3
3
  require 'postfix_admin/doveadm'
4
+ require 'terminal-table'
4
5
 
5
6
  module PostfixAdmin
6
7
  class CLI
@@ -29,127 +30,142 @@ module PostfixAdmin
29
30
  name = name.downcase if name
30
31
 
31
32
  if name =~ /@/
33
+ # address like argument
32
34
  if Admin.exists?(name)
33
- show_admin_details(name)
34
- end
35
-
36
- if Mailbox.exists?(name)
37
- show_account_details(name)
35
+ # admin
36
+ show_admin_details(name, display_password: true)
37
+ puts
38
+ show_admin_domain(name)
39
+ elsif Mailbox.exists?(name)
40
+ # mailbox
41
+ show_account_details(name, display_password: true)
38
42
  elsif Alias.exists?(name)
43
+ # alias
39
44
  show_alias_details(name)
45
+ else
46
+ raise Error, "Could not find admin/mailbox/alias #{name}"
40
47
  end
41
48
 
42
49
  return
43
50
  end
44
51
 
45
52
  show_summary(name)
53
+ puts
46
54
 
47
55
  if name
48
- show_admin(name)
49
- show_address(name)
50
- show_alias(name)
56
+ # domain name
57
+ show_domain_details(name)
51
58
  else
52
- show_domain
53
- show_admin
59
+ # no argument: show all domains and admins
60
+ show_domains
61
+ puts
62
+ show_admins
54
63
  end
55
64
  end
56
65
 
57
66
  def show_summary(domain_name = nil)
58
- title = "Summary"
59
67
  if domain_name
60
- domain_name = domain_name.downcase
61
- domain_check(domain_name)
62
- title = "Summary of #{domain_name}"
63
- end
64
-
65
- report(title) do
66
- if domain_name
67
- domain = Domain.find(domain_name)
68
- puts "Mailboxes : %4d / %4s" % [domain.rel_mailboxes.count, max_str(domain.mailboxes)]
69
- puts "Aliases : %4d / %4s" % [domain.pure_aliases.count, max_str(domain.aliases)]
70
- puts "Max Quota : %4d MB" % domain.maxquota
71
- puts "Active : %3s" % domain.active_str
72
- else
73
- puts "Domains : %4d" % Domain.without_all.count
74
- puts "Admins : %4d" % Admin.count
75
- puts "Mailboxes : %4d" % Mailbox.count
76
- puts "Aliases : %4d" % Alias.pure.count
77
- end
68
+ show_domain_summary(domain_name)
69
+ else
70
+ show_general_summary
78
71
  end
79
72
  end
80
73
 
81
- def setup_domain(domain_name, password)
74
+ # Set up a domain
75
+ # Add a domain, add an admin, and grant the admin access to the domain
76
+ def setup_domain(domain_name, password, scheme: nil, rounds: nil)
82
77
  admin = "admin@#{domain_name}"
83
78
  add_domain(domain_name)
84
- add_admin(admin, password)
79
+ add_admin(admin, password, scheme: scheme, rounds: rounds)
85
80
  add_admin_domain(admin, domain_name)
86
81
  end
87
82
 
88
- def show_account_details(user_name)
83
+ # Tear down a domain
84
+ # Delete a domain and delete an admin user for it
85
+ def teardown_domain(domain_name)
86
+ admin = "admin@#{domain_name}"
87
+ delete_domain(domain_name)
88
+ delete_admin(admin)
89
+ end
90
+
91
+ def show_account_details(user_name, display_password: false)
89
92
  account_check(user_name)
90
93
  mailbox = Mailbox.find(user_name)
91
94
  mail_alias = Alias.find(user_name)
92
95
 
93
- report("Mailbox") do
94
- puts "Address : %s" % mailbox.username
95
- puts "Name : %s" % mailbox.name
96
- puts "Password : %s" % mailbox.password
97
- puts "Quota : %d MB" % max_str(mailbox.quota / KB_TO_MB)
98
- puts "Go to : %s" % mail_alias.goto
99
- puts "Active : %s" % mailbox.active_str
100
- end
96
+ rows = []
97
+ puts_title("Mailbox")
98
+ rows << ["Address", mailbox.username]
99
+ rows << ["Name", mailbox.name]
100
+ rows << ["Password", mailbox.password] if display_password
101
+ rows << ["Quota (MB)", mailbox.quota_display_str(format: "%.1f")]
102
+ rows << ["Go to", mail_alias.goto]
103
+ rows << ["Active", mailbox.active_str]
104
+
105
+ puts_table(rows: rows)
101
106
  end
102
107
 
103
- def show_admin_details(name)
108
+ def show_admin_details(name, display_password: false)
104
109
  admin_check(name)
105
110
  admin = Admin.find(name)
106
111
 
107
- report("Admin") do
108
- puts "Name : %s" % admin.username
109
- puts "Password : %s" % admin.password
110
- puts "Domains : %s" % (admin.super_admin? ? "ALL" : admin.rel_domains.count)
111
- puts "Role : %s" % (admin.super_admin? ? "Super admin" : "Admin")
112
- puts "Active : %s" % admin.active_str
113
- end
112
+ rows = []
113
+ puts_title("Admin")
114
+ rows << ["Name", admin.username]
115
+ rows << ["Password", admin.password] if display_password
116
+ rows << ["Domains", admin.super_admin? ? "ALL" : admin.rel_domains.count.to_s]
117
+ rows << ["Role", admin.super_admin? ? "Super Admin" : "Standard Admin"]
118
+ rows << ["Active", admin.active_str]
119
+
120
+ puts_table(rows: rows)
114
121
  end
115
122
 
116
123
  def show_alias_details(name)
117
124
  alias_check(name)
118
125
  mail_alias = Alias.find(name)
119
- report("Alias") do
120
- puts "Address : %s" % mail_alias.address
121
- puts "Go to : %s" % mail_alias.goto
122
- puts "Active : %s" % mail_alias.active_str
123
- end
126
+
127
+ rows = []
128
+ puts_title("Alias")
129
+ rows << ["Address", mail_alias.address]
130
+ rows << ["Go to", mail_alias.goto]
131
+ rows << ["Active", mail_alias.active_str]
132
+
133
+ puts_table(rows: rows)
124
134
  end
125
135
 
126
- def show_domain
127
- index = " No. Domain Aliases Mailboxes Quota (MB) Active"
128
- report('Domains', index) do
129
- if Domain.without_all.empty?
130
- puts " No domains"
131
- next
132
- end
136
+ def show_domains
137
+ rows = []
138
+ headings = ["No.", "Domain", "Aliases", "Mailboxes","Max Quota (MB)",
139
+ "Active", "Description"]
133
140
 
134
- Domain.without_all.each_with_index do |d, i|
135
- puts "%4d %-30s %3d /%3s %3d /%3s %10d %-3s" %
136
- [i+1, d.domain, d.pure_aliases.count, max_str(d.aliases),
137
- d.rel_mailboxes.count, max_str(d.mailboxes), d.maxquota, d.active_str]
138
- end
141
+ puts_title("Domains")
142
+ if Domain.without_all.empty?
143
+ puts "No domains"
144
+ return
145
+ end
146
+
147
+ Domain.without_all.each_with_index do |d, i|
148
+ no = i + 1
149
+ aliases_str = "%4d / %4s" % [d.pure_aliases.count, d.aliases_str]
150
+ mailboxes_str = "%4d / %4s" % [d.rel_mailboxes.count, d.mailboxes_str]
151
+ rows << [no.to_s, d.domain, aliases_str, mailboxes_str,
152
+ d.maxquota_str, d.active_str, d.description]
139
153
  end
154
+
155
+ puts_table(headings: headings, rows: rows)
140
156
  end
141
157
 
142
- def add_domain(domain_name)
143
- @base.add_domain(domain_name)
158
+ def add_domain(domain_name, description: nil)
159
+ @base.add_domain(domain_name, description: description)
144
160
  puts_registered(domain_name, "a domain")
145
161
  end
146
162
 
147
- def change_admin_password(user_name, password)
148
- change_password(Admin, user_name, password)
163
+ def change_admin_password(user_name, password, scheme: nil, rounds: nil)
164
+ change_password(Admin, user_name, password, scheme: scheme, rounds: rounds)
149
165
  end
150
166
 
151
- def change_account_password(user_name, password)
152
- change_password(Mailbox, user_name, password)
167
+ def change_account_password(user_name, password, scheme: nil, rounds: nil)
168
+ change_password(Mailbox, user_name, password, scheme: scheme, rounds: rounds)
153
169
  end
154
170
 
155
171
  def edit_admin(admin_name, options)
@@ -163,7 +179,7 @@ module PostfixAdmin
163
179
  admin.active = options[:active] unless options[:active].nil?
164
180
  admin.save!
165
181
 
166
- puts "Successfully updated #{admin_name}"
182
+ puts "successfully updated #{admin_name}"
167
183
  show_admin_details(admin_name)
168
184
  end
169
185
 
@@ -174,9 +190,10 @@ module PostfixAdmin
174
190
  domain.mailboxes = options[:mailboxes] if options[:mailboxes]
175
191
  domain.maxquota = options[:maxquota] if options[:maxquota]
176
192
  domain.active = options[:active] unless options[:active].nil?
193
+ domain.description = options[:description] if options[:description]
177
194
  domain.save!
178
195
 
179
- puts "Successfully updated #{domain_name}"
196
+ puts "successfully updated #{domain_name}"
180
197
  show_summary(domain_name)
181
198
  end
182
199
 
@@ -185,71 +202,101 @@ module PostfixAdmin
185
202
  puts_deleted(domain_name)
186
203
  end
187
204
 
188
- def show_admin(domain_name = nil)
205
+ def show_admins(domain_name = nil)
189
206
  admins = domain_name ? Admin.select { |a| a.rel_domains.exists?(domain_name) } : Admin.all
190
- index = " No. Admin Domains Active"
191
- report("Admins", index) do
192
- if admins.empty?
193
- puts " No admins"
194
- next
195
- end
207
+ headings = ["No.", "Admin", "Domains", "Active", "Scheme Prefix"]
196
208
 
197
- admins.each_with_index do |a, i|
198
- domains = a.super_admin? ? 'Super admin' : a.rel_domains.count
199
- puts "%4d %-40s %11s %-3s" % [i+1, a.username, domains, a.active_str]
200
- end
209
+ puts_title("Admins")
210
+ if admins.empty?
211
+ puts "No admins"
212
+ return
213
+ end
214
+
215
+ rows = []
216
+ admins.each_with_index do |a, i|
217
+ no = i + 1
218
+ domains = a.super_admin? ? 'Super Admin' : a.rel_domains.count
219
+ rows << [no.to_s, a.username, domains.to_s, a.active_str, a.scheme_prefix]
201
220
  end
221
+
222
+ puts_table(headings: headings, rows: rows)
202
223
  end
203
224
 
204
- def show_address(domain_name)
205
- domain_check(domain_name)
225
+ def show_accounts(domain_name=nil)
226
+ domain_check(domain_name) if domain_name
206
227
 
207
- mailboxes = Domain.find(domain_name).rel_mailboxes
208
- index = " No. Email Name Quota (MB) Active Maildir"
209
- report("Addresses", index) do
210
- if mailboxes.empty?
211
- puts " No addresses"
212
- next
213
- end
228
+ rows = []
229
+ mailboxes = if domain_name
230
+ Domain.find(domain_name).rel_mailboxes
231
+ else
232
+ Mailbox.all
233
+ end
234
+ headings = ["No.", "Email", "Name", "Quota (MB)", "Active",
235
+ "Scheme Prefix", "Maildir"]
214
236
 
215
- mailboxes.each_with_index do |m, i|
216
- quota = m.quota.to_f/ KB_TO_MB.to_f
217
- puts "%4d %-30s %-20s %10s %-3s %s" % [i+1, m.username, m.name, max_str(quota.to_i), m.active_str, m.maildir]
218
- end
237
+ puts_title("Accounts")
238
+ if mailboxes.empty?
239
+ puts "No accounts"
240
+ return
219
241
  end
220
- end
221
242
 
222
- def show_alias(domain_name)
223
- domain_check(domain_name)
243
+ mailboxes.each_with_index do |m, i|
244
+ no = i + 1
245
+ rows << [no.to_s, m.username, m.name, m.quota_display_str,
246
+ m.active_str, m.scheme_prefix, m.maildir]
247
+ end
248
+
249
+ puts_table(headings: headings, rows: rows)
250
+ end
224
251
 
225
- forwards, aliases = Domain.find(domain_name).rel_aliases.partition { |a| a.mailbox? }
252
+ def show_forwards(domain_name=nil)
253
+ domain_check(domain_name) if domain_name
226
254
 
227
- forwards.delete_if do |f|
228
- f.address == f.goto
229
- end
255
+ forwards = if domain_name
256
+ Domain.find(domain_name).rel_aliases.forward
257
+ else
258
+ Alias.forward
259
+ end
230
260
 
231
261
  show_alias_base("Forwards", forwards)
262
+ end
263
+
264
+ def show_aliases(domain_name=nil)
265
+ domain_check(domain_name) if domain_name
266
+
267
+ aliases = if domain_name
268
+ Domain.find(domain_name).rel_aliases.pure
269
+ else
270
+ db_aliases = Alias.pure
271
+ end
272
+
232
273
  show_alias_base("Aliases", aliases)
233
274
  end
234
275
 
235
276
  def show_admin_domain(user_name)
236
277
  admin = Admin.find(user_name)
278
+ puts_title("Admin Domains (#{user_name})")
237
279
  if admin.rel_domains.empty?
238
- puts "\nNo domain in database"
280
+ puts "\nNo domains for #{user_name}"
239
281
  return
240
282
  end
241
283
 
242
- report("Domains (#{user_name})", " No. Domain") do
243
- admin.rel_domains.each_with_index do |d, i|
244
- puts "%4d %-30s" % [i + 1, d.domain]
245
- end
284
+ rows = []
285
+ admin.rel_domains.each_with_index do |d, i|
286
+ no = i + 1
287
+ rows << [no.to_s, d.domain]
246
288
  end
289
+ puts_table(rows: rows, headings: %w[No. Domain])
247
290
  end
248
291
 
249
- def add_admin(user_name, password, super_admin = false, scheme = nil)
292
+ def add_admin(user_name, password, super_admin: false,
293
+ scheme: nil, rounds: nil)
250
294
  validate_password(password)
251
295
 
252
- @base.add_admin(user_name, hashed_password(password, scheme))
296
+ h_password = hashed_password(password, user_name: user_name,
297
+ scheme: scheme, rounds: rounds)
298
+ @base.add_admin(user_name, h_password)
299
+
253
300
  if super_admin
254
301
  Admin.find(user_name).super_admin = true
255
302
  puts_registered(user_name, "a super admin")
@@ -268,12 +315,13 @@ module PostfixAdmin
268
315
  puts "#{domain_name} was successfully deleted from #{user_name}"
269
316
  end
270
317
 
271
- def add_account(address, password, scheme = nil, name = nil)
318
+ def add_account(address, password, name: nil, scheme: nil, rounds: nil)
272
319
  validate_password(password)
273
320
 
274
- @base.add_account(address, hashed_password(password, scheme), name)
321
+ h_password = hashed_password(password, user_name: address,
322
+ scheme: scheme, rounds: rounds)
323
+ @base.add_account(address, h_password, name: name)
275
324
  puts_registered(address, "an account")
276
- show_account_details(address)
277
325
  end
278
326
 
279
327
  def add_alias(address, goto)
@@ -282,10 +330,13 @@ module PostfixAdmin
282
330
  end
283
331
 
284
332
  def edit_account(address, options)
333
+ quota = options[:quota]
334
+ raise "Invalid Quota value: #{quota}" if quota && quota <= 0
335
+
285
336
  mailbox_check(address)
286
337
  mailbox = Mailbox.find(address)
287
338
  mailbox.name = options[:name] if options[:name]
288
- mailbox.quota = options[:quota] * KB_TO_MB if options[:quota]
339
+ mailbox.quota_mb = quota if quota
289
340
  mailbox.active = options[:active] unless options[:active].nil?
290
341
  mailbox.save!
291
342
 
@@ -295,7 +346,7 @@ module PostfixAdmin
295
346
  mail_alias.save!
296
347
  end
297
348
 
298
- puts "Successfully updated #{address}"
349
+ puts "successfully updated #{address}"
299
350
  show_account_details(address)
300
351
  end
301
352
 
@@ -306,7 +357,7 @@ module PostfixAdmin
306
357
  mail_alias.active = options[:active] unless options[:active].nil?
307
358
  mail_alias.save or raise "Could not save Alias"
308
359
 
309
- puts "Successfully updated #{address}"
360
+ puts "successfully updated #{address}"
310
361
  show_alias_details(address)
311
362
  end
312
363
 
@@ -325,11 +376,25 @@ module PostfixAdmin
325
376
  puts_deleted(address)
326
377
  end
327
378
 
328
- def log
329
- Log.all.each do |l|
330
- time = l.timestamp.strftime("%Y-%m-%d %X %Z")
331
- puts "#{time} #{l.username} #{l.domain} #{l.action} #{l.data}"
379
+ def log(domain: nil, last: nil)
380
+ headings = %w[Timestamp Admin Domain Action Data]
381
+ rows = []
382
+
383
+ logs = if domain
384
+ Log.where(domain: domain)
385
+ else
386
+ Log.all
387
+ end
388
+
389
+ logs = logs.last(last) if last
390
+
391
+ logs.each do |l|
392
+ # TODO: Consider if zone should be included ('%Z').
393
+ time = l.timestamp.strftime("%Y-%m-%d %X")
394
+ rows << [time, l.username, l.domain, l.action, l.data]
332
395
  end
396
+
397
+ puts_table(headings: headings, rows: rows)
333
398
  end
334
399
 
335
400
  def dump
@@ -366,17 +431,60 @@ module PostfixAdmin
366
431
 
367
432
  private
368
433
 
434
+ def show_general_summary
435
+ rows = []
436
+ title = "Summary"
437
+ rows << ["Domains", Domain.without_all.count]
438
+ rows << ["Admins", Admin.count]
439
+ rows << ["Mailboxes", Mailbox.count]
440
+ rows << ["Aliases", Alias.pure.count]
441
+
442
+ puts_title(title)
443
+ puts_table(rows: rows)
444
+ end
445
+
446
+ def show_domain_summary(domain_name)
447
+ domain_name = domain_name.downcase
448
+ domain_check(domain_name)
449
+
450
+ rows = []
451
+ domain = Domain.find(domain_name)
452
+ rows << ["Mailboxes", "%4d / %4s" % [domain.rel_mailboxes.count, domain.mailboxes_str]]
453
+ rows << ["Aliases", "%4d / %4s" % [domain.pure_aliases.count, domain.aliases_str]]
454
+ rows << ["Max Quota (MB)", domain.maxquota_str]
455
+ rows << ["Active", domain.active_str]
456
+ rows << ["Description", domain.description]
457
+
458
+ puts_title(domain_name)
459
+ puts_table(rows: rows)
460
+ end
461
+
462
+ def show_domain_details(domain_name)
463
+ show_admins(domain_name)
464
+ puts
465
+ show_accounts(domain_name)
466
+ puts
467
+ show_forwards(domain_name)
468
+ puts
469
+ show_aliases(domain_name)
470
+ end
471
+
369
472
  def show_alias_base(title, addresses)
370
- report(title, " No. Address Active Go to") do
371
- if addresses.empty?
372
- puts " No #{title.downcase}"
373
- next
374
- end
473
+ rows = []
474
+ puts_title(title)
375
475
 
376
- addresses.each_with_index do |a, i|
377
- puts "%4d %-40s %-3s %s" % [i+1, a.address, a.active_str, a.goto]
378
- end
476
+ if addresses.empty?
477
+ puts "No #{title.downcase}"
478
+ return
379
479
  end
480
+
481
+ headings = ["No.", "Address", "Active", "Go to"]
482
+ addresses.each_with_index do |a, i|
483
+ no = i + 1
484
+ rows << [no.to_s, a.address, a.active_str, a.goto]
485
+ end
486
+
487
+ puts_table(headings: headings, rows: rows)
380
488
  end
381
489
 
382
490
  def puts_registered(name, as_str)
@@ -388,7 +496,7 @@ module PostfixAdmin
388
496
  end
389
497
 
390
498
  def config_file
391
- config_file = File.expand_path(CLI.config_file)
499
+ File.expand_path(CLI.config_file)
392
500
  end
393
501
 
394
502
  def load_config
@@ -409,17 +517,12 @@ module PostfixAdmin
409
517
  File.chmod(0600, file)
410
518
  end
411
519
 
412
- def print_line
413
- puts "-"*120
520
+ def puts_table(args)
521
+ puts Terminal::Table.new(args)
414
522
  end
415
523
 
416
- def report(title, index = nil)
417
- puts "\n[#{title}]"
418
- print_line if index
419
- puts index if index
420
- print_line
421
- yield
422
- print_line
524
+ def puts_title(title)
525
+ puts "| #{title} |"
423
526
  end
424
527
 
425
528
  def account_check(user_name)
@@ -455,38 +558,44 @@ module PostfixAdmin
455
558
  end
456
559
  end
457
560
 
458
- def change_password(klass, user_name, password)
561
+ def change_password(klass, user_name, password, scheme: nil, rounds: nil)
459
562
  raise Error, "Could not find #{user_name}" unless klass.exists?(user_name)
460
563
 
461
564
  validate_password(password)
462
565
 
463
566
  obj = klass.find(user_name)
567
+ h_password = hashed_password(password, scheme: scheme, rounds: rounds,
568
+ user_name: user_name)
464
569
 
465
- if obj.update(password: hashed_password(password))
466
- puts "the password of #{user_name} was successfully changed."
570
+ if obj.update(password: h_password)
571
+ puts "the password of #{user_name} was successfully updated."
467
572
  else
468
573
  raise "Could not change password of #{klass.name}"
469
574
  end
470
575
  end
471
576
 
472
- def max_str(value)
473
- case value
474
- when 0
475
- '--'
476
- when -1
477
- '0'
478
- else
479
- value.to_s
480
- end
481
- end
482
-
483
- private
577
+ # The default number of rounds for BLF-CRYPT in `doveadm pw` is 5.
578
+ # However, this method uses 10 rounds by default, similar to
579
+ # the password_hash() function in PHP.
580
+ #
581
+ # https://www.php.net/manual/en/function.password-hash.php
582
+ # <?php
583
+ # echo password_hash("password", PASSWORD_BCRYPT);
584
+ #
585
+ # $2y$10$qzRgjWZWfH4VsNQGvp/DNObFSaMiZxXJSzgXqOOS/qtF68qIhhwFe
586
+ DEFAULT_BLF_CRYPT_ROUNDS = 10
484
587
 
485
- def hashed_password(password, in_scheme = nil)
588
+ # Generate a hashed password
589
+ def hashed_password(password, scheme: nil, rounds: nil, user_name: nil)
486
590
  prefix = @base.config[:passwordhash_prefix]
487
- scheme = in_scheme || @base.config[:scheme]
488
- puts "scheme: #{scheme}"
489
- PostfixAdmin::Doveadm.password(password, scheme, prefix)
591
+ new_scheme = scheme || @base.config[:scheme]
592
+ new_rounds = if rounds
593
+ rounds
594
+ elsif new_scheme == "BLF-CRYPT"
595
+ DEFAULT_BLF_CRYPT_ROUNDS
596
+ end
597
+ PostfixAdmin::Doveadm.password(password, new_scheme, rounds: new_rounds,
598
+ user_name: user_name, prefix: prefix)
490
599
  end
491
600
  end
492
601
  end