مراية لـ
https://github.com/postalserver/postal.git
تم المزامنة 2026-01-21 15:19:48 +00:00
refactor: remove the fast server
هذا الالتزام موجود في:
@@ -3,10 +3,7 @@
|
|||||||
config/postal.yml
|
config/postal.yml
|
||||||
config/smtp.cert
|
config/smtp.cert
|
||||||
config/smtp.key
|
config/smtp.key
|
||||||
config/lets_encrypt.pem
|
|
||||||
config/signing.key
|
config/signing.key
|
||||||
config/fast_server.cert
|
|
||||||
config/fast_server.key
|
|
||||||
public/assets
|
public/assets
|
||||||
vendor/bundle
|
vendor/bundle
|
||||||
Procfile.local
|
Procfile.local
|
||||||
|
|||||||
3
.gitignore
مباع
3
.gitignore
مباع
@@ -19,10 +19,7 @@
|
|||||||
config/postal.yml
|
config/postal.yml
|
||||||
config/smtp.cert
|
config/smtp.cert
|
||||||
config/smtp.key
|
config/smtp.key
|
||||||
config/lets_encrypt.pem
|
|
||||||
config/signing.key
|
config/signing.key
|
||||||
config/fast_server.cert
|
|
||||||
config/fast_server.key
|
|
||||||
config/postal/**/*
|
config/postal/**/*
|
||||||
|
|
||||||
spec/config/postal.local.yml
|
spec/config/postal.local.yml
|
||||||
|
|||||||
1
Gemfile
1
Gemfile
@@ -27,7 +27,6 @@ gem 'sentry-raven'
|
|||||||
gem 'gelf'
|
gem 'gelf'
|
||||||
gem 'moonrope'
|
gem 'moonrope'
|
||||||
gem 'jwt'
|
gem 'jwt'
|
||||||
gem 'acme-client'
|
|
||||||
gem 'highline', :require => false
|
gem 'highline', :require => false
|
||||||
gem 'resolv', '~> 0.2.1'
|
gem 'resolv', '~> 0.2.1'
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ GIT
|
|||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
acme-client (2.0.0)
|
|
||||||
faraday (~> 0.9, >= 0.9.1)
|
|
||||||
actioncable (5.2.6)
|
actioncable (5.2.6)
|
||||||
actionpack (= 5.2.6)
|
actionpack (= 5.2.6)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
@@ -249,7 +247,6 @@ PLATFORMS
|
|||||||
ruby
|
ruby
|
||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
acme-client
|
|
||||||
annotate
|
annotate
|
||||||
authie (~> 3.0)
|
authie (~> 3.0)
|
||||||
autoprefixer-rails
|
autoprefixer-rails
|
||||||
|
|||||||
1
Procfile
1
Procfile
@@ -1,5 +1,4 @@
|
|||||||
web: bundle exec puma -C config/puma.rb
|
web: bundle exec puma -C config/puma.rb
|
||||||
fast: bundle exec rake postal:fast_server
|
|
||||||
worker: bundle exec ruby script/worker.rb
|
worker: bundle exec ruby script/worker.rb
|
||||||
cron: bundle exec rake postal:cron
|
cron: bundle exec rake postal:cron
|
||||||
smtp: bundle exec rake postal:smtp_server
|
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')}
|
scope :ok, -> { where(:dns_status => 'OK')}
|
||||||
|
|
||||||
after_create :check_dns, :unless => :dns_status
|
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
|
before_validation do
|
||||||
self.server = self.domain.server if self.domain && self.server.nil?
|
self.server = self.domain.server if self.domain && self.server.nil?
|
||||||
@@ -73,16 +71,8 @@ class TrackDomain < ApplicationRecord
|
|||||||
dns_ok?
|
dns_ok?
|
||||||
end
|
end
|
||||||
|
|
||||||
def has_ssl?
|
|
||||||
ssl_certificate && ssl_certificate.active?
|
|
||||||
end
|
|
||||||
|
|
||||||
def use_ssl?
|
def use_ssl?
|
||||||
ssl_enabled? && has_ssl?
|
ssl_enabled?
|
||||||
end
|
|
||||||
|
|
||||||
def ssl_certificate
|
|
||||||
@ssl_certificate ||= TrackCertificate.where(:domain => self.full_name).first
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_domain_belongs_to_server
|
def validate_domain_belongs_to_server
|
||||||
@@ -91,17 +81,4 @@ class TrackDomain < ApplicationRecord
|
|||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
.navBar.navBar--secondary
|
.navBar.navBar--secondary
|
||||||
%ul
|
%ul
|
||||||
%li.navBar__item= link_to "Domains", organization_server_domains_path(organization, @server), :class => ['navBar__link', active_nav == :domains ? 'is-active' : '']
|
%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
|
%li.domainList__check.domainList__check--warning{:title => track_domain.dns_error} CNAME not configured correctly
|
||||||
|
|
||||||
- if track_domain.ssl_enabled?
|
- if track_domain.ssl_enabled?
|
||||||
- if track_domain.has_ssl?
|
%li.domainList__check.domainList__check--neutral= link_to "SSL enabled", [:toggle_ssl, organization, @server, track_domain], :remote => true, :method => :post
|
||||||
%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
|
|
||||||
- else
|
- else
|
||||||
%li.domainList__check.domainList__check--neutral-cross= link_to "SSL disabled", [:toggle_ssl, organization, @server, track_domain], :remote => true, :method => :post
|
%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"
|
run "bundle exec ruby script/default_dkim_record.rb"
|
||||||
;;
|
;;
|
||||||
|
|
||||||
register-lets-encrypt)
|
|
||||||
run "bundle exec ruby script/register_lets_encrypt.rb $2"
|
|
||||||
;;
|
|
||||||
|
|
||||||
auto-upgrade)
|
auto-upgrade)
|
||||||
run "ruby script/auto_upgrade.rb $@"
|
run "ruby script/auto_upgrade.rb $@"
|
||||||
;;
|
;;
|
||||||
|
|||||||
@@ -28,5 +28,9 @@ module Postal
|
|||||||
|
|
||||||
# Disable field_with_errors
|
# Disable field_with_errors
|
||||||
config.action_view.field_error_proc = Proc.new { |t, i| t }
|
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
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -10,10 +10,6 @@ module Clockwork
|
|||||||
SendNotificationsJob.queue(:main)
|
SendNotificationsJob.queue(:main)
|
||||||
end
|
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
|
every 1.hour, 'every-hour', :at => ['**:15'] do
|
||||||
CheckAllDNSJob.queue(:main)
|
CheckAllDNSJob.queue(:main)
|
||||||
ExpireHeldMessagesJob.queue(:main)
|
ExpireHeldMessagesJob.queue(:main)
|
||||||
|
|||||||
@@ -21,20 +21,6 @@ web_server:
|
|||||||
port: 5000
|
port: 5000
|
||||||
max_threads: 5
|
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:
|
main_db:
|
||||||
host: 127.0.0.1
|
host: 127.0.0.1
|
||||||
port: 3306
|
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
|
bind_address: 127.0.0.1
|
||||||
port: 5000
|
port: 5000
|
||||||
|
|
||||||
fast_server:
|
|
||||||
enabled: true
|
|
||||||
|
|
||||||
smtp_server:
|
smtp_server:
|
||||||
port: 2525
|
port: 2525
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ module Postal
|
|||||||
autoload :Countries
|
autoload :Countries
|
||||||
autoload :DKIMHeader
|
autoload :DKIMHeader
|
||||||
autoload :Error
|
autoload :Error
|
||||||
autoload :FastServer
|
|
||||||
autoload :Helpers
|
autoload :Helpers
|
||||||
autoload :HTTP
|
autoload :HTTP
|
||||||
autoload :HTTPSender
|
autoload :HTTPSender
|
||||||
@@ -29,6 +28,7 @@ module Postal
|
|||||||
autoload :SMTPSender
|
autoload :SMTPSender
|
||||||
autoload :SMTPServer
|
autoload :SMTPServer
|
||||||
autoload :SpamCheck
|
autoload :SpamCheck
|
||||||
|
autoload :TrackingMiddleware
|
||||||
autoload :UserCreator
|
autoload :UserCreator
|
||||||
autoload :Version
|
autoload :Version
|
||||||
autoload :Worker
|
autoload :Worker
|
||||||
@@ -37,7 +37,6 @@ module Postal
|
|||||||
def self.eager_load!
|
def self.eager_load!
|
||||||
super
|
super
|
||||||
Postal::MessageDB.eager_load!
|
Postal::MessageDB.eager_load!
|
||||||
Postal::FastServer.eager_load!
|
|
||||||
Postal::SMTPServer.eager_load!
|
Postal::SMTPServer.eager_load!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -151,35 +151,6 @@ module Postal
|
|||||||
end
|
end
|
||||||
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
|
def self.signing_key_path
|
||||||
config_root.join('signing.key')
|
config_root.join('signing.key')
|
||||||
end
|
end
|
||||||
@@ -203,19 +174,11 @@ module Postal
|
|||||||
raise ConfigError, "No config found at #{self.config_file_path}"
|
raise ConfigError, "No config found at #{self.config_file_path}"
|
||||||
end
|
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)
|
unless File.exists?(self.signing_key_path)
|
||||||
raise ConfigError, "No signing key found at #{self.signing_key_path}"
|
raise ConfigError, "No signing key found at #{self.signing_key_path}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.tracking_available?
|
|
||||||
self.config.fast_server.enabled?
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.ip_pools?
|
def self.ip_pools?
|
||||||
self.config.general.use_ip_pools?
|
self.config.general.use_ip_pools?
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
def parse(part, type = nil)
|
def parse(part, type = nil)
|
||||||
if Postal.tracking_available? && @domain.track_clicks?
|
if @domain.track_clicks?
|
||||||
part = insert_links(part, type)
|
part = insert_links(part, type)
|
||||||
end
|
end
|
||||||
|
|
||||||
if Postal.tracking_available? && @domain.track_loads? && type == :html
|
if @domain.track_loads? && type == :html
|
||||||
part = insert_tracking_image(part)
|
part = insert_tracking_image(part)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
105
lib/postal/tracking_middleware.rb
Normal file
105
lib/postal/tracking_middleware.rb
Normal file
@@ -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
|
||||||
end
|
end
|
||||||
|
|
||||||
desc 'Start the fast server'#
|
|
||||||
task :fast_server => :environment do
|
|
||||||
Postal::FastServer::Server.new.run
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Rake::Task['db:migrate'].enhance do
|
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}"
|
puts "Created example config file at #{Postal.config_file_path}"
|
||||||
end
|
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)
|
unless File.exists?(Postal.signing_key_path)
|
||||||
key = OpenSSL::PKey::RSA.new(1024).to_s
|
key = OpenSSL::PKey::RSA.new(1024).to_s
|
||||||
File.open(Postal.signing_key_path, 'w') { |f| f.write(key) }
|
File.open(Postal.signing_key_path, 'w') { |f| f.write(key) }
|
||||||
puts "Created new signing key for DKIM & HTTP requests"
|
puts "Created new signing key for DKIM & HTTP requests"
|
||||||
end
|
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
|
host: postal.example.com
|
||||||
protocol: https
|
protocol: https
|
||||||
|
|
||||||
fast_server:
|
|
||||||
enabled: true
|
|
||||||
bind_address: 0.0.0.0
|
|
||||||
port: 5010
|
|
||||||
ssl_port: 5011
|
|
||||||
|
|
||||||
general:
|
general:
|
||||||
use_ip_pools: false
|
use_ip_pools: false
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ describe Postal::MessageParser do
|
|||||||
track_domain = create(:track_domain, :server => server, :domain => message.domain)
|
track_domain = create(:track_domain, :server => server, :domain => message.domain)
|
||||||
parser = Postal::MessageParser.new(message)
|
parser = Postal::MessageParser.new(message)
|
||||||
expect(parser.actioned?).to be true
|
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
|
expect(parser.tracked_links).to eq 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
المرجع في مشكلة جديدة
حظر مستخدم