midi-smtp-server 2.3.3

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.
@@ -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