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