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.
@@ -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('.')