postfix_admin 0.2.1 → 0.3.1
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 +52 -0
- data/.gitignore +2 -0
- data/.rubocop.yml +20 -0
- data/CHANGELOG.md +26 -12
- data/Gemfile +1 -1
- data/LICENSE +1 -1
- data/README.md +26 -20
- data/Rakefile +48 -1
- data/bin/console +6 -2
- data/db/reset.rb +7 -0
- data/db/seeds.rb +26 -0
- data/docker-admin/config.local.php +6 -1
- data/docker-app/Dockerfile +2 -6
- data/docker-app/my.cnf +2 -2
- data/docker-compose.yml +20 -20
- data/lib/postfix_admin/base.rb +119 -112
- data/lib/postfix_admin/cli.rb +267 -158
- data/lib/postfix_admin/doveadm.rb +32 -20
- data/lib/postfix_admin/{admin.rb → models/admin.rb} +25 -2
- data/lib/postfix_admin/{alias.rb → models/alias.rb} +16 -26
- data/lib/postfix_admin/{application_record.rb → models/application_record.rb} +1 -1
- 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/models/domain.rb +112 -0
- data/lib/postfix_admin/models/domain_admin.rb +19 -0
- data/lib/postfix_admin/models/log.rb +20 -0
- data/lib/postfix_admin/models/mailbox.rb +126 -0
- data/lib/postfix_admin/models/quota2.rb +18 -0
- data/lib/postfix_admin/models.rb +8 -9
- data/lib/postfix_admin/runner.rb +91 -28
- data/lib/postfix_admin/version.rb +1 -1
- data/postfix_admin.gemspec +12 -10
- metadata +61 -34
- data/.github/workflows/ruby.yml +0 -37
- data/docker-app/docker-entrypoint.sh +0 -5
- data/docker-app-2.5/Dockerfile +0 -15
- data/lib/postfix_admin/concerns/dovecot_cram_md5_password.rb +0 -30
- data/lib/postfix_admin/domain.rb +0 -98
- 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 -89
- 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
@@ -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
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
-
|
49
|
-
|
50
|
-
show_alias(name)
|
56
|
+
# domain name
|
57
|
+
show_domain_details(name)
|
51
58
|
else
|
52
|
-
|
53
|
-
|
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
|
61
|
-
|
62
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
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
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
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
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
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
|
127
|
-
|
128
|
-
|
129
|
-
|
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
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
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 "
|
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 "
|
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
|
205
|
+
def show_admins(domain_name = nil)
|
189
206
|
admins = domain_name ? Admin.select { |a| a.rel_domains.exists?(domain_name) } : Admin.all
|
190
|
-
|
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
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
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
|
205
|
-
domain_check(domain_name)
|
225
|
+
def show_accounts(domain_name=nil)
|
226
|
+
domain_check(domain_name) if domain_name
|
206
227
|
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
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
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
237
|
+
puts_title("Accounts")
|
238
|
+
if mailboxes.empty?
|
239
|
+
puts "No accounts"
|
240
|
+
return
|
219
241
|
end
|
220
|
-
end
|
221
242
|
|
222
|
-
|
223
|
-
|
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
|
-
|
252
|
+
def show_forwards(domain_name=nil)
|
253
|
+
domain_check(domain_name) if domain_name
|
226
254
|
|
227
|
-
forwards
|
228
|
-
|
229
|
-
|
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
|
280
|
+
puts "\nNo domains for #{user_name}"
|
239
281
|
return
|
240
282
|
end
|
241
283
|
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
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
|
292
|
+
def add_admin(user_name, password, super_admin: false,
|
293
|
+
scheme: nil, rounds: nil)
|
250
294
|
validate_password(password)
|
251
295
|
|
252
|
-
|
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
|
318
|
+
def add_account(address, password, name: nil, scheme: nil, rounds: nil)
|
272
319
|
validate_password(password)
|
273
320
|
|
274
|
-
|
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.
|
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 "
|
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 "
|
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
|
-
|
330
|
-
|
331
|
-
|
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
|
-
|
371
|
-
|
372
|
-
puts " No #{title.downcase}"
|
373
|
-
next
|
374
|
-
end
|
473
|
+
rows = []
|
474
|
+
puts_title(title)
|
375
475
|
|
376
|
-
|
377
|
-
|
378
|
-
|
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
|
-
|
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
|
413
|
-
puts
|
520
|
+
def puts_table(args)
|
521
|
+
puts Terminal::Table.new(args)
|
414
522
|
end
|
415
523
|
|
416
|
-
def
|
417
|
-
puts "
|
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:
|
466
|
-
puts "the password of #{user_name} was successfully
|
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
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
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
|
-
|
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
|
-
|
488
|
-
|
489
|
-
|
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
|