postfix_admin 0.2.1 → 0.3.0
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 +54 -0
- data/.gitignore +2 -0
- data/.rubocop.yml +20 -0
- data/CHANGELOG.md +16 -9
- data/Gemfile +1 -1
- data/LICENSE +1 -1
- data/README.md +26 -20
- data/Rakefile +19 -1
- data/bin/console +6 -2
- data/docker-admin/config.local.php +3 -0
- data/docker-app/Dockerfile +2 -6
- data/docker-app/my.cnf +2 -2
- data/docker-compose.yml +18 -14
- data/lib/postfix_admin/admin.rb +5 -0
- data/lib/postfix_admin/alias.rb +0 -19
- data/lib/postfix_admin/base.rb +118 -98
- data/lib/postfix_admin/cli.rb +180 -125
- data/lib/postfix_admin/concerns/dovecot_cram_md5_password.rb +0 -1
- data/lib/postfix_admin/domain.rb +35 -44
- data/lib/postfix_admin/doveadm.rb +4 -8
- data/lib/postfix_admin/mailbox.rb +14 -6
- data/lib/postfix_admin/runner.rb +35 -22
- data/lib/postfix_admin/version.rb +1 -1
- data/postfix_admin.gemspec +12 -10
- metadata +48 -22
- 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/base.rb
CHANGED
@@ -8,46 +8,41 @@ module PostfixAdmin
|
|
8
8
|
attr_reader :config
|
9
9
|
|
10
10
|
DEFAULT_CONFIG = {
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
}
|
11
|
+
"database" => "mysql2://postfix:password@localhost/postfix",
|
12
|
+
"aliases" => 30,
|
13
|
+
"mailboxes" => 30,
|
14
|
+
"maxquota" => 100,
|
15
|
+
"scheme" => "CRAM-MD5",
|
16
|
+
"passwordhash_prefix" => true
|
17
|
+
}.freeze
|
18
18
|
|
19
19
|
def initialize(config)
|
20
20
|
@config = {}
|
21
|
-
@config[:database] = config[
|
22
|
-
@config[:aliases] = config[
|
23
|
-
@config[:mailboxes] = config[
|
24
|
-
@config[:maxquota] = config[
|
25
|
-
@config[:scheme] = config[
|
26
|
-
@config[:passwordhash_prefix] = if config
|
27
|
-
|
21
|
+
@config[:database] = config["database"]
|
22
|
+
@config[:aliases] = config["aliases"] || DEFAULT_CONFIG["aliases"]
|
23
|
+
@config[:mailboxes] = config["mailboxes"] || DEFAULT_CONFIG["mailboxes"]
|
24
|
+
@config[:maxquota] = config["maxquota"] || DEFAULT_CONFIG["maxquota"]
|
25
|
+
@config[:scheme] = config["scheme"] || DEFAULT_CONFIG["scheme"]
|
26
|
+
@config[:passwordhash_prefix] = if config.has_key?("passwordhash_prefix")
|
27
|
+
config["passwordhash_prefix"]
|
28
28
|
else
|
29
|
-
|
29
|
+
DEFAULT_CONFIG["passwordhash_prefix"]
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
33
|
def db_setup
|
34
|
-
|
34
|
+
database = ENV.fetch("DATABASE_URL") { @config[:database] }
|
35
35
|
|
36
|
-
|
37
|
-
|
38
|
-
if uri.scheme == "mysql"
|
39
|
-
uri.scheme = "mysql2"
|
40
|
-
warn("Deprecation Warning: Use 'mysql2' as a DB adopter instead of 'mysql' in '#{CLI.config_file}'")
|
36
|
+
unless database
|
37
|
+
raise_error "'database' parameter is required in '#{CLI.config_file}' or specify 'DATABASE_URL' environment variable"
|
41
38
|
end
|
42
39
|
|
43
|
-
|
44
|
-
raise "'#{uri.scheme}' is not supported as a DB adopter. Use 'mysql2' instead in '#{CLI.config_file}'."
|
45
|
-
end
|
40
|
+
uri = URI.parse(database)
|
46
41
|
|
47
42
|
ActiveRecord::Base.establish_connection(uri.to_s)
|
48
43
|
|
49
44
|
rescue LoadError => e
|
50
|
-
|
45
|
+
raise_error e.message
|
51
46
|
end
|
52
47
|
|
53
48
|
def add_admin_domain(user_name, domain_name)
|
@@ -57,11 +52,11 @@ module PostfixAdmin
|
|
57
52
|
domain = Domain.find(domain_name)
|
58
53
|
|
59
54
|
if admin.has_domain?(domain)
|
60
|
-
|
55
|
+
raise_error "Admin '#{user_name}' has already been registered for Domain '#{domain_name}'"
|
61
56
|
end
|
62
57
|
|
63
58
|
admin.rel_domains << domain
|
64
|
-
admin.save
|
59
|
+
admin.save || raise_error("Relation Error: Domain of Admin")
|
65
60
|
end
|
66
61
|
|
67
62
|
def delete_admin_domain(user_name, domain_name)
|
@@ -71,135 +66,116 @@ module PostfixAdmin
|
|
71
66
|
domain_admin_query = admin.domain_admins.where(domain: domain_name)
|
72
67
|
|
73
68
|
unless domain_admin_query.take
|
74
|
-
|
69
|
+
raise_error "#{user_name} is not registered as admin of #{domain_name}."
|
75
70
|
end
|
76
71
|
|
77
72
|
domain_admin_query.delete_all
|
78
73
|
end
|
79
74
|
|
80
75
|
def add_admin(username, password)
|
81
|
-
|
76
|
+
validate_password(password)
|
82
77
|
|
83
78
|
if Admin.exists?(username)
|
84
|
-
|
79
|
+
raise_error "Admin has already been registered: #{username}"
|
85
80
|
end
|
81
|
+
|
86
82
|
admin = Admin.new
|
87
83
|
admin.attributes = {
|
88
84
|
username: username,
|
89
|
-
password: password
|
85
|
+
password: password
|
90
86
|
}
|
91
|
-
unless admin.save
|
92
|
-
raise "Could not save Admin #{admin.errors.map(&:to_s).join}"
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
def add_account(address, password, in_name = nil)
|
97
|
-
name = in_name || ''
|
98
|
-
password_check(password)
|
99
|
-
|
100
|
-
if address !~ /.+\@.+\..+/
|
101
|
-
raise Error, "Invalid mail address #{address}"
|
102
|
-
end
|
103
|
-
user, domain_name = address_split(address)
|
104
|
-
path = "#{domain_name}/#{address}/"
|
105
87
|
|
106
|
-
unless
|
107
|
-
|
108
|
-
end
|
88
|
+
raise_save_error(admin) unless admin.save
|
89
|
+
end
|
109
90
|
|
110
|
-
|
111
|
-
|
112
|
-
|
91
|
+
# Adds an email account that consists of a Mailbox and an Alias.
|
92
|
+
def add_account(address, password, name: "")
|
93
|
+
validate_account(address, password)
|
113
94
|
|
114
|
-
|
95
|
+
local_part, domain_name = address_split(address)
|
96
|
+
domain_must_exist!(domain_name)
|
115
97
|
|
116
98
|
attributes = {
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
quota_mb: @config[:maxquota]
|
99
|
+
local_part: local_part,
|
100
|
+
domain: domain_name,
|
101
|
+
password: password,
|
102
|
+
name: name,
|
103
|
+
quota_mb: @config[:maxquota]
|
123
104
|
}
|
124
105
|
|
106
|
+
# An Alias also will be added when a Mailbox is saved.
|
125
107
|
mailbox = Mailbox.new(attributes)
|
126
108
|
|
127
|
-
|
128
|
-
|
129
|
-
unless domain.save
|
130
|
-
raise "Could not save Mailbox and Domain #{mailbox.errors.map(&:to_s).join} #{domain.errors.map(&:to_s).join}"
|
131
|
-
end
|
109
|
+
raise_save_error(mailbox) unless mailbox.save
|
132
110
|
end
|
133
111
|
|
134
112
|
def add_alias(address, goto)
|
135
113
|
if Mailbox.exists?(address)
|
136
|
-
|
137
|
-
end
|
138
|
-
if Alias.exists?(address)
|
139
|
-
raise Error, "alias #{address} is already registered!"
|
114
|
+
raise_error "Mailbox has already been registered: #{address}"
|
140
115
|
end
|
141
116
|
|
142
|
-
|
117
|
+
alias_must_not_exist!(address)
|
143
118
|
|
144
|
-
|
145
|
-
raise Error, "Invalid domain! #{domain_name}"
|
146
|
-
end
|
119
|
+
local_part, domain_name = address_split(address)
|
147
120
|
|
148
|
-
domain =
|
121
|
+
domain = find_domain(domain_name)
|
149
122
|
|
150
123
|
attributes = {
|
151
124
|
local_part: local_part,
|
152
125
|
goto: goto
|
153
126
|
}
|
127
|
+
|
154
128
|
domain.rel_aliases << Alias.new(attributes)
|
155
|
-
|
129
|
+
|
130
|
+
raise_save_error(domain) unless domain.save
|
156
131
|
end
|
157
132
|
|
158
133
|
def delete_alias(address)
|
159
134
|
if Mailbox.exists?(address)
|
160
|
-
|
135
|
+
raise_error "Can not delete mailbox by delete_alias. Use delete_account"
|
161
136
|
end
|
162
137
|
|
163
138
|
unless Alias.exists?(address)
|
164
|
-
|
139
|
+
raise_error "#{address} is not found!"
|
165
140
|
end
|
166
141
|
|
167
142
|
Alias.where(address: address).delete_all
|
168
143
|
end
|
169
144
|
|
170
|
-
def add_domain(domain_name)
|
145
|
+
def add_domain(domain_name, description: nil)
|
171
146
|
domain_name = domain_name.downcase
|
172
|
-
|
173
|
-
|
147
|
+
|
148
|
+
unless valid_domain_name?(domain_name)
|
149
|
+
raise_error "Invalid domain name: #{domain_name}"
|
174
150
|
end
|
151
|
+
|
175
152
|
if Domain.exists?(domain_name)
|
176
|
-
|
153
|
+
raise_error "Domain has already been registered: #{domain_name}"
|
177
154
|
end
|
155
|
+
|
156
|
+
new_description = description || domain_name
|
157
|
+
|
178
158
|
domain = Domain.new
|
179
159
|
domain.attributes = {
|
180
160
|
domain: domain_name,
|
181
|
-
description:
|
161
|
+
description: new_description,
|
182
162
|
aliases: @config[:aliases],
|
183
163
|
mailboxes: @config[:mailboxes],
|
184
|
-
maxquota: @config[:maxquota]
|
164
|
+
maxquota: @config[:maxquota]
|
185
165
|
}
|
186
166
|
domain.save!
|
187
167
|
end
|
188
168
|
|
189
169
|
def delete_domain(domain_name)
|
190
170
|
domain_name = domain_name.downcase
|
191
|
-
unless Domain.exists?(domain_name)
|
192
|
-
raise Error, "Could not find domain #{domain_name}"
|
193
|
-
end
|
194
171
|
|
195
|
-
domain =
|
196
|
-
domain.rel_mailboxes.delete_all
|
197
|
-
domain.rel_aliases.delete_all
|
172
|
+
domain = find_domain(domain_name)
|
198
173
|
|
199
174
|
admin_names = domain.admins.map(&:username)
|
200
175
|
|
201
|
-
domain.
|
176
|
+
domain.destroy!
|
202
177
|
|
178
|
+
# Remove admins who had the deleted domain only
|
203
179
|
admin_names.each do |name|
|
204
180
|
next unless Admin.exists?(name)
|
205
181
|
|
@@ -207,16 +183,14 @@ module PostfixAdmin
|
|
207
183
|
|
208
184
|
# check if the admin is needed or not
|
209
185
|
if admin.rel_domains.empty?
|
210
|
-
admin.destroy
|
186
|
+
admin.destroy!
|
211
187
|
end
|
212
188
|
end
|
213
|
-
|
214
|
-
domain.destroy
|
215
189
|
end
|
216
190
|
|
217
191
|
def delete_admin(user_name)
|
218
192
|
unless Admin.exists?(user_name)
|
219
|
-
|
193
|
+
raise_error "Could not find admin #{user_name}"
|
220
194
|
end
|
221
195
|
|
222
196
|
admin = Admin.find(user_name)
|
@@ -226,26 +200,72 @@ module PostfixAdmin
|
|
226
200
|
|
227
201
|
def delete_account(address)
|
228
202
|
unless Alias.exists?(address) && Mailbox.exists?(address)
|
229
|
-
|
203
|
+
raise_error "Could not find account: #{address}"
|
230
204
|
end
|
231
205
|
|
232
206
|
Mailbox.where(username: address).delete_all
|
233
207
|
Alias.where(address: address).delete_all
|
234
208
|
end
|
235
209
|
|
210
|
+
private
|
211
|
+
|
212
|
+
def find_domain(domain_name)
|
213
|
+
domain_must_exist!(domain_name)
|
214
|
+
Domain.find(domain_name)
|
215
|
+
end
|
216
|
+
|
217
|
+
def raise_error(message)
|
218
|
+
raise PostfixAdmin::Error, message
|
219
|
+
end
|
220
|
+
|
221
|
+
def raise_save_error(obj)
|
222
|
+
raise_error "Failed to save #{obj.class}: #{obj.errors.full_messages.join(', ')}"
|
223
|
+
end
|
224
|
+
|
236
225
|
def address_split(address)
|
237
226
|
address.split('@')
|
238
227
|
end
|
239
228
|
|
240
|
-
|
229
|
+
def valid_domain_name?(domain_name)
|
230
|
+
/.+\..+/.match?(domain_name)
|
231
|
+
end
|
232
|
+
|
233
|
+
def valid_email_address?(address)
|
234
|
+
/.+@.+\..+/.match?(address)
|
235
|
+
end
|
236
|
+
|
237
|
+
def domain_must_exist!(domain_name)
|
238
|
+
unless Domain.exists?(domain_name)
|
239
|
+
raise_error "Could not find domain: #{domain_name}"
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
def alias_must_not_exist!(address)
|
244
|
+
if Alias.exists?(address)
|
245
|
+
raise_error "Alias has already been registered: #{address}"
|
246
|
+
end
|
247
|
+
end
|
241
248
|
|
242
249
|
def admin_domain_check(user_name, domain_name)
|
243
|
-
|
244
|
-
|
250
|
+
unless Admin.exists?(user_name)
|
251
|
+
raise_error "#{user_name} is not registered as admin."
|
252
|
+
end
|
253
|
+
|
254
|
+
domain_must_exist!(domain_name)
|
255
|
+
end
|
256
|
+
|
257
|
+
def validate_password(password)
|
258
|
+
raise_error "Empty password" if password.nil? || password.empty?
|
245
259
|
end
|
246
260
|
|
247
|
-
def
|
248
|
-
|
261
|
+
def validate_account(address, password)
|
262
|
+
validate_password(password)
|
263
|
+
|
264
|
+
unless valid_email_address?(address)
|
265
|
+
raise_error "Invalid email address: #{address}"
|
266
|
+
end
|
267
|
+
|
268
|
+
alias_must_not_exist!(address)
|
249
269
|
end
|
250
270
|
end
|
251
271
|
end
|