مراية لـ
https://github.com/postalserver/postal.git
تم المزامنة 2025-12-01 05:43:04 +00:00
refactor: remove the fast server
هذا الالتزام موجود في:
@@ -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
مباع
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
|
||||
|
||||
1
Gemfile
1
Gemfile
@@ -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
Procfile
1
Procfile
@@ -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' : '']
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
المرجع في مشكلة جديدة
حظر مستخدم