test / test (push) Waiting to run
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>
97 lines
3.0 KiB
Go
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,
|
|
}
|
|
}
|