postfix_admin 0.1.0 → 0.2.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.
File without changes
@@ -0,0 +1,30 @@
1
+ require 'active_support/concern'
2
+
3
+ module DovecotCramMD5Password
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ validates :password_unencrypted, length: { minimum: 5 }, allow_blank: true
8
+ validates_confirmation_of :password_unencrypted, allow_blank: true
9
+
10
+ validate do |record|
11
+ record.errors.add(:password_unencrypted, :blank) unless record.password.present?
12
+ end
13
+
14
+ attr_reader :password_unencrypted
15
+ attr_accessor :password_unencrypted_confirmation
16
+ end
17
+
18
+ def password_unencrypted=(unencrypted_password)
19
+ if unencrypted_password.nil?
20
+ self.password = nil
21
+ elsif !unencrypted_password.empty?
22
+ @password_unencrypted = unencrypted_password
23
+ self.password = DovecotCrammd5.calc(unencrypted_password)
24
+ end
25
+ end
26
+
27
+ def authenticate(unencrypted_password)
28
+ password == DovecotCrammd5.calc(unencrypted_password) && self
29
+ end
30
+ end
@@ -0,0 +1,18 @@
1
+ require 'active_support/concern'
2
+
3
+ module ExistingTimestamp
4
+ extend ActiveSupport::Concern
5
+
6
+ class_methods do
7
+ private
8
+
9
+ def timestamp_attributes_for_create
10
+ ["created"]
11
+ end
12
+
13
+ def timestamp_attributes_for_update
14
+ ["modified"]
15
+ end
16
+ end
17
+
18
+ end
@@ -0,0 +1,98 @@
1
+ module PostfixAdmin
2
+ class Domain < ApplicationRecord
3
+ self.table_name = :domain
4
+ self.primary_key = :domain
5
+
6
+ validates :domain, presence: true, uniqueness: { case_sensitive: false },
7
+ format: { with: RE_DOMAIN_NAME_LIKE_WITH_ANCHORS,
8
+ message: "must be a valid domain name" }
9
+ validates :transport, presence: true
10
+
11
+ validates :aliases, presence: true,
12
+ numericality: { only_integer: true,
13
+ greater_than_or_equal_to: 0 }
14
+ validates :mailboxes, presence: true,
15
+ numericality: { only_integer: true,
16
+ greater_than_or_equal_to: 0 }
17
+ validates :maxquota, presence: true,
18
+ numericality: { only_integer: true,
19
+ greater_than_or_equal_to: 0 }
20
+
21
+ has_many :rel_mailboxes, class_name: "Mailbox", foreign_key: :domain,
22
+ dependent: :destroy
23
+ has_many :rel_aliases, class_name: "Alias", foreign_key: :domain,
24
+ dependent: :destroy
25
+
26
+ has_many :domain_admins, foreign_key: :domain, dependent: :delete_all
27
+ has_many :admins, through: :domain_admins
28
+
29
+ before_validation do |domain|
30
+ domain.domain = domain.domain.downcase unless domain.domain.empty?
31
+ domain.transport = "virtual"
32
+ end
33
+
34
+ scope :without_all, -> { where.not(domain: "ALL") }
35
+
36
+ def pure_aliases
37
+ rel_aliases.pure
38
+ end
39
+
40
+ def aliases_unlimited?
41
+ aliases.zero?
42
+ end
43
+
44
+ def mailboxes_unlimited?
45
+ mailboxes.zero?
46
+ end
47
+
48
+ def aliases_str
49
+ num_str(aliases)
50
+ end
51
+
52
+ def mailboxes_str
53
+ num_str(mailboxes)
54
+ end
55
+
56
+ def aliases_short_str
57
+ num_short_str(aliases)
58
+ end
59
+
60
+ def mailboxes_short_str
61
+ num_short_str(mailboxes)
62
+ end
63
+
64
+ def maxquota_str
65
+ if maxquota.zero?
66
+ "Unlimited"
67
+ else
68
+ "#{maxquota} MB"
69
+ end
70
+ end
71
+
72
+ def maxquota_short_str
73
+ if maxquota.zero?
74
+ "--"
75
+ else
76
+ "#{maxquota} MB"
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ def num_str(num)
83
+ if num.zero?
84
+ "Unlimited"
85
+ else
86
+ num.to_s
87
+ end
88
+ end
89
+
90
+ def num_short_str(num)
91
+ if num.zero?
92
+ "--"
93
+ else
94
+ num.to_s
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,8 @@
1
+ module PostfixAdmin
2
+ class DomainAdmin < ApplicationRecord
3
+ self.table_name = :domain_admins
4
+
5
+ belongs_to :admin, primary_key: :username, foreign_key: :username
6
+ belongs_to :rel_domain, class_name: "Domain", foreign_key: :domain
7
+ end
8
+ end
@@ -0,0 +1,32 @@
1
+
2
+ require 'open3'
3
+ require 'shellwords'
4
+
5
+ module PostfixAdmin
6
+ class Doveadm
7
+ def self.schemes
8
+ result = `#{self.command_name} -l`
9
+ result.split
10
+ end
11
+
12
+ def self.password(in_password, in_scheme)
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}")
16
+ if stderr.readlines.to_s =~ /Fatal:/
17
+ raise Error, stderr.readlines
18
+ else
19
+ stdout.readlines.first.chomp
20
+ end
21
+ end
22
+
23
+ def self.command_name
24
+ begin
25
+ Open3.capture3("doveadm pw -l")[2].exited?
26
+ "doveadm pw"
27
+ rescue Errno::ENOENT
28
+ "dovecotpw"
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,5 @@
1
+ module PostfixAdmin
2
+ class Log < ApplicationRecord
3
+ self.table_name = :log
4
+ end
5
+ end
@@ -0,0 +1,9 @@
1
+ module PostfixAdmin
2
+ class MailDomain < ApplicationRecord
3
+ self.table_name = :domain
4
+ self.primary_key = :domain
5
+
6
+ has_many :addresses, class_name: "Mailbox", foreign_key: :domain,
7
+ dependent: :destroy
8
+ end
9
+ end
@@ -0,0 +1,89 @@
1
+ module PostfixAdmin
2
+ class Mailbox < ApplicationRecord
3
+ self.table_name = :mailbox
4
+ self.primary_key = :username
5
+
6
+ include DovecotCramMD5Password
7
+
8
+ attribute :quota_mb, :integer
9
+
10
+ validates :username, presence: true, uniqueness: { case_sensitive: false },
11
+ format: { with: RE_EMAIL_LIKE_WITH_ANCHORS,
12
+ message: "must be a valid email address" }
13
+ validates :maildir, presence: true, uniqueness: { case_sensitive: false }
14
+ validates :local_part, presence: true
15
+ validates :quota, presence: true,
16
+ numericality: { only_integer: true,
17
+ greater_than_or_equal_to: 0 }
18
+ validates :quota_mb, presence: true,
19
+ numericality: { only_integer: true,
20
+ greater_than_or_equal_to: 0 }
21
+
22
+ belongs_to :rel_domain, class_name: "Domain", foreign_key: :domain
23
+ has_one :alias, foreign_key: :address, dependent: :destroy
24
+ has_one :quota_usage, class_name: "Quota", foreign_key: :username,
25
+ dependent: :destroy
26
+
27
+ validate on: :create do |mailbox|
28
+ domain = mailbox.rel_domain
29
+ if !domain.mailboxes.zero? && domain.rel_mailboxes.count >= domain.mailboxes
30
+ message = "already has the maximum number of mailboxes " \
31
+ "(maximum is #{domain.mailboxes} mailboxes)"
32
+ mailbox.errors.add(:domain, message)
33
+ end
34
+ end
35
+
36
+ # just in case
37
+ validate on: :update do |mailbox|
38
+ mailbox.errors.add(:username, 'cannot be changed') if mailbox.username_changed?
39
+ mailbox.errors.add(:local_part, 'cannot be changed') if mailbox.local_part_changed?
40
+ end
41
+
42
+ validate do |mailbox|
43
+ domain = mailbox.rel_domain
44
+
45
+ unless domain.maxquota.zero?
46
+ if mailbox.quota_mb.zero?
47
+ mailbox.errors.add(:quota_mb, "cannot be 0")
48
+ elsif mailbox.quota_mb > domain.maxquota
49
+ message = "must be less than or equal to #{domain.maxquota} (MB)"
50
+ mailbox.errors.add(:quota_mb, message)
51
+ end
52
+ end
53
+ end
54
+
55
+ before_validation do |mailbox|
56
+ mailbox.name = "" if mailbox.name.nil?
57
+ if mailbox.quota_mb
58
+ mailbox.quota = mailbox.quota_mb * KB_TO_MB
59
+ elsif mailbox.quota
60
+ mailbox.quota_mb = mailbox.quota / KB_TO_MB
61
+ else
62
+ mailbox.quota_mb = 0
63
+ mailbox.quota = 0
64
+ end
65
+ mailbox.username = "#{mailbox.local_part}@#{mailbox.domain}"
66
+ mailbox.maildir = "#{mailbox.domain}/#{mailbox.username}/"
67
+ mailbox.build_alias(local_part: mailbox.local_part, goto: mailbox.username,
68
+ domain: mailbox.domain)
69
+ end
70
+
71
+ def quota_usage_str
72
+ if quota_usage
73
+ usage_mb = quota_usage.bytes / KB_TO_MB
74
+ usage_mb.to_s
75
+ else
76
+ "0"
77
+ end
78
+ end
79
+
80
+ def quota_str
81
+ if quota.zero?
82
+ "--"
83
+ else
84
+ quota_mb = quota / KB_TO_MB
85
+ "#{quota_mb} MB"
86
+ end
87
+ end
88
+ end
89
+ end
@@ -1,171 +1,10 @@
1
- require 'data_mapper'
2
-
3
- #
4
- # This extension is to avoid 'ArgumentError: invalid date' when datetime value of
5
- # MySQL is '0000-00-00 00:00:00'.
6
- #
7
- class DateTime
8
- class << self
9
-
10
- alias org_new new
11
- def new(year = -4712, mon = 1, mday = 1, hour = 0, min = 0, sec = 0, offset = 0, start = Date::ITALY)
12
- if year == 0
13
- nil
14
- else
15
- org_new(year, mon, mday, hour, min, sec, offset, start)
16
- end
17
- end
18
- end
19
- end
20
-
21
- module PostfixAdmin
22
- class Admin
23
- include ::DataMapper::Resource
24
- property :username, String, :key => true
25
- property :password, String
26
- property :created, DateTime, :default => DateTime.now
27
- property :modified, DateTime, :default => DateTime.now
28
-
29
- has n, :domain_admins, :child_key => :username
30
- has n, :domains, :model => 'Domain', :through => :domain_admins, :via => :domain
31
- storage_names[:default] = 'admin'
32
-
33
- def has_domain?(domain_name)
34
- if super_admin?
35
- Domain.exist?(domain_name)
36
- else
37
- exist_domain?(domain_name)
38
- end
39
- end
40
-
41
- def super_admin=(value)
42
- if value
43
- domains << Domain.find('ALL')
44
- save or raise "Could not save ALL domain for Admin"
45
- else
46
- domain_admins(:domain_name => 'ALL').destroy or raise "Could not destroy DoaminAdmin for Admin"
47
- end
48
- end
49
-
50
- def super_admin?
51
- exist_domain?('ALL')
52
- end
53
-
54
- def self.find(username)
55
- Admin.first(:username => username)
56
- end
57
-
58
- def self.exist?(username)
59
- !!Admin.find(username)
60
- end
61
-
62
- def self.unnecessary
63
- all.delete_if do |admin|
64
- admin.domains.size > 0
65
- end
66
- end
67
-
68
- private
69
-
70
- def exist_domain?(domain_name)
71
- !!domains.first(:domain_name => domain_name)
72
- end
73
- end
74
-
75
- class Domain
76
- include ::DataMapper::Resource
77
- property :domain_name, String, :field => 'domain', :key => true
78
- property :maxaliases, Integer, :field => 'aliases'
79
- property :maxmailboxes, Integer, :field => 'mailboxes'
80
- property :maxquota, Integer
81
- property :transport, String, :default => 'virtual'
82
- property :backupmx, Integer, :default => 0
83
- property :description, String
84
- property :created, DateTime, :default => DateTime.now
85
- property :modified, DateTime, :default => DateTime.now
86
-
87
- has n, :domain_admins, :child_key => :domain_name
88
- has n, :admins, :model => 'Admin', :through => :domain_admins
89
-
90
- has n, :mailboxes, :model => 'Mailbox', :child_key => :domain_name
91
- has n, :aliases, :model => 'Alias', :child_key => :domain_name
92
- storage_names[:default] = 'domain'
93
-
94
- def self.all_without_special_domain
95
- Domain.all(:domain_name.not => 'ALL')
96
- end
97
-
98
- def self.find(domain)
99
- Domain.first(:domain_name => domain)
100
- end
101
-
102
- def self.exist?(domain)
103
- !!Domain.find(domain)
104
- end
105
-
106
- def self.num_total_aliases
107
- Alias.count - Mailbox.count
108
- end
109
-
110
- def num_total_aliases
111
- aliases.count - mailboxes.count
112
- end
113
- end
114
-
115
- class DomainAdmin
116
- include ::DataMapper::Resource
117
- property :created, DateTime, :default => DateTime.now
118
- property :domain_name, String, :field => 'domain', :key => true
119
- property :username, String, :key => true
120
-
121
- belongs_to :domain, :model => 'Domain', :child_key => :domain_name
122
- belongs_to :admin, :model => 'Admin', :child_key => :username
123
- storage_names[:default] = 'domain_admins'
124
- end
125
-
126
- class Mailbox
127
- include ::DataMapper::Resource
128
- property :username, String, :key => true
129
- property :name, String
130
- property :domain_name, String, :field => 'domain'
131
- property :password, String
132
- property :maildir, String
133
- property :quota, Integer
134
- # property :local_part, String
135
- property :created, DateTime, :default => DateTime.now
136
- property :modified, DateTime, :default => DateTime.now
137
-
138
- belongs_to :domain, :model => 'Domain', :child_key => :domain_name
139
-
140
- storage_names[:default] = 'mailbox'
141
-
142
- def self.find(username)
143
- Mailbox.first(:username => username)
144
- end
145
-
146
- def self.exist?(username)
147
- !!Mailbox.find(username)
148
- end
149
- end
150
-
151
- class Alias
152
- include ::DataMapper::Resource
153
- property :address, String, :key => true
154
- property :goto, Text
155
- property :domain_name, String, :field => 'domain'
156
- property :created, DateTime, :default => DateTime.now
157
- property :modified, DateTime, :default => DateTime.now
158
-
159
- belongs_to :domain, :model => 'Domain', :child_key => :domain_name
160
-
161
- storage_names[:default] = 'alias'
162
-
163
- def self.find(address)
164
- Alias.first(:address => address)
165
- end
166
-
167
- def self.exist?(address)
168
- !!Alias.find(address)
169
- end
170
- end
171
- end
1
+ require 'active_record'
2
+ require 'postfix_admin/application_record'
3
+ require 'postfix_admin/admin'
4
+ require 'postfix_admin/domain'
5
+ require 'postfix_admin/mailbox'
6
+ require 'postfix_admin/alias'
7
+ require 'postfix_admin/domain_admin'
8
+ require 'postfix_admin/log'
9
+ require 'postfix_admin/mail_domain'
10
+ require 'postfix_admin/quota'