postfix_admin 0.3.0 → 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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +2 -4
- data/CHANGELOG.md +21 -14
- 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 -7
- data/lib/postfix_admin/base.rb +4 -17
- data/lib/postfix_admin/cli.rb +102 -48
- data/lib/postfix_admin/doveadm.rb +31 -15
- data/lib/postfix_admin/{admin.rb → models/admin.rb} +20 -2
- data/lib/postfix_admin/{alias.rb → models/alias.rb} +16 -7
- 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/{domain.rb → models/domain.rb} +23 -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 +57 -7
- data/lib/postfix_admin/version.rb +1 -1
- metadata +15 -14
- 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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a9877c43c00661c60c1fee5e200211c97cd4cdc8d53593985c3e49f8296d27b9
|
4
|
+
data.tar.gz: 9d9744f09e0a9e3ccdc72af138a4879824f3cd00bc3a1a111d0320a30007ee84
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 86dfd30f6793fd6c13d09e532d7499f2e040dbe654f1bf86299d79b518a96906b6e208fd87c9bbda631d577ccfe39fb08f910e9cc41617be00425c84860d03f2
|
7
|
+
data.tar.gz: f553b648aefc912877d3bcc149258a1d529caa457827f24b04bc4c5d4b45d7fc1397c4189c56ba0e800360320b6295b92abbde0ff344dc56d764995d8d88c448
|
data/.github/workflows/ci.yml
CHANGED
@@ -38,9 +38,9 @@ jobs:
|
|
38
38
|
- name: Install dovecotpw
|
39
39
|
run: sudo apt-get install -y dovecot-core
|
40
40
|
- name: Copy configure file
|
41
|
-
run: cp ./
|
41
|
+
run: cp ./spec/misc/postfix_admin.conf ~/.postfix_admin.conf
|
42
42
|
- name: Copy my.cnf (for `rake setup_test_db`)
|
43
|
-
run: cp ./
|
43
|
+
run: cp ./spec/misc/ci.my.cnf ~/.my.cnf
|
44
44
|
- name: docker-compose up
|
45
45
|
run: docker-compose up -d db
|
46
46
|
- name: Sleep (work around)
|
@@ -48,7 +48,5 @@ jobs:
|
|
48
48
|
run: sleep 10
|
49
49
|
- name: Set up test database
|
50
50
|
run: bundle exec rake setup_test_db
|
51
|
-
- name: Run tests
|
52
|
-
run: bundle exec rake test
|
53
51
|
- name: Run specs
|
54
52
|
run: bundle exec rake spec
|
data/CHANGELOG.md
CHANGED
@@ -1,29 +1,36 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
|
+
## 0.3.1
|
4
|
+
+ Added `admins`, `domains`, `accounts`, `aliases` and `forwards` subcommands
|
5
|
+
* Added `teardown` subcommand for the opposite operation of `setup`
|
6
|
+
* `delete_domain` subcommand removes logs associated with the domain
|
7
|
+
* Added `-s` (scheme) and `-r` (rounds) options for subcommands that require password arguments
|
8
|
+
+ Encryption rounds are supported only for `BLF-CRYPT`, `SHA256-CRYPT`, and `SHA512-CRYPT` schemes
|
9
|
+
|
3
10
|
## 0.3.0
|
4
|
-
|
5
|
-
|
6
|
-
|
11
|
+
* Added support for table display format
|
12
|
+
* No longer supports `dovecotpw` for password hash generation
|
13
|
+
+ Only `doveadm pw` is supported
|
7
14
|
|
8
15
|
## 0.2.1
|
9
|
-
|
10
|
-
|
16
|
+
* Added support for the `superadmin` column in the `admin` table
|
17
|
+
* Added `passwordhash_prefix` keyword in the configuration format for backward compatibility
|
11
18
|
|
12
19
|
## 0.2.0
|
13
|
-
|
14
|
-
|
20
|
+
* Switched the object-relational mapper from DataMapper to ActiveRecord
|
21
|
+
* Stored password hashes now include scheme prefixes, such as `{CRAM-MD5}` and `{PLAIN}`
|
15
22
|
|
16
23
|
## 0.1.4
|
17
|
-
|
24
|
+
* Added `log` subcommand
|
18
25
|
|
19
26
|
## 0.1.3
|
20
|
-
|
21
|
-
|
27
|
+
* Added support for activation and deactivation of domains, admins, and accounts
|
28
|
+
* Added `edit_admin` subcommand
|
22
29
|
|
23
30
|
## 0.1.2
|
24
|
-
|
25
|
-
|
26
|
-
|
31
|
+
* Added support for password hashing by doveadm (external subcommand)
|
32
|
+
* Display active status
|
33
|
+
* Hide passwords in list format
|
27
34
|
|
28
35
|
## 0.1.1, released 2013-05-10
|
29
|
-
|
36
|
+
* Fixed string length issue for passwords
|
data/Rakefile
CHANGED
@@ -2,6 +2,11 @@ require "bundler/gem_tasks"
|
|
2
2
|
require "rake/testtask"
|
3
3
|
require "rspec/core/rake_task"
|
4
4
|
|
5
|
+
require "bundler/setup"
|
6
|
+
Bundler.require(:default, :development)
|
7
|
+
require "postfix_admin"
|
8
|
+
require "postfix_admin/cli"
|
9
|
+
|
5
10
|
Rake::TestTask.new(:test) do |t|
|
6
11
|
t.libs << "test"
|
7
12
|
t.libs << "lib"
|
@@ -22,3 +27,27 @@ task :setup_test_db do
|
|
22
27
|
puts import_db_cmd
|
23
28
|
puts `#{import_db_cmd}`
|
24
29
|
end
|
30
|
+
|
31
|
+
namespace :db do
|
32
|
+
desc "Loads the seed data from db/seeds.rb"
|
33
|
+
task :seed do
|
34
|
+
establish_db_connection
|
35
|
+
require_relative "db/seeds"
|
36
|
+
end
|
37
|
+
|
38
|
+
namespace :seed do
|
39
|
+
desc "Truncates tables of each database for current environment and loads the seeds"
|
40
|
+
task :replant do
|
41
|
+
establish_db_connection
|
42
|
+
|
43
|
+
require_relative "db/reset"
|
44
|
+
require_relative "db/seeds"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def establish_db_connection
|
49
|
+
FactoryBot.find_definitions
|
50
|
+
|
51
|
+
PostfixAdmin::CLI.new.db_setup
|
52
|
+
end
|
53
|
+
end
|
data/db/reset.rb
ADDED
data/db/seeds.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
include FactoryBot::Syntax::Methods
|
2
|
+
|
3
|
+
create(:domain, domain: "example.com", description: "example.com Description")
|
4
|
+
create(:domain, domain: "example.org", description: "example.org Description")
|
5
|
+
|
6
|
+
all_admin = create(:admin, username: "all@example.com")
|
7
|
+
all_admin.rel_domains << Domain.find('ALL')
|
8
|
+
all_admin.superadmin = true if all_admin.has_superadmin_column?
|
9
|
+
all_admin.save!
|
10
|
+
|
11
|
+
admin = create(:admin, username: "admin@example.com")
|
12
|
+
domain = Domain.find('example.com')
|
13
|
+
domain.admins << admin
|
14
|
+
domain.rel_aliases << build(:alias, address: "alias@example.com")
|
15
|
+
domain.rel_mailboxes << build(:mailbox, local_part: "user")
|
16
|
+
domain.rel_mailboxes << build(:mailbox, local_part: "user2")
|
17
|
+
domain.save!
|
18
|
+
|
19
|
+
# forward
|
20
|
+
user = Alias.find("user2@example.com").update(goto: "user2@example.com,forward@example.com")
|
21
|
+
|
22
|
+
create(:quota2, username: "user@example.com", bytes: 75 * PostfixAdmin::KB_TO_MB)
|
23
|
+
|
24
|
+
create(:log)
|
25
|
+
create(:log, action: "delete_domain", data: "user@example.com")
|
26
|
+
create(:log, domain: "example.org", data: "example.org")
|
@@ -16,9 +16,11 @@ $CONF['password_validation'] = array(
|
|
16
16
|
|
17
17
|
$CONF['encrypt'] = 'dovecot:CRAM-MD5';
|
18
18
|
|
19
|
+
$CONF['default_aliases'] = array();
|
19
20
|
$CONF['domain_quota'] = 'NO';
|
20
21
|
$CONF['quota'] = 'YES';
|
22
|
+
$CONF['emailcheck_resolve_domain']='NO';
|
21
23
|
|
22
24
|
// setup_password: 'password'
|
23
25
|
$CONF['setup_password'] = '87745eb0269b2f42813b23601be3231a:6e41880f73d97321f2f0b25a5ee30f57f5ab3be8';
|
24
|
-
?>
|
26
|
+
?>
|
data/docker-compose.yml
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
# docker compose up -d
|
2
2
|
#
|
3
3
|
# Run tests on service app
|
4
|
-
# You may need to run tests on Docker because of its requirement of doveadm
|
4
|
+
# You may need to run tests on Docker because of its requirement of `doveadm` command.
|
5
5
|
# docker compose exec app bundle exec rake setup_test_db
|
6
6
|
# docker compose exec app bundle exec rake test
|
7
7
|
# docker compose exec app bundle exec rake spec
|
8
|
+
#
|
9
|
+
# rspec spec/runner_spec.rb
|
8
10
|
|
9
11
|
services:
|
10
12
|
app:
|
@@ -26,12 +28,6 @@ services:
|
|
26
28
|
- ./docker-admin/config.local.php:/var/www/html/config.local.php
|
27
29
|
depends_on:
|
28
30
|
- db
|
29
|
-
# environment:
|
30
|
-
# - POSTFIXADMIN_DB_TYPE=mysqli
|
31
|
-
# - POSTFIXADMIN_DB_HOST=db
|
32
|
-
# - POSTFIXADMIN_DB_USER=postfix
|
33
|
-
# - POSTFIXADMIN_DB_PASSWORD=password
|
34
|
-
# - POSTFIXADMIN_DB_NAME=postfix
|
35
31
|
db:
|
36
32
|
image: mariadb:10
|
37
33
|
ports:
|
data/lib/postfix_admin/base.rb
CHANGED
@@ -100,7 +100,7 @@ module PostfixAdmin
|
|
100
100
|
domain: domain_name,
|
101
101
|
password: password,
|
102
102
|
name: name,
|
103
|
-
|
103
|
+
quota: @config[:maxquota] * KB_TO_MB
|
104
104
|
}
|
105
105
|
|
106
106
|
# An Alias also will be added when a Mailbox is saved.
|
@@ -121,7 +121,7 @@ module PostfixAdmin
|
|
121
121
|
domain = find_domain(domain_name)
|
122
122
|
|
123
123
|
attributes = {
|
124
|
-
|
124
|
+
address: address,
|
125
125
|
goto: goto
|
126
126
|
}
|
127
127
|
|
@@ -174,18 +174,6 @@ module PostfixAdmin
|
|
174
174
|
admin_names = domain.admins.map(&:username)
|
175
175
|
|
176
176
|
domain.destroy!
|
177
|
-
|
178
|
-
# Remove admins who had the deleted domain only
|
179
|
-
admin_names.each do |name|
|
180
|
-
next unless Admin.exists?(name)
|
181
|
-
|
182
|
-
admin = Admin.find(name)
|
183
|
-
|
184
|
-
# check if the admin is needed or not
|
185
|
-
if admin.rel_domains.empty?
|
186
|
-
admin.destroy!
|
187
|
-
end
|
188
|
-
end
|
189
177
|
end
|
190
178
|
|
191
179
|
def delete_admin(user_name)
|
@@ -194,7 +182,6 @@ module PostfixAdmin
|
|
194
182
|
end
|
195
183
|
|
196
184
|
admin = Admin.find(user_name)
|
197
|
-
admin.rel_domains.delete_all
|
198
185
|
admin.destroy!
|
199
186
|
end
|
200
187
|
|
@@ -203,8 +190,8 @@ module PostfixAdmin
|
|
203
190
|
raise_error "Could not find account: #{address}"
|
204
191
|
end
|
205
192
|
|
206
|
-
Mailbox.
|
207
|
-
|
193
|
+
mailbox = Mailbox.find(address)
|
194
|
+
mailbox.destroy!
|
208
195
|
end
|
209
196
|
|
210
197
|
private
|
data/lib/postfix_admin/cli.rb
CHANGED
@@ -57,9 +57,9 @@ 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
63
|
end
|
64
64
|
end
|
65
65
|
|
@@ -73,13 +73,21 @@ module PostfixAdmin
|
|
73
73
|
|
74
74
|
# Set up a domain
|
75
75
|
# Add a domain, add an admin, and grant the admin access to the domain
|
76
|
-
def setup_domain(domain_name, password)
|
76
|
+
def setup_domain(domain_name, password, scheme: nil, rounds: nil)
|
77
77
|
admin = "admin@#{domain_name}"
|
78
78
|
add_domain(domain_name)
|
79
|
-
add_admin(admin, password)
|
79
|
+
add_admin(admin, password, scheme: scheme, rounds: rounds)
|
80
80
|
add_admin_domain(admin, domain_name)
|
81
81
|
end
|
82
82
|
|
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
|
+
|
83
91
|
def show_account_details(user_name, display_password: false)
|
84
92
|
account_check(user_name)
|
85
93
|
mailbox = Mailbox.find(user_name)
|
@@ -90,7 +98,7 @@ module PostfixAdmin
|
|
90
98
|
rows << ["Address", mailbox.username]
|
91
99
|
rows << ["Name", mailbox.name]
|
92
100
|
rows << ["Password", mailbox.password] if display_password
|
93
|
-
rows << ["Quota (MB)", mailbox.
|
101
|
+
rows << ["Quota (MB)", mailbox.quota_display_str(format: "%.1f")]
|
94
102
|
rows << ["Go to", mail_alias.goto]
|
95
103
|
rows << ["Active", mailbox.active_str]
|
96
104
|
|
@@ -125,7 +133,7 @@ module PostfixAdmin
|
|
125
133
|
puts_table(rows: rows)
|
126
134
|
end
|
127
135
|
|
128
|
-
def
|
136
|
+
def show_domains
|
129
137
|
rows = []
|
130
138
|
headings = ["No.", "Domain", "Aliases", "Mailboxes","Max Quota (MB)",
|
131
139
|
"Active", "Description"]
|
@@ -152,12 +160,12 @@ module PostfixAdmin
|
|
152
160
|
puts_registered(domain_name, "a domain")
|
153
161
|
end
|
154
162
|
|
155
|
-
def change_admin_password(user_name, password)
|
156
|
-
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)
|
157
165
|
end
|
158
166
|
|
159
|
-
def change_account_password(user_name, password)
|
160
|
-
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)
|
161
169
|
end
|
162
170
|
|
163
171
|
def edit_admin(admin_name, options)
|
@@ -171,7 +179,7 @@ module PostfixAdmin
|
|
171
179
|
admin.active = options[:active] unless options[:active].nil?
|
172
180
|
admin.save!
|
173
181
|
|
174
|
-
puts "
|
182
|
+
puts "successfully updated #{admin_name}"
|
175
183
|
show_admin_details(admin_name)
|
176
184
|
end
|
177
185
|
|
@@ -185,7 +193,7 @@ module PostfixAdmin
|
|
185
193
|
domain.description = options[:description] if options[:description]
|
186
194
|
domain.save!
|
187
195
|
|
188
|
-
puts "
|
196
|
+
puts "successfully updated #{domain_name}"
|
189
197
|
show_summary(domain_name)
|
190
198
|
end
|
191
199
|
|
@@ -194,9 +202,9 @@ module PostfixAdmin
|
|
194
202
|
puts_deleted(domain_name)
|
195
203
|
end
|
196
204
|
|
197
|
-
def
|
205
|
+
def show_admins(domain_name = nil)
|
198
206
|
admins = domain_name ? Admin.select { |a| a.rel_domains.exists?(domain_name) } : Admin.all
|
199
|
-
headings =
|
207
|
+
headings = ["No.", "Admin", "Domains", "Active", "Scheme Prefix"]
|
200
208
|
|
201
209
|
puts_title("Admins")
|
202
210
|
if admins.empty?
|
@@ -208,45 +216,60 @@ module PostfixAdmin
|
|
208
216
|
admins.each_with_index do |a, i|
|
209
217
|
no = i + 1
|
210
218
|
domains = a.super_admin? ? 'Super Admin' : a.rel_domains.count
|
211
|
-
rows << [no.to_s, a.username, domains.to_s, a.active_str]
|
219
|
+
rows << [no.to_s, a.username, domains.to_s, a.active_str, a.scheme_prefix]
|
212
220
|
end
|
213
221
|
|
214
222
|
puts_table(headings: headings, rows: rows)
|
215
223
|
end
|
216
224
|
|
217
|
-
def
|
218
|
-
domain_check(domain_name)
|
225
|
+
def show_accounts(domain_name=nil)
|
226
|
+
domain_check(domain_name) if domain_name
|
219
227
|
|
220
228
|
rows = []
|
221
|
-
mailboxes =
|
222
|
-
|
223
|
-
|
224
|
-
|
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"]
|
236
|
+
|
237
|
+
puts_title("Accounts")
|
225
238
|
if mailboxes.empty?
|
226
|
-
puts "No
|
239
|
+
puts "No accounts"
|
227
240
|
return
|
228
241
|
end
|
229
242
|
|
230
243
|
mailboxes.each_with_index do |m, i|
|
231
244
|
no = i + 1
|
232
|
-
rows << [no.to_s, m.username, m.name, m.
|
233
|
-
m.active_str, m.maildir]
|
245
|
+
rows << [no.to_s, m.username, m.name, m.quota_display_str,
|
246
|
+
m.active_str, m.scheme_prefix, m.maildir]
|
234
247
|
end
|
235
248
|
|
236
249
|
puts_table(headings: headings, rows: rows)
|
237
250
|
end
|
238
251
|
|
239
|
-
def
|
240
|
-
domain_check(domain_name)
|
241
|
-
|
242
|
-
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
|
243
254
|
|
244
|
-
forwards
|
245
|
-
|
246
|
-
|
255
|
+
forwards = if domain_name
|
256
|
+
Domain.find(domain_name).rel_aliases.forward
|
257
|
+
else
|
258
|
+
Alias.forward
|
259
|
+
end
|
247
260
|
|
248
261
|
show_alias_base("Forwards", forwards)
|
249
|
-
|
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
|
+
|
250
273
|
show_alias_base("Aliases", aliases)
|
251
274
|
end
|
252
275
|
|
@@ -266,10 +289,14 @@ module PostfixAdmin
|
|
266
289
|
puts_table(rows: rows, headings: %w[No. Domain])
|
267
290
|
end
|
268
291
|
|
269
|
-
def add_admin(user_name, password, super_admin
|
292
|
+
def add_admin(user_name, password, super_admin: false,
|
293
|
+
scheme: nil, rounds: nil)
|
270
294
|
validate_password(password)
|
271
295
|
|
272
|
-
|
296
|
+
h_password = hashed_password(password, user_name: user_name,
|
297
|
+
scheme: scheme, rounds: rounds)
|
298
|
+
@base.add_admin(user_name, h_password)
|
299
|
+
|
273
300
|
if super_admin
|
274
301
|
Admin.find(user_name).super_admin = true
|
275
302
|
puts_registered(user_name, "a super admin")
|
@@ -288,10 +315,12 @@ module PostfixAdmin
|
|
288
315
|
puts "#{domain_name} was successfully deleted from #{user_name}"
|
289
316
|
end
|
290
317
|
|
291
|
-
def add_account(address, password, scheme
|
318
|
+
def add_account(address, password, name: nil, scheme: nil, rounds: nil)
|
292
319
|
validate_password(password)
|
293
320
|
|
294
|
-
|
321
|
+
h_password = hashed_password(password, user_name: address,
|
322
|
+
scheme: scheme, rounds: rounds)
|
323
|
+
@base.add_account(address, h_password, name: name)
|
295
324
|
puts_registered(address, "an account")
|
296
325
|
end
|
297
326
|
|
@@ -301,10 +330,13 @@ module PostfixAdmin
|
|
301
330
|
end
|
302
331
|
|
303
332
|
def edit_account(address, options)
|
333
|
+
quota = options[:quota]
|
334
|
+
raise "Invalid Quota value: #{quota}" if quota && quota <= 0
|
335
|
+
|
304
336
|
mailbox_check(address)
|
305
337
|
mailbox = Mailbox.find(address)
|
306
338
|
mailbox.name = options[:name] if options[:name]
|
307
|
-
mailbox.
|
339
|
+
mailbox.quota_mb = quota if quota
|
308
340
|
mailbox.active = options[:active] unless options[:active].nil?
|
309
341
|
mailbox.save!
|
310
342
|
|
@@ -314,7 +346,7 @@ module PostfixAdmin
|
|
314
346
|
mail_alias.save!
|
315
347
|
end
|
316
348
|
|
317
|
-
puts "
|
349
|
+
puts "successfully updated #{address}"
|
318
350
|
show_account_details(address)
|
319
351
|
end
|
320
352
|
|
@@ -325,7 +357,7 @@ module PostfixAdmin
|
|
325
357
|
mail_alias.active = options[:active] unless options[:active].nil?
|
326
358
|
mail_alias.save or raise "Could not save Alias"
|
327
359
|
|
328
|
-
puts "
|
360
|
+
puts "successfully updated #{address}"
|
329
361
|
show_alias_details(address)
|
330
362
|
end
|
331
363
|
|
@@ -428,11 +460,13 @@ module PostfixAdmin
|
|
428
460
|
end
|
429
461
|
|
430
462
|
def show_domain_details(domain_name)
|
431
|
-
|
463
|
+
show_admins(domain_name)
|
432
464
|
puts
|
433
|
-
|
465
|
+
show_accounts(domain_name)
|
434
466
|
puts
|
435
|
-
|
467
|
+
show_forwards(domain_name)
|
468
|
+
puts
|
469
|
+
show_aliases(domain_name)
|
436
470
|
end
|
437
471
|
|
438
472
|
def show_alias_base(title, addresses)
|
@@ -524,24 +558,44 @@ module PostfixAdmin
|
|
524
558
|
end
|
525
559
|
end
|
526
560
|
|
527
|
-
def change_password(klass, user_name, password)
|
561
|
+
def change_password(klass, user_name, password, scheme: nil, rounds: nil)
|
528
562
|
raise Error, "Could not find #{user_name}" unless klass.exists?(user_name)
|
529
563
|
|
530
564
|
validate_password(password)
|
531
565
|
|
532
566
|
obj = klass.find(user_name)
|
567
|
+
h_password = hashed_password(password, scheme: scheme, rounds: rounds,
|
568
|
+
user_name: user_name)
|
533
569
|
|
534
|
-
if obj.update(password:
|
535
|
-
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."
|
536
572
|
else
|
537
573
|
raise "Could not change password of #{klass.name}"
|
538
574
|
end
|
539
575
|
end
|
540
576
|
|
541
|
-
|
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
|
587
|
+
|
588
|
+
# Generate a hashed password
|
589
|
+
def hashed_password(password, scheme: nil, rounds: nil, user_name: nil)
|
542
590
|
prefix = @base.config[:passwordhash_prefix]
|
543
|
-
|
544
|
-
|
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)
|
545
599
|
end
|
546
600
|
end
|
547
601
|
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,34 @@
|
|
1
|
-
require 'postfix_admin/concerns/
|
1
|
+
require 'postfix_admin/models/concerns/has_password'
|
2
2
|
|
3
3
|
module PostfixAdmin
|
4
4
|
class Admin < ApplicationRecord
|
5
|
+
# version: 1841
|
6
|
+
# > describe admin;
|
7
|
+
# +----------------+--------------+------+-----+---------------------+-------+
|
8
|
+
# | Field | Type | Null | Key | Default | Extra |
|
9
|
+
# +----------------+--------------+------+-----+---------------------+-------+
|
10
|
+
# | username | varchar(255) | NO | PRI | NULL | |
|
11
|
+
# | password | varchar(255) | NO | | NULL | |
|
12
|
+
# | created | datetime | NO | | 2000-01-01 00:00:00 | |
|
13
|
+
# | modified | datetime | NO | | 2000-01-01 00:00:00 | |
|
14
|
+
# | active | tinyint(1) | NO | | 1 | |
|
15
|
+
# | superadmin | tinyint(1) | NO | | 0 | |
|
16
|
+
# | phone | varchar(30) | NO | | | |
|
17
|
+
# | email_other | varchar(255) | NO | | | |
|
18
|
+
# | token | varchar(255) | NO | | | |
|
19
|
+
# | token_validity | datetime | NO | | 2000-01-01 00:00:00 | |
|
20
|
+
# +----------------+--------------+------+-----+---------------------+-------+
|
21
|
+
|
5
22
|
self.table_name = :admin
|
6
23
|
self.primary_key = :username
|
7
24
|
|
8
|
-
include
|
25
|
+
include HasPassword
|
9
26
|
|
10
27
|
validates :username, presence: true, uniqueness: { case_sensitive: false },
|
11
28
|
format: { with: RE_EMAIL_LIKE_WITH_ANCHORS,
|
12
29
|
message: "must be a valid email address" }
|
13
30
|
|
31
|
+
# Admin <-> DomainAdmin <-> Domain
|
14
32
|
has_many :domain_admins, foreign_key: :username, dependent: :delete_all
|
15
33
|
has_many :rel_domains, through: :domain_admins
|
16
34
|
|