midi-smtp-server 2.3.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,301 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A small and highly customizable ruby SMTP-Server.
4
+ module MidiSmtpServer
5
+
6
+ private
7
+
8
+ # special internal exception to signal timeout
9
+ # while waiting for incoming data line
10
+ class SmtpdIOTimeoutException < RuntimeError
11
+ end
12
+
13
+ # special internal exception to signal buffer size exceedance
14
+ # while waiting for incoming data line
15
+ class SmtpdIOBufferOverrunException < RuntimeError
16
+ end
17
+
18
+ # special internal exception to signal service stop
19
+ # without creating a fatal error message
20
+ class SmtpdStopServiceException < RuntimeError
21
+ end
22
+
23
+ # special internal exception to signal connection stop while
24
+ # server shutdown without creating a fatal error message
25
+ class SmtpdStopConnectionException < RuntimeError
26
+ end
27
+
28
+ public
29
+
30
+ # generic smtp server exception class
31
+ class SmtpdException < RuntimeError
32
+
33
+ attr_reader :smtpd_return_code
34
+ attr_reader :smtpd_return_text
35
+
36
+ def initialize(msg, smtpd_return_code, smtpd_return_text)
37
+ # save reference for smtp dialog
38
+ @smtpd_return_code = smtpd_return_code
39
+ @smtpd_return_text = smtpd_return_text
40
+ # call inherited constructor
41
+ super msg
42
+ end
43
+
44
+ def smtpd_result
45
+ return "#{@smtpd_return_code} #{@smtpd_return_text}"
46
+ end
47
+
48
+ end
49
+
50
+ # 421 <domain> Service not available, closing transmission channel
51
+ class Smtpd421Exception < SmtpdException
52
+
53
+ def initialize(msg = nil)
54
+ # call inherited constructor
55
+ super msg, 421, 'Service too busy or not available, closing transmission channel'
56
+ end
57
+
58
+ end
59
+
60
+ # 450 Requested mail action not taken: mailbox unavailable
61
+ # e.g. mailbox busy
62
+ class Smtpd450Exception < SmtpdException
63
+
64
+ def initialize(msg = nil)
65
+ # call inherited constructor
66
+ super msg, 450, 'Requested mail action not taken: mailbox unavailable'
67
+ end
68
+
69
+ end
70
+
71
+ # 451 Requested action aborted: local error in processing
72
+ class Smtpd451Exception < SmtpdException
73
+
74
+ def initialize(msg = nil)
75
+ # call inherited constructor
76
+ super msg, 451, 'Requested action aborted: local error in processing'
77
+ end
78
+
79
+ end
80
+
81
+ # 452 Requested action not taken: insufficient system storage
82
+ class Smtpd452Exception < SmtpdException
83
+
84
+ def initialize(msg = nil)
85
+ # call inherited constructor
86
+ super msg, 452, 'Requested action not taken: insufficient system storage'
87
+ end
88
+
89
+ end
90
+
91
+ # 500 Syntax error, command unrecognised or error in parameters or arguments.
92
+ # This may include errors such as command line too long
93
+ class Smtpd500Exception < SmtpdException
94
+
95
+ def initialize(msg = nil)
96
+ # call inherited constructor
97
+ super msg, 500, 'Syntax error, command unrecognised or error in parameters or arguments'
98
+ end
99
+
100
+ end
101
+
102
+ # 501 Syntax error in parameters or arguments
103
+ class Smtpd501Exception < SmtpdException
104
+
105
+ def initialize(msg = nil)
106
+ # call inherited constructor
107
+ super msg, 501, 'Syntax error in parameters or arguments'
108
+ end
109
+
110
+ end
111
+
112
+ # 502 Command not implemented
113
+ class Smtpd502Exception < SmtpdException
114
+
115
+ def initialize(msg = nil)
116
+ # call inherited constructor
117
+ super msg, 502, 'Command not implemented'
118
+ end
119
+
120
+ end
121
+
122
+ # 503 Bad sequence of commands
123
+ class Smtpd503Exception < SmtpdException
124
+
125
+ def initialize(msg = nil)
126
+ # call inherited constructor
127
+ super msg, 503, 'Bad sequence of commands'
128
+ end
129
+
130
+ end
131
+
132
+ # 504 Command parameter not implemented
133
+ class Smtpd504Exception < SmtpdException
134
+
135
+ def initialize(msg = nil)
136
+ # call inherited constructor
137
+ super msg, 504, 'Command parameter not implemented'
138
+ end
139
+
140
+ end
141
+
142
+ # 521 <domain> does not accept mail [rfc1846]
143
+ class Smtpd521Exception < SmtpdException
144
+
145
+ def initialize(msg = nil)
146
+ # call inherited constructor
147
+ super msg, 521, 'Service does not accept mail'
148
+ end
149
+
150
+ end
151
+
152
+ # 550 Requested action not taken: mailbox unavailable
153
+ # e.g. mailbox not found, no access
154
+ class Smtpd550Exception < SmtpdException
155
+
156
+ def initialize(msg = nil)
157
+ # call inherited constructor
158
+ super msg, 550, 'Requested action not taken: mailbox unavailable'
159
+ end
160
+
161
+ end
162
+
163
+ # 552 Requested mail action aborted: exceeded storage allocation
164
+ class Smtpd552Exception < SmtpdException
165
+
166
+ def initialize(msg = nil)
167
+ # call inherited constructor
168
+ super msg, 552, 'Requested mail action aborted: exceeded storage allocation'
169
+ end
170
+
171
+ end
172
+
173
+ # 553 Requested action not taken: mailbox name not allowed
174
+ class Smtpd553Exception < SmtpdException
175
+
176
+ def initialize(msg = nil)
177
+ # call inherited constructor
178
+ super msg, 553, 'Requested action not taken: mailbox name not allowed'
179
+ end
180
+
181
+ end
182
+
183
+ # 554 Transaction failed
184
+ class Smtpd554Exception < SmtpdException
185
+
186
+ def initialize(msg = nil)
187
+ # call inherited constructor
188
+ super msg, 554, 'Transaction failed'
189
+ end
190
+
191
+ end
192
+
193
+ # Status when using authentication
194
+
195
+ # 432 Password transition is needed
196
+ class Smtpd432Exception < SmtpdException
197
+
198
+ def initialize(msg = nil)
199
+ # call inherited constructor
200
+ super msg, 432, 'Password transition is needed'
201
+ end
202
+
203
+ end
204
+
205
+ # 454 Temporary authentication failure
206
+ class Smtpd454Exception < SmtpdException
207
+
208
+ def initialize(msg = nil)
209
+ # call inherited constructor
210
+ super msg, 454, 'Temporary authentication failure'
211
+ end
212
+
213
+ end
214
+
215
+ # 530 Authentication required
216
+ class Smtpd530Exception < SmtpdException
217
+
218
+ def initialize(msg = nil)
219
+ # call inherited constructor
220
+ super msg, 530, 'Authentication required'
221
+ end
222
+
223
+ end
224
+
225
+ # 534 Authentication mechanism is too weak
226
+ class Smtpd534Exception < SmtpdException
227
+
228
+ def initialize(msg = nil)
229
+ # call inherited constructor
230
+ super msg, 534, 'Authentication mechanism is too weak'
231
+ end
232
+
233
+ end
234
+
235
+ # 535 Authentication credentials invalid
236
+ class Smtpd535Exception < SmtpdException
237
+
238
+ def initialize(msg = nil)
239
+ # call inherited constructor
240
+ super msg, 535, 'Authentication credentials invalid'
241
+ end
242
+
243
+ end
244
+
245
+ # 538 Encryption required for requested authentication mechanism
246
+ class Smtpd538Exception < SmtpdException
247
+
248
+ def initialize(msg = nil)
249
+ # call inherited constructor
250
+ super msg, 538, 'Encryption required for requested authentication mechanism'
251
+ end
252
+
253
+ end
254
+
255
+ # Status when using encryption
256
+
257
+ # 454 TLS not available
258
+ class Tls454Exception < SmtpdException
259
+
260
+ def initialize(msg = nil)
261
+ # call inherited constructor
262
+ super msg, 454, 'TLS not available'
263
+ end
264
+
265
+ end
266
+
267
+ # 530 Encryption required
268
+ class Tls530Exception < SmtpdException
269
+
270
+ def initialize(msg = nil)
271
+ # call inherited constructor
272
+ super msg, 530, 'Encryption required, must issue STARTTLS command first'
273
+ end
274
+
275
+ end
276
+
277
+ # Status when disabled PIPELINING
278
+
279
+ # 500 Bad input, no PIPELINING
280
+ class Smtpd500PipeliningException < SmtpdException
281
+
282
+ def initialize(msg = nil)
283
+ # call inherited constructor
284
+ super msg, 500, 'Bad input, PIPELINING is not allowed'
285
+ end
286
+
287
+ end
288
+
289
+ # Status when expeting CRLF sequence as line breaks (RFC(2)822)
290
+
291
+ # 500 Bad input, missing CRLF line termination
292
+ class Smtpd500CrLfSequenceException < SmtpdException
293
+
294
+ def initialize(msg = nil)
295
+ # call inherited constructor
296
+ super msg, 500, 'Bad input, Lines must be terminated by CRLF sequence'
297
+ end
298
+
299
+ end
300
+
301
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'resolv'
4
+
5
+ # A small and highly customizable ruby SMTP-Server.
6
+ module MidiSmtpServer
7
+
8
+ # Encryption modes
9
+ ENCRYPT_MODES = [:TLS_FORBIDDEN, :TLS_OPTIONAL, :TLS_REQUIRED].freeze
10
+ DEFAULT_ENCRYPT_MODE = :TLS_FORBIDDEN
11
+
12
+ # Encryption ciphers and methods
13
+ # check https://www.owasp.org/index.php/TLS_Cipher_String_Cheat_Sheet
14
+ TLS_CIPHERS_ADVANCED_PLUS = 'DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256'
15
+ TLS_CIPHERS_ADVANCED = (TLS_CIPHERS_ADVANCED_PLUS + ':DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256')
16
+ TLS_CIPHERS_BROAD_COMP = (TLS_CIPHERS_ADVANCED + ':ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA')
17
+ TLS_CIPHERS_WIDEST_COMP = (TLS_CIPHERS_ADVANCED + ':ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA')
18
+ TLS_CIPHERS_LEGACY = (TLS_CIPHERS_ADVANCED + ':ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA')
19
+ TLS_METHODS_ADVANCED = 'TLSv1_2'
20
+ TLS_METHODS_LEGACY = 'TLSv1_1'
21
+
22
+ # class for TlsTransport
23
+ class TlsTransport
24
+
25
+ def initialize(cert_path, key_path, ciphers, methods, cert_cn, cert_san, logger)
26
+ # if need to debug something while working with openssl
27
+ # OpenSSL::debug = true
28
+
29
+ # save references
30
+ @logger = logger
31
+ @cert_path = cert_path.to_s == '' ? nil : cert_path.strip
32
+ @key_path = key_path.to_s == '' ? nil : key_path.strip
33
+ # create SSL context
34
+ @ctx = OpenSSL::SSL::SSLContext.new
35
+ @ctx.ciphers = ciphers.to_s == '' ? TLS_CIPHERS_ADVANCED_PLUS : ciphers
36
+ @ctx.ssl_version = methods.to_s == '' ? TLS_METHODS_ADVANCED : methods
37
+ # check cert_path and key_path
38
+ if !@cert_path.nil? || !@key_path.nil?
39
+ # if any is set, test the pathes
40
+ raise "File \”#{@cert_path}\" does not exist or is not a regular file. Could not load certificate." unless File.file?(@cert_path.to_s)
41
+ raise "File \”#{@key_path}\" does not exist or is not a regular file. Could not load private key." unless File.file?(@key_path.to_s)
42
+ # try to load certificate and key
43
+ @ctx.cert = OpenSSL::X509::Certificate.new(File.open(@cert_path.to_s))
44
+ @ctx.key = OpenSSL::PKey::RSA.new(File.open(@key_path.to_s))
45
+ else
46
+ # if none cert_path was set, create a self signed test certificate
47
+ # and try to setup common subject and subject alt name(s) for cert
48
+ @cert_cn = cert_cn.to_s.strip
49
+ @cert_san = ([@cert_cn] + (cert_san.nil? ? [] : cert_san)).uniq
50
+ # as well as IP Address extension entries for subject alt name(s) if ipv4 or ipv6 address
51
+ @cert_san_ip = []
52
+ @cert_san.each { |san| @cert_san_ip << san if san =~ Resolv::IPv4::Regex || san =~ Resolv::IPv6::Regex }
53
+ # initialize self certificate and key
54
+ logger.debug("SSL: using self generated test certificate! CN=#{@cert_cn} SAN=[#{@cert_san.join(',')}]")
55
+ @ctx.key = OpenSSL::PKey::RSA.new 4096
56
+ @ctx.cert = OpenSSL::X509::Certificate.new
57
+ @ctx.cert.version = 2
58
+ @ctx.cert.serial = 1
59
+ # the subject and the issuer are identical only for test certificate
60
+ @ctx.cert.subject = OpenSSL::X509::Name.new [['CN', @cert_cn]]
61
+ @ctx.cert.issuer = @ctx.cert.subject
62
+ @ctx.cert.public_key = @ctx.key
63
+ # valid for 90 days
64
+ @ctx.cert.not_before = Time.now
65
+ @ctx.cert.not_after = Time.now + 60 * 60 * 24 * 90
66
+ # setup some cert extensions
67
+ @ef = OpenSSL::X509::ExtensionFactory.new
68
+ @ef.subject_certificate = @ctx.cert
69
+ @ef.issuer_certificate = @ctx.cert
70
+ @ctx.cert.add_extension(@ef.create_extension('basicConstraints', 'CA:FALSE', false))
71
+ @ctx.cert.add_extension(@ef.create_extension('keyUsage', 'digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment', false))
72
+ @ctx.cert.add_extension(@ef.create_extension('subjectAltName', (@cert_san.map { |san| "DNS:#{san}" } + @cert_san_ip.map { |ip| "IP:#{ip}" }).join(', '), false))
73
+ @ctx.cert.sign @ctx.key, OpenSSL::Digest::SHA256.new
74
+ logger.debug("SSL: generated test certificate\r\n#{@ctx.cert.to_text}")
75
+ end
76
+ end
77
+
78
+ # start ssl connection over existing tcpserver socket
79
+ def start(io)
80
+ # start SSL negotiation
81
+ ssl = OpenSSL::SSL::SSLSocket.new(io, @ctx)
82
+ # connect to server socket
83
+ ssl.accept
84
+ # make sure to close also the underlying io
85
+ ssl.sync_close = true
86
+ # return as new io socket
87
+ return ssl
88
+ end
89
+
90
+ end
91
+
92
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A small and highly customizable ruby SMTP-Server.
4
+ module MidiSmtpServer
5
+
6
+ module VERSION
7
+
8
+ MAJOR = 2
9
+ MINOR = 3
10
+ TINY = 3
11
+
12
+ STRING = [MAJOR, MINOR, TINY].compact.join('.')
13
+
14
+ DATE = '2022-02-12'
15
+
16
+ end
17
+
18
+ end