smtp_server.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. package server
  2. import (
  3. "bytes"
  4. "errors"
  5. "github.com/emersion/go-smtp"
  6. "io"
  7. "mime"
  8. "mime/multipart"
  9. "net/mail"
  10. "strings"
  11. "sync"
  12. )
  13. var (
  14. errInvalidDomain = errors.New("invalid domain")
  15. errInvalidAddress = errors.New("invalid address")
  16. errInvalidTopic = errors.New("invalid topic")
  17. errTooManyRecipients = errors.New("too many recipients")
  18. errUnsupportedContentType = errors.New("unsupported content type")
  19. )
  20. // smtpBackend implements SMTP server methods.
  21. type smtpBackend struct {
  22. config *Config
  23. sub subscriber
  24. success int64
  25. failure int64
  26. mu sync.Mutex
  27. }
  28. func newMailBackend(conf *Config, sub subscriber) *smtpBackend {
  29. return &smtpBackend{
  30. config: conf,
  31. sub: sub,
  32. }
  33. }
  34. func (b *smtpBackend) Login(state *smtp.ConnectionState, username, password string) (smtp.Session, error) {
  35. return &smtpSession{backend: b}, nil
  36. }
  37. func (b *smtpBackend) AnonymousLogin(state *smtp.ConnectionState) (smtp.Session, error) {
  38. return &smtpSession{backend: b}, nil
  39. }
  40. func (b *smtpBackend) Counts() (success int64, failure int64) {
  41. b.mu.Lock()
  42. defer b.mu.Unlock()
  43. return b.success, b.failure
  44. }
  45. // smtpSession is returned after EHLO.
  46. type smtpSession struct {
  47. backend *smtpBackend
  48. topic string
  49. mu sync.Mutex
  50. }
  51. func (s *smtpSession) AuthPlain(username, password string) error {
  52. return nil
  53. }
  54. func (s *smtpSession) Mail(from string, opts smtp.MailOptions) error {
  55. return nil
  56. }
  57. func (s *smtpSession) Rcpt(to string) error {
  58. return s.withFailCount(func() error {
  59. conf := s.backend.config
  60. addressList, err := mail.ParseAddressList(to)
  61. if err != nil {
  62. return err
  63. } else if len(addressList) != 1 {
  64. return errTooManyRecipients
  65. }
  66. to = addressList[0].Address
  67. if !strings.HasSuffix(to, "@"+conf.SMTPServerDomain) {
  68. return errInvalidDomain
  69. }
  70. to = strings.TrimSuffix(to, "@"+conf.SMTPServerDomain)
  71. if conf.SMTPServerAddrPrefix != "" {
  72. if !strings.HasPrefix(to, conf.SMTPServerAddrPrefix) {
  73. return errInvalidAddress
  74. }
  75. to = strings.TrimPrefix(to, conf.SMTPServerAddrPrefix)
  76. }
  77. if !topicRegex.MatchString(to) {
  78. return errInvalidTopic
  79. }
  80. s.mu.Lock()
  81. s.topic = to
  82. s.mu.Unlock()
  83. return nil
  84. })
  85. }
  86. func (s *smtpSession) Data(r io.Reader) error {
  87. return s.withFailCount(func() error {
  88. conf := s.backend.config
  89. b, err := io.ReadAll(r) // Protected by MaxMessageBytes
  90. if err != nil {
  91. return err
  92. }
  93. msg, err := mail.ReadMessage(bytes.NewReader(b))
  94. if err != nil {
  95. return err
  96. }
  97. body, err := readMailBody(msg)
  98. if err != nil {
  99. return err
  100. }
  101. body = strings.TrimSpace(body)
  102. if len(body) > conf.MessageLimit {
  103. body = body[:conf.MessageLimit]
  104. }
  105. m := newDefaultMessage(s.topic, body)
  106. subject := strings.TrimSpace(msg.Header.Get("Subject"))
  107. if subject != "" {
  108. dec := mime.WordDecoder{}
  109. subject, err := dec.DecodeHeader(subject)
  110. if err != nil {
  111. return err
  112. }
  113. m.Title = subject
  114. }
  115. if m.Title != "" && m.Message == "" {
  116. m.Message = m.Title // Flip them, this makes more sense
  117. m.Title = ""
  118. }
  119. if err := s.backend.sub(m); err != nil {
  120. return err
  121. }
  122. s.backend.mu.Lock()
  123. s.backend.success++
  124. s.backend.mu.Unlock()
  125. return nil
  126. })
  127. }
  128. func (s *smtpSession) Reset() {
  129. s.mu.Lock()
  130. s.topic = ""
  131. s.mu.Unlock()
  132. }
  133. func (s *smtpSession) Logout() error {
  134. return nil
  135. }
  136. func (s *smtpSession) withFailCount(fn func() error) error {
  137. err := fn()
  138. s.backend.mu.Lock()
  139. defer s.backend.mu.Unlock()
  140. if err != nil {
  141. s.backend.failure++
  142. }
  143. return err
  144. }
  145. func readMailBody(msg *mail.Message) (string, error) {
  146. contentType, params, err := mime.ParseMediaType(msg.Header.Get("Content-Type"))
  147. if err != nil {
  148. return "", err
  149. }
  150. if contentType == "text/plain" {
  151. body, err := io.ReadAll(msg.Body)
  152. if err != nil {
  153. return "", err
  154. }
  155. return string(body), nil
  156. }
  157. if strings.HasPrefix(contentType, "multipart/") {
  158. mr := multipart.NewReader(msg.Body, params["boundary"])
  159. for {
  160. part, err := mr.NextPart()
  161. if err != nil { // may be io.EOF
  162. return "", err
  163. }
  164. partContentType, _, err := mime.ParseMediaType(part.Header.Get("Content-Type"))
  165. if err != nil {
  166. return "", err
  167. }
  168. if partContentType != "text/plain" {
  169. continue
  170. }
  171. body, err := io.ReadAll(part)
  172. if err != nil {
  173. return "", err
  174. }
  175. return string(body), nil
  176. }
  177. }
  178. return "", errUnsupportedContentType
  179. }