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_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_waitreadable_sleep

seconds to sleep in loop when no input data is available (DEFAULT_IO_WAITREADABLE_SLEEP)

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



250
251
252
253
254
255
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
# File 'lib/midi-smtp-server.rb', line 250

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_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
)
  # 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
  @do_dns_reverse_lookup = do_dns_reverse_lookup.nil? ? true : do_dns_reverse_lookup

  # io and buffer settings
  @io_waitreadable_sleep = io_waitreadable_sleep.nil? ? DEFAULT_IO_WAITREADABLE_SLEEP : io_waitreadable_sleep
  @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_modeObject (readonly)

Authentication mode



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

def auth_mode
  @auth_mode
end

#crlf_modeObject (readonly)

CRLF handling based on conformity to RFC(2)822



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

def crlf_mode
  @crlf_mode
end

#do_dns_reverse_lookupObject (readonly)

Flag if should do reverse DNS lookups on incoming connections



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

def do_dns_reverse_lookup
  @do_dns_reverse_lookup
end

#encrypt_modeObject (readonly)

Encryption mode



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

def encrypt_mode
  @encrypt_mode
end

#internationalization_extensionsObject (readonly)

handle SMTP 8BITMIME and SMTPUTF8 extension



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

def internationalization_extensions
  @internationalization_extensions
end

#io_buffer_chunk_sizeObject (readonly)

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



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

def io_buffer_chunk_size
  @io_buffer_chunk_size
end

#io_buffer_max_sizeObject (readonly)

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



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

def io_buffer_max_size
  @io_buffer_max_size
end

#io_cmd_timeoutObject (readonly)

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



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

def io_cmd_timeout
  @io_cmd_timeout
end

#io_waitreadable_sleepObject (readonly)

Time in seconds to sleep on IO::WaitReadable exception



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

def io_waitreadable_sleep
  @io_waitreadable_sleep
end

#loggerObject (readonly)

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



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

def logger
  @logger
end

#max_connectionsObject (readonly)

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



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

def max_connections
  @max_connections
end

#max_processingsObject (readonly)

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



195
196
197
# File 'lib/midi-smtp-server.rb', line 195

def max_processings
  @max_processings
end

#pipelining_extensionObject (readonly)

handle SMTP PIPELINING extension



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

def pipelining_extension
  @pipelining_extension
end

#proxy_extensionObject (readonly)

handle PROXY connections



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

def proxy_extension
  @proxy_extension
end

Instance Method Details

#addressesObject

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



184
185
186
187
# File 'lib/midi-smtp-server.rb', line 184

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)


511
512
513
# File 'lib/midi-smtp-server.rb', line 511

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

#connectionsObject

Return the current number of connected clients



98
99
100
# File 'lib/midi-smtp-server.rb', line 98

def connections
  @connections.size
end

#connections?Boolean

Return if has active connected clients

Returns:

  • (Boolean)


103
104
105
# File 'lib/midi-smtp-server.rb', line 103

def connections?
  @connections.any?
end

#encrypted?(ctx) ⇒ Boolean

check the status of encryption for a given context

Returns:

  • (Boolean)


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

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

#hostsObject

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



178
179
180
181
# File 'lib/midi-smtp-server.rb', line 178

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



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

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)


123
124
125
# File 'lib/midi-smtp-server.rb', line 123

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:



502
503
504
505
506
507
508
# File 'lib/midi-smtp-server.rb', line 502

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



477
478
479
# File 'lib/midi-smtp-server.rb', line 477

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



482
483
484
# File 'lib/midi-smtp-server.rb', line 482

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



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

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



456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
# File 'lib/midi-smtp-server.rb', line 456

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



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

def on_mail_from_event(ctx, mail_from_data) end

#on_message_data_event(ctx) ⇒ Object

get each message after DATA <message>



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

def on_message_data_event(ctx) end

#on_message_data_headers_event(ctx) ⇒ Object

event when headers are received while receiving message DATA



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

def on_message_data_headers_event(ctx) end

#on_message_data_receiving_event(ctx) ⇒ Object

event while receiving message DATA



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

def on_message_data_receiving_event(ctx) end

#on_message_data_start_event(ctx) ⇒ Object

event when beginning with message DATA



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

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:



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

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]



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

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



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

def on_rcpt_to_event(ctx, rcpt_to_data) end

#portsObject

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



172
173
174
175
# File 'lib/midi-smtp-server.rb', line 172

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

#pre_fork?Boolean

Return if in pre-fork mode

Returns:

  • (Boolean)


118
119
120
# File 'lib/midi-smtp-server.rb', line 118

def pre_fork?
  @pre_fork > 1
end

#processingsObject

Return the current number of processed clients



108
109
110
# File 'lib/midi-smtp-server.rb', line 108

def processings
  @processings.size
end

#processings?Boolean

Return if has active processed clients

Returns:

  • (Boolean)


113
114
115
# File 'lib/midi-smtp-server.rb', line 113

def processings?
  @processings.any?
end

#shutdownObject

Schedule a shutdown for the server



88
89
90
# File 'lib/midi-smtp-server.rb', line 88

def shutdown
  @shutdown = true
end

#shutdown?Boolean

test for shutdown state

Returns:

  • (Boolean)


93
94
95
# File 'lib/midi-smtp-server.rb', line 93

def shutdown?
  @shutdown
end

#ssl_contextObject

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



190
191
192
# File 'lib/midi-smtp-server.rb', line 190

def ssl_context
  @tls&.ssl_context
end

#startObject

Create the server



50
51
52
53
54
# File 'lib/midi-smtp-server.rb', line 50

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



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

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)


83
84
85
# File 'lib/midi-smtp-server.rb', line 83

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)


128
129
130
# File 'lib/midi-smtp-server.rb', line 128

def worker?
  @is_forked
end

#workersObject

Return number of forked worker processes



133
134
135
# File 'lib/midi-smtp-server.rb', line 133

def workers
  @workers.size
end

#workers?Boolean

Return if has active forked worker processes

Returns:

  • (Boolean)


138
139
140
# File 'lib/midi-smtp-server.rb', line 138

def workers?
  @workers.any?
end