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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 34bfc6208f399c85b149fb9febe81a997940696a0802d7326aa91aa55261e8d5
4
- data.tar.gz: 77d432c13608ac84e4c9dcee94c018569d52ab9bdf486b08d28f626dd3def4ab
3
+ metadata.gz: 2c500238d8a8cfc853fd4ec9a252a7a0cde7f421e1bd31a4695c312288110e23
4
+ data.tar.gz: 80b489dd81313d68e7e8e8e479060d22dd105b3b0570357eb489340d145ab0d4
5
5
  SHA512:
6
- metadata.gz: fb60ac240af6400731e5b4031ef5d86b73739761ccc82c650a0b931ed3e4e5562fe24ed97b1d5765d01ecae9c3334622dba72b1bed3aa235179bd47ba2a8d9bc
7
- data.tar.gz: 2d5c4b97db3e4b5d1d0621bc0a4af5eb461bd44ba1165362335cf9609bb173301c5498d860e94933be95e611bc7a351d1682f6fa5ac25455bf5348d4d756e5cb
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.0)
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
@@ -1,6 +1,10 @@
1
+ require 'base64'
2
+ require 'logger'
1
3
  require 'socket'
2
4
 
5
+ require "minbox/core"
3
6
  require "minbox/publisher"
7
+ require "minbox/client"
4
8
  require "minbox/server"
5
9
  require "minbox/version"
6
10
 
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 'This is a test message.'
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 = publishers_for(options[:output])
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
@@ -0,0 +1,9 @@
1
+ module Minbox
2
+ def self.logger
3
+ @logger ||= Logger.new(STDOUT)
4
+ end
5
+
6
+ def self.logger=(logger)
7
+ @logger = logger
8
+ end
9
+ end
@@ -13,17 +13,35 @@ module Minbox
13
13
  end
14
14
 
15
15
  def publish(mail)
16
- publishers.each { |x| x.publish(mail) }
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 = STDOUT)
39
+ def initialize(logger = Minbox.logger)
22
40
  @logger = logger
23
41
  end
24
42
 
25
43
  def publish(mail)
26
- @logger.puts mail.to_s
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 = TCPServer.new(port.to_i)
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
- yield handle(server.accept)
17
+ handle(@server.accept, &block)
18
+ rescue StandardError => error
19
+ logger.error(error)
14
20
  end
15
21
  end
16
22
 
17
- def handle(client)
18
- mail = { headers: [], body: [] }
19
-
20
- client.puts "220"
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
- Mail.new(mail[:body].join)
28
+ def shutdown!
29
+ @server&.close
51
30
  end
52
31
  end
53
32
  end
@@ -1,3 +1,3 @@
1
1
  module Minbox
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
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.0
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-02-27 00:00:00.000000000 Z
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