eventmachine-email_server 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7f8d810ad36c8d3b8515f5f209afaadbb9038391
4
- data.tar.gz: d6f036b47bd9fedf1e566ad576ed02e6f8a02b71
3
+ metadata.gz: a217dda0576da20c2bace6cd1320f6bc6662a052
4
+ data.tar.gz: cf0f814c5b56a830300d89daeff7e43a457017bd
5
5
  SHA512:
6
- metadata.gz: bc9f85702c714eca052dd7b5ee52c66f84d35866df288494917e4039c29c5b533e1edd118a65f2815b4208962ef3331796808fb3da16e8ec50d45c1f311b7abf
7
- data.tar.gz: dcc7d410bdd476b78db32533a507f8812d10b97c57bb2c7b177f752c95770a972ccbd111af44b416e2e06521b3be342719e0b425e6af58df9a7a3ee9c9677698
6
+ metadata.gz: 03eb0a117e26b162c41b5a9068687d4224ca7fe39b97378004aab43f81aeb2551b3ee49bd97f346491470fc5c6ef27b0d8133052e5c28e3fa1c8a5a1208eb0a4
7
+ data.tar.gz: 6c091d1cc691535298fd2f296ac493a46cd3dacbd968a95080ea34ed25aeecb5ecc6804dc2ac836bd39c17524243ce55154dd5899a1cfb57122c9231388f9573
data/.gitignore CHANGED
@@ -13,3 +13,4 @@
13
13
  *.a
14
14
  mkmf.log
15
15
  /test/*.sqlite3
16
+ /test/test.classifier
data/README.md CHANGED
@@ -1,13 +1,24 @@
1
1
  # EventMachine::EmailServer
2
2
 
3
- This provides an EventMachine-based implementation of POP3 and SMTP services--primarily for use within the Rubot framework. However, as I add features, this might come in handy for other people as well.
3
+ This provides an EventMachine-based implementation of POP3 and SMTP services--primarily for use within the Rubot framework. However, as I add features, this might come in handy for other people as well (might make a good spamtrap).
4
4
 
5
- There are several email and user backends so that the POP3 and SMTP servers can share: Memory, Sqlite3, and Null.
5
+ There are several email and user backends so that the POP3 and SMTP servers can share: Memory, Sqlite3, and Null. (In the future, I plan to move Sqlite3 support into an external module and add filesystem storage as an external module as well.)
6
6
 
7
- The SMTP server currently only receives mail as an end-host (no relay or sending) and does no fancy routing of email (e.g., aliases and procmail). It does, however, have graylisting, DNS PTR checks, DNSBL checks, rate limiting, and simple filters.
7
+ The SMTP server currently only receives mail as an end-host (no relay or sending) and does no fancy routing of email (e.g., aliases and procmail). It does, however, have graylisting, DNS PTR checks, DNSBL checks, rate limiting, SPF checking, simple filters, and a baysian classifier.
8
8
 
9
- Writing a full-featured mail server is a multi-year, multi-person project. I would need some help.
9
+ Writing a full-featured mail server is a multi-year, multi-person project. I would need some help. If you're interested, let me know.
10
10
 
11
+ Potential features if people request them:
12
+
13
+ * SSL, StartTLS, Peer authentication (medium)
14
+ * Cram-MD5-based authentication (medium)
15
+ * IMAP (hard)
16
+ * Create a launcher in bin/ and parse configuration files (easy)
17
+ * Domain-Keys (hard, unless I find a lib that does it)
18
+ * Relay (this is easy to implement, but hard to get right)
19
+ * Aliases (easy)
20
+ * Logging (easiest)
21
+
11
22
  ## Installation
12
23
 
13
24
  Add this line to your application's Gemfile:
@@ -43,10 +54,10 @@ Everything turned on:
43
54
  require 'dnsbl/client'
44
55
  require 'sqlite3'
45
56
 
46
- s = SQLite3::Database.new("test/test.sqlite3")
57
+ s = SQLite3::Database.new("email_server.sqlite3")
47
58
  userstore = Sqlite3UserStore.new(s)
48
59
  emailstore = Sqlite3EmailStore.new(s)
49
- userstore << User.new(1, "chris", "chris", "chris@example.org")
60
+ userstore << User.new(1, "chris", "password", "chris@example.org")
50
61
 
51
62
  config = {
52
63
  'default' => RateLimit::Config.new('default', 2, 2, -2, 1, 1, 1),
@@ -54,13 +65,17 @@ Everything turned on:
54
65
  storage = RateLimit::Memory.new
55
66
  rl = RateLimit::BucketBased.new(storage, config, 'default')
56
67
 
57
- dnsbl = DNSBL::Client.new
68
+ classifier = EventMachine::EmailServer::Classifier.new("test/test.classifier", [:spam, :ham], [:spam])
69
+ classifier.train(:spam, "Amazing pillz viagra cialis levitra staxyn")
70
+ classifier.train(:ham, "Big pigs make great bacon")
58
71
 
59
72
  SMTPServer.reverse_ptr_check(true)
60
73
  SMTPServer.graylist(Hash.new)
61
74
  SMTPServer.ratelimiter(rl)
62
- SMTPServer.dnsbl(dnsbl)
75
+ SMTPServer.dnsbl_check(true)
76
+ SMTPServer.spf_check(true)
63
77
  SMTPServer.reject_filters << /viagra/i
78
+ SMTPServer.classifier(classifier)
64
79
 
65
80
  EM.run {
66
81
  pop3 = EventMachine::start_server "0.0.0.0", 2110, POP3Server, "example.org", userstore, emailstore
@@ -70,19 +85,6 @@ Everything turned on:
70
85
 
71
86
  ## Contributing
72
87
 
73
- If we want this to be "professional":
74
- *EventMachine-based SPF Checking
75
- *EventMachine-based DNSBL::Client
76
- *Abstract filtering into a class with a callback so that all sorts of filtering (e.g., baysian) could be done
77
- *Create a launcher in bin/ and parse configuration files
78
- *StartTLS
79
- *SSL
80
- *CRAM-MD5
81
- *Domain-Keys
82
- *Relay (this is easy to implement, but hard to get right)
83
- *Aliases
84
- *Logging
85
-
86
88
  1. Fork it ( https://github.com/[my-github-username]/eventmachine-email_server/fork )
87
89
  2. Create your feature branch (`git checkout -b my-new-feature`)
88
90
  3. Commit your changes (`git commit -am 'Add some feature'`)
Binary file
@@ -23,6 +23,10 @@ Gem::Specification.new do |spec|
23
23
  spec.add_runtime_dependency "ratelimit-bucketbased", ">= 0.0.1"
24
24
  spec.add_runtime_dependency "eventmachine-dnsbl", ">= 0.0.2"
25
25
  spec.add_runtime_dependency "spf", ">= 0.0.44"
26
+ spec.add_runtime_dependency "classifier", ">= 1.3.4"
27
+ spec.add_runtime_dependency "rb-gsl", ">= 1.16.0.4"
28
+ spec.add_runtime_dependency "fast-stemmer", ">= 1.0.2"
29
+ spec.add_runtime_dependency "madeleine", ">= 0.9.0"
26
30
  spec.add_development_dependency "minitest", "~> 5.5"
27
31
  spec.add_development_dependency "bundler", "~> 1.6"
28
32
  spec.add_development_dependency "rake", "~> 10.0"
@@ -3,6 +3,7 @@ require 'eventmachine/email_server/base'
3
3
  require 'eventmachine/email_server/memory'
4
4
  require 'eventmachine/email_server/null'
5
5
  require 'eventmachine/email_server/sqlite3'
6
+ require 'eventmachine/email_server/classifier'
6
7
  require 'eventmachine/email_server/pop3_server'
7
8
  require 'eventmachine/email_server/smtp_server'
8
9
  require 'eventmachine/email_server/eventmachine_dns_monkeypatch'
@@ -0,0 +1,38 @@
1
+ require 'eventmachine'
2
+ require 'classifier'
3
+ require 'madeleine'
4
+
5
+ module EventMachine
6
+ module EmailServer
7
+ class Classifier
8
+ def initialize(datafile, categories, blocked_categories)
9
+ @categories = categories || [:good, :bad]
10
+ @blocked_categories = blocked_categories || [:bad]
11
+ @categories.map! { |c| c.prepare_category_name.to_s }
12
+ @blocked_categories.map! { |c| c.prepare_category_name.to_s }
13
+ @madeleine = SnapshotMadeleine.new(datafile) {
14
+ ::Classifier::Bayes.new(*categories)
15
+ }
16
+ @classifier = @madeleine.system
17
+ end
18
+
19
+ def train(category, email)
20
+ @classifier.train(category, email)
21
+ @madeleine.take_snapshot
22
+ end
23
+
24
+ def classify(email)
25
+ @classifier.classify(email)
26
+ end
27
+
28
+ def block?(email)
29
+ c = classify(email)
30
+ if @blocked_categories.index(c).nil?
31
+ return false
32
+ end
33
+ true
34
+ end
35
+ end
36
+ end
37
+ end
38
+
@@ -11,6 +11,7 @@ module EventMachine
11
11
  @@reverse_ptr_check = false
12
12
  @@spf_check = false
13
13
  @@reject_filters = Array.new
14
+ @@classifier = nil
14
15
 
15
16
  def self.reset
16
17
  @@graylist = nil
@@ -19,6 +20,7 @@ module EventMachine
19
20
  @@reverse_ptr_check = false
20
21
  @@spf_check = false
21
22
  @@reject_filters = Array.new
23
+ @classifier = nil
22
24
  end
23
25
 
24
26
  def self.reverse_ptr_check(ptr=nil)
@@ -61,7 +63,14 @@ module EventMachine
61
63
  @@spf_check = spf
62
64
  end
63
65
  @@spf_check
64
- end
66
+ end
67
+
68
+ def self.classifier(classifier=nil)
69
+ if not classifier.nil?
70
+ @@classifier = classifier
71
+ end
72
+ @@classifier
73
+ end
65
74
 
66
75
  def initialize(hostname, userstore, emailstore)
67
76
  @hostname = hostname
@@ -74,7 +83,9 @@ module EventMachine
74
83
  @dnsbl_ok = true
75
84
  @rate_ok = true
76
85
  @gray_ok = true
86
+ @spf_ok = true
77
87
  @reject_ok = true
88
+ @classifier_ok = true
78
89
  @pending_checks = Array.new
79
90
  end
80
91
 
@@ -145,6 +156,14 @@ module EventMachine
145
156
  end
146
157
  end
147
158
 
159
+ def check_classifier
160
+ if @@classifier
161
+ if @@classifier.block?(@email_body)
162
+ @classifier_ok = false
163
+ end
164
+ end
165
+ end
166
+
148
167
  def check_spf(helo, client_ip, identity)
149
168
  if @@spf_check
150
169
  @spf_ok = false
@@ -196,7 +215,16 @@ module EventMachine
196
215
  end
197
216
 
198
217
  def send_answer
199
- if @ptr_ok and @rate_ok and @gray_ok and @reject_ok and @dnsbl_ok and @spf_ok
218
+ if @debug
219
+ puts "ptr_ok = #{@ptr_ok}"
220
+ puts "rate_ok = #{@rate_ok}"
221
+ puts "gray_ok = #{@gray_ok}"
222
+ puts "reject_ok = #{@reject_ok}"
223
+ puts "dnsbl_ok = #{@dnsbl_ok}"
224
+ puts "spf_ok = #{@spf_ok}"
225
+ puts "classifier_ok = #{@classifier_ok}"
226
+ end
227
+ if @ptr_ok and @rate_ok and @gray_ok and @reject_ok and @dnsbl_ok and @spf_ok and @classifier_ok
200
228
  ans = "250 OK"
201
229
  else
202
230
  ans = "451 Requested action aborted: local error in processing"
@@ -209,6 +237,7 @@ module EventMachine
209
237
  if (@data_mode) && (line.chomp =~ /^\.$/)
210
238
  @data_mode = false
211
239
  check_reject
240
+ check_classifier
212
241
  if @pending_checks.length == 0
213
242
  send_answer
214
243
  end
@@ -1,5 +1,5 @@
1
1
  module EventMachine
2
2
  module EmailServer
3
- VERSION = "0.0.3"
3
+ VERSION = "0.0.4"
4
4
  end
5
5
  end
@@ -27,21 +27,19 @@ class TestEmailServer < Minitest::Test
27
27
  @test_vector = Proc.new { |test_name|
28
28
  (test_name.to_s =~ /test/)
29
29
  }
30
- if File.exist?("test/test.sqlite3")
31
- File.unlink("test/test.sqlite3")
32
- end
33
- if File.exist?("email_server.sqlite3")
34
- File.unlink("email_server.sqlite3")
35
- end
30
+ remove_scraps
36
31
  end
37
32
 
38
- def teardown
39
- if File.exist?("test/test.sqlite3")
40
- File.unlink("test/test.sqlite3")
41
- end
42
- if File.exist?("email_server.sqlite3")
43
- File.unlink("email_server.sqlite3")
33
+ def remove_scraps
34
+ ["test.sqlite3", "email_server.sqlite3"].each do |f|
35
+ if File.exist?("test/#{f}")
36
+ File.unlink("test/#{f}")
37
+ end
44
38
  end
39
+ end
40
+
41
+ def teardown
42
+ remove_scraps
45
43
  end
46
44
 
47
45
  def setup_user(userstore)
@@ -69,6 +67,38 @@ Looks like we had fun!
69
67
  end
70
68
  end
71
69
 
70
+ def send_spam(expected_status="451")
71
+ from = "friend@example.org"
72
+ to = "chris@example.org"
73
+ msg = "From: friend@example.org
74
+ To: chris@example.org
75
+ Subject: What to do when you're not doing.
76
+
77
+ Could I interest you in some cialis?
78
+ "
79
+ Thread.new do
80
+ smtp = Net::SMTP.start('localhost', 2025)
81
+ ret = smtp.send_message msg, from, to
82
+ assert_equal(expected_status, ret.status)
83
+ end
84
+ end
85
+
86
+ def send_ham(expected_status="250")
87
+ from = "friend@example.org"
88
+ to = "chris@example.org"
89
+ msg = "From: friend@example.org
90
+ To: chris@example.org
91
+ Subject: Good show
92
+
93
+ Have you seen the latest Peppa Pig?
94
+ "
95
+ Thread.new do
96
+ smtp = Net::SMTP.start('localhost', 2025)
97
+ ret = smtp.send_message msg, from, to
98
+ assert_equal(expected_status, ret.status)
99
+ end
100
+ end
101
+
72
102
  def pop_some_email
73
103
  Thread.new do
74
104
  pop = Net::POP3.APOP(true).new('localhost',2110)
@@ -277,6 +307,28 @@ Looks like we had fun!
277
307
  }
278
308
 
279
309
  end
310
+
311
+ def test_classifier
312
+ return unless @test_vector.call(__method__)
313
+ userstore = MemoryUserStore.new
314
+ emailstore = MemoryEmailStore.new
315
+ setup_user(userstore)
316
+ SMTPServer.reset
317
+ classifier = EventMachine::EmailServer::Classifier.new("test/test.classifier", [:spam, :ham], [:spam])
318
+ classifier.train(:spam, "Amazing pillz viagra cialis levitra staxyn")
319
+ classifier.train(:ham, "Big pigs make great bacon")
320
+ SMTPServer.classifier(classifier)
321
+ EM.run {
322
+ smtp = EventMachine::start_server "0.0.0.0", 2025, SMTPServer, "example.org", userstore, emailstore
323
+ timer = EventMachine::Timer.new(0.1) do
324
+ send_spam("451")
325
+ send_ham("250")
326
+ end
327
+ timer2 = EventMachine::Timer.new(1) do
328
+ EM.stop
329
+ end
330
+ }
331
+ end
280
332
 
281
333
  def test_example
282
334
  return unless @test_vector.call(__method__)
@@ -297,13 +349,18 @@ Looks like we had fun!
297
349
  storage = RateLimit::Memory.new
298
350
  rl = RateLimit::BucketBased.new(storage, config, 'default')
299
351
 
352
+ classifier = EventMachine::EmailServer::Classifier.new("test/test.classifier", [:spam, :ham], [:spam])
353
+ classifier.train(:spam, "Amazing pillz viagra cialis levitra staxyn")
354
+ classifier.train(:ham, "Big pigs make great bacon")
300
355
 
301
356
  SMTPServer.reset
302
357
  SMTPServer.reverse_ptr_check(true)
303
358
  SMTPServer.graylist(Hash.new)
304
359
  SMTPServer.ratelimiter(rl)
305
360
  SMTPServer.dnsbl_check(true)
361
+ SMTPServer.spf_check(true)
306
362
  SMTPServer.reject_filters << /viagra/i
363
+ SMTPServer.classifier(classifier)
307
364
 
308
365
  EM.run {
309
366
  pop3 = EventMachine::start_server "0.0.0.0", 2110, POP3Server, "example.org", userstore, emailstore
@@ -313,7 +370,5 @@ Looks like we had fun!
313
370
  end
314
371
  }
315
372
  end
316
-
317
-
318
373
 
319
374
  end
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.3
4
+ version: 0.0.4
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-07 00:00:00.000000000 Z
11
+ date: 2015-03-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: eventmachine
@@ -80,6 +80,62 @@ dependencies:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: 0.0.44
83
+ - !ruby/object:Gem::Dependency
84
+ name: classifier
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: 1.3.4
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: 1.3.4
97
+ - !ruby/object:Gem::Dependency
98
+ name: rb-gsl
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: 1.16.0.4
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: 1.16.0.4
111
+ - !ruby/object:Gem::Dependency
112
+ name: fast-stemmer
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: 1.0.2
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: 1.0.2
125
+ - !ruby/object:Gem::Dependency
126
+ name: madeleine
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: 0.9.0
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: 0.9.0
83
139
  - !ruby/object:Gem::Dependency
84
140
  name: minitest
85
141
  requirement: !ruby/object:Gem::Requirement
@@ -135,9 +191,11 @@ files:
135
191
  - LICENSE.txt
136
192
  - README.md
137
193
  - Rakefile
194
+ - email_server.sqlite3
138
195
  - eventmachine-email_server.gemspec
139
196
  - lib/eventmachine/email_server.rb
140
197
  - lib/eventmachine/email_server/base.rb
198
+ - lib/eventmachine/email_server/classifier.rb
141
199
  - lib/eventmachine/email_server/eventmachine_dns_monkeypatch.rb
142
200
  - lib/eventmachine/email_server/memory.rb
143
201
  - lib/eventmachine/email_server/null.rb