مراية لـ
https://github.com/postalserver/postal.git
تم المزامنة 2026-03-03 14:24:06 +00:00
Compare commits
21 الالتزامات
2.0.0-beta
...
2.1.0
| المؤلف | SHA1 | التاريخ | |
|---|---|---|---|
|
|
751a249205 | ||
|
|
f2deb94998 | ||
|
|
253f4a5719 | ||
|
|
6570ff1f77 | ||
|
|
232b605f5b | ||
|
|
6004767129 | ||
|
|
b0e9bc10a4 | ||
|
|
d808d15c28 | ||
|
|
a1277baba5 | ||
|
|
724325a1b9 | ||
|
|
dfe1970afb | ||
|
|
47fbe6a470 | ||
|
|
2a11e0c0a5 | ||
|
|
32b42af2f9 | ||
|
|
476129cc1b | ||
|
|
4880eee1d9 | ||
|
|
4404b3e02c | ||
|
|
1247dae2e0 | ||
|
|
9bcb5993fa | ||
|
|
419a4e0f46 | ||
|
|
4ba2195ae0 |
@@ -1,9 +1,13 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 🙏 Help and Support
|
||||
url: https://github.com/postalhq/postal/discussions/new?category=Help-and-Support
|
||||
about: Get friendly support from the community on Github Discussions.
|
||||
- name: 💻 Installation help
|
||||
url: https://github.com/postalhq/postal/discussions/new?category=Installation-help
|
||||
about: If you have questions about running Postal on your servers, use GitHub Discussions.
|
||||
|
||||
- name: 🙏 Help with using Postal
|
||||
url: https://github.com/postalserver/postal/discussions/new?category=Help-with-using-Postal
|
||||
about: If you need help using Postal's features, use GitHub Discussions.
|
||||
|
||||
- name: 🦊 Feature Suggestions
|
||||
url: https://github.com/postalhq/postal/discussions/new?category=Feature-Suggestions
|
||||
about: Suggest a new feature that should be added to Postal.
|
||||
about: Suggest a new feature that should be added to Postal.
|
||||
|
||||
2
.gitignore
مباع
2
.gitignore
مباع
@@ -28,3 +28,5 @@ public/assets
|
||||
vendor/bundle
|
||||
|
||||
Procfile.local
|
||||
VERSION
|
||||
|
||||
|
||||
24
CHANGELOG.md
24
CHANGELOG.md
@@ -2,6 +2,30 @@
|
||||
|
||||
This file contains all the latest changes and updates to Postal.
|
||||
|
||||
## 2.1.0
|
||||
|
||||
### Features
|
||||
|
||||
- support for configuring the default spam threshold values for new servers ([724325](https://github.com/postalserver/postal/commit/724325a1b97d61ef1e134240e4f70aaad39dbf98))
|
||||
- support for using rspamd for spam filtering ([a1277b](https://github.com/postalserver/postal/commit/a1277baba56ea6d6b4da4bba87b00cd3dbf0305e))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **dkim:** fixes timing race condition when signing emails ([232b60](https://github.com/postalserver/postal/commit/232b605f5bb8ab61156e1fb9860705fed017ed41))
|
||||
- **docker:** fixes issue caused by changes to underlying ruby:2.6 image ([6570ff](https://github.com/postalserver/postal/commit/6570ff1f7797ff9a307dd96ed4ff37be14bf79ab))
|
||||
|
||||
## 2.0.0
|
||||
|
||||
### Features
|
||||
|
||||
- **ui:** add footer with links to docs and discussions ([1247da](https://github.com/postalserver/postal/commit/1247dae2e060a695a13a30ba072ca5e6dea45202))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **dkim:** ensure DKIM-Signature headers are appropriately wrapped ([476129](https://github.com/postalserver/postal/commit/476129cc1ba44e9014768d5ba7193587f78cb5d5))
|
||||
- **docs:** update port numbers to specify the actual port number the SMTP server is listening on ([4404b3](https://github.com/postalserver/postal/commit/4404b3e02c1722808157c3f590310ead9e28641d))
|
||||
- **logging:** fix spelling of graylog ([2a11e0](https://github.com/postalserver/postal/commit/2a11e0c0a5b7c7f630af28cf4af5511d9bce6dda))
|
||||
|
||||
## 2.0.0-beta.1
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
FROM ruby:2.6 AS base
|
||||
FROM ruby:2.6-buster AS base
|
||||
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
software-properties-common \
|
||||
&& apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 0xF1656F24C74CD1D8 \
|
||||
&& add-apt-repository 'deb [arch=amd64,i386,ppc64el] http://mirrors.coreix.net/mariadb/repo/10.1/ubuntu xenial main' \
|
||||
software-properties-common dirmngr apt-transport-https \
|
||||
&& apt-key adv --fetch-keys 'https://mariadb.org/mariadb_release_signing_key.asc' \
|
||||
&& add-apt-repository 'deb [arch=amd64,arm64,ppc64el] https://mirrors.xtom.nl/mariadb/repo/10.6/debian buster main' \
|
||||
&& (curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -) \
|
||||
&& (echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list) \
|
||||
&& (curl -sL https://deb.nodesource.com/setup_12.x | bash -) \
|
||||
|
||||
12
Gemfile.lock
12
Gemfile.lock
@@ -132,7 +132,7 @@ GEM
|
||||
marcel (1.0.1)
|
||||
method_source (1.0.0)
|
||||
mini_mime (1.1.0)
|
||||
mini_portile2 (2.5.3)
|
||||
mini_portile2 (2.6.1)
|
||||
minitest (5.14.4)
|
||||
mongo (2.6.2)
|
||||
bson (>= 4.3.0, < 5.0.0)
|
||||
@@ -146,13 +146,13 @@ GEM
|
||||
nilify_blanks (1.3.0)
|
||||
activerecord (>= 3.0.0)
|
||||
activesupport (>= 3.0.0)
|
||||
nio4r (2.5.7)
|
||||
nokogiri (1.11.7)
|
||||
mini_portile2 (~> 2.5.0)
|
||||
nio4r (2.5.8)
|
||||
nokogiri (1.12.5)
|
||||
mini_portile2 (~> 2.6.1)
|
||||
racc (~> 1.4)
|
||||
puma (4.3.8)
|
||||
puma (4.3.10)
|
||||
nio4r (~> 2.0)
|
||||
racc (1.5.2)
|
||||
racc (1.6.0)
|
||||
rack (2.2.3)
|
||||
rack-test (1.1.0)
|
||||
rack (>= 1.0, < 3)
|
||||
|
||||
@@ -6,6 +6,6 @@
|
||||
|
||||
* [Documentation](https://docs.postalserver.io)
|
||||
* [Installation Instructions](https://docs.postalserver.io/install/prerequisites)
|
||||
* [FAQs](https://github.com/atech/postal/wiki/FAQs) & [Features](https://github.com/atech/postal/wiki/Features)
|
||||
* [FAQs](https://docs.postalserver.io/welcome/faqs) & [Features](https://docs.postalserver.io/welcome/feature-list)
|
||||
* [Ask for help](https://github.com/postalserver/postal/discussions)
|
||||
* [Slack Channel](https://slack.k.io)
|
||||
|
||||
15
SECURITY.md
Normal file
15
SECURITY.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
We only support updates to the 2.x versions of Postal.
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 2.x.x | :white_check_mark: |
|
||||
| < 2.0 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you discover a vulnerability in Postal, please do not post an issue on GitHub. Instead you should send an
|
||||
e-mail to security@postalserver.io with details. We will get back to you directly.
|
||||
9
app/assets/images/icon.svg
Normal file
9
app/assets/images/icon.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg width="180px" height="204px" viewBox="0 0 180 204" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Postal---Solo-Avatar" transform="translate(0.950000, 0.940000)" fill="#999999" fill-rule="nonzero">
|
||||
<polygon id="Path" points="52.89 202.12 29.89 177.1 29.89 26.8 0 26.8 18.24 118.45 0 202.12"></polygon>
|
||||
<path d="M118.87,0 L34.97,0 L34.97,175.32 L94.05,175.32 L94.05,118.45 L118.84,118.45 C151.551826,118.45 178.07,91.9318257 178.07,59.22 L178.07,59.22 C178.064484,26.5237913 151.566205,0.0165601674 118.87,0 L118.87,0 Z" id="Path"></path>
|
||||
<polygon id="Path" points="39.18 179.89 59.62 202.12 59.62 179.89"></polygon>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
بعد العرض: | الارتفاع: | الحجم: 830 B |
25
app/assets/stylesheets/application/components/_footer.scss
Normal file
25
app/assets/stylesheets/application/components/_footer.scss
Normal file
@@ -0,0 +1,25 @@
|
||||
.footer__links {
|
||||
display:flex;
|
||||
margin-left:auto;
|
||||
align-items: center;
|
||||
font-size:13px;
|
||||
color:#999;
|
||||
li {
|
||||
height:24px;
|
||||
|
||||
}
|
||||
li + li {
|
||||
margin-left:18px;
|
||||
}
|
||||
a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.footer__name {
|
||||
height:16px;
|
||||
background:image-url('icon.svg') no-repeat 0 0;
|
||||
background-size:16px;
|
||||
padding-left:22px;
|
||||
font-weight:bold;
|
||||
}
|
||||
@@ -12,3 +12,10 @@
|
||||
overflow-y:scroll;
|
||||
overflow-x:hidden;
|
||||
}
|
||||
|
||||
.siteContent__footer {
|
||||
border-top:1px solid #efefef;
|
||||
margin-top:20px;
|
||||
padding:25px;
|
||||
display:flex;
|
||||
}
|
||||
|
||||
@@ -69,8 +69,8 @@ class Server < ApplicationRecord
|
||||
default_value :raw_message_retention_days, -> { 30 }
|
||||
default_value :raw_message_retention_size, -> { 2048 }
|
||||
default_value :message_retention_days, -> { 60 }
|
||||
default_value :spam_threshold, -> { 5.0 }
|
||||
default_value :spam_failure_threshold, -> { 20.0 }
|
||||
default_value :spam_threshold, -> { Postal.config.general.default_spam_threshold }
|
||||
default_value :spam_failure_threshold, -> { Postal.config.general.default_spam_failure_threshold }
|
||||
|
||||
validates :name, :presence => true, :uniqueness => {:scope => :organization_id}
|
||||
validates :mode, :inclusion => {:in => MODES}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
%p.pageContent__definitionCode= Postal.config.dns.smtp_server_hostname
|
||||
%dt Port
|
||||
%dd
|
||||
%p.pageContent__definitionCode 25 or 2525
|
||||
%p.pageContent__definitionCode= Postal.config.smtp_server.port
|
||||
%p.pageContent__definitionText
|
||||
The SMTP service supports STARTTLS if you wish to send messages securely. Be aware that security
|
||||
cannot guaranteed all the way to their final destination.
|
||||
@@ -57,4 +57,4 @@
|
||||
.u-margin
|
||||
%h2.pageContent__subTitle Sending over HTTP using our API
|
||||
%p.pageContent__text
|
||||
For full information about how to use our HTTP API, please #{link_to 'see the documentation', 'https://github.com/atech/postal/wiki/Using-the-API', :class => "u-link"}.
|
||||
For full information about how to use our HTTP API, please #{link_to 'see the documentation', 'https://docs.postalserver.io/developer/api', :class => "u-link"}.
|
||||
|
||||
@@ -52,6 +52,10 @@
|
||||
|
||||
%section.siteContent__main
|
||||
= yield
|
||||
%footer.siteContent__footer
|
||||
%ul.footer__links
|
||||
%li.footer__name
|
||||
Powered by #{link_to "Postal", "https://postalserver.io", target: '_blank'} #{Postal.version}.
|
||||
%li= link_to "Documentation", "https://docs.postalserver.io", target: '_blank'
|
||||
%li= link_to "Ask for help", "https://discussions.postalserver.io", target: '_blank'
|
||||
|
||||
- if logged_in?
|
||||
<!--- Postal Version #{Postal.version} -->
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
.fieldSet__input
|
||||
= f.select :ssl_enabled, [["Yes - use SSL for tracking whenever possible", true], ["No - never use SSL for tracking", false]], {}, :class => 'input input--select'
|
||||
%p.fieldSet__text
|
||||
If enabled, we'll try to remove the replies/signatures from the plain body and send them separately to the rest of the body.
|
||||
This is useful if you just want to see the latest message in a thread.
|
||||
If enabled, we'll use https for the tracking domain when replacing links and images. Please note that a SSL certificate
|
||||
should be installed on the tracking domain if enabled.
|
||||
|
||||
.fieldSet__field
|
||||
= f.label :track_loads, :class => 'fieldSet__label'
|
||||
|
||||
@@ -15,6 +15,8 @@ general:
|
||||
maximum_hold_expiry_days: 7
|
||||
suppression_list_removal_delay: 30
|
||||
use_local_ns_for_domains: false
|
||||
default_spam_threshold: 5.0
|
||||
default_spam_failure_threshold: 20.0
|
||||
|
||||
web_server:
|
||||
bind_address: 127.0.0.1
|
||||
@@ -34,7 +36,7 @@ logging:
|
||||
root: # Automatically determined based on config root
|
||||
max_log_file_size: 20
|
||||
max_log_files: 10
|
||||
greylog:
|
||||
graylog:
|
||||
host:
|
||||
port: 12201
|
||||
|
||||
@@ -100,6 +102,14 @@ rails:
|
||||
environment: production
|
||||
secret_key:
|
||||
|
||||
rspamd:
|
||||
enabled: false
|
||||
host: 127.0.0.1
|
||||
port: 11334
|
||||
ssl: false
|
||||
password: null
|
||||
flags: null
|
||||
|
||||
spamd:
|
||||
enabled: false
|
||||
host: 127.0.0.1
|
||||
|
||||
@@ -15,6 +15,8 @@ module Postal
|
||||
autoload :Job
|
||||
autoload :MessageDB
|
||||
autoload :MessageInspection
|
||||
autoload :MessageInspector
|
||||
autoload :MessageInspectors
|
||||
autoload :MessageParser
|
||||
autoload :MessageRequeuer
|
||||
autoload :MXLookup
|
||||
@@ -37,6 +39,7 @@ module Postal
|
||||
super
|
||||
Postal::MessageDB.eager_load!
|
||||
Postal::SMTPServer.eager_load!
|
||||
Postal::MessageInspectors.eager_load!
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -11,7 +11,7 @@ module Postal
|
||||
|
||||
def add(severity, message = nil, progname = nil)
|
||||
super
|
||||
if severity >= @level && n = self.class.greylog_notifier
|
||||
if severity >= @level && n = self.class.graylog_notifier
|
||||
begin
|
||||
if message.nil?
|
||||
message = block_given? ? yield : progname
|
||||
@@ -26,12 +26,12 @@ module Postal
|
||||
true
|
||||
end
|
||||
|
||||
def self.greylog?
|
||||
!!Postal.config.logging.greylog&.host
|
||||
def self.graylog?
|
||||
!!Postal.config.logging.graylog&.host
|
||||
end
|
||||
|
||||
def self.greylog_notifier
|
||||
@greylog_notifier ||= greylog? ? GELF::Notifier.new(Postal.config.logging.greylog.host, Postal.config.logging.greylog.port) : nil
|
||||
def self.graylog_notifier
|
||||
@graylog_notifier ||= graylog? ? GELF::Notifier.new(Postal.config.logging.graylog.host, Postal.config.logging.graylog.port) : nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ module Postal
|
||||
end
|
||||
|
||||
def dkim_header
|
||||
"DKIM-Signature: v=1;" + dkim_properties + signature
|
||||
"DKIM-Signature: v=1; " + dkim_properties.join("\r\n\t") + signature.scan(/.{1,72}/).join("\r\n\t")
|
||||
end
|
||||
|
||||
private
|
||||
@@ -96,16 +96,18 @@ module Postal
|
||||
end
|
||||
|
||||
def dkim_properties
|
||||
String.new.tap do |header|
|
||||
header << " a=rsa-sha256; c=relaxed/relaxed;"
|
||||
header << " d=#{@domain_name}; s=#{@dkim_identifier}; t=#{Time.now.utc.to_i};"
|
||||
header << " bh=#{body_hash}; h=#{header_names.join(':')};"
|
||||
header << " b="
|
||||
@dkim_properties ||= Array.new.tap do |header|
|
||||
header << "a=rsa-sha256; c=relaxed/relaxed;"
|
||||
header << "d=#{@domain_name};"
|
||||
header << "s=#{@dkim_identifier}; t=#{Time.now.utc.to_i};"
|
||||
header << "bh=#{body_hash};"
|
||||
header << "h=#{header_names.join(':')};"
|
||||
header << "b="
|
||||
end
|
||||
end
|
||||
|
||||
def dkim_header_for_signing
|
||||
"dkim-signature:v=1;" + dkim_properties
|
||||
"dkim-signature:v=1; #{dkim_properties.join(' ')}"
|
||||
end
|
||||
|
||||
def signable_header_string
|
||||
|
||||
@@ -498,14 +498,16 @@ module Postal
|
||||
# Inspect this message
|
||||
#
|
||||
def inspect_message
|
||||
if result = MessageInspection.new(self.raw_message, self.scope&.to_sym)
|
||||
# Update the messages table with the results of our inspection
|
||||
update(:inspected => 1, :spam_score => result.filtered_spam_score, :threat => result.threat?, :threat_details => result.threat_message)
|
||||
# Add any spam details into the spam checks database
|
||||
self.database.insert_multi(:spam_checks, [:message_id, :code, :score, :description], result.filtered_spam_checks.map { |d| [self.id, d.code, d.score, d.description]})
|
||||
# Return the result
|
||||
result
|
||||
end
|
||||
result = MessageInspection.scan(self, self.scope&.to_sym)
|
||||
|
||||
# Update the messages table with the results of our inspection
|
||||
update(:inspected => 1, :spam_score => result.spam_score, :threat => result.threat?, :threat_details => result.threat_message)
|
||||
|
||||
# Add any spam details into the spam checks database
|
||||
self.database.insert_multi(:spam_checks, [:message_id, :code, :score, :description], result.spam_checks.map { |d| [self.id, d.code, d.score, d.description] })
|
||||
|
||||
# Return the result
|
||||
result
|
||||
end
|
||||
|
||||
#
|
||||
|
||||
@@ -1,140 +1,41 @@
|
||||
require 'timeout'
|
||||
require 'socket'
|
||||
require 'json'
|
||||
|
||||
module Postal
|
||||
class MessageInspection
|
||||
|
||||
SPAM_EXCLUSIONS = {
|
||||
:outgoing => ['NO_RECEIVED', 'NO_RELAYS', 'ALL_TRUSTED', 'FREEMAIL_FORGED_REPLYTO', 'RDNS_DYNAMIC', 'CK_HELO_GENERIC', /^SPF\_/, /^HELO\_/, /DKIM_/, /^RCVD_IN_/],
|
||||
:incoming => []
|
||||
}
|
||||
attr_reader :message
|
||||
attr_reader :scope
|
||||
attr_reader :spam_checks
|
||||
attr_accessor :threat
|
||||
attr_accessor :threat_message
|
||||
|
||||
def initialize(message, scope = :incoming)
|
||||
def initialize(message, scope)
|
||||
@message = message
|
||||
@scope = scope
|
||||
@threat = false
|
||||
@spam_score = 0.0
|
||||
@spam_checks = []
|
||||
|
||||
if Postal.config.spamd.enabled?
|
||||
scan_for_spam
|
||||
end
|
||||
|
||||
if Postal.config.clamav.enabled?
|
||||
scan_for_threats
|
||||
end
|
||||
@threat = false
|
||||
end
|
||||
|
||||
def spam_score
|
||||
@spam_score
|
||||
end
|
||||
return 0 if @spam_checks.empty?
|
||||
|
||||
def spam_checks
|
||||
@spam_checks
|
||||
end
|
||||
|
||||
def filtered_spam_checks
|
||||
@filtered_spam_checks ||= @spam_checks.reject do |check|
|
||||
SPAM_EXCLUSIONS[@scope].any? do |item|
|
||||
item == check.code || (item.is_a?(Regexp) && item =~ check.code)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def filtered_spam_score
|
||||
filtered_spam_checks.inject(0.0) do |total, check|
|
||||
total += check.score || 0.0
|
||||
end.round(2)
|
||||
@spam_checks.sum(&:score)
|
||||
end
|
||||
|
||||
def threat?
|
||||
@threat
|
||||
@threat == true
|
||||
end
|
||||
|
||||
def threat_message
|
||||
@threat_message
|
||||
def scan
|
||||
MessageInspector.inspectors.each do |inspector|
|
||||
inspector.inspect_message(self)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def scan_for_spam
|
||||
data = nil
|
||||
Timeout.timeout(15) do
|
||||
tcp_socket = TCPSocket.new(Postal.config.spamd.host, Postal.config.spamd.port)
|
||||
tcp_socket.write("REPORT SPAMC/1.2\r\n")
|
||||
tcp_socket.write("Content-length: #{@message.bytesize}\r\n")
|
||||
tcp_socket.write("\r\n")
|
||||
tcp_socket.write(@message)
|
||||
tcp_socket.close_write
|
||||
data = tcp_socket.read
|
||||
class << self
|
||||
def scan(message, scope)
|
||||
inspection = new(message, scope)
|
||||
inspection.scan
|
||||
inspection
|
||||
end
|
||||
|
||||
spam_checks = []
|
||||
total = 0.0
|
||||
rules = data ? data.split(/^---(.*)\r?\n/).last.split(/\r?\n/) : []
|
||||
while line = rules.shift
|
||||
if line =~ /\A([\- ]?[\d\.]+)\s+(\w+)\s+(.*)/
|
||||
total += $1.to_f
|
||||
spam_checks << SPAMCheck.new($2, $1.to_f, $3)
|
||||
else
|
||||
spam_checks.last.description << " " + line.strip
|
||||
end
|
||||
end
|
||||
|
||||
@spam_score = total.round(1)
|
||||
@spam_checks = spam_checks
|
||||
|
||||
rescue Timeout::Error
|
||||
@spam_checks = [SPAMCheck.new("TIMEOUT", 0, "Timed out when scanning for spam")]
|
||||
rescue => e
|
||||
logger.error "Error talking to spamd: #{e.class} (#{e.message})"
|
||||
logger.error e.backtrace[0,5]
|
||||
@spam_checks = [SPAMCheck.new("ERROR", 0, "Error when scanning for spam")]
|
||||
ensure
|
||||
tcp_socket.close rescue nil
|
||||
end
|
||||
|
||||
def scan_for_threats
|
||||
@threat = false
|
||||
|
||||
data = nil
|
||||
Timeout.timeout(10) do
|
||||
tcp_socket = TCPSocket.new(Postal.config.clamav.host, Postal.config.clamav.port)
|
||||
tcp_socket.write("zINSTREAM\0")
|
||||
tcp_socket.write([@message.bytesize].pack("N"))
|
||||
tcp_socket.write(@message)
|
||||
tcp_socket.write([0].pack("N"))
|
||||
tcp_socket.close_write
|
||||
data = tcp_socket.read
|
||||
end
|
||||
|
||||
if data && data =~ /\Astream\:\s+(.*?)[\s\0]+?/
|
||||
if $1.upcase == 'OK'
|
||||
@threat = false
|
||||
@threat_message = "No threats found"
|
||||
else
|
||||
@threat = true
|
||||
@threat_message = $1
|
||||
end
|
||||
else
|
||||
@threat = false
|
||||
@threat_message = "Could not scan message"
|
||||
end
|
||||
rescue Timeout::Error
|
||||
@threat = false
|
||||
@threat_message = "Timed out scanning for threats"
|
||||
rescue => e
|
||||
logger.error "Error talking to clamav: #{e.class} (#{e.message})"
|
||||
logger.error e.backtrace[0,5]
|
||||
@threat = false
|
||||
@threat_message = "Error when scanning for threats"
|
||||
ensure
|
||||
tcp_socket.close rescue nil
|
||||
end
|
||||
|
||||
def logger
|
||||
Postal.logger_for(:message_inspection)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
40
lib/postal/message_inspector.rb
Normal file
40
lib/postal/message_inspector.rb
Normal file
@@ -0,0 +1,40 @@
|
||||
module Postal
|
||||
class MessageInspector
|
||||
|
||||
def initialize(config)
|
||||
@config = config
|
||||
end
|
||||
|
||||
# Inspect a message and update the inspection with the results
|
||||
# as appropriate.
|
||||
def inspect_message(message, scope, inspection)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def logger
|
||||
Postal.logger_for(:message_inspection)
|
||||
end
|
||||
|
||||
class << self
|
||||
# Return an array of all inspectors that are available for this
|
||||
# installation.
|
||||
def inspectors
|
||||
Array.new.tap do |inspectors|
|
||||
|
||||
if Postal.config.rspamd&.enabled
|
||||
inspectors << MessageInspectors::Rspamd.new(Postal.config.rspamd)
|
||||
elsif Postal.config.spamd&.enabled
|
||||
inspectors << MessageInspectors::SpamAssassin.new(Postal.config.spamd)
|
||||
end
|
||||
|
||||
if Postal.config.clamav&.enabled
|
||||
inspectors << MessageInspectors::Clamav.new(Postal.config.clamav)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
10
lib/postal/message_inspectors.rb
Normal file
10
lib/postal/message_inspectors.rb
Normal file
@@ -0,0 +1,10 @@
|
||||
module Postal
|
||||
module MessageInspectors
|
||||
extend ActiveSupport::Autoload
|
||||
eager_autoload do
|
||||
autoload :Clamav
|
||||
autoload :Rspamd
|
||||
autoload :SpamAssassin
|
||||
end
|
||||
end
|
||||
end
|
||||
45
lib/postal/message_inspectors/clamav.rb
Normal file
45
lib/postal/message_inspectors/clamav.rb
Normal file
@@ -0,0 +1,45 @@
|
||||
module Postal
|
||||
module MessageInspectors
|
||||
class Clamav < MessageInspector
|
||||
|
||||
def inspect_message(inspection)
|
||||
raw_message = inspection.message.raw_message
|
||||
|
||||
data = nil
|
||||
Timeout.timeout(10) do
|
||||
tcp_socket = TCPSocket.new(@config.host, @config.port)
|
||||
tcp_socket.write("zINSTREAM\0")
|
||||
tcp_socket.write([raw_message.bytesize].pack("N"))
|
||||
tcp_socket.write(raw_message)
|
||||
tcp_socket.write([0].pack("N"))
|
||||
tcp_socket.close_write
|
||||
data = tcp_socket.read
|
||||
end
|
||||
|
||||
if data && data =~ /\Astream\:\s+(.*?)[\s\0]+?/
|
||||
if $1.upcase == 'OK'
|
||||
inspection.threat = false
|
||||
inspection.threat_message = "No threats found"
|
||||
else
|
||||
inspection.threat = true
|
||||
inspection.threat_message = $1
|
||||
end
|
||||
else
|
||||
inspection.threat = false
|
||||
inspection.threat_message = "Could not scan message"
|
||||
end
|
||||
rescue Timeout::Error
|
||||
inspection.threat = false
|
||||
inspection.threat_message = "Timed out scanning for threats"
|
||||
rescue => e
|
||||
logger.error "Error talking to clamav: #{e.class} (#{e.message})"
|
||||
logger.error e.backtrace[0,5]
|
||||
inspection.threat = false
|
||||
inspection.threat_message = "Error when scanning for threats"
|
||||
ensure
|
||||
tcp_socket.close rescue nil
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
74
lib/postal/message_inspectors/rspamd.rb
Normal file
74
lib/postal/message_inspectors/rspamd.rb
Normal file
@@ -0,0 +1,74 @@
|
||||
require 'net/http'
|
||||
|
||||
module Postal
|
||||
module MessageInspectors
|
||||
class Rspamd < MessageInspector
|
||||
|
||||
class Error < StandardError
|
||||
end
|
||||
|
||||
def inspect_message(inspection)
|
||||
response = request(inspection.message, inspection.scope)
|
||||
response = JSON.parse(response.body)
|
||||
return unless response['symbols'].is_a?(Hash)
|
||||
|
||||
response['symbols'].values.each do |symbol|
|
||||
next if symbol['description'].blank?
|
||||
|
||||
inspection.spam_checks << SpamCheck.new(symbol['name'], symbol['score'], symbol['description'])
|
||||
end
|
||||
rescue Error => e
|
||||
inspection.spam_checks << SpamCheck.new("ERROR", 0, e.message)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def request(message, scope)
|
||||
http = Net::HTTP.new(@config.host, @config.port)
|
||||
http.use_ssl = true if @config.ssl
|
||||
http.read_timeout = 10
|
||||
http.open_timeout = 10
|
||||
|
||||
raw_message = message.raw_message
|
||||
|
||||
request = Net::HTTP::Post.new('/checkv2')
|
||||
request.body = raw_message
|
||||
request['Content-Length'] = raw_message.bytesize.to_s
|
||||
request['Password'] = @config.password if @config.password
|
||||
request['Flags'] = @config.flags if @config.flags
|
||||
request['User-Agent'] = 'Postal'
|
||||
request['Deliver-To'] = message.rcpt_to
|
||||
request['From'] = message.mail_from
|
||||
request['Rcpt'] = message.rcpt_to
|
||||
request['Queue-Id'] = message.token
|
||||
|
||||
if scope == :outgoing
|
||||
request['User'] = ''
|
||||
# We don't actually know the IP but an empty input here will
|
||||
# still trigger rspamd to treat this as an outbound email
|
||||
# and disable certain checks.
|
||||
# https://rspamd.com/doc/tutorials/scanning_outbound.html
|
||||
request['Ip'] = ''
|
||||
end
|
||||
|
||||
response = nil
|
||||
begin
|
||||
response = http.request(request)
|
||||
rescue Exception => e
|
||||
logger.error "Error talking to rspamd: #{e.class} (#{e.message})"
|
||||
logger.error e.backtrace[0,5]
|
||||
|
||||
raise Error, "Error when scanning with rspamd (#{e.class})"
|
||||
end
|
||||
|
||||
unless response.is_a?(Net::HTTPOK)
|
||||
logger.info "Got #{response.code} status from rspamd, wanted 200"
|
||||
raise Error, "Error when scanning with rspamd (got #{response.code})"
|
||||
end
|
||||
|
||||
response
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
54
lib/postal/message_inspectors/spam_assassin.rb
Normal file
54
lib/postal/message_inspectors/spam_assassin.rb
Normal file
@@ -0,0 +1,54 @@
|
||||
module Postal
|
||||
module MessageInspectors
|
||||
class SpamAssassin < MessageInspector
|
||||
|
||||
EXCLUSIONS = {
|
||||
:outgoing => ['NO_RECEIVED', 'NO_RELAYS', 'ALL_TRUSTED', 'FREEMAIL_FORGED_REPLYTO', 'RDNS_DYNAMIC', 'CK_HELO_GENERIC', /^SPF\_/, /^HELO\_/, /DKIM_/, /^RCVD_IN_/],
|
||||
:incoming => []
|
||||
}
|
||||
|
||||
def inspect_message(inspection)
|
||||
data = nil
|
||||
raw_message = inspection.message.raw_message
|
||||
Timeout.timeout(15) do
|
||||
tcp_socket = TCPSocket.new(@config.host, @config.port)
|
||||
tcp_socket.write("REPORT SPAMC/1.2\r\n")
|
||||
tcp_socket.write("Content-length: #{raw_message.bytesize}\r\n")
|
||||
tcp_socket.write("\r\n")
|
||||
tcp_socket.write(raw_message)
|
||||
tcp_socket.close_write
|
||||
data = tcp_socket.read
|
||||
end
|
||||
|
||||
spam_checks = []
|
||||
total = 0.0
|
||||
rules = data ? data.split(/^---(.*)\r?\n/).last.split(/\r?\n/) : []
|
||||
while line = rules.shift
|
||||
if line =~ /\A([\- ]?[\d\.]+)\s+(\w+)\s+(.*)/
|
||||
total += $1.to_f
|
||||
spam_checks << SpamCheck.new($2, $1.to_f, $3)
|
||||
else
|
||||
spam_checks.last.description << " " + line.strip
|
||||
end
|
||||
end
|
||||
|
||||
checks = spam_checks.reject { |s| EXCLUSIONS[inspection.scope].include?(s.code) }
|
||||
checks.each do |check|
|
||||
inspection.spam_checks << check
|
||||
end
|
||||
|
||||
rescue Timeout::Error
|
||||
inspection.spam_checks << SpamCheck.new("TIMEOUT", 0, "Timed out when scanning for spam")
|
||||
|
||||
rescue => e
|
||||
logger.error "Error talking to spamd: #{e.class} (#{e.message})"
|
||||
logger.error e.backtrace[0,5]
|
||||
inspection.spam_checks << SpamCheck.new("ERROR", 0, "Error when scanning for spam")
|
||||
|
||||
ensure
|
||||
tcp_socket.close rescue nil
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,5 +1,5 @@
|
||||
module Postal
|
||||
class SPAMCheck
|
||||
class SpamCheck
|
||||
|
||||
attr_reader :code, :score, :description
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
module Postal
|
||||
|
||||
VERSION = '1.0.0'
|
||||
REVISION = nil
|
||||
CHANNEL = 'dev'
|
||||
VERSION_PATH = File.expand_path('../../VERSION', __dir__)
|
||||
VERSION = if File.file?(VERSION_PATH)
|
||||
File.read(VERSION_PATH).strip.delete_prefix('v')
|
||||
else
|
||||
'0.0.0-dev'
|
||||
end
|
||||
|
||||
def self.version
|
||||
[VERSION, REVISION, CHANNEL].compact.join('-')
|
||||
VERSION
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
# This script will attempt to upgrade a Postal installation automatically.
|
||||
#
|
||||
# It can be run as any user that has access to /opt/postal and that can run
|
||||
# commands as postal.
|
||||
|
||||
channel = 'stable'
|
||||
safe_mode = false
|
||||
|
||||
begin
|
||||
require 'optparse'
|
||||
OptionParser.new do |opts|
|
||||
opts.banner = "Usage: postal auto-upgrade [options]"
|
||||
|
||||
opts.on("-c", "--channel CHANNEL", "The channel to pull the latest version from") do |v|
|
||||
channel = v
|
||||
end
|
||||
|
||||
opts.on("--safe", "Stop postal before running the upgrade") do |v|
|
||||
safe_mode = true
|
||||
end
|
||||
end.parse!
|
||||
rescue OptionParser::InvalidOption => e
|
||||
puts e.message
|
||||
exit 1
|
||||
end
|
||||
|
||||
unless ['beta', 'stable'].include?(channel)
|
||||
puts "Channel must be either 'stable' or 'beta'"
|
||||
exit 1
|
||||
end
|
||||
|
||||
puts "Upgrading from the \e[32m#{channel}\e[0m channel"
|
||||
|
||||
def run(command, options = {})
|
||||
if system(command)
|
||||
# Good.
|
||||
else
|
||||
puts "Failed to run: #{command}"
|
||||
exit 128 unless options[:exit_on_failure] == false
|
||||
end
|
||||
end
|
||||
|
||||
if safe_mode
|
||||
puts "Stopping current Postal instance"
|
||||
run "postal stop", :exit_on_failure => false
|
||||
end
|
||||
|
||||
if File.exist?("/opt/postal/app/.git")
|
||||
puts "Getting latest version of repository"
|
||||
run "cd /opt/postal/app && git pull"
|
||||
else
|
||||
puts "Backing up previous application files"
|
||||
run "rm -Rf /opt/postal/app.backup"
|
||||
run "cp -R /opt/postal/app /opt/postal/app.backup"
|
||||
puts "Downloading latest version of application"
|
||||
run "wget https://postal.atech.media/packages/#{channel}/latest.tgz -O - | tar zxpv -C /opt/postal/app"
|
||||
end
|
||||
|
||||
puts "Installing dependencies"
|
||||
run "postal bundle /opt/postal/vendor/bundle"
|
||||
|
||||
puts "Upgrading database & assets"
|
||||
run "postal upgrade"
|
||||
|
||||
if safe_mode
|
||||
puts "Starting Postal"
|
||||
run "postal start"
|
||||
else
|
||||
puts "Restarting Postal"
|
||||
run "postal restart"
|
||||
end
|
||||
|
||||
puts "\e[32mUpgrade complete\e[0m"
|
||||
@@ -1,110 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
# This script will build a tgz file containing a copy of Postal with the assets
|
||||
# ready to go. It will then upload the file to a web server where it can be
|
||||
# accessed for users who wish to install or upgrade their postal installations.
|
||||
#
|
||||
# This script will only be used by the Postal build manager so it's likely of
|
||||
# little use to most people.
|
||||
|
||||
require 'rubygems'
|
||||
require 'pathname'
|
||||
require 'fileutils'
|
||||
|
||||
ROOT = Pathname.new(File.expand_path('../../', __FILE__))
|
||||
BUILD_ROOT = Pathname.new("/tmp/postal-build")
|
||||
WC_PATH = BUILD_ROOT.join('wc')
|
||||
PACKAGE_PATH = BUILD_ROOT.join('package.tgz')
|
||||
CHANNEL = ARGV[0]
|
||||
|
||||
unless ['beta', 'stable'].include?(CHANNEL)
|
||||
puts "channel must be beta or stable"
|
||||
exit 1
|
||||
end
|
||||
|
||||
def system!(c)
|
||||
if system(c)
|
||||
true
|
||||
else
|
||||
puts "Couldn't execute #{c.inspect}"
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
# Prepare our build root
|
||||
FileUtils.mkdir_p(BUILD_ROOT)
|
||||
|
||||
# Get a brand new clean copy of the repository
|
||||
puts "\e[44;37mCloning clean repository\e[0m"
|
||||
system!("rm -rf #{WC_PATH}")
|
||||
system!("git clone #{ROOT} #{WC_PATH}")
|
||||
|
||||
# Install bundler dependencies so we can compile assets
|
||||
puts "\e[44;37mInstalling dependencies\e[0m"
|
||||
system!("cd #{WC_PATH} && bundle install --gemfile #{WC_PATH}/Gemfile --path #{BUILD_ROOT}/vendor/bundle")
|
||||
|
||||
# Install some configuration files
|
||||
puts "\e[44;37mInstalling configuration\e[0m"
|
||||
system!("cd #{WC_PATH} && ./bin/postal initialize-config")
|
||||
|
||||
# Get the last commit reference for the version file
|
||||
last_commit = `git -C #{WC_PATH} log --pretty=oneline -n 1`.split(/\s+/, 2).first[0,10]
|
||||
puts "\e[34mGot latest commit was #{last_commit}\e[0m"
|
||||
|
||||
# Read the version file for the version number so we it put it in the build
|
||||
# package filename and update the version file to include the REVISION and
|
||||
# CHANNEL for this build.
|
||||
version_file = File.read("#{WC_PATH}/lib/postal/version.rb")
|
||||
if version_file =~ /VERSION = '(.*)'/
|
||||
version = $1.to_s
|
||||
puts "\e[34mGot version as #{version}\e[0m"
|
||||
else
|
||||
puts "Could not determine version from version file"
|
||||
exit 1
|
||||
end
|
||||
version_file.gsub!("REVISION = nil", "REVISION = '#{last_commit}'")
|
||||
version_file.gsub!("CHANNEL = 'dev'", "CHANNEL = '#{CHANNEL}'")
|
||||
File.open("#{WC_PATH}/lib/postal/version.rb", 'w') { |f| f.write(version_file) }
|
||||
|
||||
# Compile all the assets
|
||||
unless ENV['NO_ASSETS']
|
||||
puts "\e[44;37mCompiling assets\e[0m"
|
||||
system!("cd #{WC_PATH} && RAILS_GROUPS=assets bundle exec rake assets:precompile")
|
||||
system!("touch #{WC_PATH}/public/assets/.prebuilt")
|
||||
end
|
||||
|
||||
# Remove files that shouldn't be distributed
|
||||
puts "\e[44;37mRemoving unused files\e[0m"
|
||||
system!("rm -Rf #{WC_PATH}/.git")
|
||||
system!("rm -f #{WC_PATH}/config/postal.yml")
|
||||
system!("rm -f #{WC_PATH}/config/*.cert")
|
||||
system!("rm -f #{WC_PATH}/config/*.key")
|
||||
system!("rm -f #{WC_PATH}/config/*.pem")
|
||||
system!("rm -Rf #{WC_PATH}/.bundle")
|
||||
system!("rm -Rf #{WC_PATH}/.gitignore")
|
||||
system!("rm -Rf #{WC_PATH}/tmp")
|
||||
|
||||
# Build a new tgz file
|
||||
puts "\e[44;37mCreating build package\e[0m"
|
||||
system("tar cpzf #{PACKAGE_PATH} -C #{WC_PATH} .")
|
||||
puts "\e[32mCreated build at #{PACKAGE_PATH}\e[0m"
|
||||
|
||||
# What's our filename? This is our filename.
|
||||
filename = "postal-#{version}-#{last_commit}.tgz"
|
||||
|
||||
# Upload the package to the distribution server and symlink it to latest
|
||||
# for the appropriate channel.
|
||||
require 'net/ssh'
|
||||
require 'net/scp'
|
||||
Net::SSH.start("postal.atech.media") do |ssh|
|
||||
ssh.exec!("rm -Rf /home/atechmedia/postal.atech.media/packages/#{CHANNEL}/#{filename}")
|
||||
puts "Uploading..."
|
||||
ssh.scp.upload!(PACKAGE_PATH.to_s, "/home/atechmedia/postal.atech.media/packages/#{CHANNEL}/#{filename}")
|
||||
puts "Making latest..."
|
||||
ssh.exec!("rm -Rf /home/atechmedia/postal.atech.media/packages/#{CHANNEL}/latest.tgz")
|
||||
ssh.exec!("ln -s /home/atechmedia/postal.atech.media/packages/#{CHANNEL}/#{filename} /home/atechmedia/postal.atech.media/packages/#{CHANNEL}/latest.tgz")
|
||||
end
|
||||
|
||||
puts "\e[32mDone. Package is live at https://postal.atech.media/packages/#{CHANNEL}/latest.tgz\e[0m"
|
||||
|
||||
# Yay. We're done.
|
||||
@@ -1,77 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This will install everything required to run a basic Postal installation.
|
||||
# This should be run on a clean Ubuntu 16.04 server.
|
||||
#
|
||||
# Once the installation has completed you will be able to access the Postal web
|
||||
# interface on port 443. It will have a self-signed certificate.
|
||||
#
|
||||
# * Change the MySQL & RabbitMQ passwords
|
||||
# * Create your first admin user with 'postal make-user'
|
||||
# * Replace the self-signed certificate in /etc/nginx/ssl/postal.cert
|
||||
# * Make appropriate changes to the configuration in /opt/postal/config/postal.yml
|
||||
# * Setup your DNS [ https://github.com/atech/postal/wiki/Domains-&-DNS-Configuration ]
|
||||
# * Configure the click & open tracking [ https://github.com/atech/postal/wiki/Click-&-Open-Tracking ]
|
||||
# * Configure spam & virus checking [ https://github.com/atech/postal/wiki/Spam-&-Virus-Checking ]
|
||||
|
||||
set -e
|
||||
|
||||
#
|
||||
# Dependencies
|
||||
#
|
||||
apt update
|
||||
apt install -y software-properties-common
|
||||
apt-add-repository ppa:brightbox/ruby-ng -y
|
||||
apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 0xF1656F24C74CD1D8
|
||||
add-apt-repository 'deb [arch=amd64,i386,ppc64el] http://mirrors.coreix.net/mariadb/repo/10.1/ubuntu xenial main'
|
||||
curl -sL https://www.rabbitmq.com/rabbitmq-release-signing-key.asc | apt-key add -
|
||||
add-apt-repository 'deb http://www.rabbitmq.com/debian/ testing main'
|
||||
apt update
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt install -y ruby2.3 ruby2.3-dev build-essential libssl-dev mariadb-server libmysqlclient-dev rabbitmq-server nodejs git nginx wget nano
|
||||
gem install bundler procodile --no-rdoc --no-ri
|
||||
|
||||
#
|
||||
# MySQL
|
||||
#
|
||||
echo 'CREATE DATABASE `postal` CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;' | mysql -u root
|
||||
echo 'GRANT ALL ON `postal`.* TO `postal`@`127.0.0.1` IDENTIFIED BY "p0stalpassw0rd";' | mysql -u root
|
||||
echo 'GRANT ALL PRIVILEGES ON `postal-%` . * to `postal`@`127.0.0.1` IDENTIFIED BY "p0stalpassw0rd";' | mysql -u root
|
||||
|
||||
#
|
||||
# RabbitMQ
|
||||
#
|
||||
rabbitmqctl add_vhost /postal
|
||||
rabbitmqctl add_user postal p0stalpassw0rd
|
||||
rabbitmqctl set_permissions -p /postal postal ".*" ".*" ".*"
|
||||
|
||||
#
|
||||
# System prep
|
||||
#
|
||||
useradd -r -m -d /opt/postal -s /bin/bash postal
|
||||
setcap 'cap_net_bind_service=+ep' /usr/bin/ruby2.3
|
||||
|
||||
#
|
||||
# Application Setup
|
||||
#
|
||||
sudo -i -u postal mkdir -p /opt/postal/app
|
||||
wget https://postal.atech.media/packages/stable/latest.tgz -O - | sudo -u postal tar zxpv -C /opt/postal/app
|
||||
ln -s /opt/postal/app/bin/postal /usr/bin/postal
|
||||
postal bundle /opt/postal/vendor/bundle
|
||||
postal initialize-config
|
||||
postal initialize
|
||||
postal start
|
||||
|
||||
#
|
||||
# nginx
|
||||
#
|
||||
cp /opt/postal/app/resource/nginx.cfg /etc/nginx/sites-available/default
|
||||
mkdir /etc/nginx/ssl/
|
||||
openssl req -x509 -newkey rsa:4096 -keyout /etc/nginx/ssl/postal.key -out /etc/nginx/ssl/postal.cert -days 365 -nodes -subj "/C=GB/ST=Example/L=Example/O=Example/CN=example.com"
|
||||
service nginx reload
|
||||
|
||||
#
|
||||
# All done
|
||||
#
|
||||
echo
|
||||
echo "Installation complete"
|
||||
@@ -1,78 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This will install everything required to run a basic Postal installation.
|
||||
# This should be run on a clean Ubuntu 18.04 server.
|
||||
#
|
||||
# Once the installation has completed you will be able to access the Postal web
|
||||
# interface on port 443. It will have a self-signed certificate.
|
||||
#
|
||||
# * Change the MySQL & RabbitMQ passwords
|
||||
# * Create your first admin user with 'postal make-user'
|
||||
# * Replace the self-signed certificate in /etc/nginx/ssl/postal.cert
|
||||
# * Make appropriate changes to the configuration in /opt/postal/config/postal.yml
|
||||
# * Setup your DNS [ https://github.com/atech/postal/wiki/Domains-&-DNS-Configuration ]
|
||||
# * Configure the click & open tracking [ https://github.com/atech/postal/wiki/Click-&-Open-Tracking ]
|
||||
# * Configure spam & virus checking [ https://github.com/atech/postal/wiki/Spam-&-Virus-Checking ]
|
||||
|
||||
set -e
|
||||
|
||||
#
|
||||
# Dependencies
|
||||
#
|
||||
apt update
|
||||
apt install -y software-properties-common
|
||||
apt-add-repository ppa:brightbox/ruby-ng -y
|
||||
apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 0xF1656F24C74CD1D8
|
||||
add-apt-repository 'deb [arch=amd64,i386,ppc64el] http://mirrors.coreix.net/mariadb/repo/10.1/ubuntu bionic main'
|
||||
curl -sL https://www.rabbitmq.com/rabbitmq-release-signing-key.asc | apt-key add -
|
||||
add-apt-repository 'deb http://www.rabbitmq.com/debian/ testing main'
|
||||
apt update
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt install -y ruby2.3 ruby2.3-dev build-essential libssl-dev mariadb-server libmysqlclient-dev rabbitmq-server nodejs git nginx wget nano
|
||||
gem install bundler procodile --no-rdoc --no-ri
|
||||
|
||||
#
|
||||
# MySQL
|
||||
#
|
||||
echo 'CREATE DATABASE `postal` CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;' | mysql -u root
|
||||
echo 'GRANT ALL ON `postal`.* TO `postal`@`127.0.0.1` IDENTIFIED BY "p0stalpassw0rd";' | mysql -u root
|
||||
echo 'GRANT ALL PRIVILEGES ON `postal-%` . * to `postal`@`127.0.0.1` IDENTIFIED BY "p0stalpassw0rd";' | mysql -u root
|
||||
|
||||
#
|
||||
# RabbitMQ
|
||||
#
|
||||
rabbitmqctl add_vhost /postal
|
||||
rabbitmqctl add_user postal p0stalpassw0rd
|
||||
rabbitmqctl set_permissions -p /postal postal ".*" ".*" ".*"
|
||||
|
||||
#
|
||||
# System prep
|
||||
#
|
||||
useradd -r -m -d /opt/postal -s /bin/bash postal
|
||||
setcap 'cap_net_bind_service=+ep' /usr/bin/ruby2.3
|
||||
|
||||
#
|
||||
# Application Setup
|
||||
#
|
||||
sudo -i -u postal mkdir -p /opt/postal/app
|
||||
wget https://postal.atech.media/packages/stable/latest.tgz -O - | sudo -u postal tar zxpv -C /opt/postal/app
|
||||
ln -s /opt/postal/app/bin/postal /usr/bin/postal
|
||||
postal bundle /opt/postal/vendor/bundle
|
||||
postal initialize-config
|
||||
postal initialize
|
||||
postal start
|
||||
|
||||
#
|
||||
# nginx
|
||||
#
|
||||
cp /opt/postal/app/resource/nginx.cfg /etc/nginx/sites-available/default
|
||||
mkdir /etc/nginx/ssl/
|
||||
openssl req -x509 -newkey rsa:4096 -keyout /etc/nginx/ssl/postal.key -out /etc/nginx/ssl/postal.cert -days 365 -nodes -subj "/C=GB/ST=Example/L=Example/O=Example/CN=example.com"
|
||||
service nginx reload
|
||||
|
||||
#
|
||||
# All done
|
||||
#
|
||||
echo
|
||||
echo "Installation complete"
|
||||
echo "Now run \e[1mpostal make-user\e[0m to set up your user."
|
||||
@@ -1,15 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require File.expand_path('../../lib/postal/config', __FILE__)
|
||||
worker_quantity = Postal.config.workers&.quantity || 1
|
||||
hash = {
|
||||
'root' => Postal.app_root.to_s,
|
||||
'user' => ENV['USER'],
|
||||
'processes' => {
|
||||
'worker' => {
|
||||
'quantity' => worker_quantity
|
||||
}
|
||||
}
|
||||
}.to_yaml
|
||||
|
||||
File.open(Postal.app_root.join('Procfile.local'), 'w') { |f| f.write(hash + "\n")}
|
||||
@@ -20,11 +20,12 @@ describe Postal::DKIMHeader do
|
||||
allow(domain).to receive(:dkim_key).and_return(OpenSSL::PKey::RSA.new(frontmatter['private_key']))
|
||||
allow(domain).to receive(:dkim_identifier).and_return(frontmatter['dkim_identifier'])
|
||||
|
||||
expectation = "DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; " \
|
||||
"d=#{frontmatter['domain']}; s=#{frontmatter['dkim_identifier']}; t=#{mocked_time.to_i}; " \
|
||||
"bh=#{frontmatter['bh']}; "\
|
||||
"h=#{frontmatter['headers']}; " \
|
||||
"b=#{frontmatter['b']}"
|
||||
expectation = "DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\r\n" \
|
||||
"\td=#{frontmatter['domain']};\r\n" \
|
||||
"\ts=#{frontmatter['dkim_identifier']}; t=#{mocked_time.to_i};\r\n" \
|
||||
"\tbh=#{frontmatter['bh']};\r\n"\
|
||||
"\th=#{frontmatter['headers']};\r\n" \
|
||||
"\tb=#{frontmatter['b'].scan(/.{1,72}/).join("\r\n\t")}"
|
||||
|
||||
header = described_class.new(domain, email)
|
||||
|
||||
|
||||
المرجع في مشكلة جديدة
حظر مستخدم