Fix: reply 235 when client auth success, according to rfc2554 (#1)
[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 ReplyAuthOK = ReplyLine{235, "auth success"}
35 ReplyBadSyntax = ReplyLine{501, "syntax error"}
36 ReplyBadSequence = ReplyLine{503, "bad sequence of commands"}
37 ReplyBadMailbox = ReplyLine{550, "mailbox unavailable"}
38 ReplyMailboxUnallowed = ReplyLine{553, "mailbox name not allowed"}
39 )
40
41 func DomainForAddress(addr mail.Address) string {
42 return DomainForAddressString(addr.Address)
43 }
44
45 func DomainForAddressString(address string) string {
46 domainIdx := strings.LastIndex(address, "@")
47 if domainIdx == -1 {
48 return ""
49 }
50 return address[domainIdx+1:]
51 }
52
53 type Envelope struct {
54 RemoteAddr net.Addr
55 EHLO string
56 MailFrom mail.Address
57 RcptTo []mail.Address
58 Data []byte
59 Received time.Time
60 ID string
61 }
62
63 func WriteEnvelopeForDelivery(w io.Writer, e Envelope) {
64 fmt.Fprintf(w, "Delivered-To: <%s>\r\n", e.RcptTo[0].Address)
65 fmt.Fprintf(w, "Return-Path: <%s>\r\n", e.MailFrom.Address)
66 w.Write(e.Data)
67 }
68
69 func generateEnvelopeId(prefix string, t time.Time) string {
70 var idBytes [4]byte
71 rand.Read(idBytes[:])
72 return fmt.Sprintf("%s.%d.%x", prefix, t.UnixNano(), idBytes)
73 }
74
75 // lookupRemoteHost attempts to reverse look-up the provided IP address. On
76 // success, it returns the hostname and the IP as formatted for a receive
77 // trace. If the lookup fails, it just returns the original IP.
78 func lookupRemoteHost(addr net.Addr) string {
79 rhost, _, err := net.SplitHostPort(addr.String())
80 if err != nil {
81 rhost = addr.String()
82 }
83
84 rhosts, err := net.LookupAddr(rhost)
85 if err == nil {
86 rhost = fmt.Sprintf("%s [%s]", rhosts[0], rhost)
87 }
88
89 return rhost
90 }
91
92 type Server interface {
93 Name() string
94 TLSConfig() *tls.Config
95 VerifyAddress(mail.Address) ReplyLine
96 // Verify that the authc+passwd identity can send mail as authz.
97 Authenticate(authz, authc, passwd string) bool
98 DeliverMessage(Envelope) *ReplyLine
99
100 // RelayMessage instructs the server to send the Envelope to another
101 // MTA for outbound delivery.
102 RelayMessage(Envelope)
103 }
104
105 type EmptyServerCallbacks struct{}
106
107 func (*EmptyServerCallbacks) TLSConfig() *tls.Config {
108 return nil
109 }
110
111 func (*EmptyServerCallbacks) VerifyAddress(mail.Address) ReplyLine {
112 return ReplyOK
113 }
114
115 func (*EmptyServerCallbacks) Authenticate(authz, authc, passwd string) bool {
116 return false
117 }
118
119 func (*EmptyServerCallbacks) DeliverMessage(Envelope) *ReplyLine {
120 return nil
121 }
122
123 func (*EmptyServerCallbacks) RelayMessage(Envelope) {
124 }