eventmachine-email_server 0.0.5 → 0.0.6
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.
- checksums.yaml +4 -4
- data/README.md +13 -11
- data/lib/eventmachine/email_server/pop3_server.rb +149 -123
- data/lib/eventmachine/email_server/smtp_server.rb +10 -6
- data/lib/eventmachine/email_server/version.rb +1 -1
- data/test/test_email_server.rb +2 -7
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f9618746cec3c86bbed9a6ec20828cc326279246
|
4
|
+
data.tar.gz: 99191abe30a58e83b6a1c764a354c4ec5d4337dd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4dd0f0614b1522e4421fe937c22cc064a2f2cb9da4cd231de94b0288d36ae62f28c70dd852c62c3e60b8ada66e7397dbba021d7328b98b9d4d8d07a61c302485
|
7
|
+
data.tar.gz: 461427eb727203f4850c8d2d52d3fdf716df05e23ad316c9621a91de959c0e222006a7c18bbe96cba023c6fc6e46b0849cff58cb7ff9c5404e89c81bce5596e7
|
data/README.md
CHANGED
@@ -39,6 +39,7 @@ Or install it yourself as:
|
|
39
39
|
|
40
40
|
Simple usage:
|
41
41
|
|
42
|
+
require 'eventmachine'
|
42
43
|
require 'eventmachine/email_server'
|
43
44
|
include EventMachine::EmailServer
|
44
45
|
EM.run {
|
@@ -48,38 +49,39 @@ Simple usage:
|
|
48
49
|
|
49
50
|
Everything turned on:
|
50
51
|
|
52
|
+
require 'eventmachine'
|
51
53
|
require 'eventmachine/email_server'
|
52
54
|
include EventMachine::EmailServer
|
53
55
|
require 'ratelimit/bucketbased'
|
54
|
-
require 'dnsbl/client'
|
55
|
-
require 'sqlite3'
|
56
56
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
57
|
+
userstore = MemoryUserStore.new()
|
58
|
+
emailstore = MemoryEmailStore.new()
|
59
|
+
userstore << User.new(1, "chris", "chris", "chris@example.org")
|
60
|
+
|
62
61
|
config = {
|
63
62
|
'default' => RateLimit::Config.new('default', 2, 2, -2, 1, 1, 1),
|
64
63
|
}
|
65
64
|
storage = RateLimit::Memory.new
|
66
65
|
rl = RateLimit::BucketBased.new(storage, config, 'default')
|
67
|
-
|
66
|
+
|
68
67
|
classifier = EventMachine::EmailServer::Classifier.new("test/test.classifier", [:spam, :ham], [:spam])
|
69
68
|
classifier.train(:spam, "Amazing pillz viagra cialis levitra staxyn")
|
70
69
|
classifier.train(:ham, "Big pigs make great bacon")
|
71
|
-
|
70
|
+
|
72
71
|
SMTPServer.reverse_ptr_check(true)
|
73
72
|
SMTPServer.graylist(Hash.new)
|
74
73
|
SMTPServer.ratelimiter(rl)
|
75
|
-
SMTPServer.dnsbl_check(true)
|
74
|
+
SMTPServer.dnsbl_check(true)
|
76
75
|
SMTPServer.spf_check(true)
|
77
76
|
SMTPServer.reject_filters << /viagra/i
|
78
77
|
SMTPServer.classifier(classifier)
|
79
|
-
|
78
|
+
|
80
79
|
EM.run {
|
81
80
|
pop3 = EventMachine::start_server "0.0.0.0", 2110, POP3Server, "example.org", userstore, emailstore
|
82
81
|
smtp = EventMachine::start_server "0.0.0.0", 2025, SMTPServer, "example.org", userstore, emailstore
|
82
|
+
timer = EventMachine::Timer.new(0.1) do
|
83
|
+
EM.stop
|
84
|
+
end
|
83
85
|
}
|
84
86
|
|
85
87
|
|
@@ -23,13 +23,17 @@ module EventMachine
|
|
23
23
|
|
24
24
|
def receive_data(data)
|
25
25
|
puts ">> #{data}" if @debug
|
26
|
-
|
27
|
-
|
28
|
-
puts "<< #{op}" if @debug
|
29
|
-
send_data(op+"\r\n")
|
26
|
+
data.split(/[\r\n]+/).each do |line|
|
27
|
+
process_line(line)
|
30
28
|
end
|
31
29
|
end
|
32
30
|
|
31
|
+
def send(data, close=false)
|
32
|
+
puts "<< #{data}" if @debug
|
33
|
+
send_data("#{data}\r\n")
|
34
|
+
close_connection if close
|
35
|
+
end
|
36
|
+
|
33
37
|
def unbind(reason=nil)
|
34
38
|
@emails.find_all {|e| e.marked}.each do |email|
|
35
39
|
@emailstore.delete_email(email)
|
@@ -38,107 +42,124 @@ module EventMachine
|
|
38
42
|
end
|
39
43
|
|
40
44
|
def process_line(line)
|
41
|
-
|
42
|
-
case @state
|
45
|
+
case @state
|
43
46
|
when 'auth'
|
44
|
-
|
45
|
-
when /^QUIT$/
|
46
|
-
return false, "+OK dewey POP3 server signing off"
|
47
|
-
when /^CAPA$/
|
48
|
-
return true, "+OK Capability list follows\r\n"+@@capabilities.join("\r\n")+"\r\n."
|
49
|
-
when /^USER (.+)$/
|
50
|
-
user($1)
|
51
|
-
if @user
|
52
|
-
return true, "+OK #{@user.username} is most welcome here"
|
53
|
-
else
|
54
|
-
@failed += 1
|
55
|
-
if @failed > 2
|
56
|
-
return false, "-ERR you're out!"
|
57
|
-
end
|
58
|
-
return true, "-ERR sorry, no mailbox for #{$1} here"
|
59
|
-
end
|
60
|
-
when /^PASS (.+)$/
|
61
|
-
if pass($1)
|
62
|
-
@state = 'trans'
|
63
|
-
emails
|
64
|
-
msgs, bytes = stat
|
65
|
-
return true, "+OK #{@user.username}'s maildrop has #{msgs} messages (#{bytes} octets)"
|
66
|
-
else
|
67
|
-
@failed += 1
|
68
|
-
if @failed > 2
|
69
|
-
return false, "-ERR you're out!"
|
70
|
-
end
|
71
|
-
return true, "-ERR no dope."
|
72
|
-
end
|
73
|
-
when /^APOP ([^\s]+) (.+)$/
|
74
|
-
if apop($1,$2)
|
75
|
-
@state = 'trans'
|
76
|
-
emails
|
77
|
-
return true, "+OK #{@user.username} is most welcome here"
|
78
|
-
else
|
79
|
-
@failed += 1
|
80
|
-
if @failed > 2
|
81
|
-
return false, "-ERR you're out!"
|
82
|
-
end
|
83
|
-
return true, "-ERR sorry, no mailbox for #{$1} here"
|
84
|
-
end
|
85
|
-
end
|
47
|
+
auth_state(line)
|
86
48
|
when 'trans'
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
49
|
+
trans_state(line)
|
50
|
+
when 'update'
|
51
|
+
update_state(line)
|
52
|
+
else
|
53
|
+
send("-ERR unknown command")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def auth_state(line)
|
58
|
+
case line
|
59
|
+
when /^QUIT$/
|
60
|
+
send("+OK dewey POP3 server signing off", true)
|
61
|
+
when /^CAPA$/
|
62
|
+
send("+OK Capability list follows\r\n"+@@capabilities.join("\r\n")+"\r\n.")
|
63
|
+
when /^USER (.+)$/
|
64
|
+
user($1)
|
65
|
+
if @user
|
66
|
+
send("+OK #{@user.username} is most welcome here")
|
67
|
+
else
|
68
|
+
@failed += 1
|
69
|
+
if @failed > 2
|
70
|
+
send("-ERR you're out!", true)
|
71
|
+
else
|
72
|
+
send("-ERR sorry, no mailbox for #{$1} here")
|
73
|
+
end
|
74
|
+
end
|
75
|
+
when /^PASS (.+)$/
|
76
|
+
if pass($1)
|
77
|
+
@state = 'trans'
|
78
|
+
emails
|
79
|
+
msgs, bytes = stat
|
80
|
+
send("+OK #{@user.username}'s maildrop has #{msgs} messages (#{bytes} octets)")
|
81
|
+
else
|
82
|
+
@failed += 1
|
83
|
+
if @failed > 2
|
84
|
+
send("-ERR you're out!", true)
|
85
|
+
else
|
86
|
+
send("-ERR no dope.")
|
87
|
+
end
|
88
|
+
end
|
89
|
+
when /^APOP ([^\s]+) (.+)$/
|
90
|
+
if apop($1,$2)
|
91
|
+
@state = 'trans'
|
92
|
+
emails
|
93
|
+
send("+OK #{@user.username} is most welcome here")
|
94
|
+
else
|
95
|
+
@failed += 1
|
96
|
+
if @failed > 2
|
97
|
+
send("-ERR you're out!", true)
|
98
|
+
else
|
99
|
+
send("-ERR sorry, no mailbox for #{$1} here")
|
100
|
+
end
|
101
|
+
end
|
102
|
+
else
|
103
|
+
send("-ERR unknown command")
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def trans_state(line)
|
108
|
+
case line
|
109
|
+
when /^NOOP$/
|
110
|
+
send("+OK")
|
111
|
+
when /^STAT$/
|
112
|
+
msgs, bytes = stat
|
113
|
+
send("+OK #{msgs} #{bytes}")
|
114
|
+
when /^LIST$/
|
115
|
+
msgs, bytes = stat
|
116
|
+
msg = "+OK #{msgs} messages (#{bytes} octets)\r\n"
|
117
|
+
list.each do |num, bytes|
|
118
|
+
msg += "#{num} #{bytes}\r\n"
|
119
|
+
end
|
120
|
+
msg += "."
|
121
|
+
send(msg)
|
122
|
+
when /^LIST (\d+)$/
|
123
|
+
msgs, bytes = stat
|
124
|
+
num, bytes = list($1)
|
125
|
+
if num
|
126
|
+
send("+OK #{num} #{bytes}")
|
127
|
+
else
|
128
|
+
send("-ERR no such message, only #{msgs} messages in maildrop")
|
129
|
+
end
|
130
|
+
when /^RETR (\d+)$/
|
131
|
+
msg = retr($1)
|
132
|
+
if msg
|
133
|
+
msg = "+OK #{msg.length} octets\r\n" + msg + "\r\n."
|
134
|
+
else
|
135
|
+
msg = "-ERR no such message"
|
136
|
+
end
|
137
|
+
send(msg)
|
138
|
+
when /^DELE (\d+)$/
|
139
|
+
if dele($1)
|
140
|
+
send("+OK message #{$1} marked")
|
141
|
+
else
|
142
|
+
send("-ERR message #{$1} already marked")
|
143
|
+
end
|
144
|
+
when /^RSET$/
|
145
|
+
rset
|
146
|
+
msgs, bytes = stat
|
147
|
+
send("+OK maildrop has #{msgs} messages (#{bytes} octets)")
|
148
|
+
when /^QUIT$/
|
149
|
+
@state = 'update'
|
150
|
+
quit
|
151
|
+
msgs, bytes = stat
|
152
|
+
if msgs > 0
|
153
|
+
send("+OK dewey POP3 server signing off (#{msgs} messages left)", true)
|
154
|
+
else
|
155
|
+
send("+OK dewey POP3 server signing off (maildrop empty)", true)
|
156
|
+
end
|
157
|
+
when /^TOP (\d+) (\d+)$/
|
158
|
+
lines = $2
|
159
|
+
msg = retr($1)
|
160
|
+
unless msg
|
161
|
+
send("-ERR no such message")
|
162
|
+
else
|
142
163
|
cnt = nil
|
143
164
|
final = ""
|
144
165
|
msg.split(/\n/).each do |l|
|
@@ -151,26 +172,31 @@ module EventMachine
|
|
151
172
|
cnt = 0
|
152
173
|
end
|
153
174
|
end
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
175
|
+
send("+OK\r\n"+final+"\r\n.")
|
176
|
+
end
|
177
|
+
when /^UIDL$/
|
178
|
+
msgid = 0
|
179
|
+
msg = ''
|
180
|
+
@emails.each do |e|
|
181
|
+
msgid += 1
|
182
|
+
next if e.marked
|
183
|
+
msg += "#{msgid} #{Digest::MD5.new.update(e.body).hexdigest}\r\n"
|
184
|
+
end
|
185
|
+
send("+OK\r\n#{msg}.")
|
186
|
+
else
|
187
|
+
send("-ERR unknown command")
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def update_state(line)
|
192
|
+
case line
|
193
|
+
when /^QUIT$/
|
194
|
+
send("+OK dewey POP3 server signing off", true)
|
195
|
+
else
|
196
|
+
send("-ERR unknown command")
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
174
200
|
def user(username)
|
175
201
|
@user = @userstore.user_by_username(username)
|
176
202
|
end
|
@@ -90,8 +90,7 @@ module EventMachine
|
|
90
90
|
end
|
91
91
|
|
92
92
|
def post_init
|
93
|
-
|
94
|
-
send_data "220 #{@hostname} ESMTP Service ready\n"
|
93
|
+
send "220 #{@hostname} ESMTP Service ready"
|
95
94
|
end
|
96
95
|
|
97
96
|
def receive_data(data)
|
@@ -102,7 +101,9 @@ module EventMachine
|
|
102
101
|
end
|
103
102
|
|
104
103
|
def check_ptr(helo, ip)
|
105
|
-
if
|
104
|
+
if helo =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/ and helo == ip
|
105
|
+
@ptr_ok = true
|
106
|
+
elsif @@reverse_ptr_check
|
106
107
|
@ptr_ok = false
|
107
108
|
@pending_checks << :ptr
|
108
109
|
d = EM::DNS::Resolver.resolve helo
|
@@ -175,7 +176,9 @@ module EventMachine
|
|
175
176
|
|
176
177
|
pool.perform do |dispatcher|
|
177
178
|
completion = dispatcher.dispatch do |spf_server|
|
178
|
-
|
179
|
+
if helo =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/ and helo == ip
|
180
|
+
helo = nil
|
181
|
+
end
|
179
182
|
request = SPF::Request.new(
|
180
183
|
versions: [1, 2], # optional
|
181
184
|
scope: 'mfrom', # or 'helo', 'pra'
|
@@ -235,18 +238,19 @@ module EventMachine
|
|
235
238
|
end
|
236
239
|
|
237
240
|
def process_line(line)
|
238
|
-
if (@data_mode) && (line.chomp
|
241
|
+
if (@data_mode) && (line.chomp == '.')
|
239
242
|
@data_mode = false
|
240
243
|
check_reject
|
241
244
|
check_classifier
|
242
245
|
@pending_checks -= [:content]
|
246
|
+
p @pending_checks
|
243
247
|
if @pending_checks.length == 0
|
244
248
|
send_answer
|
245
249
|
end
|
246
250
|
elsif @data_mode
|
247
251
|
@email_body += line
|
248
252
|
elsif (line =~ /^(HELO|EHLO) (.*)/)
|
249
|
-
helo = $2.chomp
|
253
|
+
helo = $2.chomp.gsub(/^\[/,'').gsub(/\]$/,'')
|
250
254
|
port, ip = Socket.unpack_sockaddr_in(get_peername)
|
251
255
|
check_ptr(helo, ip)
|
252
256
|
check_dnsbl(ip)
|
data/test/test_email_server.rb
CHANGED
@@ -49,14 +49,10 @@ Looks like we had fun!
|
|
49
49
|
@pool = EM::Pool.new
|
50
50
|
SMTPServer.reset
|
51
51
|
remove_scraps
|
52
|
+
$dns_port = 53
|
52
53
|
end
|
53
54
|
|
54
55
|
def remove_scraps
|
55
|
-
["test.sqlite3", "email_server.sqlite3"].each do |f|
|
56
|
-
if File.exist?("test/#{f}")
|
57
|
-
File.unlink("test/#{f}")
|
58
|
-
end
|
59
|
-
end
|
60
56
|
end
|
61
57
|
|
62
58
|
def teardown
|
@@ -313,7 +309,6 @@ Looks like we had fun!
|
|
313
309
|
userstore = MemoryUserStore.new
|
314
310
|
emailstore = MemoryEmailStore.new
|
315
311
|
setup_user(userstore)
|
316
|
-
$dns_port = 53
|
317
312
|
|
318
313
|
SMTPServer.spf_check(true)
|
319
314
|
EM.run {
|
@@ -360,10 +355,10 @@ Looks like we had fun!
|
|
360
355
|
|
361
356
|
def test_example
|
362
357
|
return unless @test_vector.call(__method__)
|
358
|
+
#require 'eventmachine'
|
363
359
|
#require 'eventmachine/email_server'
|
364
360
|
#include EventMachine::EmailServer
|
365
361
|
#require 'ratelimit/bucketbased'
|
366
|
-
#require 'dnsbl/client'
|
367
362
|
|
368
363
|
userstore = MemoryUserStore.new()
|
369
364
|
emailstore = MemoryEmailStore.new()
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: eventmachine-email_server
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- chrislee35
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-03-
|
11
|
+
date: 2015-03-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: eventmachine
|