smtp_server.go 5.3 KB

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