7 Commits

Author SHA1 Message Date
5808081cb3 Merge branch 'master' into docker
merge upstream
2026-01-06 16:28:32 -07:00
45a3870f8c update golang to v1.25.5 2025-12-07 08:02:26 -07:00
02b9072bd2 merge upstream 2025-12-07 14:54:52 +00:00
796b54a565 disable CGO in GO build
- We want a statically linked standalone binary.
2025-11-26 14:41:59 -07:00
bc4a31817e add explicit binary locations
- Container image seemed to be build without the binary in the right place to run.
2025-11-26 14:34:43 -07:00
55f941c7a1 disable VCS info in go build
- buildkit in the CI pipeline isn't liking it, don't really need it.
2025-11-26 14:06:17 -07:00
b30d51e9a3 add Dockerfile for smtprelay 2025-11-26 13:58:50 -07:00
11 changed files with 43 additions and 114 deletions

View File

@@ -47,7 +47,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@cdefb33c0f6224e58673d9004f47f7cb3e328b89 # v4.31.10
uses: github/codeql-action/init@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
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@cdefb33c0f6224e58673d9004f47f7cb3e328b89 # v4.31.10
uses: github/codeql-action/autobuild@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
# 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@cdefb33c0f6224e58673d9004f47f7cb3e328b89 # v4.31.10
uses: github/codeql-action/analyze@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9

View File

@@ -15,7 +15,7 @@ jobs:
egress-policy: audit
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
- uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
with:
go-version: 'stable'

View File

@@ -3,12 +3,6 @@ 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
@@ -31,25 +25,10 @@ jobs:
with:
egress-policy: audit
- 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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
ref: ${{ env.REF }}
- name: Set APP_VERSION env
run: |
# basename strips refs/... and yields the tag or branch name
echo "APP_VERSION=$(basename ${REF})" >> $GITHUB_ENV
run: echo APP_VERSION=$(echo ${GITHUB_REF} | rev | cut -d'/' -f 1 | rev ) >> ${GITHUB_ENV}
- name: Set BUILD_TIME env
run: echo BUILD_TIME=$(date) >> ${GITHUB_ENV}
@@ -60,4 +39,3 @@ jobs:
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 }}

View File

@@ -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@cdefb33c0f6224e58673d9004f47f7cb3e328b89 # v4.31.10
uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
with:
sarif_file: results.sarif

23
Dockerfile Normal file
View File

@@ -0,0 +1,23 @@
# Package versions
ARG GOLANG_VERSION="1.25.5"
# Stage 1: Build
FROM core.harbor.brds.ca/library/golang:${GOLANG_VERSION} AS builder
WORKDIR /project
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -buildvcs=false -o /smtprelay
# Stage 2: Package
FROM scratch
LABEL org.opencontainers.image.title="smtprelay"
LABEL org.opencontainers.image.authors="drew@brds.ca"
LABEL org.opencontainers.image.description="Image containing the smtprelay program."
WORKDIR /
COPY --from=builder /smtprelay /smtprelay
# Command to run
ENTRYPOINT ["/smtprelay"]

View File

@@ -46,8 +46,6 @@ 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)")
@@ -69,36 +67,6 @@ 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, err := AliasLoadFile(*aliasFile)
@@ -169,11 +137,6 @@ 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)
}
}
@@ -290,13 +253,6 @@ 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()

4
go.mod
View File

@@ -8,7 +8,7 @@ require (
github.com/peterbourgon/ff/v3 v3.4.0
github.com/rs/zerolog v1.34.0
github.com/stretchr/testify v1.11.1
golang.org/x/crypto v0.47.0
golang.org/x/crypto v0.46.0
)
require (
@@ -17,7 +17,7 @@ require (
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.40.0 // indirect
golang.org/x/sys v0.39.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

8
go.sum
View File

@@ -31,13 +31,13 @@ github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
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.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
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=

View File

@@ -7,15 +7,13 @@ import (
)
type Remote struct {
SkipVerify bool
Auth smtp.Auth
Scheme string
Hostname string
Port string
Addr string
Sender string
ClientCertPath string
ClientKeyPath string
SkipVerify bool
Auth smtp.Auth
Scheme string
Hostname string
Port string
Addr string
Sender string
}
// ParseRemote creates a remote from a given url in the following format:

16
smtp.go
View File

@@ -340,14 +340,6 @@ 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
@@ -374,14 +366,6 @@ 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)
}

View File

@@ -119,10 +119,6 @@
; 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
@@ -135,11 +131,5 @@
; 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