mirror of
https://blitiri.com.ar/repos/chasquid
synced 2026-10-28 15:02:28 +00:00
ce236e91bc
When parsing a header that has a continuation line, today we append that line to it, which copies the whole header each time. On normal messages this is negligible, but a header with a very large number of continuation lines would cause a DoS through work amplification. This patch fixes the problem by making header continuation handling linear: we now accumulate the header lines in a list, and then join them only once. Thanks to John JEAN <john@wargan.com> who reported a similar issue (fixed in a previous patch), which led to finding this bug.
89 lines
2.0 KiB
Go
89 lines
2.0 KiB
Go
package dkim
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
type header struct {
|
|
Name string
|
|
Value string
|
|
Source string
|
|
}
|
|
|
|
type headers []header
|
|
|
|
// FindAll the headers with the given name, in order of appearance.
|
|
func (h headers) FindAll(name string) headers {
|
|
hs := make(headers, 0)
|
|
for _, header := range h {
|
|
if strings.EqualFold(header.Name, name) {
|
|
hs = append(hs, header)
|
|
}
|
|
}
|
|
return hs
|
|
}
|
|
|
|
var errInvalidHeader = errors.New("invalid header")
|
|
|
|
// Parse a RFC822 message, return the headers, body, and error if any.
|
|
// We expect it to only contain CRLF line endings.
|
|
// Does NOT touch whitespace, this is important to preserve the original
|
|
// message and headers, which is required for the signature.
|
|
func parseMessage(message string) (headers, string, error) {
|
|
lines := strings.Split(message, "\r\n")
|
|
|
|
// Group the lines of each header: the first line, followed by its
|
|
// continuation lines (if any).
|
|
groups := [][]string{}
|
|
eoh := 0
|
|
for i, line := range lines {
|
|
if line == "" {
|
|
eoh = i
|
|
break
|
|
}
|
|
|
|
if strings.HasPrefix(line, " ") || strings.HasPrefix(line, "\t") {
|
|
// Continuation of the previous header.
|
|
if len(groups) == 0 {
|
|
return nil, "", fmt.Errorf(
|
|
"%w: bad continuation", errInvalidHeader)
|
|
}
|
|
last := len(groups) - 1
|
|
groups[last] = append(groups[last], line)
|
|
} else {
|
|
// New header.
|
|
groups = append(groups, []string{line})
|
|
}
|
|
}
|
|
|
|
headers := make(headers, 0, len(groups))
|
|
for _, g := range groups {
|
|
h, err := parseHeader(g)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
headers = append(headers, h)
|
|
}
|
|
|
|
return headers, strings.Join(lines[eoh+1:], "\r\n"), nil
|
|
}
|
|
|
|
// Parse a header, given its lines (the first one, followed by the
|
|
// continuation lines, if any).
|
|
func parseHeader(lines []string) (header, error) {
|
|
// The colon must be in the first line.
|
|
if !strings.Contains(lines[0], ":") {
|
|
return header{}, fmt.Errorf("%w: no colon", errInvalidHeader)
|
|
}
|
|
|
|
source := strings.Join(lines, "\r\n")
|
|
name, value, _ := strings.Cut(source, ":")
|
|
return header{
|
|
Name: name,
|
|
Value: value,
|
|
Source: source,
|
|
}, nil
|
|
}
|