mirror of
https://github.com/decke/smtprelay.git
synced 2026-05-10 01:14:23 -06:00
Compare commits
62 Commits
v1.13.0
...
49c00bf7d8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
49c00bf7d8 | ||
|
|
8dcd6c8067 | ||
|
|
d634a89b14 | ||
|
|
85a9c49471 | ||
|
|
808b662e01 | ||
|
|
50b1fff623 | ||
|
|
f0a4e5aeb5 | ||
|
|
135676e570 | ||
|
|
9def5c934f | ||
|
|
c0e4739def | ||
|
|
7554d4004d | ||
|
|
80eca86f0e | ||
|
|
7b6cbc924d | ||
|
|
c42b696f57 | ||
|
|
639d799cd4 | ||
|
|
d68f1af08a | ||
|
|
c2706569fe | ||
|
|
bfad3ce92c | ||
|
|
a50a30523b | ||
|
|
2937f7c21c | ||
|
|
78e064bf25 | ||
|
|
b718b3cb73 | ||
|
|
1fce4581f9 | ||
|
|
83e277f18f | ||
|
|
23e5e1c199 | ||
|
|
ce20bc5cfc | ||
|
|
f5ef22dcb9 | ||
|
|
da474575cc | ||
|
|
81da5ca41d | ||
|
|
06a231b868 | ||
|
|
ccc2d5a78c | ||
|
|
67e96a9730 | ||
|
|
2bf58946e0 | ||
|
|
f1eb5e5553 | ||
|
|
189771062a | ||
|
|
f1a0d69576 | ||
|
|
be2d7eb69b | ||
|
|
0e352a9bb6 | ||
|
|
42d1721751 | ||
|
|
e20797a4df | ||
|
|
54b224c8c6 | ||
|
|
8d7554031d | ||
|
|
9ad25e290f | ||
|
|
b84020b310 | ||
|
|
54bf53cd0e | ||
|
|
e1c3ef2785 | ||
|
|
5a036a6638 | ||
|
|
17f906df1e | ||
|
|
a73b625e68 | ||
|
|
780ac71f74 | ||
|
|
88b425b63d | ||
|
|
42c8136071 | ||
|
|
b0dfe3e540 | ||
|
|
2cecffe562 | ||
|
|
b8f8214922 | ||
|
|
bc2e50b4d5 | ||
|
|
87976ae630 | ||
|
|
ccb70f5cf6 | ||
|
|
0b6681b7dc | ||
|
|
51a6fb7feb | ||
|
|
7e38262340 | ||
|
|
0febc5a07a |
10
.github/workflows/codeql-analysis.yml
vendored
10
.github/workflows/codeql-analysis.yml
vendored
@@ -38,16 +38,16 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2
|
||||
uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
|
||||
uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
|
||||
uses: github/codeql-action/autobuild@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@@ -72,4 +72,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
|
||||
uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
||||
|
||||
6
.github/workflows/dependency-review.yml
vendored
6
.github/workflows/dependency-review.yml
vendored
@@ -17,11 +17,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2
|
||||
uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4.8.2
|
||||
uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0
|
||||
|
||||
6
.github/workflows/go.yml
vendored
6
.github/workflows/go.yml
vendored
@@ -10,12 +10,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2
|
||||
uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
with:
|
||||
go-version: 'stable'
|
||||
|
||||
|
||||
30
.github/workflows/release.yaml
vendored
30
.github/workflows/release.yaml
vendored
@@ -3,6 +3,12 @@ name: Release Go Binaries
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release_tag:
|
||||
description: 'Tag name to build (v1.3.1)'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
# Declare default permissions as read only.
|
||||
permissions: read-all
|
||||
@@ -21,21 +27,37 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2
|
||||
uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Determine ref to checkout
|
||||
run: |
|
||||
# If manually invoked with a release_tag input, use refs/tags/<release_tag>.
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ -n "${{ github.event.inputs.release_tag }}" ]; then
|
||||
echo "REF=refs/tags/${{ github.event.inputs.release_tag }}" >> $GITHUB_ENV
|
||||
else
|
||||
# For release events GITHUB_REF is already refs/tags/<tag>; otherwise fall back to the incoming ref.
|
||||
echo "REF=${GITHUB_REF}" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ env.REF }}
|
||||
|
||||
- name: Set APP_VERSION env
|
||||
run: echo APP_VERSION=$(echo ${GITHUB_REF} | rev | cut -d'/' -f 1 | rev ) >> ${GITHUB_ENV}
|
||||
run: |
|
||||
# basename strips refs/... and yields the tag or branch name
|
||||
echo "APP_VERSION=$(basename ${REF})" >> $GITHUB_ENV
|
||||
|
||||
- name: Set BUILD_TIME env
|
||||
run: echo BUILD_TIME=$(date) >> ${GITHUB_ENV}
|
||||
|
||||
- uses: wangyoucao577/go-release-action@481a2c1a0f1be199722e3e9b74d7199acafc30a8 # v1.53
|
||||
- uses: wangyoucao577/go-release-action@279495102627de7960cbc33434ab01a12bae144b # v1.55
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
goos: ${{ matrix.goos }}
|
||||
goarch: ${{ matrix.goarch }}
|
||||
extra_files: LICENSE README.md smtprelay.ini
|
||||
ldflags: -s -w -X "main.appVersion=${{ env.APP_VERSION }}" -X "main.buildTime=${{ env.BUILD_TIME }}"
|
||||
release_tag: ${{ env.APP_VERSION }}
|
||||
|
||||
8
.github/workflows/scorecards.yml
vendored
8
.github/workflows/scorecards.yml
vendored
@@ -36,12 +36,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2
|
||||
uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||
# format to the repository Actions tab.
|
||||
- name: "Upload artifact"
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
@@ -76,6 +76,6 @@ jobs:
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
|
||||
uses: github/codeql-action/upload-sarif@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
46
config.go
46
config.go
@@ -31,6 +31,7 @@ var (
|
||||
listenStr = flagset.String("listen", "127.0.0.1:25 [::1]:25", "Address and port to listen for incoming SMTP")
|
||||
localCert = flagset.String("local_cert", "", "SSL certificate for STARTTLS/TLS")
|
||||
localKey = flagset.String("local_key", "", "SSL private key for STARTTLS/TLS")
|
||||
tlsProfile = flagset.String("tls_profile", "default", "TLS profile: modern | intermediate | default | legacy")
|
||||
localForceTLS = flagset.Bool("local_forcetls", false, "Force STARTTLS (needs local_cert and local_key)")
|
||||
readTimeoutStr = flagset.String("read_timeout", "60s", "Socket timeout for read operations")
|
||||
writeTimeoutStr = flagset.String("write_timeout", "60s", "Socket timeout for write operations")
|
||||
@@ -46,6 +47,8 @@ var (
|
||||
command = flagset.String("command", "", "Path to pipe command")
|
||||
remotesStr = flagset.String("remotes", "", "Outgoing SMTP servers")
|
||||
strictSender = flagset.Bool("strict_sender", false, "Use only SMTP servers with Sender matches to From")
|
||||
remoteCert = flagset.String("remote_certificate", "", "Client SSL certificate for remote STARTTLS/TLS")
|
||||
remoteKey = flagset.String("remote_key", "", "Client SSL private key for remote STARTTLS/TLS")
|
||||
|
||||
// additional flags
|
||||
_ = flagset.String("config", "", "Path to config file (ini format)")
|
||||
@@ -67,9 +70,38 @@ func localAuthRequired() bool {
|
||||
return *allowedUsers != ""
|
||||
}
|
||||
|
||||
func remoteCertAndKeyReadable() bool {
|
||||
certSet := *remoteCert != ""
|
||||
keySet := *remoteKey != ""
|
||||
|
||||
// Both must be set or both must be unset
|
||||
if certSet != keySet {
|
||||
return false
|
||||
}
|
||||
|
||||
// If both are set, verify files exist and are accessible
|
||||
if certSet && keySet {
|
||||
if _, err := os.Stat(*remoteCert); err != nil {
|
||||
log.Error().
|
||||
Str("cert", *remoteCert).
|
||||
Err(err).
|
||||
Msg("cannot access remote client certificate file")
|
||||
return false
|
||||
}
|
||||
if _, err := os.Stat(*remoteKey); err != nil {
|
||||
log.Error().
|
||||
Str("key", *remoteKey).
|
||||
Err(err).
|
||||
Msg("cannot access remote client key file")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func setupAliases() {
|
||||
if *aliasFile != "" {
|
||||
aliases := make(AliasMap)
|
||||
aliases, err := AliasLoadFile(*aliasFile)
|
||||
if err != nil {
|
||||
log.Fatal().
|
||||
@@ -138,6 +170,11 @@ func setupRemotes() {
|
||||
logger.Fatal().Msg(fmt.Sprintf("error parsing url: '%s': %v", remoteURL, err))
|
||||
}
|
||||
|
||||
if *remoteCert != "" && *remoteKey != "" && (r.Scheme == "smtps" || r.Scheme == "starttls") {
|
||||
r.ClientCertPath = *remoteCert
|
||||
r.ClientKeyPath = *remoteKey
|
||||
}
|
||||
|
||||
remotes = append(remotes, r)
|
||||
}
|
||||
}
|
||||
@@ -254,6 +291,13 @@ func ConfigLoad() {
|
||||
log.Warn().Msg("no remotes or command set; mail will not be forwarded!")
|
||||
}
|
||||
|
||||
if !remoteCertAndKeyReadable() {
|
||||
log.Fatal().
|
||||
Str("remote_certificate", *remoteCert).
|
||||
Str("remote_key", *remoteKey).
|
||||
Msg("remote_certificate and remote_key must both be set or both be empty")
|
||||
}
|
||||
|
||||
setupAllowedNetworks()
|
||||
setupAllowedPatterns()
|
||||
setupAliases()
|
||||
|
||||
16
go.mod
16
go.mod
@@ -1,24 +1,24 @@
|
||||
module github.com/decke/smtprelay
|
||||
|
||||
require (
|
||||
github.com/DeRuina/timberjack v1.3.9
|
||||
github.com/chrj/smtpd v0.3.1
|
||||
github.com/fsnotify/fsnotify v1.9.0
|
||||
github.com/DeRuina/timberjack v1.4.2
|
||||
github.com/chrj/smtpd v1.0.0
|
||||
github.com/fsnotify/fsnotify v1.10.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/peterbourgon/ff/v3 v3.4.0
|
||||
github.com/rs/zerolog v1.34.0
|
||||
github.com/rs/zerolog v1.35.1
|
||||
github.com/stretchr/testify v1.11.1
|
||||
golang.org/x/crypto v0.44.0
|
||||
golang.org/x/crypto v0.50.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/klauspost/compress v1.18.1 // indirect
|
||||
github.com/klauspost/compress v1.18.4 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/sys v0.43.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
go 1.24.3
|
||||
go 1.25.0
|
||||
|
||||
37
go.sum
37
go.sum
@@ -1,43 +1,34 @@
|
||||
github.com/DeRuina/timberjack v1.3.9 h1:6UXZ1I7ExPGTX/1UNYawR58LlOJUHKBPiYC7WQ91eBo=
|
||||
github.com/DeRuina/timberjack v1.3.9/go.mod h1:RLoeQrwrCGIEF8gO5nV5b/gMD0QIy7bzQhBUgpp1EqE=
|
||||
github.com/chrj/smtpd v0.3.1 h1:kogHFkbFdKaoH3bgZkqNC9uVtKYOFfM3uV3rroBdooE=
|
||||
github.com/chrj/smtpd v0.3.1/go.mod h1:JtABvV/LzvLmEIzy0NyDnrfMGOMd8wy5frAokwf6J9Q=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/DeRuina/timberjack v1.4.2 h1:4bKlzhKdsR+2oNkgef9mqb4n11ICow8VK88RfzJPzN8=
|
||||
github.com/DeRuina/timberjack v1.4.2/go.mod h1:RLoeQrwrCGIEF8gO5nV5b/gMD0QIy7bzQhBUgpp1EqE=
|
||||
github.com/chrj/smtpd v1.0.0 h1:jChSfLzAhrSxTky0EZ6oW+UKiqdnepdknWYRkPeA+ZE=
|
||||
github.com/chrj/smtpd v1.0.0/go.mod h1:zEP61gNDlWp/jdUqBcq/ykIbgOERyRvwfMsRLl3h9gM=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
|
||||
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
|
||||
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
|
||||
github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
|
||||
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc=
|
||||
github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
||||
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
||||
github.com/rs/zerolog v1.35.1 h1:m7xQeoiLIiV0BCEY4Hs+j2NG4Gp2o2KPKmhnnLiazKI=
|
||||
github.com/rs/zerolog v1.35.1/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
|
||||
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
||||
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
||||
54
main.go
54
main.go
@@ -281,6 +281,7 @@ func generateUUID() string {
|
||||
}
|
||||
|
||||
func getTLSConfig() *tls.Config {
|
||||
// Certificate loading / validation
|
||||
if *localCert == "" || *localKey == "" {
|
||||
log.Fatal().
|
||||
Str("cert_file", *localCert).
|
||||
@@ -295,9 +296,60 @@ func getTLSConfig() *tls.Config {
|
||||
Msg("cannot load X509 keypair")
|
||||
}
|
||||
|
||||
return &tls.Config{
|
||||
// TLS profile configuration
|
||||
// tls.Config.CipherSuites only affects TLS 1.0–1.2.
|
||||
|
||||
// Base config: Go defaults unless overridden.
|
||||
conf := &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
}
|
||||
|
||||
profile := strings.ToLower(strings.TrimSpace(*tlsProfile))
|
||||
if profile == "" {
|
||||
profile = "default"
|
||||
}
|
||||
|
||||
switch profile {
|
||||
case "default":
|
||||
// Go defaults (leave MinVersion/MaxVersion/CipherSuites unset).
|
||||
|
||||
case "modern":
|
||||
// TLS 1.3+.
|
||||
conf.MinVersion = tls.VersionTLS13
|
||||
|
||||
case "intermediate":
|
||||
// TLS 1.2+ with AEAD + ECDHE cipher suites only.
|
||||
// Mozilla "intermediate" — AEAD + ECDHE only.
|
||||
conf.MinVersion = tls.VersionTLS12
|
||||
conf.CipherSuites = []uint16{
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
|
||||
}
|
||||
|
||||
case "legacy":
|
||||
// Last resort: TLS 1.0+ and everything Go exposes for TLS 1.0–1.2.
|
||||
conf.MinVersion = tls.VersionTLS10
|
||||
|
||||
allSuites := []uint16{}
|
||||
for _, cs := range tls.CipherSuites() {
|
||||
allSuites = append(allSuites, cs.ID)
|
||||
}
|
||||
for _, cs := range tls.InsecureCipherSuites() {
|
||||
allSuites = append(allSuites, cs.ID)
|
||||
}
|
||||
conf.CipherSuites = allSuites
|
||||
|
||||
default:
|
||||
log.Warn().
|
||||
Str("tls_profile", profile).
|
||||
Msg("unknown tls_profile; using default")
|
||||
}
|
||||
|
||||
return conf
|
||||
}
|
||||
|
||||
func watchAliasFile() {
|
||||
|
||||
16
remotes.go
16
remotes.go
@@ -7,13 +7,15 @@ import (
|
||||
)
|
||||
|
||||
type Remote struct {
|
||||
SkipVerify bool
|
||||
Auth smtp.Auth
|
||||
Scheme string
|
||||
Hostname string
|
||||
Port string
|
||||
Addr string
|
||||
Sender string
|
||||
SkipVerify bool
|
||||
Auth smtp.Auth
|
||||
Scheme string
|
||||
Hostname string
|
||||
Port string
|
||||
Addr string
|
||||
Sender string
|
||||
ClientCertPath string
|
||||
ClientKeyPath string
|
||||
}
|
||||
|
||||
// ParseRemote creates a remote from a given url in the following format:
|
||||
|
||||
16
smtp.go
16
smtp.go
@@ -340,6 +340,14 @@ func SendMail(r *Remote, from string, to []string, msg []byte) error {
|
||||
ServerName: r.Hostname,
|
||||
InsecureSkipVerify: r.SkipVerify,
|
||||
}
|
||||
// Load client certificate on-demand, just before connection
|
||||
if r.ClientCertPath != "" && r.ClientKeyPath != "" {
|
||||
cert, err := tls.LoadX509KeyPair(r.ClientCertPath, r.ClientKeyPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.Certificates = []tls.Certificate{cert}
|
||||
}
|
||||
conn, err := tls.Dial("tcp", r.Addr, config)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -366,6 +374,14 @@ func SendMail(r *Remote, from string, to []string, msg []byte) error {
|
||||
ServerName: c.serverName,
|
||||
InsecureSkipVerify: r.SkipVerify,
|
||||
}
|
||||
// Load client certificate on-demand, just before use
|
||||
if r.ClientCertPath != "" && r.ClientKeyPath != "" {
|
||||
cert, err := tls.LoadX509KeyPair(r.ClientCertPath, r.ClientKeyPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.Certificates = []tls.Certificate{cert}
|
||||
}
|
||||
if testHookStartTLS != nil {
|
||||
testHookStartTLS(config)
|
||||
}
|
||||
|
||||
@@ -35,6 +35,33 @@
|
||||
;local_cert = smtpd.pem
|
||||
;local_key = smtpd.key
|
||||
|
||||
; TLS PROFILE (STARTTLS / TLS listeners)
|
||||
; Controls the minimum TLS version and allowed cipher suites for inbound
|
||||
; connections when using listen protocols `starttls://` or `tls://`.
|
||||
; Profiles follow the Mozilla SSL Configuration Generator guidelines.
|
||||
;
|
||||
; default
|
||||
; Go standard library defaults. Tracks improvements as Go updates
|
||||
; its TLS defaults. Recommended for most deployments.
|
||||
;
|
||||
; modern
|
||||
; TLS 1.3+ only. Simplest and most secure, but rejects any client
|
||||
; that cannot negotiate TLS 1.3.
|
||||
; Matches Mozilla "modern" configuration.
|
||||
;
|
||||
; intermediate
|
||||
; TLS 1.2+. For TLS 1.2 connections only AEAD ciphers (AES-GCM,
|
||||
; ChaCha20-Poly1305) with ECDHE key exchange are allowed,
|
||||
; providing forward secrecy. TLS 1.3 ciphers are always available.
|
||||
; Matches Mozilla "intermediate" configuration.
|
||||
;
|
||||
; legacy
|
||||
; TLS 1.0+ with all cipher suites the Go standard library supports,
|
||||
; including insecure ones (RC4, 3DES). Use only when you must
|
||||
; support very old clients that cannot negotiate TLS 1.2.
|
||||
;
|
||||
;tls_profile=default
|
||||
|
||||
; Enforce encrypted connection on STARTTLS ports before
|
||||
; accepting mails from client.
|
||||
;local_forcetls = false
|
||||
@@ -119,6 +146,10 @@
|
||||
; Mailjet.com
|
||||
;remotes = starttls://user:pass@in-v3.mailjet.com:587
|
||||
|
||||
; Exchange Online (O365) SMTP relay
|
||||
; (Change netloc to your own Exchange MX endpoint)
|
||||
; remotes = starttls://contoso-com.mail.protection.outlook.com:25
|
||||
|
||||
; Ignore remote host certificates
|
||||
;remotes = starttls://user:pass@server:587?skipVerify
|
||||
|
||||
@@ -131,5 +162,11 @@
|
||||
; Multiple remotes, space delimited
|
||||
;remotes = smtp://127.0.0.1:1025 starttls://user:pass@smtp.mailgun.org:587
|
||||
|
||||
; Client SSL certificate for remote STARTTLS/TLS
|
||||
; remote_certificate = /path/to/certificate-chain.pem
|
||||
|
||||
; Client SSL private key for remote STARTTLS/TLS
|
||||
; remote_key = /path/to/private-key.pem
|
||||
|
||||
; Pipe messages to external command
|
||||
;command = /usr/local/bin/script
|
||||
|
||||
Reference in New Issue
Block a user