Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / internal/platform/mailer/mailer_test.go |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 720d47fae476b0d38477ae4401697d372b1ea2bc |
| Branch | feature/opportunity-to-outcome |
| Path or archive member | internal/platform/mailer/mailer_test.go |
| Original SHA-256 | ef7c7b9747e5162ca8b3eeca204d70680a626806590db0f7ac3a38214e1962c1 |
| Git blob SHA | b0d6a14eec457c56943ba2a75b5fc14258ca5c8c |
| Reading-copy SHA-256 | ef7c7b9747e5162ca8b3eeca204d70680a626806590db0f7ac3a38214e1962c1 |
Recovered source transcript
Download complete source text ↓188 lines · 7.1 KB
package mailer
import (
"context"
"errors"
"net"
"strings"
"testing"
)
// The reachability boundary, asserted address by address. This is the test that stops a customer supplied
// mail host from becoming a probe into the network this server runs inside.
func TestBlockedIP(t *testing.T) {
blocked := []string{
"127.0.0.1", "127.0.0.53", "::1",
"10.0.0.5", "172.16.4.1", "172.31.255.254", "192.168.1.1",
"169.254.169.254", // the cloud instance metadata address, the highest value target of this class
"169.254.1.1",
"100.64.0.1", "100.94.8.94", "100.127.255.255", // carrier grade NAT, where this operator's own machines live
"0.0.0.0", "240.0.0.1", "255.255.255.255",
"224.0.0.1", // multicast
"fd00::1", "fc00::1", // IPv6 unique local
"fe80::1", // IPv6 link local
}
for _, s := range blocked {
ip := net.ParseIP(s)
if ip == nil {
t.Fatalf("test bug, %q is not an address", s)
}
if !blockedIP(ip) {
t.Errorf("%s MUST be blocked and was allowed", s)
}
}
allowed := []string{
"8.8.8.8", "1.1.1.1",
"52.94.236.248", // a public cloud address
"99.255.255.255", // immediately below the carrier grade NAT range
"100.63.255.255", // immediately below 100.64.0.0/10
"100.128.0.1", // immediately above it
"2606:4700::1111",
}
for _, s := range allowed {
ip := net.ParseIP(s)
if ip == nil {
t.Fatalf("test bug, %q is not an address", s)
}
if blockedIP(ip) {
t.Errorf("%s is a legitimate public address and was blocked", s)
}
}
}
// Passing a literal address instead of a name must not be a way around the guard. This is the bypass a
// reviewer looks for first.
func TestResolveAllowedRefusesLiteralInternalAddress(t *testing.T) {
for _, s := range []string{"127.0.0.1", "169.254.169.254", "10.1.2.3", "100.94.8.94"} {
if _, err := resolveAllowed(context.Background(), s); !errors.Is(err, ErrBlockedHost) {
t.Errorf("literal %s should be refused as a blocked host, got %v", s, err)
}
}
ip, err := resolveAllowed(context.Background(), "8.8.8.8")
if err != nil || ip == nil {
t.Errorf("a public literal address should resolve, got %v", err)
}
}
// Header injection. A display name or subject carrying a line break is how a second recipient gets added to a
// message the sender never intended.
func TestSendRefusesHeaderInjection(t *testing.T) {
base := Config{Host: "8.8.8.8", Port: 587, From: "a@b.test"}
cases := []struct {
name string
cfg Config
msg Message
}{
{"from", Config{Host: "8.8.8.8", Port: 587, From: "a@b.test\r\nBcc: evil@x.test"}, Message{To: "c@d.test"}},
{"from name", func() Config { c := base; c.FromName = "Acme\r\nBcc: evil@x.test"; return c }(), Message{To: "c@d.test"}},
{"reply to", func() Config { c := base; c.ReplyTo = "x@y.test\nBcc: evil@x.test"; return c }(), Message{To: "c@d.test"}},
{"recipient", base, Message{To: "c@d.test\r\nBcc: evil@x.test"}},
{"subject", base, Message{To: "c@d.test", Subject: "Hi\r\nBcc: evil@x.test"}},
}
for _, c := range cases {
// No network is reached: the header check runs before resolution, so this returns ErrHeader without
// dialling anything.
if err := Send(context.Background(), c.cfg, c.msg); !errors.Is(err, ErrHeader) {
t.Errorf("%s with a line break should be refused, got %v", c.name, err)
}
}
}
// A line beginning with a dot terminates the DATA phase early if it is not escaped, which truncates the
// message at exactly the point an attacker chooses.
func TestComposeEscapesLeadingDot(t *testing.T) {
out := compose(Config{From: "a@b.test"}, Message{To: "c@d.test", Body: "line one\n.\nline two"})
if strings.Contains(out, "\n.\n") {
t.Error("a lone leading dot was not escaped, the message can be truncated by its own body")
}
if !strings.Contains(out, "\n..\n") {
t.Error("the escaped form is missing")
}
}
func TestComposeHeaders(t *testing.T) {
out := compose(
Config{From: "noreply@acme.test", FromName: "Acme Recruiting", ReplyTo: "talent@acme.test"},
Message{To: "pat@example.test", Subject: "Your invitation"},
)
for _, want := range []string{
`From: "Acme Recruiting" <noreply@acme.test>`,
"To: pat@example.test",
"Reply-To: talent@acme.test",
"Subject: Your invitation",
"Auto-Submitted: auto-generated",
} {
if !strings.Contains(out, want) {
t.Errorf("composed message is missing %q", want)
}
}
}
// An invitation must be multipart/ALTERNATIVE with method=REQUEST. multipart/mixed produces a message with a
// file attached, which a calendar ignores and a person has to open by hand; alternative is what makes the
// accept and decline buttons appear.
func TestCalendarPartIsAlternativeNotMixed(t *testing.T) {
out := compose(
Config{From: "talent@acme.test", FromName: "Acme"},
Message{To: "jordan@example.test", Subject: "Interview",
Body: "Your interview is confirmed.", Calendar: "BEGIN:VCALENDAR\r\nEND:VCALENDAR\r\n"},
)
if !strings.Contains(out, "Content-Type: multipart/alternative;") {
t.Errorf("an invitation must be multipart/alternative:\n%s", out)
}
if strings.Contains(out, "multipart/mixed") {
t.Error("multipart/mixed makes the invitation an attachment a calendar ignores")
}
if !strings.Contains(out, "Content-Type: text/calendar; charset=utf-8; method=REQUEST") {
t.Errorf("the calendar part is missing or has no method:\n%s", out)
}
// Both parts must be present: a plain body for a client that cannot read the calendar.
if !strings.Contains(out, "Content-Type: text/plain; charset=utf-8") {
t.Error("the plain text alternative is missing")
}
if !strings.Contains(out, "Your interview is confirmed.") {
t.Error("the human readable body is missing")
}
}
// The boundary must not be able to appear in the content it delimits.
func TestBoundaryIsDerivedFromContent(t *testing.T) {
a := compose(Config{From: "a@b.test"}, Message{To: "x@y.test", Subject: "One", Calendar: "X"})
b := compose(Config{From: "a@b.test"}, Message{To: "x@y.test", Subject: "Two", Calendar: "X"})
getBoundary := func(s string) string {
i := strings.Index(s, `boundary="`)
if i < 0 {
return ""
}
rest := s[i+10:]
return rest[:strings.IndexByte(rest, '"')]
}
ba, bb := getBoundary(a), getBoundary(b)
if ba == "" || bb == "" {
t.Fatal("no boundary was emitted")
}
if ba == bb {
t.Error("two different messages share a boundary, so it is not derived from the content")
}
}
// A cancellation must carry METHOD=CANCEL, or the meeting stays in the candidate's calendar.
func TestCalendarMethodIsCarried(t *testing.T) {
out := compose(Config{From: "a@b.test"},
Message{To: "x@y.test", Subject: "Cancelled", Calendar: "BEGIN:VCALENDAR", CalendarMethod: "CANCEL"})
if !strings.Contains(out, "method=CANCEL") {
t.Errorf("the cancel method was not carried:\n%s", out)
}
}
// A calendar object is full of folded lines that can begin with anything, including a dot, which would
// terminate the DATA phase and truncate the message.
func TestCalendarPartIsDotStuffed(t *testing.T) {
out := compose(Config{From: "a@b.test"},
Message{To: "x@y.test", Subject: "s", Calendar: "BEGIN:VCALENDAR\n.\nEND:VCALENDAR"})
if strings.Contains(out, "\n.\n") {
t.Error("a lone leading dot in the calendar part was not escaped")
}
}