eventmachine-email_server 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +90 -0
- data/Rakefile +12 -0
- data/eventmachine-email_server.gemspec +28 -0
- data/lib/eventmachine/email_server/base.rb +66 -0
- data/lib/eventmachine/email_server/eventmachine_dns_monkeypatch.rb +55 -0
- data/lib/eventmachine/email_server/memory.rb +61 -0
- data/lib/eventmachine/email_server/null.rb +56 -0
- data/lib/eventmachine/email_server/pop3_server.rb +250 -0
- data/lib/eventmachine/email_server/smtp_server.rb +242 -0
- data/lib/eventmachine/email_server/sqlite3.rb +153 -0
- data/lib/eventmachine/email_server/version.rb +5 -0
- data/lib/eventmachine/email_server.rb +8 -0
- data/test/helper.rb +5 -0
- data/test/test_email_server.rb +334 -0
- metadata +162 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 45064a738c416e1fce2c8726b3bab01c10f55860
|
4
|
+
data.tar.gz: 25c163465275237d7caf9f48d257d20348a812c9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: cecf95b16f4d4e38cf83a65c2f4f9a69c5d4807b71a31a37fbc1cf4f723eb7476c95b7450adef582e8939019c5b4d6b96e8449a4070cb2ff0b78fa5b5bab4aa0
|
7
|
+
data.tar.gz: 3cb06186363faef276e41cd92ea2127938d1d1bd3109a2bcf50f343a3d844b021e8969bd4213d3e01976547b8e8b8292482d8157bbf440cc27e45380b38a2e93
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 chrislee35
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
# EventMachine::EmailServer
|
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.
|
4
|
+
|
5
|
+
There are several email and user backends so that the POP3 and SMTP servers can share: Memory, Sqlite3, and Null.
|
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.
|
8
|
+
|
9
|
+
Writing a full-featured mail server is a multi-year, multi-person project. I would need some help.
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Add this line to your application's Gemfile:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
gem 'eventmachine-email_server'
|
17
|
+
```
|
18
|
+
|
19
|
+
And then execute:
|
20
|
+
|
21
|
+
$ bundle
|
22
|
+
|
23
|
+
Or install it yourself as:
|
24
|
+
|
25
|
+
$ gem install eventmachine-email_server
|
26
|
+
|
27
|
+
## Usage
|
28
|
+
|
29
|
+
Simple usage:
|
30
|
+
|
31
|
+
require 'eventmachine/email_server'
|
32
|
+
include EventMachine::EmailServer
|
33
|
+
EM.run {
|
34
|
+
pop3 = EventMachine::start_server "0.0.0.0", 2110, POP3Server, "example.org", userstore, emailstore
|
35
|
+
smtp = EventMachine::start_server "0.0.0.0", 2025, SMTPServer, "example.org", userstore, emailstore
|
36
|
+
}
|
37
|
+
|
38
|
+
Everything turned on:
|
39
|
+
|
40
|
+
require 'eventmachine/email_server'
|
41
|
+
include EventMachine::EmailServer
|
42
|
+
require 'ratelimit/bucketbased'
|
43
|
+
require 'dnsbl/client'
|
44
|
+
require 'sqlite3'
|
45
|
+
|
46
|
+
s = SQLite3::Database.new("test/test.sqlite3")
|
47
|
+
userstore = Sqlite3UserStore.new(s)
|
48
|
+
emailstore = Sqlite3EmailStore.new(s)
|
49
|
+
userstore << User.new(1, "chris", "chris", "chris@example.org")
|
50
|
+
|
51
|
+
config = {
|
52
|
+
'default' => RateLimit::Config.new('default', 2, 2, -2, 1, 1, 1),
|
53
|
+
}
|
54
|
+
storage = RateLimit::Memory.new
|
55
|
+
rl = RateLimit::BucketBased.new(storage, config, 'default')
|
56
|
+
|
57
|
+
dnsbl = DNSBL::Client.new
|
58
|
+
|
59
|
+
SMTPServer.reverse_ptr_check(true)
|
60
|
+
SMTPServer.graylist(Hash.new)
|
61
|
+
SMTPServer.ratelimiter(rl)
|
62
|
+
SMTPServer.dnsbl(dnsbl)
|
63
|
+
SMTPServer.reject_filters << /viagra/i
|
64
|
+
|
65
|
+
EM.run {
|
66
|
+
pop3 = EventMachine::start_server "0.0.0.0", 2110, POP3Server, "example.org", userstore, emailstore
|
67
|
+
smtp = EventMachine::start_server "0.0.0.0", 2025, SMTPServer, "example.org", userstore, emailstore
|
68
|
+
}
|
69
|
+
|
70
|
+
|
71
|
+
## Contributing
|
72
|
+
|
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
|
+
1. Fork it ( https://github.com/[my-github-username]/eventmachine-email_server/fork )
|
87
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
88
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
89
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
90
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'eventmachine/email_server/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "eventmachine-email_server"
|
8
|
+
spec.version = EventMachine::EmailServer::VERSION
|
9
|
+
spec.authors = ["chrislee35"]
|
10
|
+
spec.email = ["rubygems@chrislee.dhs.org"]
|
11
|
+
spec.summary = %q{EventMachine-based implementations of a POP3 and SMTP server}
|
12
|
+
spec.description = %q{Simple POP3 and SMTP implementation in EventMachine for use in the Rubot framework}
|
13
|
+
spec.homepage = "https://github.com/chrislee35/eventmachine-email_server/"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_runtime_dependency "eventmachine", ">= 0.12.10"
|
22
|
+
spec.add_runtime_dependency "sqlite3", ">= 1.3.6"
|
23
|
+
spec.add_runtime_dependency "ratelimit-bucketbased", ">= 0.0.1"
|
24
|
+
spec.add_runtime_dependency "eventmachine-dnsbl", ">= 0.0.2"
|
25
|
+
spec.add_development_dependency "minitest", "~> 5.5"
|
26
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
27
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
28
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module EventMachine
|
2
|
+
module EmailServer
|
3
|
+
class User < Struct.new(:id,:username,:password,:address); end
|
4
|
+
class Email < Struct.new(:id,:from,:to,:subject,:body,:uid,:marked); end
|
5
|
+
|
6
|
+
class AbstractUserStore
|
7
|
+
def add_user(user)
|
8
|
+
raise "Unimplemented, please use a subclass of #{self.class}"
|
9
|
+
end
|
10
|
+
|
11
|
+
def <<(user)
|
12
|
+
add_user(user)
|
13
|
+
end
|
14
|
+
|
15
|
+
def delete_user(user)
|
16
|
+
raise "Unimplemented, please use a subclass of #{self.class}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def -(user)
|
20
|
+
delete_user(user)
|
21
|
+
end
|
22
|
+
|
23
|
+
def user_by_username(username)
|
24
|
+
raise "Unimplemented, please use a subclass of #{self.class}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def user_by_emailaddress(email)
|
28
|
+
raise "Unimplemented, please use a subclass of #{self.class}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class AbstractEmailStore
|
33
|
+
def emails_by_userid(id)
|
34
|
+
raise "Unimplemented, please use a subclass of #{self.class}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def save_email(email)
|
38
|
+
raise "Unimplemented, please use a subclass of #{self.class}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def <<(email)
|
42
|
+
save_email(email)
|
43
|
+
end
|
44
|
+
|
45
|
+
def delete_email(email)
|
46
|
+
raise "Unimplemented, please use a subclass of #{self.class}"
|
47
|
+
end
|
48
|
+
|
49
|
+
def -(email)
|
50
|
+
delete_email(email)
|
51
|
+
end
|
52
|
+
|
53
|
+
def delete_id(id)
|
54
|
+
raise "Unimplemented, please use a subclass of #{self.class}"
|
55
|
+
end
|
56
|
+
|
57
|
+
def delete_user(uid)
|
58
|
+
raise "Unimplemented, please use a subclass of #{self.class}"
|
59
|
+
end
|
60
|
+
|
61
|
+
def count
|
62
|
+
raise "Unimplemented, please use a subclass of #{self.class}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module EventMachine
|
2
|
+
module DNS
|
3
|
+
class Resolver
|
4
|
+
def self.resolve(hostname, qclass=Resolv::DNS::Resource::IN::A)
|
5
|
+
Request.new(socket, hostname, qclass)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
class Request
|
9
|
+
def initialize(socket, hostname, qclass)
|
10
|
+
@socket = socket
|
11
|
+
@hostname = hostname
|
12
|
+
@qclass = qclass
|
13
|
+
@tries = 0
|
14
|
+
@last_send = Time.at(0)
|
15
|
+
@retry_interval = 3
|
16
|
+
@max_tries = 5
|
17
|
+
if addrs = Resolver.hosts[hostname]
|
18
|
+
succeed addrs
|
19
|
+
else
|
20
|
+
EM.next_tick { tick }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def receive_answer(msg)
|
25
|
+
addrs = []
|
26
|
+
msg.each_answer do |name,ttl,data|
|
27
|
+
if data.respond_to? :address
|
28
|
+
addrs << data.address.to_s
|
29
|
+
elsif data.respond_to? :name
|
30
|
+
addrs << data.name.to_s
|
31
|
+
elsif data.respond_to? :strings
|
32
|
+
addrs << data.strings.join("\n")
|
33
|
+
else
|
34
|
+
addrs << data.to_s
|
35
|
+
end
|
36
|
+
end
|
37
|
+
if addrs.empty?
|
38
|
+
fail "rcode=#{msg.rcode}"
|
39
|
+
else
|
40
|
+
succeed addrs
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def packet
|
47
|
+
msg = Resolv::DNS::Message.new
|
48
|
+
msg.id = id
|
49
|
+
msg.rd = 1
|
50
|
+
msg.add_question @hostname, @qclass
|
51
|
+
msg
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
|
3
|
+
module EventMachine
|
4
|
+
module EmailServer
|
5
|
+
class MemoryUserStore < AbstractUserStore
|
6
|
+
def initialize
|
7
|
+
@users = Array.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def add_user(user)
|
11
|
+
@users << user
|
12
|
+
end
|
13
|
+
|
14
|
+
def delete_user(user)
|
15
|
+
@users -= [user]
|
16
|
+
end
|
17
|
+
|
18
|
+
def user_by_username(username)
|
19
|
+
@users.find {|user| user.username == username}
|
20
|
+
end
|
21
|
+
|
22
|
+
def user_by_emailaddress(address)
|
23
|
+
@users.find {|user| user.address == address}
|
24
|
+
end
|
25
|
+
|
26
|
+
def user_by_id(id)
|
27
|
+
@users.find {|user| user.id == id}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class MemoryEmailStore < AbstractEmailStore
|
32
|
+
def initialize
|
33
|
+
@emails = Array.new
|
34
|
+
end
|
35
|
+
|
36
|
+
def emails_by_userid(uid)
|
37
|
+
@emails.find_all {|email| email.uid == uid}
|
38
|
+
end
|
39
|
+
|
40
|
+
def save_email(email)
|
41
|
+
@emails << email
|
42
|
+
end
|
43
|
+
|
44
|
+
def delete_email(email)
|
45
|
+
@emails -= [email]
|
46
|
+
end
|
47
|
+
|
48
|
+
def delete_id(id)
|
49
|
+
@emails.delete_if {|email| email.id == id}
|
50
|
+
end
|
51
|
+
|
52
|
+
def delete_user(uid)
|
53
|
+
@emails.delete_if {|email| email.uid == uid}
|
54
|
+
end
|
55
|
+
|
56
|
+
def count
|
57
|
+
@emails.length
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
|
3
|
+
module EventMachine
|
4
|
+
module EmailServer
|
5
|
+
class NullUserStore < AbstractUserStore
|
6
|
+
def initialize
|
7
|
+
@user = User.new(1,"null","null","null")
|
8
|
+
end
|
9
|
+
|
10
|
+
def add_user(user)
|
11
|
+
end
|
12
|
+
|
13
|
+
def delete_user(user)
|
14
|
+
end
|
15
|
+
|
16
|
+
def user_by_username(username)
|
17
|
+
u = @user.clone
|
18
|
+
u.userame = username
|
19
|
+
u
|
20
|
+
end
|
21
|
+
|
22
|
+
def user_by_emailaddress(address)
|
23
|
+
u = @user.clone
|
24
|
+
u.address = address
|
25
|
+
u
|
26
|
+
end
|
27
|
+
|
28
|
+
def user_by_id(id)
|
29
|
+
u = @user.clone
|
30
|
+
u.id = id
|
31
|
+
u
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class NullEmailStore < AbstractEmailStore
|
36
|
+
def initialize
|
37
|
+
end
|
38
|
+
|
39
|
+
def emails_by_userid(uid)
|
40
|
+
[]
|
41
|
+
end
|
42
|
+
|
43
|
+
def save_email(email)
|
44
|
+
end
|
45
|
+
|
46
|
+
def delete_email(email)
|
47
|
+
end
|
48
|
+
|
49
|
+
def delete_id(id)
|
50
|
+
end
|
51
|
+
|
52
|
+
def delete_user(uid)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,250 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
require 'digest/md5'
|
3
|
+
|
4
|
+
module EventMachine
|
5
|
+
module EmailServer
|
6
|
+
class POP3Server < EventMachine::Connection
|
7
|
+
@@capabilities = [ "TOP", "USER", "UIDL" ]
|
8
|
+
def initialize(hostname, userstore, emailstore)
|
9
|
+
@hostname = hostname
|
10
|
+
@userstore = userstore
|
11
|
+
@emailstore = emailstore
|
12
|
+
@state = 'auth' # 'trans' and 'data'
|
13
|
+
@auth_attempts = 0
|
14
|
+
@apop_challenge = "<#{rand(10**4 - 1)}.#{rand(10**9 - 1)}@#{@hostname}>"
|
15
|
+
@debug = true
|
16
|
+
@emails = Array.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def post_init
|
20
|
+
puts ">> +OK POP3 server ready #{@apop_challenge}" if @debug
|
21
|
+
send_data("+OK POP3 server ready #{@apop_challenge}\r\n")
|
22
|
+
end
|
23
|
+
|
24
|
+
def receive_data(data)
|
25
|
+
puts ">> #{data}" if @debug
|
26
|
+
ok, op = process_line(data)
|
27
|
+
if ok
|
28
|
+
puts "<< #{op}" if @debug
|
29
|
+
send_data(op+"\r\n")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def unbind(reason=nil)
|
34
|
+
@emails.find_all {|e| e.marked}.each do |email|
|
35
|
+
@emailstore.delete_email(email)
|
36
|
+
puts "deleted #{email.id}" if @debug
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def process_line(line)
|
41
|
+
line.chomp!
|
42
|
+
case @state
|
43
|
+
when 'auth'
|
44
|
+
case line
|
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
|
86
|
+
when 'trans'
|
87
|
+
case line
|
88
|
+
when /^NOOP$/
|
89
|
+
return true, "+OK"
|
90
|
+
when /^STAT$/
|
91
|
+
msgs, bytes = stat
|
92
|
+
return true, "+OK #{msgs} #{bytes}"
|
93
|
+
when /^LIST$/
|
94
|
+
msgs, bytes = stat
|
95
|
+
msg = "+OK #{msgs} messages (#{bytes} octets)\r\n"
|
96
|
+
list.each do |num, bytes|
|
97
|
+
msg += "#{num} #{bytes}\r\n"
|
98
|
+
end
|
99
|
+
msg += "."
|
100
|
+
return true, msg
|
101
|
+
when /^LIST (\d+)$/
|
102
|
+
msgs, bytes = stat
|
103
|
+
num, bytes = list($1)
|
104
|
+
if num
|
105
|
+
return true, "+OK #{num} #{bytes}"
|
106
|
+
else
|
107
|
+
return true, "-ERR no such message, only #{msgs} messages in maildrop"
|
108
|
+
end
|
109
|
+
when /^RETR (\d+)$/
|
110
|
+
msg = retr($1)
|
111
|
+
if msg
|
112
|
+
msg = "+OK #{msg.length} octets\r\n" + msg + "\r\n."
|
113
|
+
else
|
114
|
+
msg = "-ERR no such message"
|
115
|
+
end
|
116
|
+
return true, msg
|
117
|
+
when /^DELE (\d+)$/
|
118
|
+
if dele($1)
|
119
|
+
return true, "+OK message #{$1} marked"
|
120
|
+
else
|
121
|
+
return true, "-ERR message #{$1} already marked"
|
122
|
+
end
|
123
|
+
when /^RSET$/
|
124
|
+
rset
|
125
|
+
msgs, bytes = stat
|
126
|
+
return true, "+OK maildrop has #{msgs} messages (#{bytes} octets)"
|
127
|
+
when /^QUIT$/
|
128
|
+
@state = 'update'
|
129
|
+
quit
|
130
|
+
msgs, bytes = stat
|
131
|
+
if msgs > 0
|
132
|
+
return true, "+OK dewey POP3 server signing off (#{msgs} messages left)"
|
133
|
+
else
|
134
|
+
return true, "+OK dewey POP3 server signing off (maildrop empty)"
|
135
|
+
end
|
136
|
+
when /^TOP (\d+) (\d+)$/
|
137
|
+
lines = $2
|
138
|
+
msg = retr($1)
|
139
|
+
unless msg
|
140
|
+
return true, "-ERR no such message"
|
141
|
+
end
|
142
|
+
cnt = nil
|
143
|
+
final = ""
|
144
|
+
msg.split(/\n/).each do |l|
|
145
|
+
final += l+"\n"
|
146
|
+
if cnt
|
147
|
+
cnt += 1
|
148
|
+
break if cnt > lines
|
149
|
+
end
|
150
|
+
if l !~ /\w/
|
151
|
+
cnt = 0
|
152
|
+
end
|
153
|
+
end
|
154
|
+
return true, "+OK\r\n"+final+"\r\n."
|
155
|
+
when /^UIDL$/
|
156
|
+
msgid = 0
|
157
|
+
msg = ''
|
158
|
+
@emails.each do |e|
|
159
|
+
msgid += 1
|
160
|
+
next if e.marked
|
161
|
+
msg += "#{msgid} #{Digest::MD5.new.update(msg).hexdigest}"
|
162
|
+
end
|
163
|
+
return true, "+OK\r\n#{msg}\r\n.";
|
164
|
+
end
|
165
|
+
when 'update'
|
166
|
+
case line
|
167
|
+
when /^QUIT$/
|
168
|
+
return true, "+OK dewey POP3 server signing off"
|
169
|
+
end
|
170
|
+
end
|
171
|
+
return true, "-ERR unknown command"
|
172
|
+
end
|
173
|
+
|
174
|
+
def user(username)
|
175
|
+
@user = @userstore.user_by_username(username)
|
176
|
+
end
|
177
|
+
|
178
|
+
def pass(password)
|
179
|
+
return false unless @user
|
180
|
+
return false unless @user.password == password
|
181
|
+
true
|
182
|
+
end
|
183
|
+
|
184
|
+
def emails
|
185
|
+
@emails = @emailstore.emails_by_userid(@user.id)
|
186
|
+
end
|
187
|
+
|
188
|
+
def stat
|
189
|
+
msgs = bytes = 0
|
190
|
+
@emails.each do |e|
|
191
|
+
p e
|
192
|
+
p e.body.length
|
193
|
+
next if e.marked
|
194
|
+
msgs += 1
|
195
|
+
bytes += e.body.length
|
196
|
+
end
|
197
|
+
[msgs, bytes]
|
198
|
+
end
|
199
|
+
|
200
|
+
def list(msgid = nil)
|
201
|
+
msgid = msgid.to_i if msgid
|
202
|
+
if msgid
|
203
|
+
return false if msgid > @emails.length or @emails[msgid-1].marked
|
204
|
+
return [ [msgid, @emails[msgid].body.length] ]
|
205
|
+
else
|
206
|
+
msgs = []
|
207
|
+
@emails.each_with_index do |e,i|
|
208
|
+
msgs << [ i + 1, e.body.length ]
|
209
|
+
end
|
210
|
+
msgs
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def retr(msgid)
|
215
|
+
msgid = msgid.to_i
|
216
|
+
return false if msgid > @emails.length or @emails[msgid-1].marked
|
217
|
+
@emails[msgid-1].body
|
218
|
+
end
|
219
|
+
|
220
|
+
def dele(msgid)
|
221
|
+
msgid = msgid.to_i
|
222
|
+
return false if msgid > @emails.length
|
223
|
+
@emails[msgid-1].marked = true
|
224
|
+
end
|
225
|
+
|
226
|
+
def rset
|
227
|
+
@emails.each do |e|
|
228
|
+
e.marked = false
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def quit
|
233
|
+
@emails.find_all {|e| e.marked}.each do |email|
|
234
|
+
@emailstore.delete_email(email)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def apop(username, hash)
|
239
|
+
user(username)
|
240
|
+
return false unless @user
|
241
|
+
if Digest::MD5.new.update("#{@apop_challenge}#{@user.password}").hexdigest == hash
|
242
|
+
return true
|
243
|
+
end
|
244
|
+
false
|
245
|
+
end
|
246
|
+
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|