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

refactor: remove the fast server

هذا الالتزام موجود في:
Adam Cooke
2021-07-29 10:54:15 +00:00
الأصل 8e3294ba1a
التزام 17dd7cc757
38 ملفات معدلة مع 116 إضافات و861 حذوفات

عرض الملف

@@ -3,10 +3,7 @@
config/postal.yml
config/smtp.cert
config/smtp.key
config/lets_encrypt.pem
config/signing.key
config/fast_server.cert
config/fast_server.key
public/assets
vendor/bundle
Procfile.local

3
.gitignore مباع
عرض الملف

@@ -19,10 +19,7 @@
config/postal.yml
config/smtp.cert
config/smtp.key
config/lets_encrypt.pem
config/signing.key
config/fast_server.cert
config/fast_server.key
config/postal/**/*
spec/config/postal.local.yml

عرض الملف

@@ -27,7 +27,6 @@ gem 'sentry-raven'
gem 'gelf'
gem 'moonrope'
gem 'jwt'
gem 'acme-client'
gem 'highline', :require => false
gem 'resolv', '~> 0.2.1'

عرض الملف

@@ -9,8 +9,6 @@ GIT
GEM
remote: https://rubygems.org/
specs:
acme-client (2.0.0)
faraday (~> 0.9, >= 0.9.1)
actioncable (5.2.6)
actionpack (= 5.2.6)
nio4r (~> 2.0)
@@ -249,7 +247,6 @@ PLATFORMS
ruby
DEPENDENCIES
acme-client
annotate
authie (~> 3.0)
autoprefixer-rails

عرض الملف

@@ -1,5 +1,4 @@
web: bundle exec puma -C config/puma.rb
fast: bundle exec rake postal:fast_server
worker: bundle exec ruby script/worker.rb
cron: bundle exec rake postal:cron
smtp: bundle exec rake postal:smtp_server

عرض الملف

@@ -1,15 +0,0 @@
class RenewTrackCertificatesJob < Postal::Job
def perform
TrackCertificate.where("renew_after IS NULL OR renew_after <= ?", Time.now).each do |certificate|
log "Renewing certificate for track domain ##{certificate.id} (#{certificate.domain})"
if certificate.get
log "Successfully renewed"
else
certificate.update(:renew_after => 1.day.from_now)
log "Could not be renewed"
end
end
end
end

عرض الملف

@@ -1,108 +0,0 @@
# == Schema Information
#
# Table name: track_certificates
#
# id :integer not null, primary key
# domain :string(255)
# certificate :text(65535)
# intermediaries :text(65535)
# key :text(65535)
# expires_at :datetime
# renew_after :datetime
# verification_path :string(255)
# verification_string :string(255)
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_track_certificates_on_domain (domain)
#
class TrackCertificate < ApplicationRecord
validates :domain, :presence => true, :uniqueness => true
default_value :key, -> { OpenSSL::PKey::RSA.new(2048).to_s }
scope :active, -> { where("certificate IS NOT NULL AND expires_at > ?", Time.now) }
def active?
certificate.present?
end
def get
order = Postal::LetsEncrypt.client.new_order(identifiers: [self.domain])
authorization = order.authorizations.first
challenge = authorization.http
self.verification_path = challenge.filename
self.verification_string = challenge.file_content
self.save!
logger.info "Attempting verification of #{self.domain}"
challenge.request_validation
checks = 0
until challenge.status != "pending"
checks += 1
if checks > 30
logger.info "Status remained at pending for 30 checks"
return false
end
sleep 1
challenge.reload
end
unless challenge.status == "valid"
logger.info "Status was not valid (was: #{challenge.status})"
return false
end
csr = OpenSSL::X509::Request.new
csr.subject = OpenSSL::X509::Name.new([['CN', self.domain, OpenSSL::ASN1::UTF8STRING]])
private_key = OpenSSL::PKey::RSA.new(self.key)
csr.public_key = private_key.public_key
csr.sign(private_key, OpenSSL::Digest::SHA256.new)
logger.info "Getting certificate for #{self.domain}"
order.finalize(:csr => csr)
sleep(1) while order.status == 'processing'
https_cert = order.certificate # => PEM-formatted certificate
cert, chain = https_cert.split(/\r?\n\r?\n/, 2)
self.certificate = cert
self.intermediaries = chain
self.expires_at = certificate_object.not_after
self.renew_after = (self.expires_at - 1.month) + rand(10).days
self.save!
logger.info "Certificate issued (expires on #{self.expires_at}, will renew after #{self.renew_after})"
return true
rescue Acme::Client::Error => e
@retries = 0
if e.is_a?(Acme::Client::Error::BadNonce) && @retries < 5
@retries += 1
logger.info "Bad nounce encountered. Retrying (#{@retries} of 5 attempts)"
sleep 1
verify
else
logger.info "Error: #{e.class} (#{e.message})"
return false
end
end
def certificate_object
OpenSSL::X509::Certificate.new(self.certificate)
end
def intermediaries_array
self.intermediaries.to_s.scan(/-----BEGIN CERTIFICATE-----.+?-----END CERTIFICATE-----/m).map{|c| OpenSSL::X509::Certificate.new(c)}
end
def key_object
OpenSSL::PKey::RSA.new(self.key)
end
def logger
Postal::LetsEncrypt.logger
end
end

عرض الملف

@@ -34,8 +34,6 @@ class TrackDomain < ApplicationRecord
scope :ok, -> { where(:dns_status => 'OK')}
after_create :check_dns, :unless => :dns_status
after_create :create_ssl_certificate_if_missing
after_destroy :delete_ssl_certificate_when_not_in_use
before_validation do
self.server = self.domain.server if self.domain && self.server.nil?
@@ -73,16 +71,8 @@ class TrackDomain < ApplicationRecord
dns_ok?
end
def has_ssl?
ssl_certificate && ssl_certificate.active?
end
def use_ssl?
ssl_enabled? && has_ssl?
end
def ssl_certificate
@ssl_certificate ||= TrackCertificate.where(:domain => self.full_name).first
ssl_enabled?
end
def validate_domain_belongs_to_server
@@ -91,17 +81,4 @@ class TrackDomain < ApplicationRecord
end
end
def create_ssl_certificate_if_missing
unless TrackCertificate.where(:domain => self.full_name).exists?
TrackCertificate.create!(:domain => self.full_name)
end
end
def delete_ssl_certificate_when_not_in_use
others = TrackDomain.includes(:domain).where(:name => self.name, :domains => {:name => self.domain.name})
if others.empty?
TrackCertificate.where(:domain => self.full_name).destroy_all
end
end
end

عرض الملف

@@ -1,5 +1,4 @@
.navBar.navBar--secondary
%ul
%li.navBar__item= link_to "Domains", organization_server_domains_path(organization, @server), :class => ['navBar__link', active_nav == :domains ? 'is-active' : '']
- if Postal.tracking_available?
%li.navBar__item= link_to "Tracking Domains", organization_server_track_domains_path(organization, @server), :class => ['navBar__link', active_nav == :track_domains ? 'is-active' : '']
%li.navBar__item= link_to "Tracking Domains", organization_server_track_domains_path(organization, @server), :class => ['navBar__link', active_nav == :track_domains ? 'is-active' : '']

عرض الملف

@@ -31,10 +31,7 @@
%li.domainList__check.domainList__check--warning{:title => track_domain.dns_error} CNAME not configured correctly
- if track_domain.ssl_enabled?
- if track_domain.has_ssl?
%li.domainList__check.domainList__check--ok= link_to "SSL enabled", [:toggle_ssl, organization, @server, track_domain], :remote => true, :method => :post
- else
%li.domainList__check.domainList__check--neutral= link_to "SSL setup in progress", [:toggle_ssl, organization, @server, track_domain], :remote => true, :method => :post
%li.domainList__check.domainList__check--neutral= link_to "SSL enabled", [:toggle_ssl, organization, @server, track_domain], :remote => true, :method => :post
- else
%li.domainList__check.domainList__check--neutral-cross= link_to "SSL disabled", [:toggle_ssl, organization, @server, track_domain], :remote => true, :method => :post

عرض الملف

@@ -84,10 +84,6 @@ case "$1" in
run "bundle exec ruby script/default_dkim_record.rb"
;;
register-lets-encrypt)
run "bundle exec ruby script/register_lets_encrypt.rb $2"
;;
auto-upgrade)
run "ruby script/auto_upgrade.rb $@"
;;

عرض الملف

@@ -28,5 +28,9 @@ module Postal
# Disable field_with_errors
config.action_view.field_error_proc = Proc.new { |t, i| t }
# Load the tracking server middleware
require 'postal/tracking_middleware'
config.middleware.use Postal::TrackingMiddleware
end
end

عرض الملف

@@ -10,10 +10,6 @@ module Clockwork
SendNotificationsJob.queue(:main)
end
every 15.minutes, 'every-15-minutes', :at => ['**:00', '**:15', '**:30', '**:45'] do
RenewTrackCertificatesJob.queue(:main)
end
every 1.hour, 'every-hour', :at => ['**:15'] do
CheckAllDNSJob.queue(:main)
ExpireHeldMessagesJob.queue(:main)

عرض الملف

@@ -21,20 +21,6 @@ web_server:
port: 5000
max_threads: 5
fast_server:
enabled: false
bind_address:
# Set appropriate IP addresses to listen on. These should be dedicated IP
# addresses just used for this server. You should list IPv4 and IPv6 addresses
# as appropriate.
# - 1.2.3.4
# - abcd:a:b:c:d::1
port: 80
ssl_port: 443
proxy_protocol: false
default_private_key_path: # Defaults to config/fast_server.key
default_tls_certificate_path: # Defaults to config/fast_server.cert
main_db:
host: 127.0.0.1
port: 3306

عرض الملف

@@ -1,17 +0,0 @@
require_relative '../lib/postal/config'
threads_count = Postal.config.fast_server&.max_threads&.to_i || 5
threads threads_count, threads_count
bind_address = Postal.config.fast_server&.bind_address || '127.0.0.1'
bind_port = Postal.config.fast_server&.port&.to_i || 5010
bind "tcp://#{bind_address}:#{bind_port}"
environment Postal.config.rails&.environment || 'development'
prune_bundler
quiet false
rackup File.expand_path('../../lib/postal/fast_server/config.ru', __FILE__)
unless ENV['LOG_TO_STDOUT']
stdout_redirect Postal.app_root.join('log', 'puma.fast.log'), Postal.app_root.join('log', 'puma.fast.log'), true
end
if ENV['APP_ROOT']
directory ENV['APP_ROOT']
end

عرض الملف

@@ -1,19 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDBTCCAe2gAwIBAgIUexj/Ul7/jvs4L6q6H3MSYsW4FbkwDQYJKoZIhvcNAQEL
BQAwEjEQMA4GA1UEAwwHZGVmYXVsdDAeFw0yMTA3MjYxNjQwNTRaFw0zMTA3MjQx
NjQwNTRaMBIxEDAOBgNVBAMMB2RlZmF1bHQwggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQDRNaLmKp75o6I37JCpO2e8IIWBqLTpYGomvAWgKdB8v0AyWMcy
kE5IWZcFICBTJBpRTTHdCfrDnnqvS0AhMqpB3xPw0/DOCDhAxri6Cg4yxecXkFZP
Ckx1ga1sUb9ej8fYBWx9/g6j7waRSGtRB0zwS94tJdJa9XAVj3e97f3CEAr9wPQG
oCC/HQfSgZTho3RPik0x3Wi3Kf9JMHRbBZFDW/a/Z0KjmtFFebJNXBOH7r0Itq82
DY14UqHtG6omZUrGu/Dsx8++USaV03rOq2fSKuol4lEqBWfqI2tzal5hE/46Glbn
ukM+/Iq4+t4ydNsYrhxetsNl3mTWiDkcmYV/AgMBAAGjUzBRMB0GA1UdDgQWBBS+
6ok381yre3ATn7f5cx6VrBg9+jAfBgNVHSMEGDAWgBS+6ok381yre3ATn7f5cx6V
rBg9+jAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAQPFR90MKT
LiwMCCRjpTfMtKYF9rkNTIptCSResfeiwQ3KboBeE/jZzVzk1Pmy5Cvsy/i8zCfz
DcQbxKG82rmUwoPcS/Q0TKjmNcmwiLVvAuR8M8JJnZQiXux+Hb7yKgF/35U6FBuE
jqkWNpuTh5dVEC71hfl6JDuAFrQo+KguppMrbGyT5/yzU99YJR0wI0jjQlBvM9BC
JMf/IA5Q0XZFPpqFSDIud3xqrzbhBgdkBO1MRs/6NAY+pGDxQ5hWlVcn+f/spQZc
03PD+xZjtauUBfsrwk5PpOyGKL4bxgbt2S1aoIgLOjRZwpWFKQwuEcJNi9mDTU6j
mEYzeueHq+7M
-----END CERTIFICATE-----

عرض الملف

@@ -1,28 +0,0 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDRNaLmKp75o6I3
7JCpO2e8IIWBqLTpYGomvAWgKdB8v0AyWMcykE5IWZcFICBTJBpRTTHdCfrDnnqv
S0AhMqpB3xPw0/DOCDhAxri6Cg4yxecXkFZPCkx1ga1sUb9ej8fYBWx9/g6j7waR
SGtRB0zwS94tJdJa9XAVj3e97f3CEAr9wPQGoCC/HQfSgZTho3RPik0x3Wi3Kf9J
MHRbBZFDW/a/Z0KjmtFFebJNXBOH7r0Itq82DY14UqHtG6omZUrGu/Dsx8++USaV
03rOq2fSKuol4lEqBWfqI2tzal5hE/46GlbnukM+/Iq4+t4ydNsYrhxetsNl3mTW
iDkcmYV/AgMBAAECggEBALGFK7bWj5MQsIN3rsCK8dkGV4LP3sLw1uILRVLEaG6R
8i1Ge7CCFor5ylXFDui9h79ZG2iOIUSAY2X/GmmRDjtayRbfIEQTYXhFp1XlVmrq
1s594V6sRHipErkJHLNmmZLJ92dpfo7IMfBxXO6inTyBhAMXRsl73iHNXTPCkEJr
xbrnQPw5g1f7amGS09Iz4CEwVs7PABA7V1uhGRyCESp9nUrdQpHKPfjAIi+KMNZw
0Eb/Bj4NSHNokmbp2TUJ1lRAsFmunjHpgdZ18CR1G8naSjhtd11/HrQ5b0LWVdsE
0Igq1/i0Os75lFi2d8ZSPrxIp9FQd61wrujPXiOFqeECgYEA6z+VulbI1D/CTkft
nvc46xsop+j+N0BYLUzorstA2INCoORHM8rZseJ4QDJcou4gXg6NfdIkoncnTDaa
p0xZfNsP3o3lQHRiDDrzRfrTt8XogYA4MQQzHsEHarwHlRd4Ftsu43rCuLZX7g9r
BE0OsjsY3Wy9qW9UxKGA50XG0EkCgYEA46oJsayy+rIgm9EPn09j9ezGz9o5KOe6
Pjo5CYLKVuVkkQugAozwvf/+SumxVdepA9aUQ635Euw/Dpm/2SPaiBN4H4tLhRNp
YchpiOwWg9ThC26w7jRk0rdBWUIzisZvIeBo1w5ye/HB3Ztl2MAz6Yv3SAOj0mNv
ogXzuQ3yN4cCgYAq8RlowUy5icXzOigC+4fVSsjaFarJ9SHjawWSWqHEo/k2m1Tv
/FhOo1NmDItiZmtcH/XuAL0VNwDiZZlHbqVrKCW2b1posJXxO5WKsaWSBztQ4FHh
iK24MG4lKpuLuJQAQBRIC/GdfBOC7iePym0jVaxNRvs8AYmMtxprnQ4UmQKBgQDQ
p8oktnR/QfyS8oPP7fJetbjtTUMwE1nlqHUYG3AZPjrymOX5EHWgndvRiueGFpcI
NoVCllN+nVmZpx5nA+5I4xcoSgFYIRdnkEfxPvfPsa7kTHoKh6iUW3KgPQWCPiYi
tUtBNzOEF2ooEkHA6z9o4Ggt73AY5hutMqlSxM5nRwKBgBP0OqiyHeLbORHwOZeE
459uSE7UmoJINKgRWqdGprmfQZZvzoI0jvAvRvGIt0DYyS5o25AEAO6Y84DkkXGf
8yNHXJf6F6VPt40S5XOyJ8wikmSlsfLtRWkMsdSXZtsF7zOtE6kTCewxEwrypuzs
jNrQgpSolNPcLy6+2hgbiyu5
-----END PRIVATE KEY-----

عرض الملف

@@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEApj2HYkdZsxxHpaUZV3beSn7p1DeVueDcGTT17Gf/PpV+SHXI
lC1AplNcQPI5iPmg7Fyk2KedlaQQJe8BrCrtBm5X5xNbGDuN5s4N8fZ/AJZ+YZOi
fTEzixYUjuQtJ0KcdofOMBqr4F4DVIG6ZWGoEbEIdNzDCLUeFv5xBmKGRDKmtEDC
SnYcEEFR9ewEHU7KYZbiaGexX0mskKGHMQLiM+xFp6qxsx8isVn+SBo3h94TFdRH
wanFg7ZOUo7Vm1gQkclz7+KpKeWjAsBfuFisrl1cGpN0zODltGGiOQVOymccv93k
7Dw01OAmr5JCfm5Ch9V9pVGlkSykAG93UrfhVwIDAQABAoIBACe+7ECGGJ8nMsYa
3B06UVl0UImppIWliloIfIfCMZeP/HpslsE+tIdPxO3OPA1QybXZTJx0LswZRrcb
FsZIGRWBiki4Kv53Nq0pNNJWAfVtBdhkiOPvwIgPhmjHizgox+na/GQp7FAfiiJJ
Rfx5Rcq9De2K3qKVUxViTjmdksZ5Bw4RzLNAa4QEUw8oZ0QTELvBISo2m6IlLWQq
YRyA2X2bbOjIRbvGfouFLuzqqrtqy76jyamTlTGP3BV7PcJlS+HRhpgXRX1GDANz
MGijGkk+g8umTdj2CIuUbfC6DvYv1XiskPWsMw+1Vel/pKdSbT8trCluG5AGcPeB
08wViSkCgYEA099Nr/fXDqqjulG5uIO624kE5ED3AbsShsCJHQMDjX79xzljjXXm
C5nydj9xRiWB9xt8ptL4ZDOm/EyzFYJmkRWe0eMHU6vUZpbRBRMSHPQhVs4PMDtB
OJ2+sZL/vIaqF6W8VYzohEGVoN34sNDh7MRX283V2XSAgi7PEWdzVpUCgYEAyN0z
2wBftrd85harUfryZwPNh53HrA0Xku3AqKymb5ukS2uQFQ904cvxlIOxEZGVlW1r
HWwEgvd1QkiovT6FExSgQoMMShiRJDeJJVqaR0/UD7hHyvX5P8LKUosQaJUWrA09
Y777tskJX34WWyzJgvHTnQ2TA4MQAaJEHb08+TsCgYEAzbx+PMymuWg72tY3SRQp
o7qW1Gq9MKIOqGlX+6MMlR7mocl5gUkmHMfd6LNMdHhBjsnTLk+YwfxiP4bfM5jP
rjzXXypc2AP4GbKDv7C4GwN5SEiJ+STg/XA4V0jOKqx9iL74df6BXsQs3uwM7O25
JOe4BQoIicOWclOv4U+acU0CgYAr8ckghqpqDSa0KA1/OAnEY96ZZvmCOLMJoB5g
SLV5AXImVfgFw4XsyHOn7E/W6iTxtiiTHUi3ZnAu+jqAfKccj4yoQId1xn4qkEPe
+j16kIpyjfyW+M15F6KwAGCsoMF/Dr55jhT/3mfAjpNRizDjBwkm+QtK32enE9sX
LomidQKBgBxdncRWWwCKtD8MCW/fkK/jGFVhy4dxzgAJCGy1ThuAylkHRscTmszt
Pgp1Layd2fGWM+ohTrzPZAkoa7UqTvDxGe50ODdY4nw01/s4RGVBDbnLOBJ8yUMO
SwAgEqOjEWEyHl4tkcBWcNzu0QWPd7NPmXuh/7dAyIJ66fL3R6y1
-----END RSA PRIVATE KEY-----

عرض الملف

@@ -5,9 +5,6 @@ web_server:
bind_address: 127.0.0.1
port: 5000
fast_server:
enabled: true
smtp_server:
port: 2525

عرض الملف

@@ -9,7 +9,6 @@ module Postal
autoload :Countries
autoload :DKIMHeader
autoload :Error
autoload :FastServer
autoload :Helpers
autoload :HTTP
autoload :HTTPSender
@@ -29,6 +28,7 @@ module Postal
autoload :SMTPSender
autoload :SMTPServer
autoload :SpamCheck
autoload :TrackingMiddleware
autoload :UserCreator
autoload :Version
autoload :Worker
@@ -37,7 +37,6 @@ module Postal
def self.eager_load!
super
Postal::MessageDB.eager_load!
Postal::FastServer.eager_load!
Postal::SMTPServer.eager_load!
end

عرض الملف

@@ -151,35 +151,6 @@ module Postal
end
end
def self.fast_server_default_private_key_path
config.fast_server.default_private_key_path || config_root.join('fast_server.key')
end
def self.fast_server_default_private_key
@fast_server_default_private_key ||= OpenSSL::PKey::RSA.new(File.read(fast_server_default_private_key_path))
end
def self.fast_server_default_certificate_path
config.fast_server.default_tls_certificate_path || config_root.join('fast_server.cert')
end
def self.fast_server_default_certificate_data
@fast_server_default_certificate_data ||= File.read(fast_server_default_certificate_path)
end
def self.fast_server_default_certificates
@fast_server_default_certificates ||= begin
certs = self.fast_server_default_certificate_data.scan(/-----BEGIN CERTIFICATE-----.+?-----END CERTIFICATE-----/m)
certs.map do |c|
OpenSSL::X509::Certificate.new(c)
end.freeze
end
end
def self.lets_encrypt_private_key_path
@lets_encrypt_private_key_path ||= Postal.config_root.join('lets_encrypt.pem')
end
def self.signing_key_path
config_root.join('signing.key')
end
@@ -203,19 +174,11 @@ module Postal
raise ConfigError, "No config found at #{self.config_file_path}"
end
unless File.exists?(self.lets_encrypt_private_key_path)
raise ConfigError, "No Let's Encrypt private key found at #{self.lets_encrypt_private_key_path}"
end
unless File.exists?(self.signing_key_path)
raise ConfigError, "No signing key found at #{self.signing_key_path}"
end
end
def self.tracking_available?
self.config.fast_server.enabled?
end
def self.ip_pools?
self.config.general.use_ip_pools?
end

عرض الملف

@@ -1,12 +0,0 @@
module Postal
module FastServer
extend ActiveSupport::Autoload
eager_autoload do
autoload :Client
autoload :HTTPHeader
autoload :HTTPHeaderSet
autoload :Interface
autoload :Server
end
end
end

عرض الملف

@@ -1,172 +0,0 @@
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
if defined?(Raven)
Raven.capture_exception(e)
end
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?
ssl_context.cert = Postal.fast_server_default_certificates[0]
ssl_context.extra_chain_cert = Postal.fast_server_default_certificates[1..-1]
ssl_context.key = Postal.fast_server_default_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
if ssl_context.respond_to?('tmp_ecdh_callback=')
ssl_context.tmp_ecdh_callback = Proc.new do |*a|
OpenSSL::PKey::EC.new("prime256v1")
end
end
unless domain_name
ssl_context.servername_cb = Proc.new do |ctx, hostname|
self.ssl_context(hostname)
end
end
end
end
end
end
end

عرض الملف

@@ -1,21 +0,0 @@
module Postal
module FastServer
class HTTPHeader
attr_accessor :key, :value
def self.from_string(string)
k, v = string.to_s.split(/\:\s*/, 2)
self.new(k.to_s, v.to_s)
end
def initialize(k, v)
@key = k
@value = v
end
def to_s
@key + ": " + @value
end
end
end
end

عرض الملف

@@ -1,37 +0,0 @@
module Postal
module FastServer
class HTTPHeaderSet
attr_accessor :headers
def initialize
@headers = []
end
def self.from_string_array(array)
header_set = self.new
header_set.headers = array.map{|h|HTTPHeader.from_string(h)}
header_set
end
def select(key)
@headers.select{|h|h.key.downcase == key.downcase}
end
def [](key)
@headers.find{|h|h.key.downcase == key.downcase}
end
def []=(key, value)
self.delete(key)
@headers << HTTPHeader.new(key, value)
end
def delete(key)
@headers.delete_if{|h|h.key.downcase == key.downcase}
end
def <<(header)
@headers << header
end
end
end
end

عرض الملف

@@ -1,96 +0,0 @@
module Postal
module FastServer
class Interface
# TODO: Make this multithreaded? Thread-safe?
TRACKING_PIXEL = File.read(Rails.root.join('app', 'assets', 'images', 'tracking_pixel.png'))
def get_message_db_from_server_token(token)
if server = ::Server.find_by_token(token)
server.message_db
else
nil
end
end
def call(env)
request = Rack::Request.new(env)
if request.path =~ /\A\/(\.well-known\/.*)/
if certificate = ::TrackCertificate.find_by_verification_path($1)
return [200, {'Content-Length' => certificate.verification_string.bytesize.to_s}, [certificate.verification_string]]
else
return [404, {}, ["Verification not found"]]
end
elsif request.path =~ /\A\/img\/([a-z0-9\-]+)\/([a-z0-9\-]+)/i
server_token = $1
message_token = $2
if message_db = get_message_db_from_server_token(server_token)
begin
message = message_db.message(:token => message_token)
message.create_load(request)
rescue Postal::MessageDB::Message::NotFound
# This message has been removed, we'll just continue to serve the image
rescue => e
# Somethign else went wrong. We don't want to stop the image loading though because
# this is our problem. Log this exception though.
if defined?(Raven)
Raven.capture_exception(e)
end
end
source_image = request.params['src']
if source_image.nil?
headers = {}
headers['Content-Type'] = "image/png"
headers['Content-Length'] = TRACKING_PIXEL.bytesize.to_s
return [200, headers, [TRACKING_PIXEL]]
elsif source_image =~ /\Ahttps?\:\/\//
response = Postal::HTTP.get(source_image, :timeout => 3)
if response[:code] == 200
headers = {}
headers['Content-Type'] = response[:headers]['content-type']&.first
headers['Last-Modified'] = response[:headers]['last-modified']&.first
headers['Cache-Control'] = response[:headers]['cache-control']&.first
headers['Etag'] = response[:headers]['etag']&.first
headers['Content-Length'] = response[:body].bytesize.to_s
return [200, headers, [response[:body]]]
else
return [404, {}, ['Not found']]
end
else
return [400, {}, ['Invalid/missing source image']]
end
else
return [404, {}, ['Invalid Server Token']]
end
end
if request.path =~ /\A\/([a-z0-9\-]+)\/([a-z0-9\-]+)/i
server_token = $1
link_token = $2
if message_db = get_message_db_from_server_token(server_token)
if link = message_db.select(:links, :where => {:token => link_token}, :limit => 1).first
time = Time.now.to_f
if link['message_id']
message_db.update(:messages, {:clicked => time}, :where => {:id => link['message_id']})
message_db.insert(:clicks, {:message_id => link['message_id'], :link_id => link['id'], :ip_address => request.ip, :user_agent => request.user_agent, :timestamp => time})
SendWebhookJob.queue(:main, :server_id => message_db.server_id, :event => 'MessageLinkClicked', :payload => {:_message => link['message_id'], :url => link['url'], :token => link['token'], :ip_address => request.ip, :user_agent => request.user_agent})
end
return [307, {'Location' => link['url']}, ["Redirected to: #{link['url']}"]]
else
return [404, {}, ['Link not found']]
end
else
return [404, {}, ['Invalid Server Token']]
end
end
[200, {}, ["Hello."]]
end
end
end
end

عرض الملف

@@ -1,49 +0,0 @@
require 'socket'
require 'openssl'
module Postal
module FastServer
class Server
def run
if Postal.config.fast_server.bind_address.blank?
Postal.logger_for(:fast_server).info "Cannot start fast server because no bind address has been specified"
exit 1
end
Thread.abort_on_exception = true
TrackCertificate
bind_addresses = Postal.config.fast_server.bind_address
bind_addresses = [bind_addresses] unless bind_addresses.is_a?(Array)
server_sockets = bind_addresses.each_with_object({}) do |bind_addr, sockets|
sockets[TCPServer.new(bind_addr, Postal.config.fast_server.port)] = {:ssl => false}
sockets[TCPServer.new(bind_addr, Postal.config.fast_server.ssl_port)] = {:ssl => true}
Postal.logger_for(:fast_server).info("Fast server started listening on HTTP (#{bind_addr}:#{Postal.config.fast_server.port})")
Postal.logger_for(:fast_server).info("Fast server started listening on HTTPS port (#{bind_addr}:#{Postal.config.fast_server.ssl_port})")
end
loop do
client = nil
ios = select(server_sockets.keys, nil, nil, 1)
if ios && server_io = ios[0][0]
begin
client_io = server_io.accept_nonblock
client = Client.new(client_io, server_sockets[server_io])
Thread.new(client) { |t_client| t_client.run }
rescue IO::WaitReadable, Errno::EINTR
# Never mind, guess the client went away
rescue => e
if defined?(Raven)
Raven.capture_exception(e)
end
client_io.close rescue nil
end
end
end
end
end
end
end

عرض الملف

@@ -1,29 +0,0 @@
require 'acme-client'
module Postal
module LetsEncrypt
def self.client
@client ||= Acme::Client.new(:private_key => private_key, :directory => directory)
end
def self.private_key
@private_key ||= OpenSSL::PKey::RSA.new(File.open(Postal.lets_encrypt_private_key_path))
end
def self.directory
@directory ||= Rails.env.development? ? "https://acme-staging-v02.api.letsencrypt.org/directory" : "https://acme-v02.api.letsencrypt.org/directory"
end
def self.register_private_key(email_address)
registration = client.new_account(:contact => "mailto:#{email_address}", :terms_of_service_agreed => true)
logger.info "Successfully registered private key with address #{email_address}"
true
end
def self.logger
Postal.logger_for(:lets_encrypt)
end
end
end

عرض الملف

@@ -80,11 +80,11 @@ module Postal
end
def parse(part, type = nil)
if Postal.tracking_available? && @domain.track_clicks?
if @domain.track_clicks?
part = insert_links(part, type)
end
if Postal.tracking_available? && @domain.track_loads? && type == :html
if @domain.track_loads? && type == :html
part = insert_tracking_image(part)
end

عرض الملف

@@ -0,0 +1,105 @@
module Postal
class TrackingMiddleware
TRACKING_PIXEL = File.read(Rails.root.join('app', 'assets', 'images', 'tracking_pixel.png'))
def initialize(app = nil)
@app = app
end
def call(env)
unless env['HTTP_X_POSTAL_TRACK_HOST'].to_i == 1
return @app.call(env)
end
request = Rack::Request.new(env)
case request.path
when /\A\/img\/([a-z0-9\-]+)\/([a-z0-9\-]+)/i
server_token = $1
message_token = $2
dispatch_image_request(request, server_token, message_token)
when /\A\/([a-z0-9\-]+)\/([a-z0-9\-]+)/i
server_token = $1
link_token = $2
dispatch_redirect_request(request, server_token, link_token)
else
[200, {}, ["Hello."]]
end
end
private
def dispatch_image_request(request, server_token, message_token)
message_db = get_message_db_from_server_token(server_token)
if message_db.nil?
return [404, {}, ['Invalid Server Token']]
end
begin
message = message_db.message(:token => message_token)
message.create_load(request)
rescue Postal::MessageDB::Message::NotFound
# This message has been removed, we'll just continue to serve the image
rescue => e
# Somethign else went wrong. We don't want to stop the image loading though because
# this is our problem. Log this exception though.
Raven.capture_exception(e) if defined?(Raven)
end
source_image = request.params['src']
case source_image
when nil
headers = {}
headers['Content-Type'] = "image/png"
headers['Content-Length'] = TRACKING_PIXEL.bytesize.to_s
return [200, headers, [TRACKING_PIXEL]]
when /\Ahttps?\:\/\//
response = Postal::HTTP.get(source_image, :timeout => 3)
if response[:code] == 200
headers = {}
headers['Content-Type'] = response[:headers]['content-type']&.first
headers['Last-Modified'] = response[:headers]['last-modified']&.first
headers['Cache-Control'] = response[:headers]['cache-control']&.first
headers['Etag'] = response[:headers]['etag']&.first
headers['Content-Length'] = response[:body].bytesize.to_s
return [200, headers, [response[:body]]]
else
return [404, {}, ['Not found']]
end
else
return [400, {}, ['Invalid/missing source image']]
end
end
def dispatch_redirect_request(request, server_token, link_token)
message_db = get_message_db_from_server_token(server_token)
if message_db.nil?
return [404, {}, ['Invalid Server Token']]
end
link = message_db.select(:links, :where => {:token => link_token}, :limit => 1).first
if link.nil?
return [404, {}, ['Link not found']]
end
time = Time.now.to_f
if link['message_id']
message_db.update(:messages, {:clicked => time}, :where => {:id => link['message_id']})
message_db.insert(:clicks, {:message_id => link['message_id'], :link_id => link['id'], :ip_address => request.ip, :user_agent => request.user_agent, :timestamp => time})
SendWebhookJob.queue(:main, :server_id => message_db.server_id, :event => 'MessageLinkClicked', :payload => {:_message => link['message_id'], :url => link['url'], :token => link['token'], :ip_address => request.ip, :user_agent => request.user_agent})
end
return [307, {'Location' => link['url']}, ["Redirected to: #{link['url']}"]]
end
def get_message_db_from_server_token(token)
if server = ::Server.find_by_token(token)
server.message_db
else
nil
end
end
end
end

عرض الملف

@@ -28,11 +28,6 @@ namespace :postal do
end
end
desc 'Start the fast server'#
task :fast_server => :environment do
Postal::FastServer::Server.new.run
end
end
Rake::Task['db:migrate'].enhance do

عرض الملف

@@ -15,33 +15,9 @@ unless File.exist?(Postal.config_file_path)
puts "Created example config file at #{Postal.config_file_path}"
end
unless File.exists?(Postal.lets_encrypt_private_key_path)
key = OpenSSL::PKey::RSA.new(2048).to_s
File.open(Postal.lets_encrypt_private_key_path, 'w') { |f| f.write(key) }
puts "Created new private key for Let's Encrypt"
end
unless File.exists?(Postal.signing_key_path)
key = OpenSSL::PKey::RSA.new(1024).to_s
File.open(Postal.signing_key_path, 'w') { |f| f.write(key) }
puts "Created new signing key for DKIM & HTTP requests"
end
unless File.exists?(Postal.fast_server_default_private_key_path)
key = OpenSSL::PKey::RSA.new(2048).to_s
File.open(Postal.fast_server_default_private_key_path, 'w') { |f| f.write(key) }
puts "Created new private key for default fast server TLS connections"
end
unless File.exist?(Postal.fast_server_default_certificate_path)
cert = OpenSSL::X509::Certificate.new
cert.subject = cert.issuer = OpenSSL::X509::Name.parse("/C=GB/O=Default/OU=Default/CN=default")
cert.not_before = Time.now
cert.not_after = Time.now + (365 * 24 * 60 * 60) * 10
cert.public_key = Postal.fast_server_default_private_key.public_key
cert.serial = 0x0
cert.version = 2
cert.sign Postal.fast_server_default_private_key, OpenSSL::Digest::SHA256.new
File.open(Postal.fast_server_default_certificate_path, 'w') { |f| f.write(cert.to_pem) }
puts "Created new self signed certificate for default fast server TLS connections"
end

عرض الملف

@@ -1,15 +0,0 @@
require_relative '../config/application'
require 'postal/lets_encrypt'
if ARGV[0].nil?
puts "e-mail address missing"
exit 1
end
begin
Postal::LetsEncrypt.register_private_key(ARGV[0])
puts "Done"
rescue => e
puts "#{e.class}: #{e.message}"
exit 1
end

عرض الملف

@@ -1,19 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIC/zCCAeegAwIBAgIBADANBgkqhkiG9w0BAQsFADBDMQswCQYDVQQGEwJHQjEQ
MA4GA1UECgwHRGVmYXVsdDEQMA4GA1UECwwHRGVmYXVsdDEQMA4GA1UEAwwHZGVm
YXVsdDAeFw0xNzA1MDgxMzM2MDBaFw0yNzA1MDYxMzM2MDBaMEMxCzAJBgNVBAYT
AkdCMRAwDgYDVQQKDAdEZWZhdWx0MRAwDgYDVQQLDAdEZWZhdWx0MRAwDgYDVQQD
DAdkZWZhdWx0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA18zeoKrM
m9dlvu5l3k57ocbtB5PKm1fWcUg6XSl4Wtk+Tf9TQx8wuwjEwRvqGdgP9KWtazdW
2eN9z9lmHd3GnC3inmw7UcSkGkeEUQrwbkjihoKAgEpLBNmJoaMWatbdvcgkBvKs
WUg8J4g7yRdX5jWGa0f9Ok0jfYYNQwPZ1iugD9ayBcovvvCX3Xm2oeKCjBeqjejS
wlv1KdYmHQ2AduIx/s9+vxdWtnllqbJ+v60+WqVzwxxJWE4P+kJb+vNbgZ01xFGX
vljT/DzosDVR4OYBI5IumOYwuDz91FY/K9Yi+7QKfI7tq7y9ZKYJQSqZxpnOw2gn
qZuC/ReCBw/wlwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQDHEE35sCpFivcVFoVC
Y7q7u2PhHofjhi1pqPRnZOtiVIYr9xXAe+pTlcv+K9Kc0IenaADEnra0wM0i5imp
b3ObQ0CyRCyKKVbU8t74RYo6K3klZnru61YWte0MdsBFYwYTSAZzK9l+5EICyX1l
KYcXIGgdZJddU7GQ4EASh1BTB7qDQ7GOQRlY1uTyVd+74WdfUez8A6FgRGnx/RRD
0u/l+K8+JIaqgSU1+ZlxPw+KpHUPJhoTikZapyKgaINBoLfyuVd4svf4HsJzjwcj
w8CL5sPAdN8iLeDVkHemX/Tcl4CdWVBodb3Wiqcq7DgTVY2alDNMjCo9YvzuAPVL
99W3
-----END CERTIFICATE-----

عرض الملف

@@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEA18zeoKrMm9dlvu5l3k57ocbtB5PKm1fWcUg6XSl4Wtk+Tf9T
Qx8wuwjEwRvqGdgP9KWtazdW2eN9z9lmHd3GnC3inmw7UcSkGkeEUQrwbkjihoKA
gEpLBNmJoaMWatbdvcgkBvKsWUg8J4g7yRdX5jWGa0f9Ok0jfYYNQwPZ1iugD9ay
BcovvvCX3Xm2oeKCjBeqjejSwlv1KdYmHQ2AduIx/s9+vxdWtnllqbJ+v60+WqVz
wxxJWE4P+kJb+vNbgZ01xFGXvljT/DzosDVR4OYBI5IumOYwuDz91FY/K9Yi+7QK
fI7tq7y9ZKYJQSqZxpnOw2gnqZuC/ReCBw/wlwIDAQABAoIBADj2T/GTBA1Czw2V
+fezObkbPCfa4AkfJ3Chgx5iOu2oGGUYN08pZYCJMP5UMvf9a0DFlsANTHSZMvVx
Sh4qEynYhtAQe5v6zuJM7GVEAPDrdjfRLMAwXBr8nPK4jMtIyiE1OiVHWyz5/z7k
G/vZmI8go8mfp/0CrZKNluSQmKiKQPS1rkDXiVBkF7ifFO6j34IjnihgWsc2W/CL
Wvehfe6RmCK+KfEDREpTSw2/VFuqn4j2FYK8PpweAAJwVWtGGuDB9QSPkKT8WSgK
yZFVW3d+aouBfduHKcja/ccmrGhmBhni80lTjUxakzK7Hw4SKmNTV1FPNfJsE1ez
zPqs8tkCgYEA/a0yBSFuwaLGuwpmchjH32bcq+TR7qNaCAfv07pL7aQXd7nSjDuU
Lz/ohveubizhdsl32kJQBuPNuYeLXxhsulxCumS3tfN31/J4IYU2BXNB20mhKSkP
APneQfc5SS2T3cOdd5hUgsFlSAU2KtWC/qSrXSpR0kEyCGLbOrctJnUCgYEA2cbd
R8QOFzMBpz5Dl3DtFc3qzAyw2lS1m6Nlus3zMxNwX0Diaw9eyiZKOxjNBwN8uQa6
hOf944go2MmEmdRILWiV7lXt1ML5/fD/Kf2n15TI+xl4maETNGtxPMlZs158xsRR
g4qUvRufZp03V3Cu1e9HrypElw9D6K1lv7DvkVsCgYAvxM53gt0tX26WyBWUhLAW
lHMXd2ZEzsPkYUI7F4i2vkChDf/k4k88OoeZ6sgQ/SiTyspj8jrJoVobBrgq9xl6
WmdCXDbv72Hw6zrN7RzIF/Udyxaq/o1RvLuqplhGPGvsxapAXBIF8U8WKc0ScdRS
CUYvrAluU1KNm7f2rYm2BQKBgEEliUqPrrtn6cWzDZs/D91m9SdHYJxfnNhLQAJq
26ba2NHV4iWuumd0nt4g5CyF8YiUJ7XchInNUJLRbdZqt5DF6ZwbnoL4NLqvnlVc
aRpHivv6uaYTlmAnB//sJ+ZNjLwVPGFCUo5jtgKHY2fH8LVU2DfhSBV8Xo87V+XJ
M1FjAoGAEIkg3M/rRku0SMaT0LofchABhW1RXYwBUqUsb4TUF2fdm5iESRg1wP2g
kvMe6394KFseK4/OG2SZf69bMeUbdwi+LA8krIKj4CFhgjdG0Xs4j/POWqCExNDC
pAwJbJcn3o/RSGwIl4fO0o0JwJe8//269pklRQ5CrZt3+LSxy/E=
-----END RSA PRIVATE KEY-----

عرض الملف

@@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAvbtEZ/TZPqMZmbNJH8trasoYkuzkLSsiGBAmKWDw/V3xVkMX
JSY+MYRv6NlKKXZg6SYoZVU8DvxCvUQstJDU+CYHUfQtBthQaWGzh5XdAmjTtb+6
UTyI5UtrwbzWHPgUrwHtN/QpBGsan6A2ASbP5R+HxuUTjzvkMc6otyvdjO+86jSt
mFxGoJXmQSyyGATjMBnhTCPUNjqOI13qgwWgh0beCY2+m78URbJ60NHg+UOBOIUx
Z1e0jY0YCRxb+CuTiBgbBCS3JNlze7JTwLXDgXunV2OPUBA5qd2+4eGsaSzeGxMi
HZaat/Pcn11PEZBEYMuEr2bGb9ANxSzzN4+NKwIDAQABAoIBAQCTH1qdDfVDp++b
CrZCTFfgOBQ0IjORfufyJtugrIZSwfz5Og8BhJUnip3Ivm3Olvfw8uDSKvTmXeBW
qT3NSp2KStrURvZ/X0+DV/qBwcjeLHE2dxTAF6PDwecuFQMm9Yv8ZQ8Qm2kO/wpn
CzG1VOZSsk90AIBZSkTaBnk3iHFE7e66Ru3AVNdFOkn45W459IeZRsh7FnrLG4r4
3ko/07+e1Eb82mgnan3iyMrHYW+PACzcP/GjIJ2Xh3e39IexXeEsp8qkAbUsv0xx
lt1QL22Xi6gRc2GMv+GNIi84FfnJ6P/ju3dVuHiShFncM2zM5cc9snOHaayePyT6
HVVySp0JAoGBAPDJmdd/01W91KcmzZ59N7+uhSMhfvxvZE/fpq1dEwtTIREUW6sL
J7yfJA9KSm+SsOpy6NOG/caWACz+lPZX67Ju4eIO6aiIV5Tj3g8SvdMNvj4a6gfz
lMg7eDtiuk4AeFQroPqmo+IPMd2ia4N6OOIOt5XzaoTHl7wdIrMTFbb/AoGBAMm3
6E7DMR/dcUpe9bOrOtPA8MtGVxhMhR6AR7fpQtFRcjmpOnQnio5EASvOM4tAN/H0
DB5tyuQ9JzHmvH5tENdwXZLnPBvnKl4OobZqaX7SWQA1inb71fr1pjp8rzceABjZ
pFJ3/esN5VJw+f3DTjw8HbBkHZBtvbNNPin/5LXVAoGBAKq698dSjoHcQR1oKSG4
vb+/Og3H4WeSgDkWZvPD7A36mpamrbzhAwL+gC4LSi5EgActBSN/MxANKgC9Xtgx
TSFO+AE2+7yRODCNRdXAPzKYKw2UPd73esZjTIQnI9zM/oUIDnPLlqZiicQSN1OZ
ZR38u3WqjBur/k3XBtScsqf3AoGAGA3VJudByWH3q32tYPJvPmcIj8Tgh+ZADYYQ
h07Kh/llXJjgfo9kh1h2p0mcfeN3iGOoukwvYI4mSV4RZiYNVxNwJR9r3IvxUmv+
Pqlr0RK2SD8aNtwLBTUb0Gej4TeznUL+xFLItanfibgtJ2SNxMMKa0lU+S8M6v+w
BQQdus0CgYBu+21HRlcgTr47TtNlCUP1fCPgkpJXj72P8WBVvJnVi8JjIX++PtcR
QwxJpPxX9MsQiD/2oziEb/pXDrNytuezDwGsNrRmrVpgorL3lXaLi571AjY7urrq
/0ZT4OPIP+Em1F9ugh+u/XxOedLwmcwzz3VmKCYfSJzcQQ3jU8njjA==
-----END RSA PRIVATE KEY-----

عرض الملف

@@ -2,12 +2,6 @@ web:
host: postal.example.com
protocol: https
fast_server:
enabled: true
bind_address: 0.0.0.0
port: 5010
ssl_port: 5011
general:
use_ip_pools: false

عرض الملف

@@ -19,7 +19,7 @@ describe Postal::MessageParser do
track_domain = create(:track_domain, :server => server, :domain => message.domain)
parser = Postal::MessageParser.new(message)
expect(parser.actioned?).to be true
expect(parser.new_body).to match(/\AHello world! http:\/\/click\.#{message.domain.name}/)
expect(parser.new_body).to match(/\AHello world! https:\/\/click\.#{message.domain.name}/)
expect(parser.tracked_links).to eq 1
end
end