مراية لـ
https://github.com/postalserver/postal.git
تم المزامنة 2025-12-01 05:43:04 +00:00
169 أسطر
5.8 KiB
Ruby
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
|