postfix_admin 0.3.0 → 0.3.2

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.
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