postfix_admin 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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'