midi-smtp-server 3.1.2 → 3.3.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: 0c84077c17fd5b68cffd37292da0e473245f16662041fb9a17218cd897961730
4
- data.tar.gz: 299e676ad777d512b53d6c81a2a3b696eca00fb2d11adcaf2a3f24b6075c5fe1
3
+ metadata.gz: c8ae466dfca7a6bde0fcc780096da505a675ac4d703641097aa9980423c4976d
4
+ data.tar.gz: 92be87d1e1f857ba910a53f357519dc9a97edf0c89747d875c394f270d587d0b
5
5
  SHA512:
6
- metadata.gz: 68446f6bb248f579689091fa094752f95db702ca6fc9f6de5588b68c1c564ee45499d71acee7b674b8784b78403a275e15afb5249e867571542e96c93ae608bd
7
- data.tar.gz: 65c78a6d3ec2241756befc2a80a03691e74b37eed18093c0796ebd5a420f522b44f514e77b904f1a2ab7a97fb5862daaf7da2bcc3a5c2ba20000079111c3e994
6
+ metadata.gz: 6b57ce0eb75d4c6cdfde8e417e0a93214834c811937dbe093aed941285dc577c4d96e65ee4dfa2639db8349199e1de70d19de14609d423e914cee16a6ee36868
7
+ data.tar.gz: 9aee1d4d53f0b3878022a44ed267a3b9f3b9e570cf3eb96fd2ace0e598c8cb6fc7eb6ca1446948be01e3fa4d12170c9b3d91aade46d826c307e92c3ae6e2c479
data/CHANGELOG.md CHANGED
@@ -5,6 +5,21 @@ We suggest everybody using MidiSmtpServer to switch at least to latest 2.3.y. or
5
5
  For upgrades from previous versions or outdated _MiniSmtpServer_ gem you may follow the guides at [Appendix Upgrade](https://midi-smtp-server.readthedocs.io/appendix_upgrade/) to get your code ready for the latest releases.
6
6
 
7
7
 
8
+ #### 3.3.1 (2026-09-16)
9
+
10
+ 1. Updates and fixes for Ruby versions 3.3, 3.4 and 4.0
11
+ 2. New feature and optimization for [IO wait](https://midi-smtp-server.readthedocs.io/feature_load_balancing) by event mode
12
+ 3. Deprecate `io_waitreadable_sleep` in favor of `io_wait_mode` and `io_wait_available`
13
+ 4. Test fixes for newer Ruby (3.3+) and OS-specific socket behaviour
14
+ 5. mkdocs update for readthedocs
15
+
16
+
17
+ #### 3.2.1 (2023-08-15)
18
+
19
+ 1. New feature [proxy](https://midi-smtp-server.readthedocs.io/feature_proxy) ([check issue 49](https://github.com/4commerce-technologies-AG/midi-smtp-server/issues/49))
20
+ 2. mkdocs update for readthedocs
21
+
22
+
8
23
  #### 3.1.2 (2023-05-06)
9
24
 
10
25
  1. Minor fix for backwards compatibility to method `start` when not using pre_fork options.
data/README.md CHANGED
@@ -76,7 +76,11 @@ This source code shows the example to receive messages via SMTP and store them t
76
76
 
77
77
  ## Installation
78
78
 
79
- MidiSmtpServer is packaged as a RubyGem so that you can easily install by entering following at your command line:
79
+ MidiSmtpServer is packaged as a RubyGem and hosted on rubygems.
80
+
81
+ #### CLI
82
+
83
+ You can easily install the package by entering following at your command line:
80
84
 
81
85
  `gem install midi-smtp-server`
82
86
 
@@ -84,6 +88,14 @@ Use the component in your project sources by:
84
88
 
85
89
  `require 'midi-smtp-server'`
86
90
 
91
+ #### Gemfile
92
+
93
+ When a `Gemfile` handles your dependencies, please consider to use the [pessimistic operator](https://thoughtbot.com/blog/rubys-pessimistic-operator) at least:
94
+
95
+ `gem 'midi-smtp-server', '~> 3.1.2''`
96
+
97
+ All changes by PATCH versions are always functional and compatible with no issues on update!
98
+
87
99
  <br>
88
100
 
89
101
 
@@ -147,9 +159,13 @@ We suggest everybody using MidiSmtpServer to switch at least to latest 2.3.y. or
147
159
  For upgrades from previous versions or outdated _MiniSmtpServer_ gem you may follow the guides (see appendix) how to change your existing code to be compatible with the latest releases.
148
160
 
149
161
 
150
- #### Latest release: 3.1.2 (2023-05-06)
162
+ #### Latest release: 3.3.1 (2026-09-16)
151
163
 
152
- 1. Minor fix for backwards compatibility to method `start` when not using pre_fork options.
164
+ 1. Updates and fixes for Ruby versions 3.3, 3.4 and 4.0
165
+ 2. New feature and optimization for [IO wait](https://midi-smtp-server.readthedocs.io/feature_load_balancing) by event mode
166
+ 3. Deprecate `io_waitreadable_sleep` in favor of `io_wait_mode` and `io_wait_available`
167
+ 4. Test fixes for newer Ruby (3.3+) and OS-specific socket behaviour
168
+ 5. mkdocs update for readthedocs
153
169
 
154
170
 
155
171
  #### Changelog history
@@ -192,4 +208,4 @@ Author: [Tom Freudenberg](http://about.me/tom.freudenberg)
192
208
 
193
209
  [MidiSmtpServer Class](https://github.com/4commerce-technologies-AG/midi-smtp-server/) is inspired from [MiniSmtpServer Class](https://github.com/aarongough/mini-smtp-server) and code written by [Aaron Gough](https://github.com/aarongough) and [Peter Cooper](https://github.com/4commerce-technologies-AG/midi-smtp-server#author--credits)
194
210
 
195
- Copyright (c) 2014-2023 [Tom Freudenberg](https://github.com/TomFreudenberg), [4commerce technologies AG](https://www.4commerce.de/), released under the MIT license
211
+ Copyright (c) 2014-2026 [Tom Freudenberg](https://github.com/TomFreudenberg), [4commerce technologies AG](https://www.4commerce.de/), released under the MIT license
@@ -38,7 +38,7 @@ module MidiSmtpServer
38
38
  @smtpd_return_code = smtpd_return_code
39
39
  @smtpd_return_text = smtpd_return_text
40
40
  # call inherited constructor
41
- super msg
41
+ super(msg)
42
42
  end
43
43
 
44
44
  def smtpd_result
@@ -52,7 +52,7 @@ module MidiSmtpServer
52
52
 
53
53
  def initialize(msg = nil)
54
54
  # call inherited constructor
55
- super msg, 421, 'Service too busy or not available, closing transmission channel'
55
+ super(msg, 421, 'Service too busy or not available, closing transmission channel')
56
56
  end
57
57
 
58
58
  end
@@ -63,7 +63,7 @@ module MidiSmtpServer
63
63
 
64
64
  def initialize(msg = nil)
65
65
  # call inherited constructor
66
- super msg, 450, 'Requested mail action not taken: mailbox unavailable'
66
+ super(msg, 450, 'Requested mail action not taken: mailbox unavailable')
67
67
  end
68
68
 
69
69
  end
@@ -73,7 +73,7 @@ module MidiSmtpServer
73
73
 
74
74
  def initialize(msg = nil)
75
75
  # call inherited constructor
76
- super msg, 451, 'Requested action aborted: local error in processing'
76
+ super(msg, 451, 'Requested action aborted: local error in processing')
77
77
  end
78
78
 
79
79
  end
@@ -83,7 +83,7 @@ module MidiSmtpServer
83
83
 
84
84
  def initialize(msg = nil)
85
85
  # call inherited constructor
86
- super msg, 452, 'Requested action not taken: insufficient system storage'
86
+ super(msg, 452, 'Requested action not taken: insufficient system storage')
87
87
  end
88
88
 
89
89
  end
@@ -94,7 +94,7 @@ module MidiSmtpServer
94
94
 
95
95
  def initialize(msg = nil)
96
96
  # call inherited constructor
97
- super msg, 500, 'Syntax error, command unrecognised or error in parameters or arguments'
97
+ super(msg, 500, 'Syntax error, command unrecognised or error in parameters or arguments')
98
98
  end
99
99
 
100
100
  end
@@ -104,7 +104,7 @@ module MidiSmtpServer
104
104
 
105
105
  def initialize(msg = nil)
106
106
  # call inherited constructor
107
- super msg, 501, 'Syntax error in parameters or arguments'
107
+ super(msg, 501, 'Syntax error in parameters or arguments')
108
108
  end
109
109
 
110
110
  end
@@ -114,7 +114,7 @@ module MidiSmtpServer
114
114
 
115
115
  def initialize(msg = nil)
116
116
  # call inherited constructor
117
- super msg, 502, 'Command not implemented'
117
+ super(msg, 502, 'Command not implemented')
118
118
  end
119
119
 
120
120
  end
@@ -124,7 +124,7 @@ module MidiSmtpServer
124
124
 
125
125
  def initialize(msg = nil)
126
126
  # call inherited constructor
127
- super msg, 503, 'Bad sequence of commands'
127
+ super(msg, 503, 'Bad sequence of commands')
128
128
  end
129
129
 
130
130
  end
@@ -134,7 +134,7 @@ module MidiSmtpServer
134
134
 
135
135
  def initialize(msg = nil)
136
136
  # call inherited constructor
137
- super msg, 504, 'Command parameter not implemented'
137
+ super(msg, 504, 'Command parameter not implemented')
138
138
  end
139
139
 
140
140
  end
@@ -144,7 +144,7 @@ module MidiSmtpServer
144
144
 
145
145
  def initialize(msg = nil)
146
146
  # call inherited constructor
147
- super msg, 521, 'Service does not accept mail'
147
+ super(msg, 521, 'Service does not accept mail')
148
148
  end
149
149
 
150
150
  end
@@ -155,7 +155,7 @@ module MidiSmtpServer
155
155
 
156
156
  def initialize(msg = nil)
157
157
  # call inherited constructor
158
- super msg, 550, 'Requested action not taken: mailbox unavailable'
158
+ super(msg, 550, 'Requested action not taken: mailbox unavailable')
159
159
  end
160
160
 
161
161
  end
@@ -165,7 +165,7 @@ module MidiSmtpServer
165
165
 
166
166
  def initialize(msg = nil)
167
167
  # call inherited constructor
168
- super msg, 552, 'Requested mail action aborted: exceeded storage allocation'
168
+ super(msg, 552, 'Requested mail action aborted: exceeded storage allocation')
169
169
  end
170
170
 
171
171
  end
@@ -175,7 +175,7 @@ module MidiSmtpServer
175
175
 
176
176
  def initialize(msg = nil)
177
177
  # call inherited constructor
178
- super msg, 553, 'Requested action not taken: mailbox name not allowed'
178
+ super(msg, 553, 'Requested action not taken: mailbox name not allowed')
179
179
  end
180
180
 
181
181
  end
@@ -185,7 +185,7 @@ module MidiSmtpServer
185
185
 
186
186
  def initialize(msg = nil)
187
187
  # call inherited constructor
188
- super msg, 554, 'Transaction failed'
188
+ super(msg, 554, 'Transaction failed')
189
189
  end
190
190
 
191
191
  end
@@ -197,7 +197,7 @@ module MidiSmtpServer
197
197
 
198
198
  def initialize(msg = nil)
199
199
  # call inherited constructor
200
- super msg, 432, 'Password transition is needed'
200
+ super(msg, 432, 'Password transition is needed')
201
201
  end
202
202
 
203
203
  end
@@ -207,7 +207,7 @@ module MidiSmtpServer
207
207
 
208
208
  def initialize(msg = nil)
209
209
  # call inherited constructor
210
- super msg, 454, 'Temporary authentication failure'
210
+ super(msg, 454, 'Temporary authentication failure')
211
211
  end
212
212
 
213
213
  end
@@ -217,7 +217,7 @@ module MidiSmtpServer
217
217
 
218
218
  def initialize(msg = nil)
219
219
  # call inherited constructor
220
- super msg, 530, 'Authentication required'
220
+ super(msg, 530, 'Authentication required')
221
221
  end
222
222
 
223
223
  end
@@ -227,7 +227,7 @@ module MidiSmtpServer
227
227
 
228
228
  def initialize(msg = nil)
229
229
  # call inherited constructor
230
- super msg, 534, 'Authentication mechanism is too weak'
230
+ super(msg, 534, 'Authentication mechanism is too weak')
231
231
  end
232
232
 
233
233
  end
@@ -237,7 +237,7 @@ module MidiSmtpServer
237
237
 
238
238
  def initialize(msg = nil)
239
239
  # call inherited constructor
240
- super msg, 535, 'Authentication credentials invalid'
240
+ super(msg, 535, 'Authentication credentials invalid')
241
241
  end
242
242
 
243
243
  end
@@ -247,7 +247,7 @@ module MidiSmtpServer
247
247
 
248
248
  def initialize(msg = nil)
249
249
  # call inherited constructor
250
- super msg, 538, 'Encryption required for requested authentication mechanism'
250
+ super(msg, 538, 'Encryption required for requested authentication mechanism')
251
251
  end
252
252
 
253
253
  end
@@ -259,7 +259,7 @@ module MidiSmtpServer
259
259
 
260
260
  def initialize(msg = nil)
261
261
  # call inherited constructor
262
- super msg, 454, 'TLS not available'
262
+ super(msg, 454, 'TLS not available')
263
263
  end
264
264
 
265
265
  end
@@ -269,7 +269,7 @@ module MidiSmtpServer
269
269
 
270
270
  def initialize(msg = nil)
271
271
  # call inherited constructor
272
- super msg, 530, 'Encryption required, must issue STARTTLS command first'
272
+ super(msg, 530, 'Encryption required, must issue STARTTLS command first')
273
273
  end
274
274
 
275
275
  end
@@ -281,7 +281,7 @@ module MidiSmtpServer
281
281
 
282
282
  def initialize(msg = nil)
283
283
  # call inherited constructor
284
- super msg, 500, 'Bad input, PIPELINING is not allowed'
284
+ super(msg, 500, 'Bad input, PIPELINING is not allowed')
285
285
  end
286
286
 
287
287
  end
@@ -293,7 +293,7 @@ module MidiSmtpServer
293
293
 
294
294
  def initialize(msg = nil)
295
295
  # call inherited constructor
296
- super msg, 500, 'Bad input, Lines must be terminated by CRLF sequence'
296
+ super(msg, 500, 'Bad input, Lines must be terminated by CRLF sequence')
297
297
  end
298
298
 
299
299
  end
@@ -6,12 +6,12 @@ module MidiSmtpServer
6
6
  module VERSION
7
7
 
8
8
  MAJOR = 3
9
- MINOR = 1
10
- TINY = 2
9
+ MINOR = 3
10
+ TINY = 1
11
11
 
12
12
  STRING = [MAJOR, MINOR, TINY].compact.join('.')
13
13
 
14
- DATE = '2023-05-06'
14
+ DATE = '2026-09-16'
15
15
 
16
16
  end
17
17
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'logger'
4
4
  require 'socket'
5
+ require 'io/wait'
5
6
  require 'resolv'
6
7
  require 'base64'
7
8
 
@@ -27,12 +28,15 @@ module MidiSmtpServer
27
28
  DEFAULT_CRLF_MODE = :CRLF_ENSURE
28
29
 
29
30
  # default values for IO operations
30
- DEFAULT_IO_WAITREADABLE_SLEEP = 0.1
31
+ IO_WAIT_MODES = [:IO_WAIT_SLEEP, :IO_WAIT_EVENT].freeze
32
+ DEFAULT_IO_WAIT_MODE = :IO_WAIT_EVENT
33
+ DEFAULT_IO_WAIT_AVAILABLE = 0.1
31
34
  DEFAULT_IO_CMD_TIMEOUT = 30
32
35
  DEFAULT_IO_BUFFER_CHUNK_SIZE = 4 * 1024
33
36
  DEFAULT_IO_BUFFER_MAX_SIZE = 1 * 1024 * 1024
34
37
 
35
38
  # default value for SMTPD extensions support
39
+ DEFAULT_PROXY_EXTENSION_ENABLED = false
36
40
  DEFAULT_PIPELINING_EXTENSION_ENABLED = false
37
41
  DEFAULT_INTERNATIONALIZATION_EXTENSIONS_ENABLED = false
38
42
 
@@ -196,8 +200,10 @@ module MidiSmtpServer
196
200
  attr_reader :max_connections
197
201
  # CRLF handling based on conformity to RFC(2)822
198
202
  attr_reader :crlf_mode
199
- # Time in seconds to sleep on IO::WaitReadable exception
200
- attr_reader :io_waitreadable_sleep
203
+ # how to wait on IO::WaitReadable exception
204
+ attr_reader :io_wait_mode
205
+ # Time in fraction of a second to wait on IO::WaitReadable exception
206
+ attr_reader :io_wait_available
201
207
  # Maximum time in seconds to wait for a complete incoming data line, as a FixNum
202
208
  attr_reader :io_cmd_timeout
203
209
  # Bytes to read non-blocking from socket into buffer, as a FixNum
@@ -214,6 +220,8 @@ module MidiSmtpServer
214
220
  attr_reader :pipelining_extension
215
221
  # handle SMTP 8BITMIME and SMTPUTF8 extension
216
222
  attr_reader :internationalization_extensions
223
+ # handle PROXY connections
224
+ attr_reader :proxy_extension
217
225
 
218
226
  # logging object, may be overridden by special loggers like YELL or others
219
227
  attr_reader :logger
@@ -227,12 +235,14 @@ module MidiSmtpServer
227
235
  # +max_connections+:: maximum number of connections, this does limit the number of concurrent TCP connections (not set or nil => unlimited)
228
236
  # +crlf_mode+:: CRLF handling support (:CRLF_ENSURE [default], :CRLF_LEAVE, :CRLF_STRICT)
229
237
  # +do_dns_reverse_lookup+:: flag if this smtp server should do reverse DNS lookups on incoming connections
230
- # +io_waitreadable_sleep+:: seconds to sleep in loop when no input data is available (DEFAULT_IO_WAITREADABLE_SLEEP)
238
+ # +io_wait_mode+:: how to wait when no input data is available (DEFAULT_IO_WAIT_MODE)
239
+ # +io_wait_available+:: fraction of a second to wait in loop when no input data is available (DEFAULT_IO_WAIT_AVAILABLE)
231
240
  # +io_cmd_timeout+:: time in seconds to wait until complete line of data is expected (DEFAULT_IO_CMD_TIMEOUT, nil => disabled test)
232
241
  # +io_buffer_chunk_size+:: size of chunks (bytes) to read non-blocking from socket (DEFAULT_IO_BUFFER_CHUNK_SIZE)
233
242
  # +io_buffer_max_size+:: max size of buffer (max line length) until \lf ist expected (DEFAULT_IO_BUFFER_MAX_SIZE, nil => disabled test)
234
243
  # +pipelining_extension+:: set to true for support of SMTP PIPELINING extension (DEFAULT_PIPELINING_EXTENSION_ENABLED)
235
244
  # +internationalization_extensions+:: set to true for support of SMTP 8BITMIME and SMTPUTF8 extensions (DEFAULT_INTERNATIONALIZATION_EXTENSIONS_ENABLED)
245
+ # +proxy_extension+:: set to true for supporting PROXY connections (DEFAULT_PROXY_EXTENSION_ENABLED)
236
246
  # +auth_mode+:: enable builtin authentication support (:AUTH_FORBIDDEN [default], :AUTH_OPTIONAL, :AUTH_REQUIRED)
237
247
  # +tls_mode+:: enable builtin TLS support (:TLS_FORBIDDEN [default], :TLS_OPTIONAL, :TLS_REQUIRED)
238
248
  # +tls_cert_path+:: path to tls certificate chain file
@@ -251,12 +261,15 @@ module MidiSmtpServer
251
261
  max_connections: nil,
252
262
  crlf_mode: nil,
253
263
  do_dns_reverse_lookup: nil,
254
- io_waitreadable_sleep: nil,
264
+ io_wait_mode: nil,
265
+ io_wait_available: nil,
266
+ io_waitreadable_sleep: nil, # deprecated, but for compatibility
255
267
  io_cmd_timeout: nil,
256
268
  io_buffer_chunk_size: nil,
257
269
  io_buffer_max_size: nil,
258
270
  pipelining_extension: nil,
259
271
  internationalization_extensions: nil,
272
+ proxy_extension: nil,
260
273
  auth_mode: nil,
261
274
  tls_mode: nil,
262
275
  tls_cert_path: nil,
@@ -338,7 +351,7 @@ module MidiSmtpServer
338
351
  # test for all local valid ipv4 and ipv6 ip_addresses
339
352
  # check question on stackoverflow for details
340
353
  # https://stackoverflow.com/questions/59770803/identify-all-relevant-ip-addresses-from-ruby-socket-ip-address-list
341
- ip_addresses_for_host << a.ip_address if \
354
+ ip_addresses_for_host << a.ip_address if
342
355
  (a.ipv4? &&
343
356
  (a.ipv4_loopback? || a.ipv4_private? ||
344
357
  !(a.ipv4_loopback? || a.ipv4_private? || a.ipv4_multicast?)
@@ -386,10 +399,21 @@ module MidiSmtpServer
386
399
  # always prevent auto resolving hostnames to prevent a delay on socket connect
387
400
  BasicSocket.do_not_reverse_lookup = true
388
401
  # do reverse lookups manually if enabled by io.addr and io.peeraddr
402
+ # rubocop: disable-next Style/RedundantCondition
389
403
  @do_dns_reverse_lookup = do_dns_reverse_lookup.nil? ? true : do_dns_reverse_lookup
390
404
 
405
+ # deprecated compatibility
406
+ raise 'Not allowed to use io_wait_available and deprecated io_waitreadable_sleep at the same time!' unless io_waitreadable_sleep.nil? || io_wait_available.nil?
407
+ unless io_waitreadable_sleep.nil?
408
+ io_wait_available = io_waitreadable_sleep
409
+ io_wait_mode = :IO_WAIT_SLEEP
410
+ @logger_protected.warn('Deprecated: "io_waitreadable_sleep" was replaced! Please use "io_wait_available" and "io_wait_mode" instead.')
411
+ end
391
412
  # io and buffer settings
392
- @io_waitreadable_sleep = io_waitreadable_sleep.nil? ? DEFAULT_IO_WAITREADABLE_SLEEP : io_waitreadable_sleep
413
+ @io_wait_mode = io_wait_mode.nil? ? DEFAULT_IO_WAIT_MODE : io_wait_mode
414
+ raise "Unknown io wait mode #{@io_wait_mode} was given!" unless IO_WAIT_MODES.include?(@io_wait_mode)
415
+ @io_wait_available = io_wait_available.nil? ? DEFAULT_IO_WAIT_AVAILABLE : io_wait_available
416
+ raise 'Value of time must be positive (io_wait_available)' unless @io_wait_available.positive?
393
417
  @io_cmd_timeout = io_cmd_timeout.nil? ? DEFAULT_IO_CMD_TIMEOUT : io_cmd_timeout
394
418
  @io_buffer_chunk_size = io_buffer_chunk_size.nil? ? DEFAULT_IO_BUFFER_CHUNK_SIZE : io_buffer_chunk_size
395
419
  @io_buffer_max_size = io_buffer_max_size.nil? ? DEFAULT_IO_BUFFER_MAX_SIZE : io_buffer_max_size
@@ -397,6 +421,8 @@ module MidiSmtpServer
397
421
  # smtp extensions
398
422
  @pipelining_extension = pipelining_extension.nil? ? DEFAULT_PIPELINING_EXTENSION_ENABLED : pipelining_extension
399
423
  @internationalization_extensions = internationalization_extensions.nil? ? DEFAULT_INTERNATIONALIZATION_EXTENSIONS_ENABLED : internationalization_extensions
424
+ @proxy_extension = proxy_extension.nil? ? DEFAULT_PROXY_EXTENSION_ENABLED : proxy_extension
425
+ require 'ipaddr' if @proxy_extension
400
426
 
401
427
  # check for authentication
402
428
  @auth_mode = auth_mode.nil? ? DEFAULT_AUTH_MODE : auth_mode
@@ -482,6 +508,13 @@ module MidiSmtpServer
482
508
  # the value is not allowed to return CR nor LF chars and will be stripped
483
509
  def on_helo_event(ctx, helo_data) end
484
510
 
511
+ # event on PROXY
512
+ # you may raise an exception if you want to block some addresses
513
+ # you also may change or add any value of the hash:
514
+ # {proto, source_ip, source_host, source_port, dest_ip, dest_host, dest_port}
515
+ # a returned value hash is set as ctx[:server][:proxy]
516
+ def on_proxy_event(ctx, proxy_data) end
517
+
485
518
  # check the authentication on AUTH
486
519
  # if any value returned, that will be used for ongoing processing
487
520
  # otherwise the original value will be used for authorization_id
@@ -755,10 +788,25 @@ module MidiSmtpServer
755
788
  io_buffer_line_lf = io_buffer.index("\n")
756
789
  end
757
790
 
758
- # ignore exception when no input data is available yet
791
+ # handle no input data is available yet
759
792
  rescue IO::WaitReadable
760
- # but wait a few moment to slow down system utilization
761
- sleep @io_waitreadable_sleep
793
+ if @io_wait_mode == :IO_WAIT_SLEEP
794
+ # reduce the processing speed and thereby the system
795
+ # utilization by sleeping the configured time
796
+ sleep @io_wait_available
797
+ else
798
+ # block on the underlying socket until new data has arrived,
799
+ # but wake up at least after wait time so that the loop
800
+ # keeps checking shutdown? and io_cmd_timeout
801
+ # (io.to_io resolves the plain socket behind an
802
+ # OpenSSL::SSL::SSLSocket, otherwise wait_readable is not defined
803
+ io.to_io.wait_readable(@io_wait_available)
804
+ end
805
+
806
+ # during TLS (re)negotiation a read may require the socket
807
+ # to become writable first. this always waits by event
808
+ rescue IO::WaitWritable
809
+ io.to_io.wait_writable(@io_wait_available)
762
810
  end
763
811
 
764
812
  # check if io_buffer is filled and contains already a line-feed
@@ -772,7 +820,7 @@ module MidiSmtpServer
772
820
  # process commands and handle special SmtpdExceptions
773
821
  begin
774
822
  # check for pipelining extension or violation
775
- raise Smtpd500PipeliningException unless @pipelining_extension || !io_buffer_line_lf || (session[:cmd_sequence] == :CMD_DATA)
823
+ raise Smtpd500PipeliningException unless !io_buffer_line_lf || @pipelining_extension || (proxy_extension && (session[:cmd_sequence] == :CMD_HELO)) || (session[:cmd_sequence] == :CMD_DATA)
776
824
 
777
825
  # handle input line based on @crlf_mode
778
826
  case crlf_mode
@@ -932,8 +980,8 @@ module MidiSmtpServer
932
980
  # check whether to answer as HELO or EHLO
933
981
  case line
934
982
  when (/^EHLO/i)
935
- # rubocop:disable Style/StringConcatenation
936
983
  # reply supported extensions
984
+ # rubocop: disable-next Style/StringConcatenation
937
985
  return "250-#{session[:ctx][:server][:helo_response].to_s.strip}\r\n" +
938
986
  # respond with 8BITMIME extension
939
987
  (@internationalization_extensions ? "250-8BITMIME\r\n" : '') +
@@ -946,12 +994,93 @@ module MidiSmtpServer
946
994
  # respond with STARTTLS if available and not already enabled
947
995
  (@encrypt_mode == :TLS_FORBIDDEN || encrypted?(session[:ctx]) ? '' : "250-STARTTLS\r\n") +
948
996
  '250 OK'
949
- # rubocop:enable all
950
997
  else
951
998
  # reply ok only
952
999
  return "250 OK #{session[:ctx][:server][:helo_response].to_s.strip}".strip
953
1000
  end
954
1001
 
1002
+ when @proxy_extension && (/^PROXY(\s+)/i)
1003
+ # PROXY
1004
+ # 250 Requested mail action okay, completed
1005
+ # 421 <domain> Service not available, closing transmission channel
1006
+ # 500 Syntax error, command unrecognised
1007
+ # 501 Syntax error in parameters or arguments
1008
+ # ---------
1009
+ # Documentation at https://github.com/haproxy/haproxy/blob/master/doc/proxy-protocol.txt
1010
+ # syntax
1011
+ # PROXY PROTO source-ip dest-ip source-port dest-port
1012
+ # supported commands:
1013
+ # PROXY TCP4 255.255.255.255 255.255.255.255 65535 65535
1014
+ # PROXY TCP6 ffff:f...f:ffff ffff:f...f:ffff 65535 65535
1015
+ # PROXY UNKNOWN
1016
+ # PROXY UNKNOWN ffff:f...f:ffff ffff:f...f:ffff 65535 65535
1017
+ # ---------
1018
+ # check valid command sequence
1019
+ raise Smtpd503Exception if session[:cmd_sequence] != :CMD_HELO
1020
+ # check valid command
1021
+ raise Smtpd421Exception, 'Abort connection while illegal PROXY command!' unless line.match?(/^PROXY(\s+)(UNKNOWN(|(\s+).*)|TCP(4|6)(\s+)([0-9a-f.:]+)(\s+)([0-9a-f.:]+)(\s+)([0-9]+)(\s+)([0-9]+)(\s*))$/i)
1022
+ # check command usage is allowed only once
1023
+ raise Smtpd421Exception, 'Abort connection while PROXY already set!' if session[:ctx][:server][:proxy]
1024
+ # get values from proxy command
1025
+ cmd_data = line.gsub(/^PROXY\ /i, '').strip.gsub(/\s+/, ' ').split
1026
+ # create an empty hash to inspect by event
1027
+ proxy_data = {
1028
+ proto: cmd_data[0].upcase,
1029
+ source_ip: nil,
1030
+ source_host: nil,
1031
+ source_port: nil,
1032
+ dest_ip: nil,
1033
+ dest_host: nil,
1034
+ dest_port: nil
1035
+ }
1036
+ # test proto
1037
+ unless proxy_data[:proto] == 'UNKNOWN'
1038
+ begin
1039
+ # try to build valid addresses from given strings
1040
+ proxy_data[:source_ip] = IPAddr.new(cmd_data[1])
1041
+ proxy_data[:source_port] = cmd_data[3].to_i
1042
+ proxy_data[:dest_ip] = IPAddr.new(cmd_data[2])
1043
+ proxy_data[:dest_port] = cmd_data[4].to_i
1044
+ # check that given addresses correct by type
1045
+ if proxy_data[:proto] == 'TCP4'
1046
+ raise unless proxy_data[:source_ip].ipv4?
1047
+ raise unless proxy_data[:dest_ip].ipv4?
1048
+ else
1049
+ raise unless proxy_data[:source_ip].ipv6?
1050
+ raise unless proxy_data[:dest_ip].ipv6?
1051
+ end
1052
+ # check that ports within valid ranges
1053
+ raise unless proxy_data[:source_port].between?(1, 65_535)
1054
+ raise unless proxy_data[:dest_port].between?(1, 65_535)
1055
+
1056
+ # update hash to inspect by event
1057
+ # normalize ip addresses
1058
+ proxy_data[:source_ip] = proxy_data[:source_ip].to_s
1059
+ proxy_data[:source_host] = proxy_data[:source_ip]
1060
+ proxy_data[:dest_ip] = proxy_data[:dest_ip].to_s
1061
+ proxy_data[:dest_host] = proxy_data[:dest_ip]
1062
+
1063
+ rescue StandardError
1064
+ # change exception into Smtpd exception and drop connection
1065
+ raise Smtpd421Exception, 'Abort connection for unsupported PROXY parameters!'
1066
+ end
1067
+ end
1068
+
1069
+ # call event to handle data
1070
+ return_value = on_proxy_event(session[:ctx], proxy_data)
1071
+ if return_value
1072
+ # overwrite data with returned value
1073
+ proxy_data = return_value
1074
+ end
1075
+ # if no error raised, append to server hash
1076
+ session[:ctx][:server][:proxy] = proxy_data
1077
+
1078
+ # reply nothing
1079
+ # otherwise on buffering clients or enabled feature pipelining
1080
+ # the original client will receive unhandleable responses
1081
+ # and gets interrupted
1082
+ return ''
1083
+
955
1084
  when /^STARTTLS\s*$/i
956
1085
  # STARTTLS
957
1086
  # 220 Ready to start TLS
@@ -1206,7 +1335,7 @@ module MidiSmtpServer
1206
1335
  # If we are in date mode then ...
1207
1336
 
1208
1337
  # call event to signal beginning of message data transfer
1209
- on_message_data_start_event(session[:ctx]) unless session[:ctx][:message][:data][0]
1338
+ on_message_data_start_event(session[:ctx]) if session[:ctx][:message][:data].empty?
1210
1339
 
1211
1340
  # ... and the entire new message data (line) does NOT consists
1212
1341
  # solely of a period (.) on a line by itself then we are being
@@ -1294,6 +1423,7 @@ module MidiSmtpServer
1294
1423
  remote_host: +'',
1295
1424
  remote_ip: +'',
1296
1425
  remote_port: +'',
1426
+ proxy: nil,
1297
1427
  helo: +'',
1298
1428
  helo_response: +'',
1299
1429
  connected: +'',
metadata CHANGED
@@ -1,15 +1,42 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: midi-smtp-server
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.2
4
+ version: 3.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tom Freudenberg
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2023-05-06 00:00:00.000000000 Z
12
- dependencies: []
10
+ date: 2026-09-16 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: base64
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '0.2'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '0.2'
26
+ - !ruby/object:Gem::Dependency
27
+ name: logger
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '1.6'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.6'
13
40
  description: MidiSmtpServer is the highly customizable ruby SMTP-Server and SMTP-Service
14
41
  library with builtin support for AUTH and SSL/STARTTLS, 8BITMIME and SMTPUTF8, IPv4
15
42
  and IPv6 and additional features.
@@ -34,9 +61,9 @@ metadata:
34
61
  source_code_uri: https://github.com/4commerce-technologies-AG/midi-smtp-server
35
62
  changelog_uri: https://github.com/4commerce-technologies-AG/midi-smtp-server#changes-and-updates
36
63
  bug_tracker_uri: https://github.com/4commerce-technologies-AG/midi-smtp-server/issues
37
- documentation_uri: https://www.rubydoc.info/gems/midi-smtp-server/3.1.2
64
+ documentation_uri: https://www.rubydoc.info/gems/midi-smtp-server/3.3.1
38
65
  wiki_uri: https://midi-smtp-server.readthedocs.io/
39
- post_install_message:
66
+ rubygems_mfa_required: 'true'
40
67
  rdoc_options: []
41
68
  require_paths:
42
69
  - lib
@@ -44,15 +71,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
44
71
  requirements:
45
72
  - - ">="
46
73
  - !ruby/object:Gem::Version
47
- version: 2.6.0
74
+ version: 2.7.0
48
75
  required_rubygems_version: !ruby/object:Gem::Requirement
49
76
  requirements:
50
77
  - - ">="
51
78
  - !ruby/object:Gem::Version
52
79
  version: '0'
53
80
  requirements: []
54
- rubygems_version: 3.4.1
55
- signing_key:
81
+ rubygems_version: 3.6.9
56
82
  specification_version: 4
57
83
  summary: MidiSmtpServer Class
58
84
  test_files: []