Files
mixandClaude Opus 5.5 f1d90a2a1b
test / test (push) Waiting to run
go-milter: build it patched not to keep every header
The security documentation's last open item of panel-dkim-signing (the
owner's decision, 2026-10-16): go-milter v0.4.1 added every header of a
session to a map of its own, emptied only on ABORT, and handed it to
Headers - which no milter of the panel reads. Postfix bounds one header,
not their number, so a message of nothing but headers up to the message
size limit was held whole by the library in the sender check, the signer
and the journal milter on 465/587 and in the inbound journal milter on port
25, where strangers send; the signer's 1 MiB refusal came after it was all
in.

third_party/go-milter is v0.4.1 from the module cache (BSD 2-Clause,
LICENSE kept) with session.go changed: CodeHeader hands the header to
Header and keeps nothing, CodeEOH hands Headers an empty map. PATCHES.md
says what and why, and how to take a new upstream version. go.mod replaces
the module with it; the Dockerfile copies third_party before "go mod
download". TestTheLibraryKeepsNoHeaders holds it through the library's own
client over two messages of one connection. Comments, security.md (the
accepted risk becomes a note), development.md's library table, the plan and
CHANGELOG follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 09:19:57 +03:00

97 lines
3.0 KiB
Go

// Modifier instance is provided to milter handlers to modify email messages
package milter
import (
"bytes"
"encoding/binary"
"fmt"
"net/textproto"
)
// postfix wants LF lines endings. Using CRLF results in double CR sequences.
func crlfToLF(b []byte) []byte {
return bytes.ReplaceAll(b, []byte{'\r', '\n'}, []byte{'\n'})
}
// Modifier provides access to Macros, Headers and Body data to callback handlers. It also defines a
// number of functions that can be used by callback handlers to modify processing of the email message
type Modifier struct {
Macros map[string]string
Headers textproto.MIMEHeader
writePacket func(*Message) error
}
// AddRecipient appends a new envelope recipient for current message
func (m *Modifier) AddRecipient(r string) error {
data := []byte(fmt.Sprintf("<%s>", r) + null)
return m.writePacket(NewResponse('+', data).Response())
}
// DeleteRecipient removes an envelope recipient address from message
func (m *Modifier) DeleteRecipient(r string) error {
data := []byte(fmt.Sprintf("<%s>", r) + null)
return m.writePacket(NewResponse('-', data).Response())
}
// ReplaceBody substitutes message body with provided body
func (m *Modifier) ReplaceBody(body []byte) error {
body = crlfToLF(body)
return m.writePacket(NewResponse('b', body).Response())
}
// AddHeader appends a new email message header the message
func (m *Modifier) AddHeader(name, value string) error {
var buffer bytes.Buffer
buffer.WriteString(name + null)
buffer.Write(crlfToLF([]byte(value)))
buffer.WriteString(null)
return m.writePacket(NewResponse('h', buffer.Bytes()).Response())
}
// Quarantine a message by giving a reason to hold it
func (m *Modifier) Quarantine(reason string) error {
return m.writePacket(NewResponse('q', []byte(reason+null)).Response())
}
// ChangeHeader replaces the header at the specified position with a new one.
// The index is per name.
func (m *Modifier) ChangeHeader(index int, name, value string) error {
var buffer bytes.Buffer
if err := binary.Write(&buffer, binary.BigEndian, uint32(index)); err != nil {
return err
}
buffer.WriteString(name + null)
buffer.Write(crlfToLF([]byte(value)))
buffer.WriteString(null)
return m.writePacket(NewResponse('m', buffer.Bytes()).Response())
}
// InsertHeader inserts the header at the specified position
func (m *Modifier) InsertHeader(index int, name, value string) error {
var buffer bytes.Buffer
if err := binary.Write(&buffer, binary.BigEndian, uint32(index)); err != nil {
return err
}
buffer.WriteString(name + null)
buffer.Write(crlfToLF([]byte(value)))
buffer.WriteString(null)
return m.writePacket(NewResponse('i', buffer.Bytes()).Response())
}
// ChangeFrom replaces the FROM envelope header with a new one
func (m *Modifier) ChangeFrom(value string) error {
data := []byte(value + null)
return m.writePacket(NewResponse('e', data).Response())
}
// newModifier creates a new Modifier instance from milterSession
func newModifier(s *milterSession) *Modifier {
return &Modifier{
Macros: s.macros,
Headers: s.headers,
writePacket: s.WritePacket,
}
}