minbox 0.1.0 → 0.1.1
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/Dockerfile +10 -0
- data/Gemfile.lock +7 -1
- data/bin/docker-build +14 -0
- data/lib/minbox.rb +4 -0
- data/lib/minbox/cli.rb +4 -20
- data/lib/minbox/client.rb +135 -0
- data/lib/minbox/core.rb +9 -0
- data/lib/minbox/publisher.rb +21 -3
- data/lib/minbox/server.rb +17 -38
- data/lib/minbox/version.rb +1 -1
- data/minbox.gemspec +1 -0
- metadata +20 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2c500238d8a8cfc853fd4ec9a252a7a0cde7f421e1bd31a4695c312288110e23
|
4
|
+
data.tar.gz: 80b489dd81313d68e7e8e8e479060d22dd105b3b0570357eb489340d145ab0d4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7bd692169477f0e2fe78d1611ac32bdc83cdd9980a8feacf15c6574cd6d471ebe98503e6d049fba23ef991db8d5eaafc960bad31ec38d50e8c47e02efaf3b3a7
|
7
|
+
data.tar.gz: 601d89064f6f2189b097615815a6b46f13d4a66989c4138815a9ea5bfb8e56c449aa829d6df10e8ddf065d737dba0520d07b98ddbe9b9f9ac1ea088a79ebe1ce
|
data/Dockerfile
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
FROM ruby:2.6-alpine
|
2
|
+
ENV PACKAGES build-base tzdata
|
3
|
+
RUN apk update && \
|
4
|
+
apk upgrade && \
|
5
|
+
apk add $PACKAGES && \
|
6
|
+
rm -fr /var/cache/apk/* && \
|
7
|
+
apk del build-base
|
8
|
+
WORKDIR /opt/minbox
|
9
|
+
RUN gem install minbox
|
10
|
+
CMD ["minbox", "server", "localhost", "25", "--output=stdout file"]
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
minbox (0.1.
|
4
|
+
minbox (0.1.1)
|
5
5
|
mail (~> 2.7)
|
6
6
|
redis (~> 4.1)
|
7
7
|
thor (~> 0.20)
|
@@ -9,7 +9,12 @@ PATH
|
|
9
9
|
GEM
|
10
10
|
remote: https://rubygems.org/
|
11
11
|
specs:
|
12
|
+
concurrent-ruby (1.1.4)
|
12
13
|
diff-lcs (1.3)
|
14
|
+
faker (1.9.3)
|
15
|
+
i18n (>= 0.7)
|
16
|
+
i18n (1.6.0)
|
17
|
+
concurrent-ruby (~> 1.0)
|
13
18
|
mail (2.7.1)
|
14
19
|
mini_mime (>= 0.1.1)
|
15
20
|
mini_mime (1.0.1)
|
@@ -35,6 +40,7 @@ PLATFORMS
|
|
35
40
|
|
36
41
|
DEPENDENCIES
|
37
42
|
bundler (~> 2.0)
|
43
|
+
faker (~> 1.9)
|
38
44
|
minbox!
|
39
45
|
rake (~> 10.0)
|
40
46
|
rspec (~> 3.0)
|
data/bin/docker-build
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
|
3
|
+
cp Dockerfile pkg/
|
4
|
+
docker pull ruby:2.6-alpine
|
5
|
+
docker image build \
|
6
|
+
-t "mokhan/minbox:latest" \
|
7
|
+
-f pkg/Dockerfile pkg/
|
8
|
+
|
9
|
+
if [[ -z "${DOCKER_PUSH}" ]]; then
|
10
|
+
echo 'skip push'
|
11
|
+
else
|
12
|
+
echo 'pushing...'
|
13
|
+
docker push "mokhan/minbox:latest"
|
14
|
+
fi
|
data/lib/minbox.rb
CHANGED
data/lib/minbox/cli.rb
CHANGED
@@ -14,18 +14,19 @@ module Minbox
|
|
14
14
|
from 'Your Name <me@example.org>'
|
15
15
|
to 'Destination Address <them@example.com>'
|
16
16
|
subject 'test message'
|
17
|
-
body
|
17
|
+
body "#{Time.now} This is a test message."
|
18
18
|
end
|
19
19
|
require 'net/smtp'
|
20
20
|
Net::SMTP.start(host, port) do |smtp|
|
21
|
-
smtp.send_message(mail.to_s, 'me@example.org', 'them@example.com')
|
21
|
+
smtp.send_message(mail.to_s, 'me+1@example.org', 'them+1@example.com')
|
22
|
+
smtp.send_message(mail.to_s, 'me+2@example.org', 'them+2@example.com')
|
22
23
|
end
|
23
24
|
end
|
24
25
|
|
25
26
|
method_option :output, type: :array, default: ['stdout']
|
26
27
|
desc 'server <HOST> <PORT>', 'SMTP server'
|
27
28
|
def server(host = 'localhost', port = '25')
|
28
|
-
publisher =
|
29
|
+
publisher = Publisher.from(options[:output])
|
29
30
|
Server.new(host, port).listen! do |mail|
|
30
31
|
publisher.publish(mail)
|
31
32
|
end
|
@@ -35,23 +36,6 @@ module Minbox
|
|
35
36
|
def version
|
36
37
|
say Minbox::VERSION
|
37
38
|
end
|
38
|
-
|
39
|
-
private
|
40
|
-
|
41
|
-
def publishers_for(output)
|
42
|
-
publisher = Publisher.new
|
43
|
-
output.each do |x|
|
44
|
-
case x
|
45
|
-
when 'stdout'
|
46
|
-
publisher.add(LogPublisher.new)
|
47
|
-
when 'redis'
|
48
|
-
publisher.add(RedisPublisher.new)
|
49
|
-
when 'file'
|
50
|
-
publisher.add(FilePublisher.new)
|
51
|
-
end
|
52
|
-
end
|
53
|
-
publisher
|
54
|
-
end
|
55
39
|
end
|
56
40
|
end
|
57
41
|
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
module Minbox
|
2
|
+
class Client
|
3
|
+
attr_reader :host, :socket, :logger
|
4
|
+
|
5
|
+
def initialize(host, socket, logger)
|
6
|
+
@host = host
|
7
|
+
@logger = logger
|
8
|
+
@socket = socket
|
9
|
+
end
|
10
|
+
|
11
|
+
def handle(&block)
|
12
|
+
write "220"
|
13
|
+
while connected? && (line = read)
|
14
|
+
case line
|
15
|
+
when /^EHLO/i then ehlo(line)
|
16
|
+
when /^HELO/i then helo(line)
|
17
|
+
when /^MAIL FROM/i then mail_from(line)
|
18
|
+
when /^RCPT TO/i then rcpt_to(line)
|
19
|
+
when /^DATA/i then data(line, &block)
|
20
|
+
when /^QUIT/i then quit
|
21
|
+
when /^STARTTLS/i then start_tls
|
22
|
+
when /^RSET/i then reset
|
23
|
+
when /^NOOP/i then noop
|
24
|
+
when /^AUTH PLAIN/i then auth_plain(line)
|
25
|
+
when /^AUTH LOGIN/i then auth_login(line)
|
26
|
+
else
|
27
|
+
logger.error(line)
|
28
|
+
write '502 Invalid/unsupported command'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def quit
|
36
|
+
write "221 Bye"
|
37
|
+
close
|
38
|
+
end
|
39
|
+
|
40
|
+
def data(line, &block)
|
41
|
+
write "354 End data with <CR><LF>.<CR><LF>"
|
42
|
+
body = []
|
43
|
+
line = read
|
44
|
+
until line.nil? || line.match(/^\.\r\n$/)
|
45
|
+
body << line
|
46
|
+
line = read
|
47
|
+
end
|
48
|
+
write "250 OK"
|
49
|
+
block.call(Mail.new(body.join))
|
50
|
+
end
|
51
|
+
|
52
|
+
def rcpt_to(line)
|
53
|
+
write "250 OK"
|
54
|
+
end
|
55
|
+
|
56
|
+
def mail_from(line)
|
57
|
+
write "250 OK"
|
58
|
+
end
|
59
|
+
|
60
|
+
def ehlo(line)
|
61
|
+
_ehlo, _client_domain = line.split(" ")
|
62
|
+
write "250-#{host}"
|
63
|
+
#write "250 AUTH PLAIN LOGIN"
|
64
|
+
write "250-ENHANCEDSTATUSCODES"
|
65
|
+
write "250 OK"
|
66
|
+
end
|
67
|
+
|
68
|
+
def helo(line)
|
69
|
+
_ehlo, _client_domain = line.split(" ")
|
70
|
+
write "250 #{host}"
|
71
|
+
end
|
72
|
+
|
73
|
+
def start_tls
|
74
|
+
write "502 TLS not available"
|
75
|
+
end
|
76
|
+
|
77
|
+
def reset
|
78
|
+
write '250 OK'
|
79
|
+
end
|
80
|
+
|
81
|
+
def noop
|
82
|
+
write '250 OK'
|
83
|
+
end
|
84
|
+
|
85
|
+
def auth_plain(line)
|
86
|
+
data = line.gsub(/AUTH PLAIN ?/i, '')
|
87
|
+
if data.strip == ''
|
88
|
+
write '334'
|
89
|
+
data = read
|
90
|
+
end
|
91
|
+
parts = Base64.decode64(data).split("\0")
|
92
|
+
username, password = parts[-2], parts[-1]
|
93
|
+
logger.debug("#{username}:#{password}")
|
94
|
+
return write '535 Authenticated failed - protocol error' unless username && password
|
95
|
+
write "235 2.7.0 Authentication successful"
|
96
|
+
end
|
97
|
+
|
98
|
+
def auth_login(line)
|
99
|
+
username = line.gsub!(/AUTH LOGIN ?/i, '')
|
100
|
+
if username.strip == ''
|
101
|
+
write '334 VXNlcm5hbWU6'
|
102
|
+
username = read
|
103
|
+
write '334 UGFzc3dvcmQ6'
|
104
|
+
else
|
105
|
+
write '334 UGFzc3dvcmQ6'
|
106
|
+
end
|
107
|
+
password = Base64.decode64(read)
|
108
|
+
logger.debug("#{username}:#{password}")
|
109
|
+
|
110
|
+
return write '535 Authenticated failed - protocol error' unless username && password
|
111
|
+
write "235 2.7.0 Authentication successful"
|
112
|
+
end
|
113
|
+
|
114
|
+
def write(message)
|
115
|
+
message = "#{message}\r\n"
|
116
|
+
logger.debug("S: #{message.inspect}")
|
117
|
+
socket.puts message
|
118
|
+
end
|
119
|
+
|
120
|
+
def read
|
121
|
+
line = socket.gets
|
122
|
+
logger.debug("C: #{line.inspect}")
|
123
|
+
line
|
124
|
+
end
|
125
|
+
|
126
|
+
def close
|
127
|
+
socket&.close
|
128
|
+
@socket = nil
|
129
|
+
end
|
130
|
+
|
131
|
+
def connected?
|
132
|
+
@socket
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
data/lib/minbox/core.rb
ADDED
data/lib/minbox/publisher.rb
CHANGED
@@ -13,17 +13,35 @@ module Minbox
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def publish(mail)
|
16
|
-
|
16
|
+
Thread.new do
|
17
|
+
Minbox.logger.debug("Publishing: #{mail.message_id}")
|
18
|
+
publishers.each { |x| x.publish(mail) }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.from(outputs)
|
23
|
+
publisher = Publisher.new
|
24
|
+
outputs.each do |x|
|
25
|
+
case x
|
26
|
+
when 'stdout'
|
27
|
+
publisher.add(LogPublisher.new)
|
28
|
+
when 'redis'
|
29
|
+
publisher.add(RedisPublisher.new)
|
30
|
+
when 'file'
|
31
|
+
publisher.add(FilePublisher.new)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
publisher
|
17
35
|
end
|
18
36
|
end
|
19
37
|
|
20
38
|
class LogPublisher
|
21
|
-
def initialize(logger =
|
39
|
+
def initialize(logger = Minbox.logger)
|
22
40
|
@logger = logger
|
23
41
|
end
|
24
42
|
|
25
43
|
def publish(mail)
|
26
|
-
@logger.
|
44
|
+
@logger.debug(mail.to_s)
|
27
45
|
end
|
28
46
|
end
|
29
47
|
|
data/lib/minbox/server.rb
CHANGED
@@ -1,53 +1,32 @@
|
|
1
1
|
module Minbox
|
2
2
|
class Server
|
3
|
-
attr_reader :host, :port
|
3
|
+
attr_reader :host, :port, :logger
|
4
4
|
|
5
|
-
def initialize(host, port)
|
5
|
+
def initialize(host = 'localhost', port = 25, logger = Minbox.logger)
|
6
6
|
@host = host
|
7
7
|
@port = port
|
8
|
+
@logger = logger
|
8
9
|
end
|
9
10
|
|
10
|
-
def listen!
|
11
|
-
server
|
11
|
+
def listen!(&block)
|
12
|
+
logger.debug("Starting server on port #{port}...")
|
13
|
+
@server = TCPServer.new(port.to_i)
|
14
|
+
logger.debug("Server started!")
|
15
|
+
|
12
16
|
loop do
|
13
|
-
|
17
|
+
handle(@server.accept, &block)
|
18
|
+
rescue StandardError => error
|
19
|
+
logger.error(error)
|
14
20
|
end
|
15
21
|
end
|
16
22
|
|
17
|
-
def handle(
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
ehlo, _client_domain = client.gets.split(" ")
|
22
|
-
|
23
|
-
if ["HELO", "EHLO"].include?(ehlo)
|
24
|
-
client.puts "250-#{host}"
|
25
|
-
client.puts "250 OK"
|
26
|
-
else
|
27
|
-
puts 'Ooops...'
|
28
|
-
client.close
|
29
|
-
return
|
30
|
-
end
|
31
|
-
|
32
|
-
data = client.gets
|
33
|
-
until data.start_with?("DATA")
|
34
|
-
mail[:headers] << data
|
35
|
-
client.puts "250 OK"
|
36
|
-
data = client.gets
|
37
|
-
end
|
38
|
-
client.puts "354 End data with <CR><LF>.<CR><LF>"
|
39
|
-
|
40
|
-
data = client.gets
|
41
|
-
until data.match(/^\.\r\n$/)
|
42
|
-
mail[:body] << data
|
43
|
-
data = client.gets
|
44
|
-
end
|
45
|
-
|
46
|
-
client.puts "250 OK"
|
47
|
-
client.puts "221 Bye"
|
48
|
-
client.close
|
23
|
+
def handle(socket, &block)
|
24
|
+
logger.debug("client connected: #{socket.inspect}")
|
25
|
+
Client.new(host, socket, logger).handle(&block)
|
26
|
+
end
|
49
27
|
|
50
|
-
|
28
|
+
def shutdown!
|
29
|
+
@server&.close
|
51
30
|
end
|
52
31
|
end
|
53
32
|
end
|
data/lib/minbox/version.rb
CHANGED
data/minbox.gemspec
CHANGED
@@ -38,6 +38,7 @@ Gem::Specification.new do |spec|
|
|
38
38
|
spec.add_dependency "redis", "~> 4.1"
|
39
39
|
spec.add_dependency "thor", "~> 0.20"
|
40
40
|
spec.add_development_dependency "bundler", "~> 2.0"
|
41
|
+
spec.add_development_dependency "faker", "~> 1.9"
|
41
42
|
spec.add_development_dependency "rake", "~> 10.0"
|
42
43
|
spec.add_development_dependency "rspec", "~> 3.0"
|
43
44
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: minbox
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- mo khan
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-03-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mail
|
@@ -66,6 +66,20 @@ dependencies:
|
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '2.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: faker
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1.9'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.9'
|
69
83
|
- !ruby/object:Gem::Dependency
|
70
84
|
name: rake
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -105,16 +119,20 @@ files:
|
|
105
119
|
- ".gitignore"
|
106
120
|
- ".rspec"
|
107
121
|
- ".travis.yml"
|
122
|
+
- Dockerfile
|
108
123
|
- Gemfile
|
109
124
|
- Gemfile.lock
|
110
125
|
- LICENSE.txt
|
111
126
|
- README.md
|
112
127
|
- Rakefile
|
113
128
|
- bin/console
|
129
|
+
- bin/docker-build
|
114
130
|
- bin/setup
|
115
131
|
- exe/minbox
|
116
132
|
- lib/minbox.rb
|
117
133
|
- lib/minbox/cli.rb
|
134
|
+
- lib/minbox/client.rb
|
135
|
+
- lib/minbox/core.rb
|
118
136
|
- lib/minbox/publisher.rb
|
119
137
|
- lib/minbox/server.rb
|
120
138
|
- lib/minbox/version.rb
|