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/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,181 +66,193 @@ 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: @config[:maxquota] * KB_TO_MB
|
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
|
+
address: address,
|
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.
|
202
|
-
|
203
|
-
admin_names.each do |name|
|
204
|
-
next unless Admin.exists?(name)
|
205
|
-
|
206
|
-
admin = Admin.find(name)
|
207
|
-
|
208
|
-
# check if the admin is needed or not
|
209
|
-
if admin.rel_domains.empty?
|
210
|
-
admin.destroy
|
211
|
-
end
|
212
|
-
end
|
213
|
-
|
214
|
-
domain.destroy
|
176
|
+
domain.destroy!
|
215
177
|
end
|
216
178
|
|
217
179
|
def delete_admin(user_name)
|
218
180
|
unless Admin.exists?(user_name)
|
219
|
-
|
181
|
+
raise_error "Could not find admin #{user_name}"
|
220
182
|
end
|
221
183
|
|
222
184
|
admin = Admin.find(user_name)
|
223
|
-
admin.rel_domains.delete_all
|
224
185
|
admin.destroy!
|
225
186
|
end
|
226
187
|
|
227
188
|
def delete_account(address)
|
228
189
|
unless Alias.exists?(address) && Mailbox.exists?(address)
|
229
|
-
|
190
|
+
raise_error "Could not find account: #{address}"
|
230
191
|
end
|
231
192
|
|
232
|
-
Mailbox.
|
233
|
-
|
193
|
+
mailbox = Mailbox.find(address)
|
194
|
+
mailbox.destroy!
|
195
|
+
end
|
196
|
+
|
197
|
+
private
|
198
|
+
|
199
|
+
def find_domain(domain_name)
|
200
|
+
domain_must_exist!(domain_name)
|
201
|
+
Domain.find(domain_name)
|
202
|
+
end
|
203
|
+
|
204
|
+
def raise_error(message)
|
205
|
+
raise PostfixAdmin::Error, message
|
206
|
+
end
|
207
|
+
|
208
|
+
def raise_save_error(obj)
|
209
|
+
raise_error "Failed to save #{obj.class}: #{obj.errors.full_messages.join(', ')}"
|
234
210
|
end
|
235
211
|
|
236
212
|
def address_split(address)
|
237
213
|
address.split('@')
|
238
214
|
end
|
239
215
|
|
240
|
-
|
216
|
+
def valid_domain_name?(domain_name)
|
217
|
+
/.+\..+/.match?(domain_name)
|
218
|
+
end
|
219
|
+
|
220
|
+
def valid_email_address?(address)
|
221
|
+
/.+@.+\..+/.match?(address)
|
222
|
+
end
|
223
|
+
|
224
|
+
def domain_must_exist!(domain_name)
|
225
|
+
unless Domain.exists?(domain_name)
|
226
|
+
raise_error "Could not find domain: #{domain_name}"
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def alias_must_not_exist!(address)
|
231
|
+
if Alias.exists?(address)
|
232
|
+
raise_error "Alias has already been registered: #{address}"
|
233
|
+
end
|
234
|
+
end
|
241
235
|
|
242
236
|
def admin_domain_check(user_name, domain_name)
|
243
|
-
|
244
|
-
|
237
|
+
unless Admin.exists?(user_name)
|
238
|
+
raise_error "#{user_name} is not registered as admin."
|
239
|
+
end
|
240
|
+
|
241
|
+
domain_must_exist!(domain_name)
|
245
242
|
end
|
246
243
|
|
247
|
-
def
|
248
|
-
|
244
|
+
def validate_password(password)
|
245
|
+
raise_error "Empty password" if password.nil? || password.empty?
|
246
|
+
end
|
247
|
+
|
248
|
+
def validate_account(address, password)
|
249
|
+
validate_password(password)
|
250
|
+
|
251
|
+
unless valid_email_address?(address)
|
252
|
+
raise_error "Invalid email address: #{address}"
|
253
|
+
end
|
254
|
+
|
255
|
+
alias_must_not_exist!(address)
|
249
256
|
end
|
250
257
|
end
|
251
258
|
end
|