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>
This commit is contained in:
Vendored
+96
@@ -0,0 +1,96 @@
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user