Skip to content

Commit 6944e33

Browse files
authored
Implement MT-PRIORITY (RFC 6710)
1 parent 54dd31d commit 6944e33

File tree

6 files changed

+145
-0
lines changed

6 files changed

+145
-0
lines changed

client.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,12 @@ func (c *Client) Rcpt(to string, opts *RcptOptions) error {
544544
}
545545
sb.WriteString(arg)
546546
}
547+
if _, ok := c.ext["MT-PRIORITY"]; ok && opts != nil && opts.MTPriority != nil {
548+
if *opts.MTPriority < -9 || *opts.MTPriority > 9 {
549+
return errors.New("smtp: MT-PRIORITY must be between -9 and 9")
550+
}
551+
sb.WriteString(fmt.Sprintf(" MT-PRIORITY=%d", *opts.MTPriority))
552+
}
547553
if _, _, err := c.cmd(25, "%s", sb.String()); err != nil {
548554
return err
549555
}

client_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1115,3 +1115,36 @@ func TestClientDELIVERBY(t *testing.T) {
11151115
t.Errorf("wrote %q; want %q", actualcmds, client)
11161116
}
11171117
}
1118+
1119+
var mtPriorityServer = `220 hello world
1120+
250 ok
1121+
`
1122+
1123+
var mtPriorityClient = `RCPT TO:<root@nsa.gov> MT-PRIORITY=6
1124+
`
1125+
1126+
func TestClientMTPRIORITY(t *testing.T) {
1127+
server := strings.Join(strings.Split(mtPriorityServer, "\n"), "\r\n")
1128+
client := strings.Join(strings.Split(mtPriorityClient, "\n"), "\r\n")
1129+
1130+
var wrote bytes.Buffer
1131+
var fake faker
1132+
fake.ReadWriter = struct {
1133+
io.Reader
1134+
io.Writer
1135+
}{
1136+
strings.NewReader(server),
1137+
&wrote,
1138+
}
1139+
c := NewClient(fake)
1140+
c.didHello = true
1141+
c.ext = map[string]string{"MT-PRIORITY": ""}
1142+
priority := 6
1143+
c.Rcpt("root@nsa.gov", &RcptOptions{
1144+
MTPriority: &priority,
1145+
})
1146+
c.Close()
1147+
if actualcmds := wrote.String(); client != actualcmds {
1148+
t.Errorf("wrote %q; want %q", actualcmds, client)
1149+
}
1150+
}

conn.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,13 @@ func (c *Conn) handleGreet(enhanced bool, arg string) {
301301
caps = append(caps, fmt.Sprintf("DELIVERBY %d", int(c.server.MinimumDeliverByTime.Seconds())))
302302
}
303303
}
304+
if c.server.EnableMTPRIORITY {
305+
if c.server.MtPriorityProfile == PriorityUnspecified {
306+
caps = append(caps, "MT-PRIORITY")
307+
} else {
308+
caps = append(caps, fmt.Sprintf("MT-PRIORITY %s", c.server.MtPriorityProfile))
309+
}
310+
}
304311

305312
args := []string{"Hello " + domain}
306313
args = append(args, caps...)
@@ -755,6 +762,21 @@ func (c *Conn) handleRcpt(arg string) {
755762
return
756763
}
757764
opts.DeliverBy = deliverBy
765+
case "MT-PRIORITY":
766+
if !c.server.EnableMTPRIORITY {
767+
c.writeResponse(504, EnhancedCode{5, 5, 4}, "MT-PRIORITY is not implemented")
768+
return
769+
}
770+
mtPriority, err := strconv.Atoi(value)
771+
if err != nil {
772+
c.writeResponse(501, EnhancedCode{5, 5, 4}, "Malformed MT-PRIORITY parameter value")
773+
return
774+
}
775+
if mtPriority < -9 || mtPriority > 9 {
776+
c.writeResponse(501, EnhancedCode{5, 5, 4}, "MT-PRIORITY is outside valid range")
777+
return
778+
}
779+
opts.MTPriority = &mtPriority
758780
default:
759781
c.writeResponse(500, EnhancedCode{5, 5, 4}, "Unknown RCPT TO argument")
760782
return

server.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,15 @@ type Server struct {
7070
// Only use if DELIVERBY is enabled.
7171
MinimumDeliverByTime time.Duration
7272

73+
// Advertise MT-PRIORITY (RFC 6710) capability.
74+
// Should only be used if backend supports it.
75+
EnableMTPRIORITY bool
76+
// The priority profile mapping as defined
77+
// in RFC 6710 section 10.2.
78+
//
79+
// Default value of NONE to advertise no specific profile.
80+
MtPriorityProfile PriorityProfile
81+
7382
// The server backend.
7483
Backend Backend
7584

server_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1650,3 +1650,65 @@ func TestServerDELIVERBY(t *testing.T) {
16501650
t.Fatal("Incorrect BY parameter value:", fmt.Sprintf("expected %#v, got %#v", expectedDeliverByOpts, deliverByOpts))
16511651
}
16521652
}
1653+
1654+
func TestServerMTPRIORITY(t *testing.T) {
1655+
be, s, c, scanner, caps := testServerEhlo(t,
1656+
func(s *smtp.Server) {
1657+
s.EnableMTPRIORITY = true
1658+
})
1659+
defer s.Close()
1660+
defer c.Close()
1661+
1662+
if _, ok := caps["MT-PRIORITY"]; !ok {
1663+
t.Fatal("Missing capability: MT-PRIORITY")
1664+
}
1665+
1666+
io.WriteString(c, "MAIL FROM:<root@nsa.gov>\r\n")
1667+
scanner.Scan()
1668+
1669+
malformedMsgs := []string{
1670+
"RCPT TO:<root@gchq.gov.uk> MT-PRIORITY=",
1671+
"RCPT TO:<root@gchq.gov.uk> MT-PRIORITY=foo",
1672+
"RCPT TO:<root@gchq.gov.uk> MT-PRIORITY=-10",
1673+
"RCPT TO:<root@gchq.gov.uk> MT-PRIORITY=10",
1674+
}
1675+
1676+
for _, msg := range malformedMsgs {
1677+
io.WriteString(c, msg+"\r\n")
1678+
scanner.Scan()
1679+
if !strings.HasPrefix(scanner.Text(), "501 5.5.4") {
1680+
t.Fatal("Unexpected res on malformed MT-PRIORITY parameter value:", scanner.Text())
1681+
}
1682+
}
1683+
1684+
expectedPriority := -2
1685+
1686+
io.WriteString(c, fmt.Sprintf("RCPT TO:<root@gchq.gov.uk> MT-PRIORITY=%d\r\n", expectedPriority))
1687+
scanner.Scan()
1688+
1689+
if !strings.HasPrefix(scanner.Text(), "250 ") {
1690+
t.Fatal("Invalid MT-PRIORITY parameter value:", scanner.Text())
1691+
}
1692+
1693+
// complete the transaction
1694+
io.WriteString(c, "DATA\r\n")
1695+
scanner.Scan()
1696+
io.WriteString(c, "Hey <3\r\n")
1697+
io.WriteString(c, ".\r\n")
1698+
scanner.Scan()
1699+
1700+
opts := be.anonmsgs[0].RcptOpts
1701+
if opts == nil || len(opts) != 1 {
1702+
t.Fatal("Invalid number of recipients:", opts)
1703+
}
1704+
1705+
priority := opts[0].MTPriority
1706+
1707+
if priority == nil {
1708+
t.Fatal("MtPriority is nil:", opts)
1709+
}
1710+
1711+
if *priority != expectedPriority {
1712+
t.Fatal("Incorrect MtPriority parameter value:", fmt.Sprintf("expected %d, got %d", expectedPriority, *priority))
1713+
}
1714+
}

smtp.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
// - STARTTLS (RFC 3207)
1212
// - DSN (RFC 3461, RFC 6533)
1313
// - SMTPUTF8 (RFC 6531)
14+
// - MT-PRIORITY (RFC 6710)
1415
// - RRVS (RFC 7293)
1516
// - REQUIRETLS (RFC 8689)
1617
//
@@ -101,6 +102,15 @@ type DeliverByOptions struct {
101102
Trace bool
102103
}
103104

105+
type PriorityProfile string
106+
107+
const (
108+
PriorityUnspecified PriorityProfile = ""
109+
PriorityMIXER PriorityProfile = "MIXER"
110+
PrioritySTANAG4406 PriorityProfile = "STANAG4406"
111+
PriorityNSEP PriorityProfile = "NSEP"
112+
)
113+
104114
// RcptOptions contains parameters for the RCPT command.
105115
type RcptOptions struct {
106116
// Value of NOTIFY= argument, NEVER or a combination of either of
@@ -117,4 +127,7 @@ type RcptOptions struct {
117127

118128
// Value of BY= argument or nil if unset.
119129
DeliverBy *DeliverByOptions
130+
131+
// Value of MT-PRIORITY= or nil if unset.
132+
MTPriority *int
120133
}

0 commit comments

Comments
 (0)