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
+144
@@ -0,0 +1,144 @@
|
||||
package milter
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/textproto"
|
||||
)
|
||||
|
||||
// Milter protocol version implemented by the server.
|
||||
//
|
||||
// Note: Not exported as we might want to support multiple versions
|
||||
// transparently in the future.
|
||||
var serverProtocolVersion uint32 = 2
|
||||
|
||||
// ErrServerClosed is returned by the Server's Serve method after a call to
|
||||
// Close.
|
||||
var ErrServerClosed = errors.New("milter: server closed")
|
||||
|
||||
// Milter is an interface for milter callback handlers.
|
||||
type Milter interface {
|
||||
// Connect is called to provide SMTP connection data for incoming message.
|
||||
// Suppress with OptNoConnect.
|
||||
Connect(host string, family string, port uint16, addr net.IP, m *Modifier) (Response, error)
|
||||
|
||||
// Helo is called to process any HELO/EHLO related filters. Suppress with
|
||||
// OptNoHelo.
|
||||
Helo(name string, m *Modifier) (Response, error)
|
||||
|
||||
// MailFrom is called to process filters on envelope FROM address. Suppress
|
||||
// with OptNoMailFrom.
|
||||
MailFrom(from string, m *Modifier) (Response, error)
|
||||
|
||||
// RcptTo is called to process filters on envelope TO address. Suppress with
|
||||
// OptNoRcptTo.
|
||||
RcptTo(rcptTo string, m *Modifier) (Response, error)
|
||||
|
||||
// Header is called once for each header in incoming message. Suppress with
|
||||
// OptNoHeaders.
|
||||
Header(name string, value string, m *Modifier) (Response, error)
|
||||
|
||||
// Headers is called when all message headers have been processed. Suppress
|
||||
// with OptNoEOH.
|
||||
Headers(h textproto.MIMEHeader, m *Modifier) (Response, error)
|
||||
|
||||
// BodyChunk is called to process next message body chunk data (up to 64KB
|
||||
// in size). Suppress with OptNoBody.
|
||||
BodyChunk(chunk []byte, m *Modifier) (Response, error)
|
||||
|
||||
// Body is called at the end of each message. All changes to message's
|
||||
// content & attributes must be done here.
|
||||
Body(m *Modifier) (Response, error)
|
||||
|
||||
// Abort is called is the current message has been aborted. All message data
|
||||
// should be reset to prior to the Helo callback. Connection data should be
|
||||
// preserved.
|
||||
Abort(m *Modifier) error
|
||||
}
|
||||
|
||||
// NoOpMilter is a dummy Milter implementation that does nothing.
|
||||
type NoOpMilter struct{}
|
||||
|
||||
var _ Milter = NoOpMilter{}
|
||||
|
||||
func (NoOpMilter) Connect(host string, family string, port uint16, addr net.IP, m *Modifier) (Response, error) {
|
||||
return RespContinue, nil
|
||||
}
|
||||
|
||||
func (NoOpMilter) Helo(name string, m *Modifier) (Response, error) {
|
||||
return RespContinue, nil
|
||||
}
|
||||
|
||||
func (NoOpMilter) MailFrom(from string, m *Modifier) (Response, error) {
|
||||
return RespContinue, nil
|
||||
}
|
||||
|
||||
func (NoOpMilter) RcptTo(rcptTo string, m *Modifier) (Response, error) {
|
||||
return RespContinue, nil
|
||||
}
|
||||
|
||||
func (NoOpMilter) Header(name string, value string, m *Modifier) (Response, error) {
|
||||
return RespContinue, nil
|
||||
}
|
||||
|
||||
func (NoOpMilter) Headers(h textproto.MIMEHeader, m *Modifier) (Response, error) {
|
||||
return RespContinue, nil
|
||||
}
|
||||
|
||||
func (NoOpMilter) BodyChunk(chunk []byte, m *Modifier) (Response, error) {
|
||||
return RespContinue, nil
|
||||
}
|
||||
|
||||
func (NoOpMilter) Body(m *Modifier) (Response, error) {
|
||||
return RespAccept, nil
|
||||
}
|
||||
|
||||
func (NoOpMilter) Abort(m *Modifier) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Server is a milter server.
|
||||
type Server struct {
|
||||
NewMilter func() Milter
|
||||
Actions OptAction
|
||||
Protocol OptProtocol
|
||||
|
||||
listeners []net.Listener
|
||||
closed bool
|
||||
}
|
||||
|
||||
// Serve starts the server.
|
||||
func (s *Server) Serve(ln net.Listener) error {
|
||||
defer ln.Close()
|
||||
|
||||
s.listeners = append(s.listeners, ln)
|
||||
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
if s.closed {
|
||||
return ErrServerClosed
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
session := milterSession{
|
||||
server: s,
|
||||
actions: s.Actions,
|
||||
protocol: s.Protocol,
|
||||
conn: conn,
|
||||
backend: s.NewMilter(),
|
||||
}
|
||||
go session.HandleMilterCommands()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) Close() error {
|
||||
s.closed = true
|
||||
for _, ln := range s.listeners {
|
||||
if err := ln.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user