1
0
مراية لـ https://github.com/postalserver/postal.git تم المزامنة 2025-12-01 05:43:04 +00:00
الملفات
postal/lib/postal/fast_server/client.rb

169 أسطر
5.8 KiB
Ruby

require 'stringio'
module Postal
module FastServer
class Client
class ClientWentAway < StandardError; end
class BadRequest < StandardError; end
def initialize(socket, options)
@raw_socket = socket
@options = options
end
def run
Timeout.timeout(15) do
if Postal.config.fast_server.proxy_protocol
# gets without readahead
line = ""
char = nil
while(char != "\n")
char = @raw_socket.read(1)
line << char
end
line.chomp!
if m = line.match(/\APROXY (.+) (.+) (.+) (.+) (.+)\z/)
@remote_ip = m[2]
else
return false
end
end
if self.ssl?
@socket = OpenSSL::SSL::SSLSocket.new(@raw_socket, self.class.ssl_context)
@socket.accept
else
@socket = @raw_socket
end
Timeout::timeout(20) do
# Read the request line
request = @socket.gets.to_s.chomp
# Split the request into its 3 parts
method, path, protocol = request.split(' ', 3)
raise BadRequest unless method && path && protocol
# Create an empty header set
header_set = HTTPHeaderSet.new
# Read each header and populate the header set
loop do
header = @socket.gets
if header.nil?
raise ClientWentAway
elsif header.chomp == ""
break
else
header_set << HTTPHeader.from_string(header.chomp)
end
end
# At this point, one might want to read the request body, but I don't think we need it.
# Build rack request
server_name, server_port = header_set['Host'].try(:value).to_s.split(":", 2)
request = {
"REQUEST_METHOD" => method,
"SCRIPT_NAME" => "",
"PATH_INFO" => path.split('?', 2)[0],
"QUERY_STRING" => path.split('?', 2)[1],
"SERVER_NAME" => server_name || "",
"SERVER_PORT" => server_name || "",
"rack.version" => [1, 3],
"rack.url_scheme" => ssl? ? "https" : "http",
"rack.input" => StringIO.new(""),
"rack.errors" => STDERR,
"rack.multithread" => true,
"rack.multiprocess" => true,
"rack.run_once" => false,
"rack.hijack" => false,
"rack.hijack_io" => false,
"REMOTE_ADDR" => remote_ip,
}
# Add request headers to rack hash
header_set.headers.each do |header|
request["HTTP_" + header.key.gsub('-', '_').upcase] = header.value
end
# Call the rack app and process the result
code, headers, body = Interface.new.call(request)
response = "HTTP/1.1 #{code} #{Rack::Utils::HTTP_STATUS_CODES[code]}\r\n"
headers.each do |k,v|
response << "#{k}:#{v}\r\n"
end
response << "\r\n"
body.each do |data|
response << data
end
@socket.write(response)
end
end
rescue ClientWentAway, Timeout::Error, Errno::ECONNRESET
# We don't really care if a client has disapeared, close the sockets and carry on.
rescue OpenSSL::SSL::SSLError
# Don't worry about SSL negotiation failures, disconnect and carry on
rescue BadRequest
# We couldn't read a proper HTTP request, disconnect the client
rescue => e
Raven.capture_exception(e)
ensure
@socket.close rescue nil
@raw_socket.close rescue nil
end
def ssl?
!!@options[:ssl]
end
def remote_ip
@remote_ip || @raw_socket.peeraddr[3].sub('::ffff:', '')
end
def self.ssl_context(domain_name = nil)
@ssl_certificates ||= {}
unless @ssl_certificates_refreshed && @ssl_certificates_refreshed > Time.now.utc.beginning_of_day
@ssl_certificates_refreshed = Time.now.utc
@ssl_certificates = {}
end
@ssl_certificates[domain_name] ||= OpenSSL::SSL::SSLContext.new.tap do |ssl_context|
if domain_name
if domain = TrackCertificate.active.where(:domain => domain_name).first
ssl_context.cert = domain.certificate_object
ssl_context.extra_chain_cert = domain.intermediaries_array
ssl_context.key = domain.key_object
end
end
if ssl_context.cert.nil?
certs = Postal.smtp_certificates
ssl_context.cert = certs.shift
ssl_context.extra_chain_cert = certs
ssl_context.key = Postal.smtp_private_key
end
ssl_context.ssl_version = "SSLv23"
ssl_context.ciphers = 'EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS !RC4 !DH'
ssl_context.options = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options] |
OpenSSL::SSL::OP_NO_SSLv2 |
OpenSSL::SSL::OP_NO_SSLv3 |
OpenSSL::SSL::OP_NO_COMPRESSION |
OpenSSL::SSL::OP_CIPHER_SERVER_PREFERENCE
ssl_context.tmp_ecdh_callback = Proc.new do |*a|
OpenSSL::PKey::EC.new("prime256v1")
end
unless domain_name
ssl_context.servername_cb = Proc.new do |ctx, hostname|
self.ssl_context(hostname)
end
end
end
end
end
end
end