مراية لـ
https://github.com/postalserver/postal.git
تم المزامنة 2025-11-30 21:32:30 +00:00
170 أسطر
4.9 KiB
Ruby
170 أسطر
4.9 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module SMTPClient
|
|
class Endpoint
|
|
|
|
class SMTPSessionNotStartedError < StandardError
|
|
end
|
|
|
|
attr_reader :server
|
|
attr_reader :ip_address
|
|
attr_accessor :smtp_client
|
|
|
|
# @param server [Server] the server that this IP address is for
|
|
# @param ip_address [String] the IP address
|
|
def initialize(server, ip_address)
|
|
@server = server
|
|
@ip_address = ip_address
|
|
end
|
|
|
|
# Return a description of this server with its IP address
|
|
#
|
|
# @return [String]
|
|
def description
|
|
"#{@ip_address}:#{@server.port} (#{@server.hostname})"
|
|
end
|
|
|
|
# Return a string representation of this server
|
|
#
|
|
# @return [String]
|
|
def to_s
|
|
description
|
|
end
|
|
|
|
# Return true if this is an IPv6 address
|
|
#
|
|
# @return [Boolean]
|
|
def ipv6?
|
|
@ip_address.include?(":")
|
|
end
|
|
|
|
# Return true if this is an IPv4 address
|
|
#
|
|
# @return [Boolean]
|
|
def ipv4?
|
|
!ipv6?
|
|
end
|
|
|
|
# Start a new SMTP session and store the client with this server for future use as needed
|
|
#
|
|
# @param source_ip_address [IPAddress] the IP address to use as the source address for the connection
|
|
# @param allow_ssl [Boolean] whether to allow SSL for this connection, if false SSL mode is ignored
|
|
#
|
|
# @return [Net::SMTP]
|
|
def start_smtp_session(source_ip_address: nil, allow_ssl: true)
|
|
@smtp_client = Net::SMTP.new(@ip_address, @server.port)
|
|
@smtp_client.open_timeout = Postal::Config.smtp_client.open_timeout
|
|
@smtp_client.read_timeout = Postal::Config.smtp_client.read_timeout
|
|
@smtp_client.tls_hostname = @server.hostname
|
|
|
|
if source_ip_address
|
|
@source_ip_address = source_ip_address
|
|
end
|
|
|
|
if @source_ip_address
|
|
@smtp_client.source_address = ipv6? ? @source_ip_address.ipv6 : @source_ip_address.ipv4
|
|
end
|
|
|
|
if allow_ssl
|
|
case @server.ssl_mode
|
|
when SSLModes::AUTO
|
|
@smtp_client.enable_starttls_auto(self.class.ssl_context_without_verify)
|
|
when SSLModes::STARTTLS
|
|
@smtp_client.enable_starttls(self.class.ssl_context_with_verify)
|
|
when SSLModes::TLS
|
|
@smtp_client.enable_tls(self.class.ssl_context_with_verify)
|
|
else
|
|
@smtp_client.disable_starttls
|
|
@smtp_client.disable_tls
|
|
end
|
|
else
|
|
@smtp_client.disable_starttls
|
|
@smtp_client.disable_tls
|
|
end
|
|
|
|
@smtp_client.start(@source_ip_address ? @source_ip_address.hostname : self.class.default_helo_hostname)
|
|
|
|
@smtp_client
|
|
end
|
|
|
|
# Send a message to the current SMTP session (or create one if there isn't one for this endpoint).
|
|
# If sending messsage encouters some connection errors, retry again after re-establishing the SMTP
|
|
# session.
|
|
#
|
|
# @param raw_message [String] the raw message to send
|
|
# @param mail_from [String] the MAIL FROM address
|
|
# @param rcpt_to [String] the RCPT TO address
|
|
# @param retry_on_connection_error [Boolean] whether to retry the connection if there is a connection error
|
|
#
|
|
# @return [void]
|
|
def send_message(raw_message, mail_from, rcpt_to, retry_on_connection_error: true)
|
|
raise SMTPSessionNotStartedError if @smtp_client.nil? || (@smtp_client && !@smtp_client.started?)
|
|
|
|
@smtp_client.rset_errors
|
|
@smtp_client.send_message(raw_message, mail_from, [rcpt_to])
|
|
rescue Errno::ECONNRESET, Errno::EPIPE, OpenSSL::SSL::SSLError
|
|
if retry_on_connection_error
|
|
finish_smtp_session
|
|
start_smtp_session
|
|
return send_message(raw_message, mail_from, rcpt_to, retry_on_connection_error: false)
|
|
end
|
|
|
|
raise
|
|
end
|
|
|
|
# Reset the current SMTP session for this server if possible otherwise
|
|
# finish the session
|
|
#
|
|
# @return [void]
|
|
def reset_smtp_session
|
|
@smtp_client&.rset
|
|
rescue StandardError
|
|
finish_smtp_session
|
|
end
|
|
|
|
# Finish the current SMTP session for this server if possible.
|
|
#
|
|
# @return [void]
|
|
def finish_smtp_session
|
|
@smtp_client&.finish
|
|
rescue StandardError
|
|
nil
|
|
ensure
|
|
@smtp_client = nil
|
|
end
|
|
|
|
class << self
|
|
|
|
# Return the default HELO hostname to present to SMTP servers that
|
|
# we connect to
|
|
#
|
|
# @return [String]
|
|
def default_helo_hostname
|
|
Postal::Config.dns.helo_hostname ||
|
|
Postal::Config.postal.smtp_hostname ||
|
|
"localhost"
|
|
end
|
|
|
|
def ssl_context_with_verify
|
|
@ssl_context_with_verify ||= begin
|
|
c = OpenSSL::SSL::SSLContext.new
|
|
c.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
|
c.cert_store = OpenSSL::X509::Store.new
|
|
c.cert_store.set_default_paths
|
|
c
|
|
end
|
|
end
|
|
|
|
def ssl_context_without_verify
|
|
@ssl_context_without_verify ||= begin
|
|
c = OpenSSL::SSL::SSLContext.new
|
|
c.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
|
c
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
end
|