مراية لـ
https://github.com/postalserver/postal.git
تم المزامنة 2026-03-04 06:44:06 +00:00
Compare commits
13 الالتزامات
| المؤلف | SHA1 | التاريخ | |
|---|---|---|---|
|
|
4c27baee7f | ||
|
|
9399e32234 | ||
|
|
22dcd4901f | ||
|
|
6df963651d | ||
|
|
4acfffd1d8 | ||
|
|
e2d642c0cb | ||
|
|
4e1deb2d2a | ||
|
|
d1e5b68200 | ||
|
|
33513a77c0 | ||
|
|
3785c99851 | ||
|
|
9bf6152060 | ||
|
|
0dc7359431 | ||
|
|
2c20ba65f6 |
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
".": "3.0.2"
|
".": "3.1.1"
|
||||||
}
|
}
|
||||||
|
|||||||
34
CHANGELOG.md
34
CHANGELOG.md
@@ -2,6 +2,40 @@
|
|||||||
|
|
||||||
This file contains all the latest changes and updates to Postal.
|
This file contains all the latest changes and updates to Postal.
|
||||||
|
|
||||||
|
## [3.1.1](https://github.com/postalserver/postal/compare/3.1.0...3.1.1) (2024-03-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* don't override paths in dockerfile ([9399e32](https://github.com/postalserver/postal/commit/9399e3223467cdacd010e70b58ad6093e128213d))
|
||||||
|
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
* **smtp-sender:** add more tests for AUTH LOGIN ([22dcd49](https://github.com/postalserver/postal/commit/22dcd4901f188915cf4b3c758c6f2fc637a4e1e3))
|
||||||
|
|
||||||
|
## [3.1.0](https://github.com/postalserver/postal/compare/3.0.2...3.1.0) (2024-03-06)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* configurable trusted proxies for web requests ([3785c99](https://github.com/postalserver/postal/commit/3785c998513c634d225b489ccb43e926ce3f270a))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **message-dequeuer:** ensure SMTP endpoints are sent to SMTP sender appropriately ([e2d642c](https://github.com/postalserver/postal/commit/e2d642c0cbf443550886d90abc3a6edf3e4bc4fc)), closes [#2853](https://github.com/postalserver/postal/issues/2853)
|
||||||
|
* **smtp-server:** listen on all interfaces by default ([d1e5b68](https://github.com/postalserver/postal/commit/d1e5b68200ea4b9710cc8714afb3271bad1f4f66)), closes [#2852](https://github.com/postalserver/postal/issues/2852)
|
||||||
|
* **smtp-server:** remove ::ffff: from the start of ipv4 addresses ([0dc7359](https://github.com/postalserver/postal/commit/0dc7359431001c9ef1222913f8d1344093397596))
|
||||||
|
* **smtp-server:** reset ansi sequence after logging ([9bf6152](https://github.com/postalserver/postal/commit/9bf6152060ffb8b611b66818c1d1ac7c929b7ffe))
|
||||||
|
* **ui:** fixes typo on queue page ([33513a7](https://github.com/postalserver/postal/commit/33513a77c0df24d832ab7ed5237d68e2b1bde887))
|
||||||
|
* **web-server:** allow for trusted proxies not be set ([4e1deb2](https://github.com/postalserver/postal/commit/4e1deb2d2aeb61d9dddb3729916411c94e73c1c6))
|
||||||
|
|
||||||
|
|
||||||
|
### Styles
|
||||||
|
|
||||||
|
* **rubocop:** use _ when not using a variable in helm config exporter ([2c20ba6](https://github.com/postalserver/postal/commit/2c20ba65f64ccb0f8174e3f523dedb3806478782))
|
||||||
|
|
||||||
## [3.0.2](https://github.com/postalserver/postal/compare/3.0.1...3.0.2) (2024-03-05)
|
## [3.0.2](https://github.com/postalserver/postal/compare/3.0.1...3.0.2) (2024-03-05)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -48,9 +48,6 @@ RUN echo $VERSION > VERSION
|
|||||||
|
|
||||||
# Set paths for when running in a container
|
# Set paths for when running in a container
|
||||||
ENV POSTAL_CONFIG_FILE_PATH=/config/postal.yml
|
ENV POSTAL_CONFIG_FILE_PATH=/config/postal.yml
|
||||||
ENV POSTAL_SIGNING_KEY_PATH=/config/signing.key
|
|
||||||
ENV SMTP_SERVER_TLS_CERTIFICATE_PATH=/config/smtp.cert
|
|
||||||
ENV SMTP_SERVER_TLS_PRIVATE_KEY_PATH=/config/smtp.key
|
|
||||||
|
|
||||||
# Set the CMD
|
# Set the CMD
|
||||||
ENTRYPOINT [ "/docker-entrypoint.sh" ]
|
ENTRYPOINT [ "/docker-entrypoint.sh" ]
|
||||||
|
|||||||
2
Gemfile
2
Gemfile
@@ -16,7 +16,7 @@ gem "hashie"
|
|||||||
gem "highline", require: false
|
gem "highline", require: false
|
||||||
gem "kaminari"
|
gem "kaminari"
|
||||||
gem "klogger-logger"
|
gem "klogger-logger"
|
||||||
gem "konfig-config", "~> 2.0"
|
gem "konfig-config", "~> 3.0"
|
||||||
gem "mail"
|
gem "mail"
|
||||||
gem "moonrope"
|
gem "moonrope"
|
||||||
gem "mysql2"
|
gem "mysql2"
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ GEM
|
|||||||
concurrent-ruby (>= 1.0, < 2.0)
|
concurrent-ruby (>= 1.0, < 2.0)
|
||||||
json
|
json
|
||||||
rouge (>= 3.30, < 5.0)
|
rouge (>= 3.30, < 5.0)
|
||||||
konfig-config (2.1.1)
|
konfig-config (3.0.0)
|
||||||
hashie
|
hashie
|
||||||
loofah (2.22.0)
|
loofah (2.22.0)
|
||||||
crass (~> 1.0.2)
|
crass (~> 1.0.2)
|
||||||
@@ -353,7 +353,7 @@ DEPENDENCIES
|
|||||||
jquery-rails
|
jquery-rails
|
||||||
kaminari
|
kaminari
|
||||||
klogger-logger
|
klogger-logger
|
||||||
konfig-config (~> 2.0)
|
konfig-config (~> 3.0)
|
||||||
mail
|
mail
|
||||||
moonrope
|
moonrope
|
||||||
mysql2
|
mysql2
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ module MessageDequeuer
|
|||||||
|
|
||||||
case queued_message.message.endpoint
|
case queued_message.message.endpoint
|
||||||
when SMTPEndpoint
|
when SMTPEndpoint
|
||||||
sender = @state.sender_for(SMTPSender, queued_message.message.recipient_domain, nil, servers: [queued_message.message.endpoint])
|
sender = @state.sender_for(SMTPSender, queued_message.message.recipient_domain, nil, servers: [queued_message.message.endpoint.to_smtp_client_server])
|
||||||
when HTTPEndpoint
|
when HTTPEndpoint
|
||||||
sender = @state.sender_for(HTTPSender, queued_message.message.endpoint)
|
sender = @state.sender_for(HTTPSender, queued_message.message.endpoint)
|
||||||
when AddressEndpoint
|
when AddressEndpoint
|
||||||
|
|||||||
@@ -112,21 +112,23 @@ module SMTPServer
|
|||||||
# Accept the connection
|
# Accept the connection
|
||||||
new_io = io.accept
|
new_io = io.accept
|
||||||
increment_prometheus_counter :postal_smtp_server_connections_total
|
increment_prometheus_counter :postal_smtp_server_connections_total
|
||||||
|
# Get the client's IP address and strip `::ffff:` for consistency.
|
||||||
|
client_ip_address = new_io.remote_address.ip_address.sub(/\A::ffff:/, "")
|
||||||
if Postal::Config.smtp_server.proxy_protocol?
|
if Postal::Config.smtp_server.proxy_protocol?
|
||||||
# If we are using the haproxy proxy protocol, we will be sent the
|
# If we are using the haproxy proxy protocol, we will be sent the
|
||||||
# client's IP later. Delay the welcome process.
|
# client's IP later. Delay the welcome process.
|
||||||
client = Client.new(nil)
|
client = Client.new(nil)
|
||||||
if Postal::Config.smtp_server.log_connections?
|
if Postal::Config.smtp_server.log_connections?
|
||||||
client.logger&.debug "Connection opened from #{new_io.remote_address.ip_address}"
|
client.logger&.debug "Connection opened from #{client_ip_address}"
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
# We're not using the proxy protocol so we already know the client's IP
|
# We're not using the proxy protocol so we already know the client's IP
|
||||||
client = Client.new(new_io.remote_address.ip_address)
|
client = Client.new(client_ip_address)
|
||||||
if Postal::Config.smtp_server.log_connections?
|
if Postal::Config.smtp_server.log_connections?
|
||||||
client.logger&.debug "Connection opened from #{new_io.remote_address.ip_address}"
|
client.logger&.debug "Connection opened from #{client_ip_address}"
|
||||||
end
|
end
|
||||||
# We know who the client is, welcome them.
|
# We know who the client is, welcome them.
|
||||||
client.logger&.debug "Client identified as #{new_io.remote_address.ip_address}"
|
client.logger&.debug "Client identified as #{client_ip_address}"
|
||||||
new_io.print("220 #{Postal::Config.postal.smtp_hostname} ESMTP Postal/#{client.trace_id}")
|
new_io.print("220 #{Postal::Config.postal.smtp_hostname} ESMTP Postal/#{client.trace_id}")
|
||||||
end
|
end
|
||||||
# Register the client and its socket with nio4r
|
# Register the client and its socket with nio4r
|
||||||
@@ -205,7 +207,7 @@ module SMTPServer
|
|||||||
|
|
||||||
result = [result] unless result.is_a?(Array)
|
result = [result] unless result.is_a?(Array)
|
||||||
result.compact.each do |iline|
|
result.compact.each do |iline|
|
||||||
client.logger&.debug "\e[34m=> #{iline.strip}"
|
client.logger&.debug "\e[34m=> #{iline.strip}\e[0m"
|
||||||
begin
|
begin
|
||||||
io.write(iline.to_s + "\r\n")
|
io.write(iline.to_s + "\r\n")
|
||||||
io.flush
|
io.flush
|
||||||
|
|||||||
@@ -47,4 +47,8 @@ class SMTPEndpoint < ApplicationRecord
|
|||||||
routes.each { |r| r.update(endpoint: nil, mode: "Reject") }
|
routes.each { |r| r.update(endpoint: nil, mode: "Reject") }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def to_smtp_client_server
|
||||||
|
SMTPClient::Server.new(hostname, port: port || 25, ssl_mode: ssl_mode)
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
- if @messages.empty?
|
- if @messages.empty?
|
||||||
.pageContent--compact
|
.pageContent--compact
|
||||||
.noData.noData--clean
|
.noData.noData--clean
|
||||||
%h2.noData__title Your queue is current empty.
|
%h2.noData__title Your queue is currently empty.
|
||||||
%p.noData__text
|
%p.noData__text
|
||||||
Messages which haven't yet been delivered successfully will appear in your queue until
|
Messages which haven't yet been delivered successfully will appear in your queue until
|
||||||
we've delivered them or we've given up trying.
|
we've delivered them or we've given up trying.
|
||||||
|
|||||||
@@ -1,15 +1,10 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module Rack
|
Rack::Request.ip_filter = lambda { |ip|
|
||||||
class Request
|
if Postal::Config.postal.trusted_proxies&.any? { |net| net.include?(ip) } ||
|
||||||
|
ip.match(/\A127\.0\.0\.1\Z|\A::1\Z|\Afd[0-9a-f]{2}:.+|\Alocalhost\Z|\Aunix\Z|\Aunix:/i)
|
||||||
module Helpers
|
true
|
||||||
|
else
|
||||||
def trusted_proxy?(ip)
|
false
|
||||||
ip =~ /^127\.0\.0\.1$|^localhost$|^unix$$/i
|
|
||||||
end
|
end
|
||||||
|
}
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ This document contains all the environment variables which are available for thi
|
|||||||
| `POSTAL_USE_RESENT_SENDER_HEADER` | Boolean | Append a Resend-Sender header to all outgoing e-mails | true |
|
| `POSTAL_USE_RESENT_SENDER_HEADER` | Boolean | Append a Resend-Sender header to all outgoing e-mails | true |
|
||||||
| `POSTAL_SIGNING_KEY_PATH` | String | Path to the private key used for signing | config/postal/signing.key |
|
| `POSTAL_SIGNING_KEY_PATH` | String | Path to the private key used for signing | config/postal/signing.key |
|
||||||
| `POSTAL_SMTP_RELAYS` | Array of strings | An array of SMTP relays in the format of smtp://host:port | |
|
| `POSTAL_SMTP_RELAYS` | Array of strings | An array of SMTP relays in the format of smtp://host:port | |
|
||||||
|
| `POSTAL_TRUSTED_PROXIES` | Array of strings | An array of IP addresses to trust for proxying requests to Postal (in addition to localhost addresses) | |
|
||||||
| `WEB_SERVER_DEFAULT_PORT` | Integer | The default port the web server should listen on unless overriden by the PORT environment variable | 5000 |
|
| `WEB_SERVER_DEFAULT_PORT` | Integer | The default port the web server should listen on unless overriden by the PORT environment variable | 5000 |
|
||||||
| `WEB_SERVER_DEFAULT_BIND_ADDRESS` | String | The default bind address the web server should listen on unless overriden by the BIND_ADDRESS environment variable | 127.0.0.1 |
|
| `WEB_SERVER_DEFAULT_BIND_ADDRESS` | String | The default bind address the web server should listen on unless overriden by the BIND_ADDRESS environment variable | 127.0.0.1 |
|
||||||
| `WEB_SERVER_MAX_THREADS` | Integer | The maximum number of threads which can be used by the web server | 5 |
|
| `WEB_SERVER_MAX_THREADS` | Integer | The maximum number of threads which can be used by the web server | 5 |
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ postal:
|
|||||||
signing_key_path: config/postal/signing.key
|
signing_key_path: config/postal/signing.key
|
||||||
# An array of SMTP relays in the format of smtp://host:port
|
# An array of SMTP relays in the format of smtp://host:port
|
||||||
smtp_relays: []
|
smtp_relays: []
|
||||||
|
# An array of IP addresses to trust for proxying requests to Postal (in addition to localhost addresses)
|
||||||
|
trusted_proxies: []
|
||||||
|
|
||||||
web_server:
|
web_server:
|
||||||
# The default port the web server should listen on unless overriden by the PORT environment variable
|
# The default port the web server should listen on unless overriden by the PORT environment variable
|
||||||
|
|||||||
@@ -68,7 +68,8 @@ module Postal
|
|||||||
|
|
||||||
string :signing_key_path do
|
string :signing_key_path do
|
||||||
description "Path to the private key used for signing"
|
description "Path to the private key used for signing"
|
||||||
default "config/postal/signing.key"
|
default "$config-file-root/signing.key"
|
||||||
|
transform { |v| Postal.substitute_config_file_root(v) }
|
||||||
end
|
end
|
||||||
|
|
||||||
string :smtp_relays do
|
string :smtp_relays do
|
||||||
@@ -84,6 +85,12 @@ module Postal
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
string :trusted_proxies do
|
||||||
|
array
|
||||||
|
description "An array of IP addresses to trust for proxying requests to Postal (in addition to localhost addresses)"
|
||||||
|
transform { |ip| IPAddr.new(ip) }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
group :web_server do
|
group :web_server do
|
||||||
@@ -227,7 +234,7 @@ module Postal
|
|||||||
|
|
||||||
string :default_bind_address do
|
string :default_bind_address do
|
||||||
description "The default bind address the SMTP server should listen on unless overriden by the BIND_ADDRESS environment variable"
|
description "The default bind address the SMTP server should listen on unless overriden by the BIND_ADDRESS environment variable"
|
||||||
default "127.0.0.1"
|
default "::"
|
||||||
end
|
end
|
||||||
|
|
||||||
integer :default_health_server_port do
|
integer :default_health_server_port do
|
||||||
@@ -247,12 +254,14 @@ module Postal
|
|||||||
|
|
||||||
string :tls_certificate_path do
|
string :tls_certificate_path do
|
||||||
description "The path to the SMTP server's TLS certificate"
|
description "The path to the SMTP server's TLS certificate"
|
||||||
default "config/postal/smtp.cert"
|
default "$config-file-root/smtp.cert"
|
||||||
|
transform { |v| Postal.substitute_config_file_root(v) }
|
||||||
end
|
end
|
||||||
|
|
||||||
string :tls_private_key_path do
|
string :tls_private_key_path do
|
||||||
description "The path to the SMTP server's TLS private key"
|
description "The path to the SMTP server's TLS private key"
|
||||||
default "config/postal/smtp.key"
|
default "$config-file-root/smtp.key"
|
||||||
|
transform { |v| Postal.substitute_config_file_root(v) }
|
||||||
end
|
end
|
||||||
|
|
||||||
string :tls_ciphers do
|
string :tls_ciphers do
|
||||||
@@ -496,4 +505,14 @@ module Postal
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class << self
|
||||||
|
|
||||||
|
def substitute_config_file_root(string)
|
||||||
|
return if string.nil?
|
||||||
|
|
||||||
|
string.gsub(/\$config-file-root/i, File.dirname(Postal.config_file_path))
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ module Postal
|
|||||||
|
|
||||||
@schema.groups.each do |group_name, group|
|
@schema.groups.each do |group_name, group|
|
||||||
path << group_name
|
path << group_name
|
||||||
group.attributes.each do |name, attr|
|
group.attributes.each do |name, _|
|
||||||
env_var = Konfig::Sources::Environment.path_to_env_var(path + [name])
|
env_var = Konfig::Sources::Environment.path_to_env_var(path + [name])
|
||||||
contents << <<~VAR.strip
|
contents << <<~VAR.strip
|
||||||
{{ include "app.envVar" (dict "name" "#{env_var}" "spec" .Values.postal.#{path.join('.')}.#{name} "root" . ) }}
|
{{ include "app.envVar" (dict "name" "#{env_var}" "spec" .Values.postal.#{path.join('.')}.#{name} "root" . ) }}
|
||||||
|
|||||||
@@ -422,7 +422,7 @@ module MessageDequeuer
|
|||||||
it "gets a sender from the state and sends the message to it" do
|
it "gets a sender from the state and sends the message to it" do
|
||||||
smtp_sender_double = double("SMTPSender")
|
smtp_sender_double = double("SMTPSender")
|
||||||
expect(smtp_sender_double).to receive(:send_message).with(queued_message.message).and_return(SendResult.new)
|
expect(smtp_sender_double).to receive(:send_message).with(queued_message.message).and_return(SendResult.new)
|
||||||
expect(state).to receive(:sender_for).with(SMTPSender, message.recipient_domain, nil, { servers: [endpoint] }).and_return(smtp_sender_double)
|
expect(state).to receive(:sender_for).with(SMTPSender, message.recipient_domain, nil, { servers: [kind_of(SMTPClient::Server)] }).and_return(smtp_sender_double)
|
||||||
processor.process
|
processor.process
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -17,7 +17,9 @@ module Postal
|
|||||||
# by the schema itself. Otherwise, we might see a value returned that
|
# by the schema itself. Otherwise, we might see a value returned that
|
||||||
# looks correct but is actually the default rather than the value from
|
# looks correct but is actually the default rather than the value from
|
||||||
# config file.
|
# config file.
|
||||||
allow_any_instance_of(Konfig::SchemaAttribute).to receive(:default).and_return(nil)
|
allow_any_instance_of(Konfig::SchemaAttribute).to receive(:default) do |a|
|
||||||
|
a.array? ? [] : nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:source) { described_class.new(SOURCE_CONFIG) }
|
let(:source) { described_class.new(SOURCE_CONFIG) }
|
||||||
|
|||||||
@@ -55,6 +55,26 @@ module SMTPServer
|
|||||||
it "requests the username" do
|
it "requests the username" do
|
||||||
expect(client.handle("AUTH LOGIN")).to eq("334 VXNlcm5hbWU6")
|
expect(client.handle("AUTH LOGIN")).to eq("334 VXNlcm5hbWU6")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "requests a password after a username" do
|
||||||
|
client.handle("AUTH LOGIN")
|
||||||
|
expect(client.handle("xx")).to eq("334 UGFzc3dvcmQ6")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "authenticates and returns a response if the password is correct" do
|
||||||
|
client.handle("AUTH LOGIN")
|
||||||
|
client.handle("xx")
|
||||||
|
credential = create(:credential, type: "SMTP")
|
||||||
|
password = Base64.encode64(credential.key)
|
||||||
|
expect(client.handle(password)).to match(/235 Granted for/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns an error when an invalid credential is provided" do
|
||||||
|
client.handle("AUTH LOGIN")
|
||||||
|
client.handle("xx")
|
||||||
|
password = Base64.encode64("xx")
|
||||||
|
expect(client.handle(password)).to eq("535 Invalid credential")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when a username is provided on the first line" do
|
context "when a username is provided on the first line" do
|
||||||
@@ -71,9 +91,7 @@ module SMTPServer
|
|||||||
expect(client.handle(password)).to match(/235 Granted for/)
|
expect(client.handle(password)).to match(/235 Granted for/)
|
||||||
expect(client.credential).to eq credential
|
expect(client.credential).to eq credential
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
context "when invalid credentials are provided" do
|
|
||||||
it "returns an error and resets the state" do
|
it "returns an error and resets the state" do
|
||||||
username = Base64.encode64("xx")
|
username = Base64.encode64("xx")
|
||||||
password = Base64.encode64("xx")
|
password = Base64.encode64("xx")
|
||||||
|
|||||||
المرجع في مشكلة جديدة
حظر مستخدم