midi-smtp-server 2.3.3 → 3.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +112 -0
- data/MIT-LICENSE.txt +1 -1
- data/README.md +35 -950
- data/lib/midi-smtp-server/logger.rb +37 -0
- data/lib/midi-smtp-server/tls-transport.rb +54 -33
- data/lib/midi-smtp-server/version.rb +2 -2
- data/lib/midi-smtp-server.rb +178 -121
- metadata +5 -3
data/lib/midi-smtp-server.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'logger'
|
3
4
|
require 'socket'
|
4
5
|
require 'resolv'
|
5
6
|
require 'base64'
|
@@ -10,6 +11,7 @@ module MidiSmtpServer
|
|
10
11
|
# import sources
|
11
12
|
require 'midi-smtp-server/version'
|
12
13
|
require 'midi-smtp-server/exceptions'
|
14
|
+
require 'midi-smtp-server/logger'
|
13
15
|
require 'midi-smtp-server/tls-transport'
|
14
16
|
|
15
17
|
# default values
|
@@ -47,7 +49,7 @@ module MidiSmtpServer
|
|
47
49
|
end
|
48
50
|
|
49
51
|
# Stop the server
|
50
|
-
def stop(wait_seconds_before_close
|
52
|
+
def stop(wait_seconds_before_close: 2, gracefully: true)
|
51
53
|
# always signal shutdown
|
52
54
|
shutdown if gracefully
|
53
55
|
# wait if some connection(s) need(s) more time to handle shutdown
|
@@ -101,7 +103,7 @@ module MidiSmtpServer
|
|
101
103
|
# Join with the server thread(s)
|
102
104
|
# before joining the server threads, check and wait optionally a few seconds
|
103
105
|
# to let the service(s) come up
|
104
|
-
def join(sleep_seconds_before_join
|
106
|
+
def join(sleep_seconds_before_join: 1)
|
105
107
|
# check already existing TCPServers
|
106
108
|
return if @tcp_servers.empty?
|
107
109
|
# wait some seconds before joininig the upcoming threads
|
@@ -125,30 +127,23 @@ module MidiSmtpServer
|
|
125
127
|
@ports.dup
|
126
128
|
end
|
127
129
|
|
128
|
-
# New but deprecated method to access the old port attr for compatibility reasons
|
129
|
-
def port
|
130
|
-
logger.debug('Deprecated method port is used. Please update to ports.join() etc.')
|
131
|
-
ports.join(', ')
|
132
|
-
end
|
133
|
-
|
134
130
|
# Array of hosts / ip_addresses on which to bind, set as string seperated by commata like 'name.domain.com, 127.0.0.1, ::1'
|
135
131
|
def hosts
|
136
132
|
# prevent original array from being changed
|
137
133
|
@hosts.dup
|
138
134
|
end
|
139
135
|
|
140
|
-
# New but deprecated method to access the old host attr for compatibility reasons
|
141
|
-
def host
|
142
|
-
logger.debug('Deprecated method host is used. Please update to hosts.join() etc.')
|
143
|
-
hosts.join(', ')
|
144
|
-
end
|
145
|
-
|
146
136
|
# Array of ip_address:port which get bound and build up from given hosts and ports
|
147
137
|
def addresses
|
148
138
|
# prevent original array from being changed
|
149
139
|
@addresses.dup
|
150
140
|
end
|
151
141
|
|
142
|
+
# Current TLS OpenSSL::SSL::SSLContext when initalized by :TLS_OPTIONAL, :TLS_REQUIRED
|
143
|
+
def ssl_context
|
144
|
+
@tls&.ssl_context
|
145
|
+
end
|
146
|
+
|
152
147
|
# Maximum number of simultaneous processed connections, this does not limit the TCP connections itself, as a FixNum
|
153
148
|
attr_reader :max_processings
|
154
149
|
# Maximum number of allowed connections, this does limit the TCP connections, as a FixNum
|
@@ -177,38 +172,63 @@ module MidiSmtpServer
|
|
177
172
|
|
178
173
|
# Initialize SMTP Server class
|
179
174
|
#
|
180
|
-
# +ports+:: ports to listen on. Allows multiple ports like "2525, 3535" or "2525:3535, 2525"
|
181
|
-
# +hosts+:: interface ip or hostname to listen on or "*" to listen on all interfaces,
|
182
|
-
# +max_processings+:: maximum number of simultaneous processed connections, this does not limit the number of concurrent TCP connections
|
183
|
-
# +
|
184
|
-
# +
|
185
|
-
# +
|
186
|
-
# +
|
187
|
-
# +
|
188
|
-
# +
|
189
|
-
# +
|
190
|
-
# +
|
191
|
-
# +
|
192
|
-
# +
|
193
|
-
# +
|
194
|
-
# +
|
195
|
-
# +
|
196
|
-
# +
|
197
|
-
# +
|
198
|
-
# +
|
199
|
-
# +
|
200
|
-
# +
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
175
|
+
# +ports+:: ports to listen on. Allows multiple ports like "2525, 3535" or "2525:3535, 2525". Default value = DEFAULT_SMTPD_PORT
|
176
|
+
# +hosts+:: interface ip or hostname to listen on or "*" to listen on all interfaces, allows multiple hostnames and ip_addresses like "name.domain.com, 127.0.0.1, ::1". Default value = DEFAULT_SMTPD_HOST
|
177
|
+
# +max_processings+:: maximum number of simultaneous processed connections, this does not limit the number of concurrent TCP connections. Default value = DEFAULT_SMTPD_MAX_PROCESSINGS
|
178
|
+
# +max_connections+:: maximum number of connections, this does limit the number of concurrent TCP connections (not set or nil => unlimited)
|
179
|
+
# +crlf_mode+:: CRLF handling support (:CRLF_ENSURE [default], :CRLF_LEAVE, :CRLF_STRICT)
|
180
|
+
# +do_dns_reverse_lookup+:: flag if this smtp server should do reverse DNS lookups on incoming connections
|
181
|
+
# +io_cmd_timeout+:: time in seconds to wait until complete line of data is expected (DEFAULT_IO_CMD_TIMEOUT, nil => disabled test)
|
182
|
+
# +io_buffer_chunk_size+:: size of chunks (bytes) to read non-blocking from socket (DEFAULT_IO_BUFFER_CHUNK_SIZE)
|
183
|
+
# +io_buffer_max_size+:: max size of buffer (max line length) until \lf ist expected (DEFAULT_IO_BUFFER_MAX_SIZE, nil => disabled test)
|
184
|
+
# +pipelining_extension+:: set to true for support of SMTP PIPELINING extension (DEFAULT_PIPELINING_EXTENSION_ENABLED)
|
185
|
+
# +internationalization_extensions+:: set to true for support of SMTP 8BITMIME and SMTPUTF8 extensions (DEFAULT_INTERNATIONALIZATION_EXTENSIONS_ENABLED)
|
186
|
+
# +auth_mode+:: enable builtin authentication support (:AUTH_FORBIDDEN [default], :AUTH_OPTIONAL, :AUTH_REQUIRED)
|
187
|
+
# +tls_mode+:: enable builtin TLS support (:TLS_FORBIDDEN [default], :TLS_OPTIONAL, :TLS_REQUIRED)
|
188
|
+
# +tls_cert_path+:: path to tls cerificate chain file
|
189
|
+
# +tls_key_path+:: path to tls key file
|
190
|
+
# +tls_ciphers+:: allowed ciphers for connection
|
191
|
+
# +tls_methods+:: allowed methods for protocol
|
192
|
+
# +tls_cert_cn+:: set subject (CN) for self signed certificate "cn.domain.com"
|
193
|
+
# +tls_cert_san+:: set subject alternative (SAN) for self signed certificate, allows multiple names like "alt1.domain.com, alt2.domain.com"
|
194
|
+
# +logger+:: own logger class, otherwise default logger is created (DEPRECATED: use on_logging_event for special needs on logging)
|
195
|
+
# +logger_severity+:: set or override the logger level if given
|
196
|
+
def initialize(
|
197
|
+
ports: DEFAULT_SMTPD_PORT,
|
198
|
+
hosts: DEFAULT_SMTPD_HOST,
|
199
|
+
max_processings: DEFAULT_SMTPD_MAX_PROCESSINGS,
|
200
|
+
max_connections: nil,
|
201
|
+
crlf_mode: nil,
|
202
|
+
do_dns_reverse_lookup: nil,
|
203
|
+
io_cmd_timeout: nil,
|
204
|
+
io_buffer_chunk_size: nil,
|
205
|
+
io_buffer_max_size: nil,
|
206
|
+
pipelining_extension: nil,
|
207
|
+
internationalization_extensions: nil,
|
208
|
+
auth_mode: nil,
|
209
|
+
tls_mode: nil,
|
210
|
+
tls_cert_path: nil,
|
211
|
+
tls_key_path: nil,
|
212
|
+
tls_ciphers: nil,
|
213
|
+
tls_methods: nil,
|
214
|
+
tls_cert_cn: nil,
|
215
|
+
tls_cert_san: nil,
|
216
|
+
logger: nil,
|
217
|
+
logger_severity: nil
|
218
|
+
)
|
219
|
+
# create an exposed logger to forward loggings to the on_logging_event
|
220
|
+
@logger = MidiSmtpServer::ForwardingLogger.new(method(:on_logging_event))
|
221
|
+
|
222
|
+
# external logging
|
223
|
+
if logger.nil?
|
224
|
+
@logger_protected = Logger.new($stdout)
|
225
|
+
@logger_protected.datetime_format = '%Y-%m-%d %H:%M:%S'
|
226
|
+
@logger_protected.formatter = proc { |severity, datetime, _progname, msg| "#{datetime}: [#{severity}] #{msg.chomp}\n" }
|
227
|
+
@logger_protected.level = logger_severity.nil? ? Logger::DEBUG : logger_severity
|
206
228
|
else
|
207
|
-
|
208
|
-
@
|
209
|
-
|
210
|
-
@logger.formatter = proc { |severity, datetime, _progname, msg| "#{datetime}: [#{severity}] #{msg.chomp}\n" }
|
211
|
-
@logger.level = opts.include?(:logger_severity) ? opts[:logger_severity] : Logger::DEBUG
|
229
|
+
@logger_protected = logger
|
230
|
+
@logger_protected.level = logger_severity unless logger_severity.nil?
|
231
|
+
logger.warn('Deprecated: "logger" was set on new! Please use "on_logging_event" instead.')
|
212
232
|
end
|
213
233
|
|
214
234
|
# list of TCPServers
|
@@ -235,8 +255,6 @@ module MidiSmtpServer
|
|
235
255
|
# build array of hosts
|
236
256
|
# split string into array to instantiate multiple servers
|
237
257
|
@hosts = hosts.to_s.delete(' ').split(',')
|
238
|
-
# Deprecated default if empty bind to (first found) local host ip_address
|
239
|
-
# Check that not also the '' wildcard for hosts is added somewhere to the list
|
240
258
|
#
|
241
259
|
# Check source of TCPServer.c at https://github.com/ruby/ruby/blob/trunk/ext/socket/tcpserver.c#L25-L31
|
242
260
|
# * Internally, TCPServer.new calls getaddrinfo() function to obtain ip_addresses.
|
@@ -247,14 +265,9 @@ module MidiSmtpServer
|
|
247
265
|
# We won't support that magic anymore. If wish to bind on all local ip_addresses
|
248
266
|
# and interfaces, use new "*" wildcard, otherwise specify ip_addresses and / or hostnames
|
249
267
|
#
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
@hosts << '*'
|
254
|
-
elsif @hosts.include?('')
|
255
|
-
# raise exception when founding inner wildcard like "a.b.c.d,,e.f.g.h", guess miss-coding
|
256
|
-
raise 'Deprecated empty hosts wildcard "" is used. Please use specific hostnames and / or ip_addresses or "*" for wildcard!'
|
257
|
-
end
|
268
|
+
# raise exception when found empty or inner empty hosts specification like "" or "a.b.c.d,,e.f.g.h", guess miss-coding
|
269
|
+
raise 'No hosts defined! Please use specific hostnames and / or ip_addresses or "*" for wildcard!' if @hosts.empty?
|
270
|
+
raise 'Detected an empty identifier in given hosts! Please use specific hostnames and / or ip_addresses or "*" for wildcard!' if @hosts.include?('')
|
258
271
|
|
259
272
|
# build array of addresses for ip_addresses and ports to use
|
260
273
|
@addresses = []
|
@@ -300,51 +313,55 @@ module MidiSmtpServer
|
|
300
313
|
|
301
314
|
# read max_processings
|
302
315
|
@max_processings = max_processings
|
316
|
+
raise 'Number of simultaneous processings (max_processings) must be a positive integer!' unless @max_processings.is_a?(Integer) && @max_processings.positive?
|
303
317
|
# check max_connections
|
304
|
-
@max_connections =
|
318
|
+
@max_connections = max_connections
|
305
319
|
raise 'Number of concurrent connections is lower than number of simultaneous processings!' if @max_connections && @max_connections < @max_processings
|
306
320
|
|
307
321
|
# check for crlf mode
|
308
|
-
@crlf_mode =
|
309
|
-
raise "Unknown CRLF mode #{@crlf_mode} was given
|
322
|
+
@crlf_mode = crlf_mode.nil? ? DEFAULT_CRLF_MODE : crlf_mode
|
323
|
+
raise "Unknown CRLF mode #{@crlf_mode} was given!" unless CRLF_MODES.include?(@crlf_mode)
|
310
324
|
|
311
325
|
# always prevent auto resolving hostnames to prevent a delay on socket connect
|
312
326
|
BasicSocket.do_not_reverse_lookup = true
|
313
327
|
# do reverse lookups manually if enabled by io.addr and io.peeraddr
|
314
|
-
@do_dns_reverse_lookup =
|
328
|
+
@do_dns_reverse_lookup = do_dns_reverse_lookup.nil? ? true : do_dns_reverse_lookup
|
315
329
|
|
316
330
|
# io and buffer settings
|
317
|
-
@io_cmd_timeout =
|
318
|
-
@io_buffer_chunk_size =
|
319
|
-
@io_buffer_max_size =
|
331
|
+
@io_cmd_timeout = io_cmd_timeout.nil? ? DEFAULT_IO_CMD_TIMEOUT : io_cmd_timeout
|
332
|
+
@io_buffer_chunk_size = io_buffer_chunk_size.nil? ? DEFAULT_IO_BUFFER_CHUNK_SIZE : io_buffer_chunk_size
|
333
|
+
@io_buffer_max_size = io_buffer_max_size.nil? ? DEFAULT_IO_BUFFER_MAX_SIZE : io_buffer_max_size
|
320
334
|
|
321
335
|
# smtp extensions
|
322
|
-
@pipelining_extension =
|
323
|
-
@internationalization_extensions =
|
336
|
+
@pipelining_extension = pipelining_extension.nil? ? DEFAULT_PIPELINING_EXTENSION_ENABLED : pipelining_extension
|
337
|
+
@internationalization_extensions = internationalization_extensions.nil? ? DEFAULT_INTERNATIONALIZATION_EXTENSIONS_ENABLED : internationalization_extensions
|
324
338
|
|
325
339
|
# check for authentification
|
326
|
-
@auth_mode =
|
327
|
-
raise "Unknown authentification mode #{@auth_mode} was given
|
340
|
+
@auth_mode = auth_mode.nil? ? DEFAULT_AUTH_MODE : auth_mode
|
341
|
+
raise "Unknown authentification mode #{@auth_mode} was given!" unless AUTH_MODES.include?(@auth_mode)
|
328
342
|
|
329
343
|
# check for encryption
|
330
|
-
@encrypt_mode =
|
331
|
-
raise "Unknown encryption mode #{@encrypt_mode} was given
|
344
|
+
@encrypt_mode = tls_mode.nil? ? DEFAULT_ENCRYPT_MODE : tls_mode
|
345
|
+
raise "Unknown encryption mode #{@encrypt_mode} was given!" unless ENCRYPT_MODES.include?(@encrypt_mode)
|
332
346
|
# SSL transport layer for STARTTLS
|
333
347
|
if @encrypt_mode == :TLS_FORBIDDEN
|
334
348
|
@tls = nil
|
335
349
|
else
|
336
|
-
|
350
|
+
# try to load openssl gem now
|
351
|
+
begin
|
352
|
+
require 'openssl'
|
353
|
+
rescue LoadError
|
354
|
+
end
|
355
|
+
# check for openssl gem when enabling TLS / SSL
|
356
|
+
raise 'The openssl library gem is not installed!' unless defined?(OpenSSL)
|
337
357
|
# check for given CN and SAN
|
338
|
-
if
|
339
|
-
tls_cert_cn = opts[:tls_cert_cn].to_s.strip
|
340
|
-
tls_cert_san = opts[:tls_cert_san].to_s.delete(' ').split(',')
|
341
|
-
else
|
358
|
+
if tls_cert_cn.nil?
|
342
359
|
# build generic set of "valid" self signed certificate CN and SAN
|
343
360
|
# using all given hosts and detected ip_addresses but not "*" wildcard
|
344
361
|
tls_cert_san = ([] + @hosts + @addresses.map { |address| address.rpartition(':').first }).uniq
|
345
362
|
tls_cert_san.delete('*')
|
346
363
|
# build generic CN based on first SAN
|
347
|
-
if tls_cert_san.first
|
364
|
+
if tls_cert_san.first.match?(/^(127\.0?0?0\.0?0?0\.0?0?1|::1|localhost)$/i)
|
348
365
|
# used generic localhost.local
|
349
366
|
tls_cert_cn = 'localhost.local'
|
350
367
|
else
|
@@ -353,9 +370,33 @@ module MidiSmtpServer
|
|
353
370
|
tls_cert_cn = tls_cert_san.first
|
354
371
|
tls_cert_san.slice!(0)
|
355
372
|
end
|
373
|
+
else
|
374
|
+
tls_cert_cn = tls_cert_cn.to_s.strip
|
375
|
+
tls_cert_san = tls_cert_san.to_s.delete(' ').split(',')
|
356
376
|
end
|
357
377
|
# create ssl transport service
|
358
|
-
@tls = TlsTransport.new(
|
378
|
+
@tls = TlsTransport.new(tls_cert_path, tls_key_path, tls_ciphers, tls_methods, tls_cert_cn, tls_cert_san, @logger)
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
# event on LOGGING
|
383
|
+
# the exposed logger property is from class MidiSmtpServer::ForwardingLogger
|
384
|
+
# and pushes any logging message to this on_logging_event.
|
385
|
+
# if logging occurs from inside session, the _ctx should be not nil
|
386
|
+
# if logging occurs from an error, the err object should be filled
|
387
|
+
def on_logging_event(_ctx, severity, msg, err: nil)
|
388
|
+
case severity
|
389
|
+
when Logger::INFO
|
390
|
+
@logger_protected.info(msg)
|
391
|
+
when Logger::WARN
|
392
|
+
@logger_protected.warn(msg)
|
393
|
+
when Logger::ERROR
|
394
|
+
@logger_protected.error(msg)
|
395
|
+
when Logger::FATAL
|
396
|
+
@logger_protected.fatal(msg)
|
397
|
+
err.backtrace.each { |log| @logger_protected.fatal("#{log}") }
|
398
|
+
else
|
399
|
+
@logger_protected.debug(msg)
|
359
400
|
end
|
360
401
|
end
|
361
402
|
|
@@ -365,12 +406,12 @@ module MidiSmtpServer
|
|
365
406
|
# that these will be used as local welcome and greeting strings
|
366
407
|
# the values are not allowed to return CR nor LF chars and will be stripped
|
367
408
|
def on_connect_event(ctx)
|
368
|
-
|
409
|
+
on_logging_event(ctx, Logger::DEBUG, "Client connect from #{ctx[:server][:remote_ip]}:#{ctx[:server][:remote_port]} to #{ctx[:server][:local_ip]}:#{ctx[:server][:local_port]}")
|
369
410
|
end
|
370
411
|
|
371
412
|
# event before DISONNECT
|
372
413
|
def on_disconnect_event(ctx)
|
373
|
-
|
414
|
+
on_logging_event(ctx, Logger::DEBUG, "Client disconnect from #{ctx[:server][:remote_ip]}:#{ctx[:server][:remote_port]} on #{ctx[:server][:local_ip]}:#{ctx[:server][:local_port]}")
|
374
415
|
end
|
375
416
|
|
376
417
|
# event on HELO/EHLO
|
@@ -386,7 +427,7 @@ module MidiSmtpServer
|
|
386
427
|
# if authentification is used, override this event
|
387
428
|
# and implement your own user management.
|
388
429
|
# otherwise all authentifications are blocked per default
|
389
|
-
|
430
|
+
on_logging_event(ctx, Logger::DEBUG, "Deny access from #{ctx[:server][:remote_ip]}:#{ctx[:server][:remote_port]} for #{authentication_id}" + (authorization_id == '' ? '' : "/#{authorization_id}") + " with #{authentication}")
|
390
431
|
raise Smtpd535Exception
|
391
432
|
end
|
392
433
|
|
@@ -453,7 +494,7 @@ module MidiSmtpServer
|
|
453
494
|
# log information
|
454
495
|
logger.info("Starting service on #{ip_address}:#{port}")
|
455
496
|
# check that there is a specific ip_address defined
|
456
|
-
raise '
|
497
|
+
raise 'Unable to start TCP listener on missing or empty ip_address!' if ip_address.empty?
|
457
498
|
# instantiate the service for ip_address and port
|
458
499
|
tcp_server = TCPServer.new(ip_address, port)
|
459
500
|
# append this server to the list of TCPServers
|
@@ -482,7 +523,7 @@ module MidiSmtpServer
|
|
482
523
|
# ignore this exception due to service shutdown
|
483
524
|
rescue StandardError => e
|
484
525
|
# log fatal error while handling connection
|
485
|
-
|
526
|
+
on_logging_event(nil, Logger::FATAL, "#{e} (#{e.class})".strip, err: e.clone)
|
486
527
|
ensure
|
487
528
|
begin
|
488
529
|
# always gracefully shutdown connection.
|
@@ -509,7 +550,7 @@ module MidiSmtpServer
|
|
509
550
|
# ignore this exception due to service shutdown
|
510
551
|
rescue StandardError => e
|
511
552
|
# log fatal error while starting new thread
|
512
|
-
|
553
|
+
on_logging_event(nil, Logger::FATAL, "#{e} (#{e.class})".strip, err: e.clone)
|
513
554
|
ensure
|
514
555
|
begin
|
515
556
|
# drop the service
|
@@ -545,7 +586,7 @@ module MidiSmtpServer
|
|
545
586
|
# 220 <domain> Service ready
|
546
587
|
# 421 <domain> Service not available, closing transmission channel
|
547
588
|
# Reset and initialize message
|
548
|
-
process_reset_session(session, true)
|
589
|
+
process_reset_session(session, connection_initialize: true)
|
549
590
|
|
550
591
|
# get local address info
|
551
592
|
_, session[:ctx][:server][:local_port], session[:ctx][:server][:local_host], session[:ctx][:server][:local_ip] = @do_dns_reverse_lookup ? io.addr(:hostname) : io.addr(:numeric)
|
@@ -578,7 +619,7 @@ module MidiSmtpServer
|
|
578
619
|
output = +"220 #{session[:ctx][:server][:local_response].to_s.strip}\r\n"
|
579
620
|
|
580
621
|
# log and show to client
|
581
|
-
|
622
|
+
on_logging_event(session[:ctx], Logger::DEBUG, +'>>> ' << output)
|
582
623
|
io.print output unless io.closed?
|
583
624
|
|
584
625
|
# initialize \r\n for line_break, this is used for CRLF_ENSURE and CRLF_STRICT and mark as mutable
|
@@ -646,27 +687,27 @@ module MidiSmtpServer
|
|
646
687
|
# remove any \r or \n occurence from line
|
647
688
|
line.delete!("\r\n")
|
648
689
|
# log line, verbosity based on log severity and command sequence
|
649
|
-
|
690
|
+
on_logging_event(session[:ctx], Logger::DEBUG, +'<<< ' << line << "\n") if session[:cmd_sequence] != :CMD_DATA
|
650
691
|
|
651
692
|
when :CRLF_LEAVE
|
652
693
|
# use input line_break for line_break
|
653
|
-
line_break = line[-2
|
694
|
+
line_break = line[-2..] == "\r\n" ? "\r\n" : "\n"
|
654
695
|
# check to override session crlf info, only when CRLF_LEAVE is used and in DATA mode
|
655
696
|
session[:ctx][:message][:crlf] = line_break if session[:cmd_sequence] == :CMD_DATA
|
656
697
|
# remove any line_break from line
|
657
698
|
line.chomp!
|
658
699
|
# log line, verbosity based on log severity and command sequence
|
659
|
-
|
700
|
+
on_logging_event(session[:ctx], Logger::DEBUG, +'<<< ' << line.gsub("\r", '[\r]') << "\n") if session[:cmd_sequence] != :CMD_DATA
|
660
701
|
|
661
702
|
when :CRLF_STRICT
|
662
703
|
# check line ends up by \r\n
|
663
|
-
raise Smtpd500CrLfSequenceException unless line[-2
|
704
|
+
raise Smtpd500CrLfSequenceException unless line[-2..] == "\r\n"
|
664
705
|
# remove any line_break from line
|
665
706
|
line.chomp!
|
666
707
|
# check line for additional \r
|
667
708
|
raise Smtpd500Exception, 'Line contains additional CR chars!' if line.index("\r")
|
668
709
|
# log line, verbosity based on log severity and command sequence
|
669
|
-
|
710
|
+
on_logging_event(session[:ctx], Logger::DEBUG, +'<<< ' << line << "\n") if session[:cmd_sequence] != :CMD_DATA
|
670
711
|
end
|
671
712
|
|
672
713
|
# process line and mark output as mutable
|
@@ -681,8 +722,10 @@ module MidiSmtpServer
|
|
681
722
|
rescue SmtpdException => e
|
682
723
|
# inc number of detected exceptions during this session
|
683
724
|
session[:ctx][:server][:exceptions] += 1
|
725
|
+
# save clone of error object to context
|
726
|
+
session[:ctx][:server][:errors].push(e.clone)
|
684
727
|
# log error info if logging
|
685
|
-
|
728
|
+
on_logging_event(session[:ctx], Logger::ERROR, "#{e} (#{e.class})".strip, err: e)
|
686
729
|
# get the given smtp dialog result
|
687
730
|
output = +"#{e.smtpd_result}"
|
688
731
|
|
@@ -690,8 +733,10 @@ module MidiSmtpServer
|
|
690
733
|
rescue StandardError => e
|
691
734
|
# inc number of detected exceptions during this session
|
692
735
|
session[:ctx][:server][:exceptions] += 1
|
736
|
+
# save clone of error object to context
|
737
|
+
session[:ctx][:server][:errors].push(e.clone)
|
693
738
|
# log error info if logging
|
694
|
-
|
739
|
+
on_logging_event(session[:ctx], Logger::ERROR, "#{e} (#{e.class})".strip, err: e)
|
695
740
|
# set default smtp server dialog error
|
696
741
|
output = +"#{Smtpd500Exception.new.smtpd_result}"
|
697
742
|
end
|
@@ -699,7 +744,7 @@ module MidiSmtpServer
|
|
699
744
|
# check result
|
700
745
|
unless output.empty?
|
701
746
|
# log smtp dialog // message data is stored separate
|
702
|
-
|
747
|
+
on_logging_event(session[:ctx], Logger::DEBUG, +'>>> ' << output)
|
703
748
|
# append line feed
|
704
749
|
output << "\r\n"
|
705
750
|
# smtp dialog response
|
@@ -719,21 +764,25 @@ module MidiSmtpServer
|
|
719
764
|
io.print(output) unless io.closed?
|
720
765
|
|
721
766
|
# connection was simply closed / aborted by remote closing socket
|
722
|
-
rescue EOFError
|
767
|
+
rescue EOFError => e
|
723
768
|
# log info but only while debugging otherwise ignore message
|
724
|
-
|
769
|
+
on_logging_event(session[:ctx], Logger::DEBUG, 'Connection lost due abort by client! (EOFError)', err: e)
|
725
770
|
|
726
771
|
rescue StandardError => e
|
772
|
+
# inc number of detected exceptions during this session
|
773
|
+
session[:ctx][:server][:exceptions] += 1
|
774
|
+
# save clone of error object to context
|
775
|
+
session[:ctx][:server][:errors].push(e.clone)
|
727
776
|
# log error info if logging
|
728
|
-
|
777
|
+
on_logging_event(session[:ctx], Logger::ERROR, "#{e} (#{e.class})".strip, err: e)
|
729
778
|
# power down connection
|
730
779
|
# ignore IOErrors when sending final smtp abort return code 421
|
731
780
|
begin
|
732
781
|
output = +"#{Smtpd421Exception.new.smtpd_result}\r\n"
|
733
782
|
# smtp dialog response
|
734
783
|
io.print(output) unless io.closed?
|
735
|
-
rescue StandardError
|
736
|
-
|
784
|
+
rescue StandardError => e
|
785
|
+
on_logging_event(session[:ctx], Logger::DEBUG, 'Can\'t send 421 abort code! (IOError)', err: e)
|
737
786
|
end
|
738
787
|
end
|
739
788
|
|
@@ -790,6 +839,7 @@ module MidiSmtpServer
|
|
790
839
|
# check whether to answer as HELO or EHLO
|
791
840
|
case line
|
792
841
|
when (/^EHLO/i)
|
842
|
+
# rubocop:disable Style/StringConcatenation
|
793
843
|
# reply supported extensions
|
794
844
|
return "250-#{session[:ctx][:server][:helo_response].to_s.strip}\r\n" +
|
795
845
|
# respond with 8BITMIME extension
|
@@ -803,6 +853,7 @@ module MidiSmtpServer
|
|
803
853
|
# respond with STARTTLS if available and not already enabled
|
804
854
|
(@encrypt_mode == :TLS_FORBIDDEN || encrypted?(session[:ctx]) ? '' : "250-STARTTLS\r\n") +
|
805
855
|
'250 OK'
|
856
|
+
# rubocop:enable all
|
806
857
|
else
|
807
858
|
# reply ok only
|
808
859
|
return "250 OK #{session[:ctx][:server][:helo_response].to_s.strip}".strip
|
@@ -847,7 +898,7 @@ module MidiSmtpServer
|
|
847
898
|
# check that not already authenticated
|
848
899
|
raise Smtpd503Exception if authenticated?(session[:ctx])
|
849
900
|
# handle command line
|
850
|
-
auth_data = line.gsub(/^AUTH\ /i, '').strip.gsub(/\s+/, ' ').split
|
901
|
+
auth_data = line.gsub(/^AUTH\ /i, '').strip.gsub(/\s+/, ' ').split
|
851
902
|
# handle auth command
|
852
903
|
case auth_data[0]
|
853
904
|
|
@@ -865,26 +916,31 @@ module MidiSmtpServer
|
|
865
916
|
|
866
917
|
when (/LOGIN/i)
|
867
918
|
# check if auth_id was sent too
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
|
873
|
-
|
874
|
-
|
875
|
-
|
876
|
-
|
877
|
-
|
878
|
-
|
879
|
-
|
919
|
+
case auth_data.length
|
920
|
+
|
921
|
+
when 1
|
922
|
+
# reset auth_challenge
|
923
|
+
session[:auth_challenge] = {}
|
924
|
+
# set sequence for next command input
|
925
|
+
session[:cmd_sequence] = :CMD_AUTH_LOGIN_USER
|
926
|
+
# response code with request for Username
|
927
|
+
return +'' << '334 ' << Base64.strict_encode64('Username:')
|
928
|
+
|
929
|
+
when 2
|
930
|
+
# handle next sequence
|
931
|
+
process_auth_login_user(session, auth_data[1])
|
932
|
+
|
933
|
+
else
|
934
|
+
raise Smtpd500Exception
|
880
935
|
end
|
881
936
|
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
|
886
|
-
|
887
|
-
|
937
|
+
# not supported in case of also unencrypted data delivery
|
938
|
+
# instead of supporting password encryption only, we will
|
939
|
+
# provide optional SMTPS service instead
|
940
|
+
# read discussion on https://github.com/4commerce-technologies-AG/midi-smtp-server/issues/3#issuecomment-126898711
|
941
|
+
#
|
942
|
+
# when (/CRAM-MD5/i)
|
943
|
+
# raise Smtpd500Exception
|
888
944
|
|
889
945
|
else
|
890
946
|
# unknown auth method
|
@@ -921,7 +977,7 @@ module MidiSmtpServer
|
|
921
977
|
session[:cmd_sequence] = :CMD_QUIT
|
922
978
|
return ''
|
923
979
|
|
924
|
-
when (/^MAIL FROM
|
980
|
+
when (/^MAIL FROM:/i)
|
925
981
|
# MAIL
|
926
982
|
# 250 Requested mail action okay, completed
|
927
983
|
# 421 <domain> Service not available, closing transmission channel
|
@@ -938,7 +994,7 @@ module MidiSmtpServer
|
|
938
994
|
# check that authentication is enabled if necessary
|
939
995
|
raise Smtpd530Exception if @auth_mode == :AUTH_REQUIRED && !authenticated?(session[:ctx])
|
940
996
|
# handle command
|
941
|
-
cmd_data = line.gsub(/^MAIL FROM
|
997
|
+
cmd_data = line.gsub(/^MAIL FROM:/i, '').strip
|
942
998
|
# check for BODY= parameter
|
943
999
|
case cmd_data
|
944
1000
|
# test for explicit 7bit
|
@@ -982,7 +1038,7 @@ module MidiSmtpServer
|
|
982
1038
|
# reply ok
|
983
1039
|
return '250 OK'
|
984
1040
|
|
985
|
-
when (/^RCPT TO
|
1041
|
+
when (/^RCPT TO:/i)
|
986
1042
|
# RCPT
|
987
1043
|
# 250 Requested mail action okay, completed
|
988
1044
|
# 251 User not local; will forward to <forward-path>
|
@@ -1006,7 +1062,7 @@ module MidiSmtpServer
|
|
1006
1062
|
# check that authentication is enabled if necessary
|
1007
1063
|
raise Smtpd530Exception if @auth_mode == :AUTH_REQUIRED && !authenticated?(session[:ctx])
|
1008
1064
|
# handle command
|
1009
|
-
cmd_data = line.gsub(/^RCPT TO
|
1065
|
+
cmd_data = line.gsub(/^RCPT TO:/i, '').strip
|
1010
1066
|
# call event to handle data
|
1011
1067
|
return_value = on_rcpt_to_event(session[:ctx], cmd_data)
|
1012
1068
|
if return_value
|
@@ -1125,7 +1181,7 @@ module MidiSmtpServer
|
|
1125
1181
|
end
|
1126
1182
|
|
1127
1183
|
# reset the context of current smtpd dialog
|
1128
|
-
def process_reset_session(session, connection_initialize
|
1184
|
+
def process_reset_session(session, connection_initialize: false)
|
1129
1185
|
# set active command sequence info
|
1130
1186
|
session[:cmd_sequence] = connection_initialize ? :CMD_HELO : :CMD_RSET
|
1131
1187
|
# drop any auth challenge
|
@@ -1149,6 +1205,7 @@ module MidiSmtpServer
|
|
1149
1205
|
helo_response: +'',
|
1150
1206
|
connected: +'',
|
1151
1207
|
exceptions: 0,
|
1208
|
+
errors: [],
|
1152
1209
|
authorization_id: +'',
|
1153
1210
|
authentication_id: +'',
|
1154
1211
|
authenticated: +'',
|
@@ -1211,7 +1268,7 @@ module MidiSmtpServer
|
|
1211
1268
|
# set sequence for next command input
|
1212
1269
|
session[:cmd_sequence] = :CMD_AUTH_LOGIN_PASS
|
1213
1270
|
# response code with request for Password
|
1214
|
-
return '334 '
|
1271
|
+
return +'' << '334 ' << Base64.strict_encode64('Password:')
|
1215
1272
|
end
|
1216
1273
|
|
1217
1274
|
def process_auth_login_pass(session, encoded_auth_response)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: midi-smtp-server
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tom Freudenberg
|
@@ -17,10 +17,12 @@ executables: []
|
|
17
17
|
extensions: []
|
18
18
|
extra_rdoc_files: []
|
19
19
|
files:
|
20
|
+
- CHANGELOG.md
|
20
21
|
- MIT-LICENSE.txt
|
21
22
|
- README.md
|
22
23
|
- lib/midi-smtp-server.rb
|
23
24
|
- lib/midi-smtp-server/exceptions.rb
|
25
|
+
- lib/midi-smtp-server/logger.rb
|
24
26
|
- lib/midi-smtp-server/tls-transport.rb
|
25
27
|
- lib/midi-smtp-server/version.rb
|
26
28
|
homepage:
|
@@ -31,7 +33,7 @@ metadata:
|
|
31
33
|
source_code_uri: https://github.com/4commerce-technologies-AG/midi-smtp-server
|
32
34
|
changelog_uri: https://github.com/4commerce-technologies-AG/midi-smtp-server#changes-and-updates
|
33
35
|
bug_tracker_uri: https://github.com/4commerce-technologies-AG/midi-smtp-server/issues
|
34
|
-
documentation_uri: https://www.rubydoc.info/gems/midi-smtp-server/
|
36
|
+
documentation_uri: https://www.rubydoc.info/gems/midi-smtp-server/3.0.3
|
35
37
|
wiki_uri: https://midi-smtp-server.readthedocs.io/
|
36
38
|
post_install_message:
|
37
39
|
rdoc_options: []
|
@@ -41,7 +43,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
41
43
|
requirements:
|
42
44
|
- - ">="
|
43
45
|
- !ruby/object:Gem::Version
|
44
|
-
version:
|
46
|
+
version: 2.6.0
|
45
47
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
46
48
|
requirements:
|
47
49
|
- - ">="
|