Produce delivery-status failure notifications when failing to relay a message.
[mailpopbox.git] / smtp / server.go
1 // mailpopbox
2 // Copyright 2020 Blue Static <https://www.bluestatic.org>
3 // This program is free software licensed under the GNU General Public License,
4 // version 3.0. The full text of the license can be found in LICENSE.txt.
5 // SPDX-License-Identifier: GPL-3.0-only
6
7 package smtp
8
9 import (
10 "crypto/rand"
11 "crypto/tls"
12 "fmt"
13 "io"
14 "net"
15 "net/mail"
16 "regexp"
17 "strings"
18 "time"
19 )
20
21 type ReplyLine struct {
22 Code int
23 Message string
24 }
25
26 func (l ReplyLine) String() string {
27 return fmt.Sprintf("%d %s", l.Code, l.Message)
28 }
29
30 var SendAsSubject = regexp.MustCompile(`(?i)\[sendas:\s*([a-zA-Z0-9\.\-_]+)\]`)
31
32 var (
33 ReplyOK = ReplyLine{250, "OK"}
34 ReplyBadSyntax = ReplyLine{501, "syntax error"}
35 ReplyBadSequence = ReplyLine{503, "bad sequence of commands"}
36 ReplyBadMailbox = ReplyLine{550, "mailbox unavailable"}
37 ReplyMailboxUnallowed = ReplyLine{553, "mailbox name not allowed"}
38 )
39
40 func DomainForAddress(addr mail.Address) string {
41 return DomainForAddressString(addr.Address)
42 }
43
44 func DomainForAddressString(address string) string {
45 domainIdx := strings.LastIndex(address, "@")
46 if domainIdx == -1 {
47 return ""
48 }
49 return address[domainIdx+1:]
50 }
51
52 type Envelope struct {
53 RemoteAddr net.Addr
54 EHLO string
55 MailFrom mail.Address
56 RcptTo []mail.Address
57 Data []byte
58 Received time.Time
59 ID string
60 }
61
62 func WriteEnvelopeForDelivery(w io.Writer, e Envelope) {
63 fmt.Fprintf(w, "Delivered-To: <%s>\r\n", e.RcptTo[0].Address)
64 fmt.Fprintf(w, "Return-Path: <%s>\r\n", e.MailFrom.Address)
65 w.Write(e.Data)
66 }
67
68 func generateEnvelopeId(prefix string, t time.Time) string {
69 var idBytes [4]byte
70 rand.Read(idBytes[:])
71 return fmt.Sprintf("%s.%d.%x", prefix, t.UnixNano(), idBytes)
72 }
73
74 type Server interface {
75 Name() string
76 TLSConfig() *tls.Config
77 VerifyAddress(mail.Address) ReplyLine
78 // Verify that the authc+passwd identity can send mail as authz.
79 Authenticate(authz, authc, passwd string) bool
80 OnMessageDelivered(Envelope) *ReplyLine
81
82 // RelayMessage instructs the server to send the Envelope to another
83 // MTA for outbound delivery.
84 RelayMessage(Envelope)
85 }
86
87 type EmptyServerCallbacks struct{}
88
89 func (*EmptyServerCallbacks) TLSConfig() *tls.Config {
90 return nil
91 }
92
93 func (*EmptyServerCallbacks) VerifyAddress(mail.Address) ReplyLine {
94 return ReplyOK
95 }
96
97 func (*EmptyServerCallbacks) Authenticate(authz, authc, passwd string) bool {
98 return false
99 }
100
101 func (*EmptyServerCallbacks) OnMessageDelivered(Envelope) *ReplyLine {
102 return nil
103 }
104
105 func (*EmptyServerCallbacks) RelayMessage(Envelope) {
106 }