eventmachine-email_server 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,242 @@
1
+ require 'eventmachine'
2
+ require 'eventmachine/dnsbl'
3
+
4
+ module EventMachine
5
+ module EmailServer
6
+ class SMTPServer < EventMachine::Connection
7
+ @@graylist = nil
8
+ @@dnsbl_check = nil
9
+ @@ratelimiter = nil
10
+ @@reverse_ptr_check = false
11
+ @@spf_check = false
12
+ @@reject_filters = Array.new
13
+
14
+ def self.reverse_ptr_check(ptr=nil)
15
+ if not ptr.nil?
16
+ @@reverse_ptr_check = ptr
17
+ end
18
+ @@reverse_ptr_check
19
+ end
20
+
21
+ def self.graylist(graylist=nil)
22
+ if graylist
23
+ @@graylist = graylist
24
+ end
25
+ @@graylist
26
+ end
27
+
28
+ def self.dnsbl_check(dnsbl_check=nil)
29
+ if not dnsbl_check.nil?
30
+ @@dnsbl_check = dnsbl_check
31
+ end
32
+ @@dnsbl_check
33
+ end
34
+
35
+ def self.ratelimiter(ratelimiter=nil)
36
+ if ratelimiter
37
+ @@ratelimiter = ratelimiter
38
+ end
39
+ @@ratelimiter
40
+ end
41
+
42
+ def self.reject_filters(filters=nil)
43
+ if filters
44
+ @@reject_filters = filters
45
+ end
46
+ @@reject_filters
47
+ end
48
+
49
+ def self.spf_check(spf=nil)
50
+ if not spf.nil?
51
+ @@spf_check = spf
52
+ end
53
+ @@spf_check
54
+ end
55
+
56
+ def initialize(hostname, userstore, emailstore)
57
+ @hostname = hostname
58
+ @userstore = userstore
59
+ @emailstore = emailstore
60
+ @debug = true
61
+ @data_mode = false
62
+ @email_body = ""
63
+ @ptr_ok = true
64
+ @dnsbl_ok = true
65
+ @rate_ok = true
66
+ @gray_ok = true
67
+ @reject_ok = true
68
+ @pending_checks = Array.new
69
+ end
70
+
71
+ def post_init
72
+ puts ">> 220 hello" if @debug
73
+ send_data "220 #{@hostname} ESMTP Service ready\n"
74
+ end
75
+
76
+ def receive_data(data)
77
+ puts ">> #{data}" if @debug
78
+ data.split(/\n/).each do |line|
79
+ ok, op = process_line(line+"\n")
80
+ if op
81
+ puts "<< #{op}" if @debug
82
+ send_data(op+"\r\n")
83
+ end
84
+ end
85
+ end
86
+
87
+ def check_ptr(helo, ip)
88
+ if @@reverse_ptr_check
89
+ @ptr_ok = false
90
+ @pending_checks << :ptr
91
+ d = EM::DNS::Resolver.resolve helo
92
+ d.callback { |r|
93
+ @ptr_ok = r.include?(ip)
94
+ @pending_checks -= [:ptr]
95
+ if @pending_checks.length == 0
96
+ send_answer
97
+ end
98
+ }
99
+ end
100
+ end
101
+
102
+ def check_dnsbl(ip)
103
+ if @@dnsbl_check
104
+ @dnsbl_ok = false
105
+ @pending_checks << :dnsbl
106
+ EventMachine::DNSBL::Client.check(ip) do |results|
107
+ @dnsbl_ok = ! EventMachine::DNSBL::Client.blacklisted?(results)
108
+ @pending_checks -= [:dnsbl]
109
+ if @pending_checks.length == 0
110
+ send_answer
111
+ end
112
+ end
113
+ end
114
+ end
115
+
116
+ def check_ratelimit(ip)
117
+ if @@ratelimiter
118
+ @rate_ok = @@ratelimiter.use(ip)
119
+ end
120
+ end
121
+
122
+ def check_gray(ip)
123
+ if @@graylist
124
+ @gray_ok = @@graylist.has_key?(ip)
125
+ @@graylist[ip] = true
126
+ end
127
+ end
128
+
129
+ def check_reject
130
+ @@reject_filters.each do |filter|
131
+ if filter.match(@email_body)
132
+ @reject_ok = false
133
+ return
134
+ end
135
+ end
136
+ end
137
+
138
+ def check_spf(from_domain)
139
+ if @@spf_check
140
+ @spf_ok = false
141
+ @pending_checks << :spf
142
+ d = EM::DNS::Resolver.resolve(from_domain, Resolv::DNS::Resource::IN::TXT)
143
+ d.errback { |r|
144
+ # fail open?
145
+ @spf_ok = true
146
+ @pending_checks -= [:spf]
147
+ if @pending_checks.length == 0
148
+ send_answer
149
+ end
150
+ }
151
+ d.callback { |r|
152
+ r.each do |rec|
153
+ #pp rec
154
+ if rec.start_with?('v=spf1')
155
+ if rec == "v=spf1 -all"
156
+ @spf_ok = false
157
+ else
158
+ # I need to create an SPF checker now :(
159
+ @spf_ok = true
160
+ end
161
+ end
162
+ end
163
+ @pending_checks -= [:spf]
164
+ if @pending_checks.length == 0
165
+ send_answer
166
+ end
167
+ }
168
+ end
169
+ end
170
+
171
+ def send_answer
172
+ if @ptr_ok and @rate_ok and @gray_ok and @reject_ok and @dnsbl_ok and @spf_ok
173
+ ans = "250 OK"
174
+ else
175
+ ans = "451 Requested action aborted: local error in processing"
176
+ end
177
+ puts "<< #{ans}" if @debug
178
+ send_data(ans+"\r\n")
179
+ end
180
+
181
+ def process_line(line)
182
+ if (@data_mode) && (line.chomp =~ /^\.$/)
183
+ @data_mode = false
184
+ check_reject
185
+ if @pending_checks.length == 0
186
+ send_answer
187
+ end
188
+ return true, nil
189
+ elsif @data_mode
190
+ @email_body += line
191
+ return true, nil
192
+ elsif (line =~ /^(HELO|EHLO) (.*)/)
193
+ helo = $2.chomp
194
+ port, ip = Socket.unpack_sockaddr_in(get_peername)
195
+ check_ptr(helo, ip)
196
+ check_dnsbl(ip)
197
+ check_gray(ip)
198
+ check_ratelimit(ip)
199
+ return true, "250 hello #{ip} (#{helo})"
200
+ elsif (line =~ /^QUIT/)
201
+ return false, "221 bye bye"
202
+ elsif (line =~ /^MAIL FROM\:/)
203
+ @mail_from = (/^MAIL FROM\:<(.+)>.*$/).match(line)[1]
204
+ if @@spf_check
205
+ check_spf(@mail_from.split(/@/,2)[1])
206
+ end
207
+ return true, "250 OK"
208
+ elsif (line =~ /^RCPT TO\:/)
209
+ rcpt_to = (/^RCPT TO\:<(.+)>.*$/).match(line)[1]
210
+ if @userstore.user_by_emailaddress(rcpt_to.strip)
211
+ @rcpt_to = rcpt_to
212
+ return true, "250 OK"
213
+ end
214
+ return false, "550 No such user here"
215
+ elsif (line =~ /^DATA/)
216
+ if @rcpt_to
217
+ @data_mode = true
218
+ @email_body = ''
219
+ return true, "354 Enter message, ending with \".\" on a line by itself"
220
+ end
221
+ return true, "500 ERROR"
222
+ else
223
+ return true, "500 ERROR"
224
+ end
225
+ end
226
+
227
+ def save
228
+ begin
229
+ subject = @email_body.match(/Subject\: (.*?)[\r\n]/i)[1]
230
+ u = @userstore.user_by_emailaddress(@rcpt_to.strip)
231
+ rescue Exception => err
232
+ puts err
233
+ return
234
+ end
235
+ if u and @mail_from and @rcpt_to
236
+ subject ||= ''
237
+ @emailstore << Email.new(nil, @mail_from, @rcpt_to, subject, @email_body, u.id)
238
+ end
239
+ end
240
+ end
241
+ end
242
+ end
@@ -0,0 +1,153 @@
1
+ require_relative 'base'
2
+ require 'sqlite3'
3
+
4
+ module EventMachine
5
+ module EmailServer
6
+ class Sqlite3UserStore < AbstractUserStore
7
+ def initialize(sqlite3, tablename = "users")
8
+ @class = User
9
+ @fields = @class.members.map {|x| x.to_s}.join(", ")
10
+ @tablename = tablename
11
+ if sqlite3.class == SQLite3::Database
12
+ @db = sqlite3
13
+ else
14
+ @db = SQLite3::Database.new(sqlite3)
15
+ end
16
+ if @db.table_info(tablename).length == 0
17
+ @db.execute("CREATE TABLE #{@tablename} (#{@fields})")
18
+ end
19
+ end
20
+
21
+ def add_user(user)
22
+ if user.id
23
+ u = user_by_id(user.id)
24
+ end
25
+ if u
26
+ @db.execute("UPDATE #{@tablename} SET username=?, password=?, address=? WHERE id=?",
27
+ user.username,
28
+ user.password,
29
+ user.address,
30
+ user.id)
31
+ else
32
+ @db.execute("INSERT INTO #{@tablename} (id,username,password,address) VALUES (?,?,?,?)",
33
+ user.id,
34
+ user.username,
35
+ user.password,
36
+ user.address)
37
+ end
38
+ end
39
+
40
+ def delete_user(user)
41
+ if user.id
42
+ @db.execute("DELETE FROM #{@tablename} WHERE id = ?", user.id)
43
+ end
44
+ end
45
+
46
+ def user_by_field(field, value)
47
+ rs = @db.execute("SELECT #{@fields} FROM #{@tablename} WHERE #{field}='#{value}'")
48
+ return nil unless rs
49
+ rs.each do |row|
50
+ return User.new(*row)
51
+ end
52
+ return nil
53
+ end
54
+
55
+ def user_by_username(username)
56
+ user_by_field("username", username)
57
+ end
58
+
59
+ def user_by_emailaddress(address)
60
+ user_by_field("address", address)
61
+ end
62
+
63
+ def user_by_id(id)
64
+ user_by_field("id", id)
65
+ end
66
+ end
67
+
68
+ class Sqlite3EmailStore < AbstractEmailStore
69
+ def initialize(sqlite3, tablename = "emails")
70
+ @class = Email
71
+ @fields = "'"+@class.members.map {|x| x.to_s}.join("', '")+"'"
72
+ @tablename = tablename
73
+ if sqlite3.class == SQLite3::Database
74
+ @db = sqlite3
75
+ else
76
+ @db = SQLite3::Database.new(sqlite3)
77
+ end
78
+ if @db.table_info(tablename).length == 0
79
+ fields = @fields.gsub(/'id'/, 'id integer primary key autoincrement')
80
+ @db.execute("CREATE TABLE #{@tablename} (#{fields})")
81
+ end
82
+ end
83
+
84
+ def emails_by_field(field, value)
85
+ rs = @db.execute("SELECT #{@fields} FROM #{@tablename} WHERE ?=?", field, value)
86
+ return nil unless rs
87
+ emails = Array.new
88
+ rs.each do |row|
89
+ emails << Email.new(*row)
90
+ end
91
+ emails
92
+ end
93
+
94
+ def emails_by_userid(uid)
95
+ emails_by_field("uid", uid)
96
+ end
97
+
98
+ def quote( string )
99
+ string.gsub( /'/, "''" )
100
+ end
101
+ private :quote
102
+
103
+ def save_email(email)
104
+ if email.id
105
+ # I'm being too crafty here.. this is bad style
106
+ args = (@class.members - [:id]).map{|f| email.send(f)}
107
+ args << email.send(:id)
108
+ @db.execute("UPDATE #{@tablename} SET " +
109
+ (@class.members - [:id]).map { |field| "#{field} = ?"}.join(", ") +
110
+ " WHERE id = ?", *args)
111
+ else
112
+ email.id = "NULL"
113
+ args = (@class.members).map{|f| email.send(f)}
114
+ qs = args.map{|x| "'#{quote(x.to_s)}'"}.join(",").gsub(/'NULL'/, "NULL")
115
+ @db.execute("INSERT INTO #{@tablename} (#{@fields}) VALUES (#{qs})")
116
+ rs = @db.execute("SELECT last_insert_rowid()")
117
+ rs.each do |row|
118
+ email.id = *row
119
+ end
120
+ end
121
+ email.id
122
+ end
123
+
124
+ def delete_email(email)
125
+ if email.id
126
+ @db.execute("DROP FROM #{@tablename} WHERE id = ?", email.id)
127
+ end
128
+ end
129
+
130
+ def delete_by_field(field, value)
131
+ @db.execute("DROP FROM #{@tablename} WHERE #{field} = ?", value)
132
+ end
133
+
134
+ def delete_id(id)
135
+ delete_by_field("id", id)
136
+ end
137
+
138
+ def delete_user(uid)
139
+ delete_by_field("uid", uid)
140
+ end
141
+
142
+ def count
143
+ sql = "SELECT COUNT(*) FROM #{@tablename}"
144
+ rs = @db.execute(sql)
145
+ c = 0
146
+ rs.each do |row|
147
+ c = row[0]
148
+ end
149
+ c
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,5 @@
1
+ module EventMachine
2
+ module EmailServer
3
+ VERSION = "0.0.2"
4
+ end
5
+ end
@@ -0,0 +1,8 @@
1
+ require "eventmachine/email_server/version"
2
+ require 'eventmachine/email_server/base'
3
+ require 'eventmachine/email_server/memory'
4
+ require 'eventmachine/email_server/null'
5
+ require 'eventmachine/email_server/sqlite3'
6
+ require 'eventmachine/email_server/pop3_server'
7
+ require 'eventmachine/email_server/smtp_server'
8
+ require 'eventmachine/email_server/eventmachine_dns_monkeypatch'
data/test/helper.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'minitest/autorun'
2
+ require 'minitest/test'
3
+ require 'minitest/unit'
4
+ include MiniTest::Assertions
5
+ require File.expand_path('../../lib/eventmachine/email_server.rb', __FILE__)