Skip to content
Snippets Groups Projects
Commit b449df60 authored by ale's avatar ale
Browse files

Initial import

parents
No related branches found
No related tags found
No related merge requests found
Pipeline #6447 passed
image: docker:latest
stages:
- build
- release
services:
- docker:dind
variables:
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
RELEASE_TAG: $CI_REGISTRY_IMAGE:latest
before_script:
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN registry.git.autistici.org
build:
stage: build
script:
- docker build --pull -t $IMAGE_TAG .
- docker push $IMAGE_TAG
release:
stage: release
script:
- docker pull $IMAGE_TAG
- docker tag $IMAGE_TAG $RELEASE_TAG
- docker push $RELEASE_TAG
only:
- master
FROM golang:latest AS build
COPY sendmail.go /sendmail.go
RUN go build -tags netgo -o /sendmail /sendmail.go && strip /sendmail
FROM scratch
COPY --from=build /sendmail /
sendmail-go
===
A minimal */usr/sbin/sendmail* drop-in replacement that will accept
emails on standard input and will forward them via a relay host using
(authenticated) SMTP.
# Usage
The tool supports the most common sendmail command-line options. It
will ignore most of them except for *-f* (to set the envelope sender),
and *-t* (to extract recipients from the message itself). If *-t* is
not specified, the tool will expect recipients to be passed as
command-line arguments.
The tool will read a configuration file (default */etc/sendmail.json*,
it can be overridden with the *SENDMAIL_CONFIG* environment variable
or by using the *-C* option) containing relay and authentication
parameters. The configuration file should be encoded in JSON format,
as a dictionary containing the following attributes:
* `smtp_server` server (host:port) to connect to (default
"localhost:25"). The tool will automatically switch to TLS if
supported.
* `smtp_user`, `smtp_pass`: credentials for authentication
# Building
You can build a static *sendmail* executable by running:
```shell
$ go build -tags netgo -o sendmail sendmail.go
```
Or reuse our Docker image within a multi-stage Dockerfile:
```
FROM registry.git.autistici.org/ai3/tools/sendmail-go:master AS sendmail
[...]
COPY --from=sendmail /sendmail /usr/sbin/sendmail
```
// A minimal 'sendmail' clone that forwards all email remotely over
// (authenticated) SMTP.
//
package main
import (
"bytes"
"encoding/json"
"flag"
"io/ioutil"
"log"
"net/mail"
"net/smtp"
"os"
)
var (
configPath = flag.String("C", getenvDefault("SENDMAIL_CONFIG", "/etc/sendmail.json"), "location of the configuration file")
envelopeSender = flag.String("f", "", "envelope sender")
recipientsFromMsg = flag.Bool("t", false, "extract recipients from message")
// Some common sendmail options that we wish to ignore,
// luckily the Go flag parser will accept the "short flag"
// format for them. Ignoring these options makes it less
// likely that we'll have to modify the existing sendmail
// commands used inside applications.
ignoredFlagI = flag.Bool("i", false, "ignored")
ignoredFlagM = flag.Bool("m", false, "ignored")
ignoredFlagN = flag.Bool("n", false, "ignored")
ignoredFlagO = flag.String("O", "", "ignored")
ignoredFlagOEE = flag.Bool("oee", false, "ignored")
ignoredFlagOEM = flag.Bool("oem", false, "ignored")
ignoredFlagOEP = flag.Bool("oep", false, "ignored")
ignoredFlagOEQ = flag.Bool("oeq", false, "ignored")
ignoredFlagOEW = flag.Bool("oew", false, "ignored")
ignoredFlagOI = flag.Bool("oi", false, "ignored")
ignoredFlagOM = flag.Bool("om", false, "ignored")
ignoredFlagOO = flag.Bool("oo", false, "ignored")
ignoredFlagV = flag.Bool("v", false, "ignored")
defaultSMTPServer = "localhost:25"
)
// The configuration is stored in a JSON-encoded file.
type config struct {
Server string `json:"smtp_server"`
User string `json:"smtp_user"`
Pass string `json:"smtp_pass"`
}
func readConfig(path string) (server string, auth smtp.Auth, err error) {
var data []byte
data, err = ioutil.ReadFile(path)
if err != nil {
return
}
var config config
if err = json.Unmarshal(data, &config); err != nil {
return
}
server = config.Server
if config.User != "" {
auth = smtp.PlainAuth("", config.User, config.Pass, "")
}
return
}
func uniqueRecipients(rcpts []string) []string {
tmp := make(map[string]struct{})
for _, r := range rcpts {
tmp[r] = struct{}{}
}
out := make([]string, 0, len(tmp))
for r := range tmp {
out = append(out, r)
}
return out
}
func getenvDefault(key, dflt string) string {
if s := os.Getenv(key); s != "" {
return s
}
return dflt
}
func main() {
log.SetFlags(0)
flag.Parse()
smtpServer, smtpAuth, err := readConfig(*configPath)
if err != nil {
log.Fatalf("error reading configuration: %v", err)
}
if smtpServer == "" {
smtpServer = defaultSMTPServer
}
// Read the whole message from stdin, and parse it.
msgData, err := ioutil.ReadAll(os.Stdin)
if err != nil {
log.Fatal(err)
}
msg, err := mail.ReadMessage(bytes.NewReader(msgData))
if err != nil {
log.Fatal(err)
}
header := msg.Header
// Find envelope sender, from args or from message.
sender := *envelopeSender
if sender == "" {
senderAddr, err := mail.ParseAddress(header.Get("From"))
if err != nil {
log.Fatal("could not parse envelope sender")
}
sender = senderAddr.Address
}
// Find recipients, from args or from message.
recipients := flag.Args()
if *recipientsFromMsg {
for _, rcptHdr := range []string{"To", "Cc", "Bcc"} {
rcptAddrs, err := header.AddressList(rcptHdr)
if err != nil && err != mail.ErrHeaderNotPresent {
log.Fatalf("could not parse recipients: %v", err)
}
for _, addr := range rcptAddrs {
recipients = append(recipients, addr.Address)
}
}
}
recipients = uniqueRecipients(recipients)
if len(recipients) == 0 {
log.Fatal("message has no recipients")
}
// Send the message using the net/smtp package.
err = smtp.SendMail(smtpServer, smtpAuth, sender, recipients, msgData)
if err != nil {
log.Fatalf("error sending message: %v", err)
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment