Class: MidiSmtpServer::Smtpd

Inherits:
Object
  • Object
show all
Defined in:
lib/midi-smtp-server.rb

Overview

class for SmtpServer

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(ports: DEFAULT_SMTPD_PORT, hosts: DEFAULT_SMTPD_HOST, pre_fork: DEFAULT_SMTPD_PRE_FORK, max_processings: DEFAULT_SMTPD_MAX_PROCESSINGS, max_connections: nil, crlf_mode: nil, do_dns_reverse_lookup: nil, io_wait_mode: nil, io_wait_available: nil, io_waitreadable_sleep: nil, io_cmd_timeout: nil, io_buffer_chunk_size: nil, io_buffer_max_size: nil, pipelining_extension: nil, internationalization_extensions: nil, proxy_extension: nil, auth_mode: nil, tls_mode: nil, tls_cert_path: nil, tls_key_path: nil, tls_ciphers: nil, tls_methods: nil, tls_cert_cn: nil, tls_cert_san: nil, logger: nil, logger_severity: nil) ⇒ Smtpd

Initialize SMTP Server class

ports

ports to listen on. Allows multiple ports like "2525, 3535" or "2525:3535, 2525". Default value = DEFAULT_SMTPD_PORT

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

pre_fork

number of processes to pre-fork to enable load balancing over multiple cores. Default value = DEFAULT_SMTPD_PRE_FORK

max_processings

maximum number of simultaneous processed connections, this does not limit the number of concurrent TCP connections. Default value = DEFAULT_SMTPD_MAX_PROCESSINGS

max_connections

maximum number of connections, this does limit the number of concurrent TCP connections (not set or nil => unlimited)

crlf_mode

CRLF handling support (:CRLF_ENSURE [default], :CRLF_LEAVE, :CRLF_STRICT)

do_dns_reverse_lookup

flag if this smtp server should do reverse DNS lookups on incoming connections

io_wait_mode

how to wait when no input data is available (DEFAULT_IO_WAIT_MODE)

io_wait_available

fraction of a second to wait in loop when no input data is available (DEFAULT_IO_WAIT_AVAILABLE)

io_cmd_timeout

time in seconds to wait until complete line of data is expected (DEFAULT_IO_CMD_TIMEOUT, nil => disabled test)

io_buffer_chunk_size

size of chunks (bytes) to read non-blocking from socket (DEFAULT_IO_BUFFER_CHUNK_SIZE)

io_buffer_max_size

max size of buffer (max line length) until \lf ist expected (DEFAULT_IO_BUFFER_MAX_SIZE, nil => disabled test)

pipelining_extension

set to true for support of SMTP PIPELINING extension (DEFAULT_PIPELINING_EXTENSION_ENABLED)

internationalization_extensions

set to true for support of SMTP 8BITMIME and SMTPUTF8 extensions (DEFAULT_INTERNATIONALIZATION_EXTENSIONS_ENABLED)

proxy_extension

set to true for supporting PROXY connections (DEFAULT_PROXY_EXTENSION_ENABLED)

auth_mode

enable builtin authentication support (:AUTH_FORBIDDEN [default], :AUTH_OPTIONAL, :AUTH_REQUIRED)

tls_mode

enable builtin TLS support (:TLS_FORBIDDEN [default], :TLS_OPTIONAL, :TLS_REQUIRED)

tls_cert_path

path to tls certificate chain file

tls_key_path

path to tls key file

tls_ciphers

allowed ciphers for connection

tls_methods

allowed methods for protocol

tls_cert_cn

set subject (CN) for self signed certificate "cn.domain.com"

tls_cert_san

set subject alternative (SAN) for self signed certificate, allows multiple names like "alt1.domain.com, alt2.domain.com"

logger

own logger class, otherwise default logger is created (DEPRECATED: use on_logging_event for special needs on logging)

logger_severity

set or override the logger level if given



256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
# File 'lib/midi-smtp-server.rb', line 256

def initialize(
  ports: DEFAULT_SMTPD_PORT,
  hosts: DEFAULT_SMTPD_HOST,
  pre_fork: DEFAULT_SMTPD_PRE_FORK,
  max_processings: DEFAULT_SMTPD_MAX_PROCESSINGS,
  max_connections: nil,
  crlf_mode: nil,
  do_dns_reverse_lookup: nil,
  io_wait_mode: nil,
  io_wait_available: nil,
  io_waitreadable_sleep: nil, # deprecated, but for compatibility
  io_cmd_timeout: nil,
  io_buffer_chunk_size: nil,
  io_buffer_max_size: nil,
  pipelining_extension: nil,
  internationalization_extensions: nil,
  proxy_extension: nil,
  auth_mode: nil,
  tls_mode: nil,
  tls_cert_path: nil,
  tls_key_path: nil,
  tls_ciphers: nil,
  tls_methods: nil,
  tls_cert_cn: nil,
  tls_cert_san: nil,
  logger: nil,
  logger_severity: nil
)
  # create an exposed logger to forward logging to the on_logging_event
  @logger = MidiSmtpServer::ForwardingLogger.new(method(:on_logging_event))

  # external logging
  if logger.nil?
    @logger_protected = Logger.new($stdout)
    @logger_protected.datetime_format = '%Y-%m-%d %H:%M:%S'
    @logger_protected.formatter = proc { |severity, datetime, _progname, msg| "#{datetime}: [#{severity}] #{msg.chomp}\n" }
    @logger_protected.level = logger_severity.nil? ? Logger::DEBUG : logger_severity
  else
    @logger_protected = logger
    @logger_protected.level = logger_severity unless logger_severity.nil?
    logger.warn('Deprecated: "logger" was set on new! Please use "on_logging_event" instead.')
  end

  # initialize as master process
  @is_forked = false
  # no forked worker processes
  @workers = []

  # list of TCPServers
  @tcp_servers = []
  # list of running threads
  @tcp_server_threads = []

  # lists for connections and thread management
  @connections = []
  @processings = []
  @connections_mutex = Mutex.new
  @connections_cv = ConditionVariable.new

  # settings

  # build array of ports
  # split string into array to instantiate multiple servers
  @ports = ports.to_s.delete(' ').split(',')
  # check for at least one port specification
  raise 'Missing port(s) to bind service(s) to!' if @ports.empty?
  # check that not also a '' empty item for port is added to the list
  raise 'Do not use empty value "" for port(s). Please use specific port(s)!' if @ports.include?('')

  # build array of hosts
  # split string into array to instantiate multiple servers
  @hosts = hosts.to_s.delete(' ').split(',')
  #
  # Check source of TCPServer.c at https://github.com/ruby/ruby/blob/trunk/ext/socket/tcpserver.c#L25-L31
  # * Internally, TCPServer.new calls getaddrinfo() function to obtain ip_addresses.
  # * If getaddrinfo() returns multiple ip_addresses,
  # * TCPServer.new TRIES to create a server socket for EACH address and RETURNS FIRST one that is SUCCESSFUL.
  #
  # So for that it was a small portion of luck which address had been used then.
  # We won't support that magic anymore. If wish to bind on all local ip_addresses
  # and interfaces, use new "*" wildcard, otherwise specify ip_addresses and / or hostnames
  #
  # raise exception when found empty or inner empty hosts specification like "" or "a.b.c.d,,e.f.g.h", guess miss-coding
  raise 'No hosts defined! Please use specific hostnames and / or ip_addresses or "*" for wildcard!' if @hosts.empty?
  raise 'Detected an empty identifier in given hosts! Please use specific hostnames and / or ip_addresses or "*" for wildcard!' if @hosts.include?('')

  # build array of addresses for ip_addresses and ports to use
  @addresses = []
  @hosts.each_with_index do |host, index|
    # resolv ip_addresses for host if not wildcard / all hosts
    # if host is "*" wildcard (all) interfaces are used
    # otherwise it will be bind to the found host ip_addresses
    if host == '*'
      ip_addresses_for_host = []
      Socket.ip_address_list.each do |a|
        # test for all local valid ipv4 and ipv6 ip_addresses
        # check question on stackoverflow for details
        # https://stackoverflow.com/questions/59770803/identify-all-relevant-ip-addresses-from-ruby-socket-ip-address-list
        ip_addresses_for_host << a.ip_address if
          (a.ipv4? &&
            (a.ipv4_loopback? || a.ipv4_private? ||
             !(a.ipv4_loopback? || a.ipv4_private? || a.ipv4_multicast?)
            )
          ) ||
          (a.ipv6? &&
            (a.ipv6_loopback? || a.ipv6_unique_local? ||
             !(a.ipv6_loopback? || a.ipv6_unique_local? || a.ipv6_linklocal? || a.ipv6_multicast? || a.ipv6_sitelocal? ||
               a.ipv6_mc_global? || a.ipv6_mc_linklocal? || a.ipv6_mc_nodelocal? || a.ipv6_mc_orglocal? || a.ipv6_mc_sitelocal? ||
               a.ipv6_v4compat? || a.ipv6_v4mapped? || a.ipv6_unspecified?)
            )
          )
      end
    else
      ip_addresses_for_host = Resolv.new.getaddresses(host).uniq
    end
    # get ports for that host entry
    # if ports at index are not specified, use last item
    # of ports array. if multiple ports specified by
    # item like 2525:3535:4545, then all ports will be instantiated
    ports_for_host = (index < @ports.length ? @ports[index] : @ports.last).to_s.split(':')
    # append combination of ip_address and ports to the list of serving addresses
    ip_addresses_for_host.each do |ip_address|
      ports_for_host.each do |port|
        @addresses << "#{ip_address}:#{port}"
      end
    end
  end

  # read pre_fork
  @pre_fork = pre_fork
  raise 'Number of processes to pre-fork (pre_fork) must be zero or a positive integer greater than 1!' unless @pre_fork.is_a?(Integer) && (@pre_fork.zero? || pre_fork?)
  # read max_processings
  @max_processings = max_processings
  raise 'Number of simultaneous processings (max_processings) must be a positive integer!' unless @max_processings.is_a?(Integer) && @max_processings.positive?
  # check max_connections
  @max_connections = max_connections
  raise 'Number of concurrent connections (max_connections) must be nil or a positive integer!' unless @max_connections.nil? || (@max_connections.is_a?(Integer) && @max_connections.positive?)
  raise 'Number of concurrent connections (max_connections) is lower than number of simultaneous processings (max_processings)!' if !@max_connections.nil? && @max_connections < @max_processings

  # check for crlf mode
  @crlf_mode = crlf_mode.nil? ? DEFAULT_CRLF_MODE : crlf_mode
  raise "Unknown CRLF mode #{@crlf_mode} was given!" unless CRLF_MODES.include?(@crlf_mode)

  # always prevent auto resolving hostnames to prevent a delay on socket connect
  BasicSocket.do_not_reverse_lookup = true
  # do reverse lookups manually if enabled by io.addr and io.peeraddr
  # rubocop: disable-next Style/RedundantCondition
  @do_dns_reverse_lookup = do_dns_reverse_lookup.nil? ? true : do_dns_reverse_lookup

  # deprecated compatibility
  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?
  unless io_waitreadable_sleep.nil?
    io_wait_available = io_waitreadable_sleep
    io_wait_mode = :IO_WAIT_SLEEP
    @logger_protected.warn('Deprecated: "io_waitreadable_sleep" was replaced! Please use "io_wait_available" and "io_wait_mode" instead.')
  end
  # io and buffer settings
  @io_wait_mode = io_wait_mode.nil? ? DEFAULT_IO_WAIT_MODE : io_wait_mode
  raise "Unknown io wait mode #{@io_wait_mode} was given!" unless IO_WAIT_MODES.include?(@io_wait_mode)
  @io_wait_available = io_wait_available.nil? ? DEFAULT_IO_WAIT_AVAILABLE : io_wait_available
  raise 'Value of time must be positive (io_wait_available)' unless @io_wait_available.positive?
  @io_cmd_timeout = io_cmd_timeout.nil? ? DEFAULT_IO_CMD_TIMEOUT : io_cmd_timeout
  @io_buffer_chunk_size = io_buffer_chunk_size.nil? ? DEFAULT_IO_BUFFER_CHUNK_SIZE : io_buffer_chunk_size
  @io_buffer_max_size = io_buffer_max_size.nil? ? DEFAULT_IO_BUFFER_MAX_SIZE : io_buffer_max_size

  # smtp extensions
  @pipelining_extension = pipelining_extension.nil? ? DEFAULT_PIPELINING_EXTENSION_ENABLED : pipelining_extension
  @internationalization_extensions = internationalization_extensions.nil? ? DEFAULT_INTERNATIONALIZATION_EXTENSIONS_ENABLED : internationalization_extensions
  @proxy_extension = proxy_extension.nil? ? DEFAULT_PROXY_EXTENSION_ENABLED : proxy_extension
  require 'ipaddr' if @proxy_extension

  # check for authentication
  @auth_mode = auth_mode.nil? ? DEFAULT_AUTH_MODE : auth_mode
  raise "Unknown authentication mode #{@auth_mode} was given!" unless AUTH_MODES.include?(@auth_mode)

  # check for encryption
  @encrypt_mode = tls_mode.nil? ? DEFAULT_ENCRYPT_MODE : tls_mode
  raise "Unknown encryption mode #{@encrypt_mode} was given!" unless ENCRYPT_MODES.include?(@encrypt_mode)
  # SSL transport layer for STARTTLS
  if @encrypt_mode == :TLS_FORBIDDEN
    @tls = nil
  else
    # try to load openssl gem now
    begin
      require 'openssl'
    rescue LoadError
    end
    # check for openssl gem when enabling  TLS / SSL
    raise 'The openssl library gem is not installed!' unless defined?(OpenSSL)
    # check for given CN and SAN
    if tls_cert_cn.nil?
      # build generic set of "valid" self signed certificate CN and SAN
      # using all given hosts and detected ip_addresses but not "*" wildcard
      tls_cert_san = ([] + @hosts + @addresses.map { |address| address.rpartition(':').first }).uniq
      tls_cert_san.delete('*')
      # build generic CN based on first SAN
      if tls_cert_san.first.match?(/^(127\.0?0?0\.0?0?0\.0?0?1|::1|localhost)$/i)
        # used generic localhost.local
        tls_cert_cn = 'localhost.local'
      else
        # use first element from detected hosts and ip_addresses
        # drop that element from SAN
        tls_cert_cn = tls_cert_san.first
        tls_cert_san.slice!(0)
      end
    else
      tls_cert_cn = tls_cert_cn.to_s.strip
      tls_cert_san = tls_cert_san.to_s.delete(' ').split(',')
    end
    # create ssl transport service
    @tls = TlsTransport.new(tls_cert_path, tls_key_path, tls_ciphers, tls_methods, tls_cert_cn, tls_cert_san, @logger)
  end
end

Instance Attribute Details

#auth_mode ⇒ Object (readonly)

Authentication mode



216
217
218
# File 'lib/midi-smtp-server.rb', line 216

def auth_mode
  @auth_mode
end

#crlf_mode ⇒ Object (readonly)

CRLF handling based on conformity to RFC(2)822



202
203
204
# File 'lib/midi-smtp-server.rb', line 202

def crlf_mode
  @crlf_mode
end

#do_dns_reverse_lookup ⇒ Object (readonly)

Flag if should do reverse DNS lookups on incoming connections



214
215
216
# File 'lib/midi-smtp-server.rb', line 214

def do_dns_reverse_lookup
  @do_dns_reverse_lookup
end

#encrypt_mode ⇒ Object (readonly)

Encryption mode



218
219
220
# File 'lib/midi-smtp-server.rb', line 218

def encrypt_mode
  @encrypt_mode
end

#internationalization_extensions ⇒ Object (readonly)

handle SMTP 8BITMIME and SMTPUTF8 extension



222
223
224
# File 'lib/midi-smtp-server.rb', line 222

def internationalization_extensions
  @internationalization_extensions
end

#io_buffer_chunk_size ⇒ Object (readonly)

Bytes to read non-blocking from socket into buffer, as a FixNum



210
211
212
# File 'lib/midi-smtp-server.rb', line 210

def io_buffer_chunk_size
  @io_buffer_chunk_size
end

#io_buffer_max_size ⇒ Object (readonly)

Maximum bytes to read as buffer before expecting completed incoming data line, as a FixNum



212
213
214
# File 'lib/midi-smtp-server.rb', line 212

def io_buffer_max_size
  @io_buffer_max_size
end

#io_cmd_timeout ⇒ Object (readonly)

Maximum time in seconds to wait for a complete incoming data line, as a FixNum



208
209
210
# File 'lib/midi-smtp-server.rb', line 208

def io_cmd_timeout
  @io_cmd_timeout
end

#io_wait_available ⇒ Object (readonly)

Time in fraction of a second to wait on IO::WaitReadable exception



206
207
208
# File 'lib/midi-smtp-server.rb', line 206

def io_wait_available
  @io_wait_available
end

#io_wait_mode ⇒ Object (readonly)

how to wait on IO::WaitReadable exception



204
205
206
# File 'lib/midi-smtp-server.rb', line 204

def io_wait_mode
  @io_wait_mode
end

#logger ⇒ Object (readonly)

logging object, may be overridden by special loggers like YELL or others



227
228
229
# File 'lib/midi-smtp-server.rb', line 227

def logger
  @logger
end

#max_connections ⇒ Object (readonly)

Maximum number of allowed connections, this does limit the TCP connections, as a FixNum



200
201
202
# File 'lib/midi-smtp-server.rb', line 200

def max_connections
  @max_connections
end

#max_processings ⇒ Object (readonly)

Maximum number of simultaneous processed connections, this does not limit the TCP connections itself, as a FixNum



198
199
200
# File 'lib/midi-smtp-server.rb', line 198

def max_processings
  @max_processings
end

#pipelining_extension ⇒ Object (readonly)

handle SMTP PIPELINING extension



220
221
222
# File 'lib/midi-smtp-server.rb', line 220

def pipelining_extension
  @pipelining_extension
end

#proxy_extension ⇒ Object (readonly)

handle PROXY connections



224
225
226
# File 'lib/midi-smtp-server.rb', line 224

def proxy_extension
  @proxy_extension
end

Instance Method Details

#addresses ⇒ Object

Array of ip_address:port which get bound and build up from given hosts and ports



187
188
189
190
# File 'lib/midi-smtp-server.rb', line 187

def addresses
  # prevent original array from being changed
  @addresses.dup
end

#authenticated?(ctx) ⇒ Boolean

check the status of authentication for a given context

Returns:

  • (Boolean)


530
531
532
# File 'lib/midi-smtp-server.rb', line 530

def authenticated?(ctx)
  ctx[:server][:authenticated] && !ctx[:server][:authenticated].to_s.empty?
end

#connections ⇒ Object

Return the current number of connected clients



101
102
103
# File 'lib/midi-smtp-server.rb', line 101

def connections
  @connections.size
end

#connections? ⇒ Boolean

Return if has active connected clients

Returns:

  • (Boolean)


106
107
108
# File 'lib/midi-smtp-server.rb', line 106

def connections?
  @connections.any?
end

#encrypted?(ctx) ⇒ Boolean

check the status of encryption for a given context

Returns:

  • (Boolean)


535
536
537
# File 'lib/midi-smtp-server.rb', line 535

def encrypted?(ctx)
  ctx[:server][:encrypted] && !ctx[:server][:encrypted].to_s.empty?
end

#hosts ⇒ Object

Array of hosts / ip_addresses on which to bind, set as string separated by commata like 'name.domain.com, 127.0.0.1, ::1'



181
182
183
184
# File 'lib/midi-smtp-server.rb', line 181

def hosts
  # prevent original array from being changed
  @hosts.dup
end

#join(sleep_seconds_before_join: 1) ⇒ Object

Join with the server thread(s) before joining the server threads, check and wait optionally a few seconds to let the service(s) come up



148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/midi-smtp-server.rb', line 148

def join(sleep_seconds_before_join: 1)
  # check already existing TCPServers
  return if @tcp_servers.empty?
  # check number of processes to pre-fork

  if pre_fork?
    # create a number of pre-fork processes and attach and join threads within workers
    @pre_fork.times do
      # append worker pid to list of workers
      @workers << fork do
        # set state for a forked process
        @is_forked = true
        # just attach and join the threads to forked worker process
        attach_threads
        join_threads(sleep_seconds_before_join: sleep_seconds_before_join)
      end
    end
    # Blocking wait until each worker process has been finished
    @workers.each { |pid| Process.waitpid(pid) }

  else
    # just join the threads to running single master process (default)
    join_threads(sleep_seconds_before_join: sleep_seconds_before_join)
  end
end

#master? ⇒ Boolean

Return if this is the master process

Returns:

  • (Boolean)


126
127
128
# File 'lib/midi-smtp-server.rb', line 126

def master?
  !@is_forked
end

#on_auth_event(ctx, authorization_id, authentication_id, authentication) ⇒ Object

check the authentication on AUTH if any value returned, that will be used for ongoing processing otherwise the original value will be used for authorization_id

Raises:



521
522
523
524
525
526
527
# File 'lib/midi-smtp-server.rb', line 521

def on_auth_event(ctx, authorization_id, authentication_id, authentication)
  # if authentication is used, override this event
  # and implement your own user management.
  # otherwise all authentications are blocked per default
  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}")
  raise Smtpd535Exception
end

#on_connect_event(ctx) ⇒ Object

event on CONNECTION you may change the ctx[:local_response] and you may change the ctx[:helo_response] in here so that these will be used as local welcome and greeting strings the values are not allowed to return CR nor LF chars and will be stripped



496
497
498
# File 'lib/midi-smtp-server.rb', line 496

def on_connect_event(ctx)
  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]}")
end

#on_disconnect_event(ctx) ⇒ Object

event before DISCONNECT



501
502
503
# File 'lib/midi-smtp-server.rb', line 501

def on_disconnect_event(ctx)
  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]}")
end

#on_helo_event(ctx, helo_data) ⇒ Object

event on HELO/EHLO you may change the ctx[:helo_response] in here so that this will be used as greeting string the value is not allowed to return CR nor LF chars and will be stripped



509
# File 'lib/midi-smtp-server.rb', line 509

def on_helo_event(ctx, helo_data) end

#on_logging_event(_ctx, severity, msg, err: nil) ⇒ Object

event on LOGGING the exposed logger property is from class MidiSmtpServer::ForwardingLogger and pushes any logging message to this on_logging_event. if logging occurs from inside session, the _ctx should be not nil if logging occurs from an error, the err object should be filled



475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
# File 'lib/midi-smtp-server.rb', line 475

def on_logging_event(_ctx, severity, msg, err: nil)
  case severity
    when Logger::INFO
      @logger_protected.info(msg)
    when Logger::WARN
      @logger_protected.warn(msg)
    when Logger::ERROR
      @logger_protected.error(msg)
    when Logger::FATAL
      @logger_protected.fatal(msg)
      err.backtrace.each { |log| @logger_protected.fatal("#{log}") }
    else
      @logger_protected.debug(msg)
  end
end

#on_mail_from_event(ctx, mail_from_data) ⇒ Object

get address send in MAIL FROM if any value returned, that will be used for ongoing processing otherwise the original value will be used



542
# File 'lib/midi-smtp-server.rb', line 542

def on_mail_from_event(ctx, mail_from_data) end

#on_message_data_event(ctx) ⇒ Object

get each message after DATA



559
# File 'lib/midi-smtp-server.rb', line 559

def on_message_data_event(ctx) end

#on_message_data_headers_event(ctx) ⇒ Object

event when headers are received while receiving message DATA



556
# File 'lib/midi-smtp-server.rb', line 556

def on_message_data_headers_event(ctx) end

#on_message_data_receiving_event(ctx) ⇒ Object

event while receiving message DATA



553
# File 'lib/midi-smtp-server.rb', line 553

def on_message_data_receiving_event(ctx) end

#on_message_data_start_event(ctx) ⇒ Object

event when beginning with message DATA



550
# File 'lib/midi-smtp-server.rb', line 550

def on_message_data_start_event(ctx) end

#on_process_line_unknown_event(_ctx, _line) ⇒ Object

event when process_line identifies an unknown command line allows to abort sessions for a series of unknown activities to prevent denial of service attacks etc.

Raises:



564
565
566
567
# File 'lib/midi-smtp-server.rb', line 564

def on_process_line_unknown_event(_ctx, _line)
  # per default we encounter an error
  raise Smtpd500Exception
end

#on_proxy_event(ctx, proxy_data) ⇒ Object

event on PROXY you may raise an exception if you want to block some addresses you also may change or add any value of the hash: source_ip, source_host, source_port, dest_ip, dest_host, dest_port a returned value hash is set as ctx[:proxy]



516
# File 'lib/midi-smtp-server.rb', line 516

def on_proxy_event(ctx, proxy_data) end

#on_rcpt_to_event(ctx, rcpt_to_data) ⇒ Object

get each address send in RCPT TO if any value returned, that will be used for ongoing processing otherwise the original value will be used



547
# File 'lib/midi-smtp-server.rb', line 547

def on_rcpt_to_event(ctx, rcpt_to_data) end

#ports ⇒ Object

Array of ports on which to bind, set as string separated by commata like '2525, 3535' or '2525:3535, 2525'



175
176
177
178
# File 'lib/midi-smtp-server.rb', line 175

def ports
  # prevent original array from being changed
  @ports.dup
end

#pre_fork? ⇒ Boolean

Return if in pre-fork mode

Returns:

  • (Boolean)


121
122
123
# File 'lib/midi-smtp-server.rb', line 121

def pre_fork?
  @pre_fork > 1
end

#processings ⇒ Object

Return the current number of processed clients



111
112
113
# File 'lib/midi-smtp-server.rb', line 111

def processings
  @processings.size
end

#processings? ⇒ Boolean

Return if has active processed clients

Returns:

  • (Boolean)


116
117
118
# File 'lib/midi-smtp-server.rb', line 116

def processings?
  @processings.any?
end

#shutdown ⇒ Object

Schedule a shutdown for the server



91
92
93
# File 'lib/midi-smtp-server.rb', line 91

def shutdown
  @shutdown = true
end

#shutdown? ⇒ Boolean

test for shutdown state

Returns:

  • (Boolean)


96
97
98
# File 'lib/midi-smtp-server.rb', line 96

def shutdown?
  @shutdown
end

#ssl_context ⇒ Object

Current TLS OpenSSL::SSL::SSLContext when initialized by :TLS_OPTIONAL, :TLS_REQUIRED



193
194
195
# File 'lib/midi-smtp-server.rb', line 193

def ssl_context
  @tls&.ssl_context
end

#start ⇒ Object

Create the server



53
54
55
56
57
# File 'lib/midi-smtp-server.rb', line 53

def start
  create_service
  # immediately attach the threads to running single master process (default)
  attach_threads unless pre_fork?
end

#stop(wait_seconds_before_close: 2, gracefully: true) ⇒ Object

Stop the server



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/midi-smtp-server.rb', line 60

def stop(wait_seconds_before_close: 2, gracefully: true)
  begin
    # signal pre_forked workers to stop
    @workers.each { |worker_pid| Process.kill(:TERM, worker_pid) } if pre_fork? && master?
    # always signal shutdown
    shutdown if gracefully
    # wait if some connection(s) need(s) more time to handle shutdown
    sleep wait_seconds_before_close if connections?
    # drop tcp_servers while raising SmtpdStopServiceException
    @connections_mutex.synchronize do
      @tcp_server_threads.each do |tcp_server_thread|
        # use safe navigation (&.) to make sure that obj exists like ... if tcp_server_thread
        tcp_server_thread&.raise SmtpdStopServiceException
      end
    end

  ensure
    # check for removing TCPServers
    @tcp_servers.each { |tcp_server| remove_tcp_server(tcp_server) } if master?
  end

  # wait if some connection(s) still need(s) more time to come down
  sleep wait_seconds_before_close if connections? || !stopped?
end

#stopped? ⇒ Boolean

Returns true if the server has stopped.

Returns:

  • (Boolean)


86
87
88
# File 'lib/midi-smtp-server.rb', line 86

def stopped?
  master? ? @workers.empty? && @tcp_server_threads.empty? && @tcp_servers.empty? : @tcp_server_threads.empty?
end

#worker? ⇒ Boolean

Return if this is a forked worker process

Returns:

  • (Boolean)


131
132
133
# File 'lib/midi-smtp-server.rb', line 131

def worker?
  @is_forked
end

#workers ⇒ Object

Return number of forked worker processes



136
137
138
# File 'lib/midi-smtp-server.rb', line 136

def workers
  @workers.size
end

#workers? ⇒ Boolean

Return if has active forked worker processes

Returns:

  • (Boolean)


141
142
143
# File 'lib/midi-smtp-server.rb', line 141

def workers?
  @workers.any?
end