postfix_admin 0.3.0 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +2 -4
  3. data/CHANGELOG.md +24 -14
  4. data/README.md +14 -0
  5. data/Rakefile +29 -0
  6. data/db/reset.rb +7 -0
  7. data/db/seeds.rb +26 -0
  8. data/docker-admin/config.local.php +3 -1
  9. data/docker-compose.yml +3 -8
  10. data/lib/postfix_admin/base.rb +11 -22
  11. data/lib/postfix_admin/cli.rb +140 -56
  12. data/lib/postfix_admin/doveadm.rb +31 -15
  13. data/lib/postfix_admin/{admin.rb → models/admin.rb} +21 -2
  14. data/lib/postfix_admin/models/alias.rb +63 -0
  15. data/lib/postfix_admin/{application_record.rb → models/application_record.rb} +2 -2
  16. data/lib/postfix_admin/{concerns → models/concerns}/existing_timestamp.rb +1 -2
  17. data/lib/postfix_admin/models/concerns/has_password.rb +16 -0
  18. data/lib/postfix_admin/{domain.rb → models/domain.rb} +66 -2
  19. data/lib/postfix_admin/models/domain_admin.rb +21 -0
  20. data/lib/postfix_admin/models/log.rb +22 -0
  21. data/lib/postfix_admin/models/mailbox.rb +143 -0
  22. data/lib/postfix_admin/models/quota2.rb +20 -0
  23. data/lib/postfix_admin/models.rb +8 -9
  24. data/lib/postfix_admin/runner.rb +68 -14
  25. data/lib/postfix_admin/version.rb +1 -1
  26. metadata +15 -14
  27. data/lib/postfix_admin/alias.rb +0 -46
  28. data/lib/postfix_admin/concerns/dovecot_cram_md5_password.rb +0 -29
  29. data/lib/postfix_admin/domain_admin.rb +0 -8
  30. data/lib/postfix_admin/log.rb +0 -5
  31. data/lib/postfix_admin/mail_domain.rb +0 -9
  32. data/lib/postfix_admin/mailbox.rb +0 -97
  33. data/lib/postfix_admin/quota.rb +0 -6
  34. /data/lib/postfix_admin/{concerns → models/concerns}/.keep +0 -0
@@ -57,9 +57,11 @@ module PostfixAdmin
57
57
  show_domain_details(name)
58
58
  else
59
59
  # no argument: show all domains and admins
60
- show_domain
60
+ show_domains
61
61
  puts
62
- show_admin
62
+ show_admins
63
+ puts
64
+ show_recent_logs
63
65
  end
64
66
  end
65
67
 
@@ -71,15 +73,35 @@ module PostfixAdmin
71
73
  end
72
74
  end
73
75
 
76
+ def show_recent_logs
77
+ if Log.count.zero?
78
+ puts "No logs"
79
+ return
80
+ end
81
+
82
+ logs = Log.last(10)
83
+ puts_title("Recent Logs")
84
+ puts_log_table(logs)
85
+ end
86
+
74
87
  # Set up a domain
75
88
  # Add a domain, add an admin, and grant the admin access to the domain
76
- def setup_domain(domain_name, password)
89
+ def setup_domain(domain_name, password, description: nil,
90
+ scheme: nil, rounds: nil)
77
91
  admin = "admin@#{domain_name}"
78
- add_domain(domain_name)
79
- add_admin(admin, password)
92
+ add_domain(domain_name, description: description)
93
+ add_admin(admin, password, scheme: scheme, rounds: rounds)
80
94
  add_admin_domain(admin, domain_name)
81
95
  end
82
96
 
97
+ # Tear down a domain
98
+ # Delete a domain and delete an admin user for it
99
+ def teardown_domain(domain_name)
100
+ admin = "admin@#{domain_name}"
101
+ delete_domain(domain_name)
102
+ delete_admin(admin)
103
+ end
104
+
83
105
  def show_account_details(user_name, display_password: false)
84
106
  account_check(user_name)
85
107
  mailbox = Mailbox.find(user_name)
@@ -90,7 +112,7 @@ module PostfixAdmin
90
112
  rows << ["Address", mailbox.username]
91
113
  rows << ["Name", mailbox.name]
92
114
  rows << ["Password", mailbox.password] if display_password
93
- rows << ["Quota (MB)", mailbox.quota_mb_str]
115
+ rows << ["Quota (MB)", mailbox.quota_display_str(format: "%.1f")]
94
116
  rows << ["Go to", mail_alias.goto]
95
117
  rows << ["Active", mailbox.active_str]
96
118
 
@@ -125,12 +147,13 @@ module PostfixAdmin
125
147
  puts_table(rows: rows)
126
148
  end
127
149
 
128
- def show_domain
150
+ def show_domains
129
151
  rows = []
130
152
  headings = ["No.", "Domain", "Aliases", "Mailboxes","Max Quota (MB)",
131
153
  "Active", "Description"]
132
154
 
133
155
  puts_title("Domains")
156
+
134
157
  if Domain.without_all.empty?
135
158
  puts "No domains"
136
159
  return
@@ -138,9 +161,7 @@ module PostfixAdmin
138
161
 
139
162
  Domain.without_all.each_with_index do |d, i|
140
163
  no = i + 1
141
- aliases_str = "%4d / %4s" % [d.pure_aliases.count, d.aliases_str]
142
- mailboxes_str = "%4d / %4s" % [d.rel_mailboxes.count, d.mailboxes_str]
143
- rows << [no.to_s, d.domain, aliases_str, mailboxes_str,
164
+ rows << [no.to_s, d.domain, d.alias_usage_display_str, d.mailbox_usage_display_str,
144
165
  d.maxquota_str, d.active_str, d.description]
145
166
  end
146
167
 
@@ -152,12 +173,12 @@ module PostfixAdmin
152
173
  puts_registered(domain_name, "a domain")
153
174
  end
154
175
 
155
- def change_admin_password(user_name, password)
156
- change_password(Admin, user_name, password)
176
+ def change_admin_password(user_name, password, scheme: nil, rounds: nil)
177
+ change_password(Admin, user_name, password, scheme: scheme, rounds: rounds)
157
178
  end
158
179
 
159
- def change_account_password(user_name, password)
160
- change_password(Mailbox, user_name, password)
180
+ def change_account_password(user_name, password, scheme: nil, rounds: nil)
181
+ change_password(Mailbox, user_name, password, scheme: scheme, rounds: rounds)
161
182
  end
162
183
 
163
184
  def edit_admin(admin_name, options)
@@ -171,7 +192,7 @@ module PostfixAdmin
171
192
  admin.active = options[:active] unless options[:active].nil?
172
193
  admin.save!
173
194
 
174
- puts "Successfully updated #{admin_name}"
195
+ puts "successfully updated #{admin_name}"
175
196
  show_admin_details(admin_name)
176
197
  end
177
198
 
@@ -185,7 +206,7 @@ module PostfixAdmin
185
206
  domain.description = options[:description] if options[:description]
186
207
  domain.save!
187
208
 
188
- puts "Successfully updated #{domain_name}"
209
+ puts "successfully updated #{domain_name}"
189
210
  show_summary(domain_name)
190
211
  end
191
212
 
@@ -194,11 +215,12 @@ module PostfixAdmin
194
215
  puts_deleted(domain_name)
195
216
  end
196
217
 
197
- def show_admin(domain_name = nil)
218
+ def show_admins(domain_name = nil)
198
219
  admins = domain_name ? Admin.select { |a| a.rel_domains.exists?(domain_name) } : Admin.all
199
- headings = %w[No. Admin Domains Active]
220
+ headings = ["No.", "Admin", "Domains", "Active", "Scheme Prefix"]
200
221
 
201
222
  puts_title("Admins")
223
+
202
224
  if admins.empty?
203
225
  puts "No admins"
204
226
  return
@@ -208,51 +230,68 @@ module PostfixAdmin
208
230
  admins.each_with_index do |a, i|
209
231
  no = i + 1
210
232
  domains = a.super_admin? ? 'Super Admin' : a.rel_domains.count
211
- rows << [no.to_s, a.username, domains.to_s, a.active_str]
233
+ rows << [no.to_s, a.username, domains.to_s, a.active_str, a.scheme_prefix]
212
234
  end
213
235
 
214
236
  puts_table(headings: headings, rows: rows)
215
237
  end
216
238
 
217
- def show_address(domain_name)
218
- domain_check(domain_name)
239
+ def show_accounts(domain_name=nil)
240
+ domain_check(domain_name) if domain_name
219
241
 
220
242
  rows = []
221
- mailboxes = Domain.find(domain_name).rel_mailboxes
222
- headings = ["No.", "Email", "Name", "Quota (MB)", "Active", "Maildir"]
243
+ mailboxes = if domain_name
244
+ Domain.find(domain_name).rel_mailboxes
245
+ else
246
+ Mailbox.all
247
+ end
248
+ headings = ["No.", "Email", "Name", "Quota (MB)", "Active",
249
+ "Scheme Prefix", "Maildir"]
250
+
251
+ puts_title("Accounts")
223
252
 
224
- puts_title("Addresses")
225
253
  if mailboxes.empty?
226
- puts "No addresses"
254
+ puts "No accounts"
227
255
  return
228
256
  end
229
257
 
230
258
  mailboxes.each_with_index do |m, i|
231
259
  no = i + 1
232
- rows << [no.to_s, m.username, m.name, m.quota_mb_str,
233
- m.active_str, m.maildir]
260
+ rows << [no.to_s, m.username, m.name, m.quota_display_str,
261
+ m.active_str, m.scheme_prefix, m.maildir]
234
262
  end
235
263
 
236
264
  puts_table(headings: headings, rows: rows)
237
265
  end
238
266
 
239
- def show_alias(domain_name)
240
- domain_check(domain_name)
267
+ def show_forwards(domain_name=nil)
268
+ domain_check(domain_name) if domain_name
241
269
 
242
- forwards, aliases = Domain.find(domain_name).rel_aliases.partition { |a| a.mailbox? }
243
-
244
- forwards.delete_if do |f|
245
- f.address == f.goto
246
- end
270
+ forwards = if domain_name
271
+ Domain.find(domain_name).rel_aliases.forward
272
+ else
273
+ Alias.forward
274
+ end
247
275
 
248
276
  show_alias_base("Forwards", forwards)
249
- puts
277
+ end
278
+
279
+ def show_aliases(domain_name=nil)
280
+ domain_check(domain_name) if domain_name
281
+
282
+ aliases = if domain_name
283
+ Domain.find(domain_name).rel_aliases.pure
284
+ else
285
+ db_aliases = Alias.pure
286
+ end
287
+
250
288
  show_alias_base("Aliases", aliases)
251
289
  end
252
290
 
253
291
  def show_admin_domain(user_name)
254
292
  admin = Admin.find(user_name)
255
293
  puts_title("Admin Domains (#{user_name})")
294
+
256
295
  if admin.rel_domains.empty?
257
296
  puts "\nNo domains for #{user_name}"
258
297
  return
@@ -263,13 +302,18 @@ module PostfixAdmin
263
302
  no = i + 1
264
303
  rows << [no.to_s, d.domain]
265
304
  end
305
+
266
306
  puts_table(rows: rows, headings: %w[No. Domain])
267
307
  end
268
308
 
269
- def add_admin(user_name, password, super_admin = false, scheme = nil)
309
+ def add_admin(user_name, password, super_admin: false,
310
+ scheme: nil, rounds: nil)
270
311
  validate_password(password)
271
312
 
272
- @base.add_admin(user_name, hashed_password(password, scheme))
313
+ h_password = hashed_password(password, user_name: user_name,
314
+ scheme: scheme, rounds: rounds)
315
+ @base.add_admin(user_name, h_password)
316
+
273
317
  if super_admin
274
318
  Admin.find(user_name).super_admin = true
275
319
  puts_registered(user_name, "a super admin")
@@ -288,10 +332,12 @@ module PostfixAdmin
288
332
  puts "#{domain_name} was successfully deleted from #{user_name}"
289
333
  end
290
334
 
291
- def add_account(address, password, scheme = nil, name = nil)
335
+ def add_account(address, password, name: nil, scheme: nil, rounds: nil)
292
336
  validate_password(password)
293
337
 
294
- @base.add_account(address, hashed_password(password, scheme), name: name)
338
+ h_password = hashed_password(password, user_name: address,
339
+ scheme: scheme, rounds: rounds)
340
+ @base.add_account(address, h_password, name: name)
295
341
  puts_registered(address, "an account")
296
342
  end
297
343
 
@@ -301,10 +347,13 @@ module PostfixAdmin
301
347
  end
302
348
 
303
349
  def edit_account(address, options)
350
+ quota = options[:quota]
351
+ raise "Invalid Quota value: #{quota}" if quota && quota < 0
352
+
304
353
  mailbox_check(address)
305
354
  mailbox = Mailbox.find(address)
306
355
  mailbox.name = options[:name] if options[:name]
307
- mailbox.quota = options[:quota] * KB_TO_MB if options[:quota]
356
+ mailbox.quota_mb = quota if quota
308
357
  mailbox.active = options[:active] unless options[:active].nil?
309
358
  mailbox.save!
310
359
 
@@ -314,7 +363,7 @@ module PostfixAdmin
314
363
  mail_alias.save!
315
364
  end
316
365
 
317
- puts "Successfully updated #{address}"
366
+ puts "successfully updated #{address}"
318
367
  show_account_details(address)
319
368
  end
320
369
 
@@ -325,7 +374,7 @@ module PostfixAdmin
325
374
  mail_alias.active = options[:active] unless options[:active].nil?
326
375
  mail_alias.save or raise "Could not save Alias"
327
376
 
328
- puts "Successfully updated #{address}"
377
+ puts "successfully updated #{address}"
329
378
  show_alias_details(address)
330
379
  end
331
380
 
@@ -345,7 +394,7 @@ module PostfixAdmin
345
394
  end
346
395
 
347
396
  def log(domain: nil, last: nil)
348
- headings = %w[Timestamp Admin Domain Action Data]
397
+ headings = %w[No. Timestamp Admin Domain Action Data]
349
398
  rows = []
350
399
 
351
400
  logs = if domain
@@ -356,10 +405,17 @@ module PostfixAdmin
356
405
 
357
406
  logs = logs.last(last) if last
358
407
 
359
- logs.each do |l|
408
+ puts_log_table(logs)
409
+ end
410
+
411
+ def puts_log_table(logs)
412
+ headings = %w[No. Timestamp Admin Domain Action Data]
413
+ rows = []
414
+ logs.each_with_index do |l, i|
415
+ no = i + 1
360
416
  # TODO: Consider if zone should be included ('%Z').
361
417
  time = l.timestamp.strftime("%Y-%m-%d %X")
362
- rows << [time, l.username, l.domain, l.action, l.data]
418
+ rows << [no, time, l.username, l.domain, l.action, l.data]
363
419
  end
364
420
 
365
421
  puts_table(headings: headings, rows: rows)
@@ -372,24 +428,28 @@ module PostfixAdmin
372
428
  puts [a.username, %Q!"#{a.password}"!, a.super_admin?, a.active].join(',')
373
429
  end
374
430
  puts
431
+
375
432
  puts "Domains"
376
433
  puts "Domain Name,Max Quota,Active"
377
434
  Domain.without_all.each do |d|
378
435
  puts [d.domain, d.maxquota, d.active].join(',')
379
436
  end
380
437
  puts
438
+
381
439
  puts "Mailboxes"
382
440
  puts "User Name,Name,Password,Quota,Maildir,Active"
383
441
  Mailbox.all.each do |m|
384
442
  puts [m.username, %Q!"#{m.name}"!, %Q!"#{m.password}"!, m.quota, %Q!"#{m.maildir}"!, m.active].join(',')
385
443
  end
386
444
  puts
445
+
387
446
  puts "Aliases"
388
447
  puts "Address,Go to,Active"
389
448
  Alias.all.select { |a| !a.mailbox? }.each do |a|
390
449
  puts [a.address, %Q!"#{a.goto}"!, a.active].join(',')
391
450
  end
392
451
  puts
452
+
393
453
  puts "Forwards"
394
454
  puts "Address,Go to,Active"
395
455
  Alias.all.select { |a| a.mailbox? && a.goto != a.address }.each do |a|
@@ -406,6 +466,7 @@ module PostfixAdmin
406
466
  rows << ["Admins", Admin.count]
407
467
  rows << ["Mailboxes", Mailbox.count]
408
468
  rows << ["Aliases", Alias.pure.count]
469
+ rows << ["Logs", Log.count]
409
470
 
410
471
  puts_title(title)
411
472
  puts_table(rows: rows)
@@ -417,8 +478,8 @@ module PostfixAdmin
417
478
 
418
479
  rows = []
419
480
  domain = Domain.find(domain_name)
420
- rows << ["Mailboxes", "%4d / %4s" % [domain.rel_mailboxes.count, domain.mailboxes_str]]
421
- rows << ["Aliases", "%4d / %4s" % [domain.pure_aliases.count, domain.aliases_str]]
481
+ rows << ["Mailboxes", domain.mailbox_usage_display_str]
482
+ rows << ["Aliases", domain.alias_usage_display_str]
422
483
  rows << ["Max Quota (MB)", domain.maxquota_str]
423
484
  rows << ["Active", domain.active_str]
424
485
  rows << ["Description", domain.description]
@@ -428,11 +489,13 @@ module PostfixAdmin
428
489
  end
429
490
 
430
491
  def show_domain_details(domain_name)
431
- show_admin(domain_name)
492
+ show_admins(domain_name)
493
+ puts
494
+ show_accounts(domain_name)
432
495
  puts
433
- show_address(domain_name)
496
+ show_forwards(domain_name)
434
497
  puts
435
- show_alias(domain_name)
498
+ show_aliases(domain_name)
436
499
  end
437
500
 
438
501
  def show_alias_base(title, addresses)
@@ -471,6 +534,7 @@ module PostfixAdmin
471
534
  puts "configure file: #{config_file} was generated.\nPlease execute after edit it."
472
535
  exit
473
536
  end
537
+
474
538
  open(config_file) do |f|
475
539
  YAML.load(f.read)
476
540
  end
@@ -524,24 +588,44 @@ module PostfixAdmin
524
588
  end
525
589
  end
526
590
 
527
- def change_password(klass, user_name, password)
591
+ def change_password(klass, user_name, password, scheme: nil, rounds: nil)
528
592
  raise Error, "Could not find #{user_name}" unless klass.exists?(user_name)
529
593
 
530
594
  validate_password(password)
531
595
 
532
596
  obj = klass.find(user_name)
597
+ h_password = hashed_password(password, scheme: scheme, rounds: rounds,
598
+ user_name: user_name)
533
599
 
534
- if obj.update(password: hashed_password(password))
535
- puts "the password of #{user_name} was successfully changed."
600
+ if obj.update(password: h_password)
601
+ puts "the password of #{user_name} was successfully updated."
536
602
  else
537
603
  raise "Could not change password of #{klass.name}"
538
604
  end
539
605
  end
540
606
 
541
- def hashed_password(password, in_scheme = nil)
607
+ # The default number of rounds for BLF-CRYPT in `doveadm pw` is 5.
608
+ # However, this method uses 10 rounds by default, similar to
609
+ # the password_hash() function in PHP.
610
+ #
611
+ # https://www.php.net/manual/en/function.password-hash.php
612
+ # <?php
613
+ # echo password_hash("password", PASSWORD_BCRYPT);
614
+ #
615
+ # $2y$10$qzRgjWZWfH4VsNQGvp/DNObFSaMiZxXJSzgXqOOS/qtF68qIhhwFe
616
+ DEFAULT_BLF_CRYPT_ROUNDS = 10
617
+
618
+ # Generate a hashed password
619
+ def hashed_password(password, scheme: nil, rounds: nil, user_name: nil)
542
620
  prefix = @base.config[:passwordhash_prefix]
543
- scheme = in_scheme || @base.config[:scheme]
544
- PostfixAdmin::Doveadm.password(password, scheme, prefix)
621
+ new_scheme = scheme || @base.config[:scheme]
622
+ new_rounds = if rounds
623
+ rounds
624
+ elsif new_scheme == "BLF-CRYPT"
625
+ DEFAULT_BLF_CRYPT_ROUNDS
626
+ end
627
+ PostfixAdmin::Doveadm.password(password, new_scheme, rounds: new_rounds,
628
+ user_name: user_name, prefix: prefix)
545
629
  end
546
630
  end
547
631
  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 = `#{self.command_name} -l`
11
+ result = `#{CMD_DOVEADM_PW} -l`
9
12
  result.split
10
13
  end
11
14
 
12
- def self.password(in_password, in_scheme, prefix)
13
- password = Shellwords.escape(in_password)
14
- scheme = Shellwords.escape(in_scheme)
15
- _stdin, stdout, stderr = Open3.popen3("#{self.command_name} -s #{scheme} -p #{password}")
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
- if stderr.readlines.to_s =~ /Fatal:/
18
- raise Error, stderr.readlines
19
- else
20
- res = stdout.readlines.first.chomp
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
- res.gsub("{#{scheme}}", "")
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,35 @@
1
- require 'postfix_admin/concerns/dovecot_cram_md5_password'
1
+ require "postfix_admin/models/application_record"
2
+ require "postfix_admin/models/concerns/has_password"
2
3
 
3
4
  module PostfixAdmin
4
5
  class Admin < ApplicationRecord
6
+ # version: 1841
7
+ # > describe admin;
8
+ # +----------------+--------------+------+-----+---------------------+-------+
9
+ # | Field | Type | Null | Key | Default | Extra |
10
+ # +----------------+--------------+------+-----+---------------------+-------+
11
+ # | username | varchar(255) | NO | PRI | NULL | |
12
+ # | password | varchar(255) | NO | | NULL | |
13
+ # | created | datetime | NO | | 2000-01-01 00:00:00 | |
14
+ # | modified | datetime | NO | | 2000-01-01 00:00:00 | |
15
+ # | active | tinyint(1) | NO | | 1 | |
16
+ # | superadmin | tinyint(1) | NO | | 0 | |
17
+ # | phone | varchar(30) | NO | | | |
18
+ # | email_other | varchar(255) | NO | | | |
19
+ # | token | varchar(255) | NO | | | |
20
+ # | token_validity | datetime | NO | | 2000-01-01 00:00:00 | |
21
+ # +----------------+--------------+------+-----+---------------------+-------+
22
+
5
23
  self.table_name = :admin
6
24
  self.primary_key = :username
7
25
 
8
- include DovecotCramMD5Password
26
+ include HasPassword
9
27
 
10
28
  validates :username, presence: true, uniqueness: { case_sensitive: false },
11
29
  format: { with: RE_EMAIL_LIKE_WITH_ANCHORS,
12
30
  message: "must be a valid email address" }
13
31
 
32
+ # Admin <-> DomainAdmin <-> Domain
14
33
  has_many :domain_admins, foreign_key: :username, dependent: :delete_all
15
34
  has_many :rel_domains, through: :domain_admins
16
35
 
@@ -0,0 +1,63 @@
1
+ require "postfix_admin/models/application_record"
2
+
3
+ module PostfixAdmin
4
+ class Alias < ApplicationRecord
5
+ # version: 1841
6
+ # > describe alias;
7
+ # +----------+--------------+------+-----+---------------------+-------+
8
+ # | Field | Type | Null | Key | Default | Extra |
9
+ # +----------+--------------+------+-----+---------------------+-------+
10
+ # | address | varchar(255) | NO | PRI | NULL | |
11
+ # | goto | text | NO | | NULL | |
12
+ # | domain | varchar(255) | NO | MUL | NULL | |
13
+ # | created | datetime | NO | | 2000-01-01 00:00:00 | |
14
+ # | modified | datetime | NO | | 2000-01-01 00:00:00 | |
15
+ # | active | tinyint(1) | NO | | 1 | |
16
+ # +----------+--------------+------+-----+---------------------+-------+
17
+
18
+ self.table_name = :alias
19
+ self.primary_key = :address
20
+
21
+ validate on: :create do |a|
22
+ domain = a.rel_domain
23
+
24
+ if domain.alias_unlimited?
25
+ # unlimited: do nothing
26
+ elsif domain.alias_disabled?
27
+ # disabled
28
+ a.errors.add(:domain, "has a disabled status for aliases")
29
+ elsif domain.pure_alias_count >= domain.aliases
30
+ # exceeding alias limit
31
+ message = "has already reached the maximum number of aliases " \
32
+ "(maximum: #{domain.aliases})"
33
+ a.errors.add(:domain, message)
34
+ end
35
+ end
36
+
37
+ validates :address, presence: true, uniqueness: { case_sensitive: false },
38
+ format: { with: RE_EMAIL_LIKE_WITH_ANCHORS,
39
+ message: "must be a valid email address" }
40
+ validates :goto, presence: true
41
+
42
+ belongs_to :rel_domain, class_name: "Domain", foreign_key: :domain
43
+ belongs_to :mailbox, foreign_key: :address, optional: true
44
+
45
+ # aliases which do not belong to any mailbox
46
+ scope :pure, -> { joins("LEFT OUTER JOIN mailbox ON alias.address = mailbox.username").where("mailbox.username" => nil) }
47
+
48
+ # aliases which belong to a mailbox and have forwardings to other addresses
49
+ scope :forward, -> { joins("LEFT OUTER JOIN mailbox ON alias.address = mailbox.username").where("mailbox.username <> alias.goto") }
50
+
51
+ def mailbox?
52
+ !!mailbox
53
+ end
54
+
55
+ def pure_alias?
56
+ !mailbox
57
+ end
58
+
59
+ def gotos
60
+ goto.split(",")
61
+ end
62
+ end
63
+ end
@@ -1,5 +1,5 @@
1
- require 'active_record'
2
- require 'postfix_admin/concerns/existing_timestamp'
1
+ require "active_record"
2
+ require "postfix_admin/models/concerns/existing_timestamp"
3
3
 
4
4
  module PostfixAdmin
5
5
  class ApplicationRecord < ActiveRecord::Base
@@ -14,5 +14,4 @@ module ExistingTimestamp
14
14
  ["modified"]
15
15
  end
16
16
  end
17
-
18
- end
17
+ end
@@ -0,0 +1,16 @@
1
+ require 'active_support/concern'
2
+
3
+ module HasPassword
4
+ extend ActiveSupport::Concern
5
+
6
+ # example: {CRAM-MD5}, {BLF-CRYPT}, {PLAIN}
7
+ # return nil if no scheme prefix
8
+ def scheme_prefix
9
+ res = password&.match(/^\{.*?\}/)
10
+ if res
11
+ res[0]
12
+ else
13
+ nil
14
+ end
15
+ end
16
+ end