postfix_admin 0.3.0 → 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 +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
|
|