midi-smtp-server 2.3.3 → 3.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
5
+ # A small and highly customizable ruby SMTP-Server.
6
+ module MidiSmtpServer
7
+
8
+ # class for Logging and support of on_logging_event
9
+ class ForwardingLogger
10
+
11
+ def initialize(on_logging_event)
12
+ @on_logging_event = on_logging_event
13
+ end
14
+
15
+ def info(msg)
16
+ @on_logging_event.call(nil, Logger::INFO, msg)
17
+ end
18
+
19
+ def warn(msg)
20
+ @on_logging_event.call(nil, Logger::WARN, msg)
21
+ end
22
+
23
+ def error(msg)
24
+ @on_logging_event.call(nil, Logger::ERROR, msg)
25
+ end
26
+
27
+ def fatal(msg)
28
+ @on_logging_event.call(nil, Logger::FATAL, msg)
29
+ end
30
+
31
+ def debug(msg)
32
+ @on_logging_event.call(nil, Logger::DEBUG, msg)
33
+ end
34
+
35
+ end
36
+
37
+ end
@@ -12,16 +12,19 @@ module MidiSmtpServer
12
12
  # Encryption ciphers and methods
13
13
  # check https://www.owasp.org/index.php/TLS_Cipher_String_Cheat_Sheet
14
14
  TLS_CIPHERS_ADVANCED_PLUS = 'DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256'
15
- TLS_CIPHERS_ADVANCED = (TLS_CIPHERS_ADVANCED_PLUS + ':DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256')
16
- TLS_CIPHERS_BROAD_COMP = (TLS_CIPHERS_ADVANCED + ':ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA')
17
- TLS_CIPHERS_WIDEST_COMP = (TLS_CIPHERS_ADVANCED + ':ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA')
18
- TLS_CIPHERS_LEGACY = (TLS_CIPHERS_ADVANCED + ':ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA')
15
+ TLS_CIPHERS_ADVANCED = "#{TLS_CIPHERS_ADVANCED_PLUS}:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256"
16
+ TLS_CIPHERS_BROAD_COMP = "#{TLS_CIPHERS_ADVANCED}:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA"
17
+ TLS_CIPHERS_WIDEST_COMP = "#{TLS_CIPHERS_ADVANCED}:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA"
18
+ TLS_CIPHERS_LEGACY = "#{TLS_CIPHERS_ADVANCED}:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA"
19
19
  TLS_METHODS_ADVANCED = 'TLSv1_2'
20
20
  TLS_METHODS_LEGACY = 'TLSv1_1'
21
21
 
22
22
  # class for TlsTransport
23
23
  class TlsTransport
24
24
 
25
+ # current TLS OpenSSL::SSL::SSLContext
26
+ attr_reader :ssl_context
27
+
25
28
  def initialize(cert_path, key_path, ciphers, methods, cert_cn, cert_san, logger)
26
29
  # if need to debug something while working with openssl
27
30
  # OpenSSL::debug = true
@@ -31,18 +34,11 @@ module MidiSmtpServer
31
34
  @cert_path = cert_path.to_s == '' ? nil : cert_path.strip
32
35
  @key_path = key_path.to_s == '' ? nil : key_path.strip
33
36
  # create SSL context
34
- @ctx = OpenSSL::SSL::SSLContext.new
35
- @ctx.ciphers = ciphers.to_s == '' ? TLS_CIPHERS_ADVANCED_PLUS : ciphers
36
- @ctx.ssl_version = methods.to_s == '' ? TLS_METHODS_ADVANCED : methods
37
+ @ssl_context = OpenSSL::SSL::SSLContext.new
38
+ @ssl_context.ciphers = ciphers.to_s == '' ? TLS_CIPHERS_ADVANCED_PLUS : ciphers
39
+ @ssl_context.ssl_version = methods.to_s == '' ? TLS_METHODS_ADVANCED : methods
37
40
  # check cert_path and key_path
38
- if !@cert_path.nil? || !@key_path.nil?
39
- # if any is set, test the pathes
40
- raise "File \”#{@cert_path}\" does not exist or is not a regular file. Could not load certificate." unless File.file?(@cert_path.to_s)
41
- raise "File \”#{@key_path}\" does not exist or is not a regular file. Could not load private key." unless File.file?(@key_path.to_s)
42
- # try to load certificate and key
43
- @ctx.cert = OpenSSL::X509::Certificate.new(File.open(@cert_path.to_s))
44
- @ctx.key = OpenSSL::PKey::RSA.new(File.open(@key_path.to_s))
45
- else
41
+ if @cert_path.nil?
46
42
  # if none cert_path was set, create a self signed test certificate
47
43
  # and try to setup common subject and subject alt name(s) for cert
48
44
  @cert_cn = cert_cn.to_s.strip
@@ -52,33 +48,58 @@ module MidiSmtpServer
52
48
  @cert_san.each { |san| @cert_san_ip << san if san =~ Resolv::IPv4::Regex || san =~ Resolv::IPv6::Regex }
53
49
  # initialize self certificate and key
54
50
  logger.debug("SSL: using self generated test certificate! CN=#{@cert_cn} SAN=[#{@cert_san.join(',')}]")
55
- @ctx.key = OpenSSL::PKey::RSA.new 4096
56
- @ctx.cert = OpenSSL::X509::Certificate.new
57
- @ctx.cert.version = 2
58
- @ctx.cert.serial = 1
51
+ @ssl_context.key = OpenSSL::PKey::RSA.new 4096
52
+ @ssl_context.cert = OpenSSL::X509::Certificate.new
53
+ @ssl_context.cert.version = 2
54
+ @ssl_context.cert.serial = 1
59
55
  # the subject and the issuer are identical only for test certificate
60
- @ctx.cert.subject = OpenSSL::X509::Name.new [['CN', @cert_cn]]
61
- @ctx.cert.issuer = @ctx.cert.subject
62
- @ctx.cert.public_key = @ctx.key
56
+ @ssl_context.cert.subject = OpenSSL::X509::Name.new [['CN', @cert_cn]]
57
+ @ssl_context.cert.issuer = @ssl_context.cert.subject
58
+ @ssl_context.cert.public_key = @ssl_context.key
63
59
  # valid for 90 days
64
- @ctx.cert.not_before = Time.now
65
- @ctx.cert.not_after = Time.now + 60 * 60 * 24 * 90
60
+ @ssl_context.cert.not_before = Time.now
61
+ @ssl_context.cert.not_after = Time.now + (60 * 60 * 24 * 90)
66
62
  # setup some cert extensions
67
- @ef = OpenSSL::X509::ExtensionFactory.new
68
- @ef.subject_certificate = @ctx.cert
69
- @ef.issuer_certificate = @ctx.cert
70
- @ctx.cert.add_extension(@ef.create_extension('basicConstraints', 'CA:FALSE', false))
71
- @ctx.cert.add_extension(@ef.create_extension('keyUsage', 'digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment', false))
72
- @ctx.cert.add_extension(@ef.create_extension('subjectAltName', (@cert_san.map { |san| "DNS:#{san}" } + @cert_san_ip.map { |ip| "IP:#{ip}" }).join(', '), false))
73
- @ctx.cert.sign @ctx.key, OpenSSL::Digest::SHA256.new
74
- logger.debug("SSL: generated test certificate\r\n#{@ctx.cert.to_text}")
63
+ x509_extension_factory = OpenSSL::X509::ExtensionFactory.new
64
+ x509_extension_factory.subject_certificate = @ssl_context.cert
65
+ x509_extension_factory.issuer_certificate = @ssl_context.cert
66
+ @ssl_context.cert.add_extension(x509_extension_factory.create_extension('basicConstraints', 'CA:FALSE', false))
67
+ @ssl_context.cert.add_extension(x509_extension_factory.create_extension('keyUsage', 'digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment', false))
68
+ @ssl_context.cert.add_extension(x509_extension_factory.create_extension('subjectAltName', (@cert_san.map { |san| "DNS:#{san}" } + @cert_san_ip.map { |ip| "IP:#{ip}" }).join(', '), false))
69
+ @ssl_context.cert.sign @ssl_context.key, OpenSSL::Digest.new('SHA256')
70
+ logger.debug("SSL: generated test certificate\r\n#{@ssl_context.cert.to_text}")
71
+ else
72
+ # if any is set, test the pathes
73
+ raise "File \”#{@cert_path}\" does not exist or is not a regular file. Could not load certificate." unless File.file?(@cert_path.to_s)
74
+ raise "File \”#{@key_path}\" does not exist or is not a regular file. Could not load private key." unless @key_path.nil? || File.file?(@key_path.to_s)
75
+ # try to load certificate and key
76
+ cert_lines = File.read(@cert_path.to_s).lines
77
+ # check if the cert file contains a chain of certs
78
+ cert_indexes = cert_lines.each_with_index.map { |line, index| index if line.downcase.include?('-begin certificate-') }.compact
79
+ # create each cert in the chain
80
+ certs = []
81
+ cert_indexes.each_with_index do |cert_index, current_index|
82
+ end_index = current_index + 1 < cert_indexes.length ? cert_indexes[current_index + 1] : -1
83
+ certs << OpenSSL::X509::Certificate.new(cert_lines[cert_index..end_index].join)
84
+ end
85
+ # add the cert and optional found chain to context
86
+ @ssl_context.cert = certs.first
87
+ @ssl_context.extra_chain_cert = certs[1..]
88
+ # check if key was given by separate file or should be included in cert
89
+ if @key_path.nil?
90
+ key_index = cert_lines.index { |line| line =~ /-begin[^-]+key-/i }
91
+ end_index = cert_lines.index { |line| line =~ /-end[^-]+key-/i }
92
+ @ssl_context.key = OpenSSL::PKey::RSA.new(cert_lines[key_index..end_index].join)
93
+ else
94
+ @ssl_context.key = OpenSSL::PKey::RSA.new(File.open(@key_path.to_s))
95
+ end
75
96
  end
76
97
  end
77
98
 
78
99
  # start ssl connection over existing tcpserver socket
79
100
  def start(io)
80
101
  # start SSL negotiation
81
- ssl = OpenSSL::SSL::SSLSocket.new(io, @ctx)
102
+ ssl = OpenSSL::SSL::SSLSocket.new(io, @ssl_context)
82
103
  # connect to server socket
83
104
  ssl.accept
84
105
  # make sure to close also the underlying io
@@ -5,8 +5,8 @@ module MidiSmtpServer
5
5
 
6
6
  module VERSION
7
7
 
8
- MAJOR = 2
9
- MINOR = 3
8
+ MAJOR = 3
9
+ MINOR = 0
10
10
  TINY = 3
11
11
 
12
12
  STRING = [MAJOR, MINOR, TINY].compact.join('.')