From 9e532a60076df3b48dcde21e8902b873f232e070 Mon Sep 17 00:00:00 2001 From: Aine Date: Sat, 19 Nov 2022 16:41:53 +0200 Subject: [PATCH] initial cc support --- bot/email.go | 2 ++ bot/settings_room.go | 2 ++ smtp/server.go | 2 ++ smtp/session.go | 69 +++++++++++++++----------------------------- utils/email.go | 57 ++++++++++++++++++++++++++++++++++-- 5 files changed, 84 insertions(+), 48 deletions(-) diff --git a/bot/email.go b/bot/email.go index 8cb5a2e..d3e4d21 100644 --- a/bot/email.go +++ b/bot/email.go @@ -25,8 +25,10 @@ const ( eventReferencesKey = "cc.etke.postmoogle.references" eventInReplyToKey = "cc.etke.postmoogle.inReplyTo" eventSubjectKey = "cc.etke.postmoogle.subject" + eventRcptToKey = "cc.etke.postmoogle.rcptTo" eventFromKey = "cc.etke.postmoogle.from" eventToKey = "cc.etke.postmoogle.to" + eventCcKey = "cc.etke.postmoogle.cc" ) // SetSendmail sets mail sending func to the bot diff --git a/bot/settings_room.go b/bot/settings_room.go index f46c97a..91edda9 100644 --- a/bot/settings_room.go +++ b/bot/settings_room.go @@ -152,7 +152,9 @@ func (s roomSettings) ContentOptions() *utils.ContentOptions { Threads: !s.NoThreads(), ToKey: eventToKey, + CcKey: eventCcKey, FromKey: eventFromKey, + RcptToKey: eventRcptToKey, SubjectKey: eventSubjectKey, MessageIDKey: eventMessageIDkey, InReplyToKey: eventInReplyToKey, diff --git a/smtp/server.go b/smtp/server.go index 3301b87..cefdd9a 100644 --- a/smtp/server.go +++ b/smtp/server.go @@ -60,6 +60,7 @@ func (m *mailServer) Login(state *smtp.ConnectionState, username, password strin from: username, log: m.log, domains: m.domains, + tos: []string{}, }, nil } @@ -80,6 +81,7 @@ func (m *mailServer) AnonymousLogin(state *smtp.ConnectionState) (smtp.Session, log: m.log, domains: m.domains, addr: state.RemoteAddr, + tos: []string{}, }, nil } diff --git a/smtp/session.go b/smtp/session.go index ca75024..661ae6e 100644 --- a/smtp/session.go +++ b/smtp/session.go @@ -28,7 +28,7 @@ type incomingSession struct { ctx context.Context addr net.Addr - to string + tos []string from string } @@ -46,7 +46,7 @@ func (s *incomingSession) Mail(from string, opts smtp.MailOptions) error { func (s *incomingSession) Rcpt(to string) error { sentry.GetHubFromContext(s.ctx).Scope().SetTag("to", to) - s.to = to + s.tos = append(s.tos, to) var domainok bool for _, domain := range s.domains { if utils.Hostname(to) == domain { @@ -66,7 +66,7 @@ func (s *incomingSession) Rcpt(to string) error { } validations := s.getFilters(roomID) - if !validateEmail(s.from, s.to, s.log, validations) { + if !validateEmail(s.from, to, s.log, validations) { s.ban(s.addr) return ErrBanned } @@ -89,20 +89,15 @@ func (s *incomingSession) Data(r io.Reader) error { return err } - files := parseAttachments(eml.Attachments, s.log) - - email := utils.NewEmail( - eml.GetHeader("Message-Id"), - eml.GetHeader("In-Reply-To"), - eml.GetHeader("References"), - eml.GetHeader("Subject"), - s.from, - s.to, - eml.Text, - eml.HTML, - files) - - return s.receiveEmail(s.ctx, email) + email := utils.FromEnvelope(s.tos[0], eml) + for _, to := range s.tos { + email.RcptTo = to + err := s.receiveEmail(s.ctx, email) + if err != nil { + return err + } + } + return nil } func (s *incomingSession) Reset() {} func (s *incomingSession) Logout() error { return nil } @@ -115,7 +110,7 @@ type outgoingSession struct { domains []string ctx context.Context - to string + tos []string from string } @@ -129,7 +124,7 @@ func (s *outgoingSession) Mail(from string, opts smtp.MailOptions) error { func (s *outgoingSession) Rcpt(to string) error { sentry.GetHubFromContext(s.ctx).Scope().SetTag("to", to) - s.to = to + s.tos = append(s.tos, to) s.log.Debug("mail to %s", to) return nil @@ -141,21 +136,16 @@ func (s *outgoingSession) Data(r io.Reader) error { if err != nil { return err } + email := utils.FromEnvelope(s.tos[0], eml) + for _, to := range s.tos { + email.RcptTo = to + err := s.sendmail(email.From, to, email.Compose(s.privkey)) + if err != nil { + return err + } + } - files := parseAttachments(eml.Attachments, s.log) - - email := utils.NewEmail( - eml.GetHeader("Message-Id"), - eml.GetHeader("In-Reply-To"), - eml.GetHeader("References"), - eml.GetHeader("Subject"), - s.from, - s.to, - eml.Text, - eml.HTML, - files) - - return s.sendmail(email.From, email.To, email.Compose(s.privkey)) + return nil } func (s *outgoingSession) Reset() {} func (s *outgoingSession) Logout() error { return nil } @@ -170,16 +160,3 @@ func validateEmail(from, to string, log *logger.Logger, options utils.IncomingFi return v.Email(from) } - -func parseAttachments(parts []*enmime.Part, log *logger.Logger) []*utils.File { - files := make([]*utils.File, 0, len(parts)) - for _, attachment := range parts { - for _, err := range attachment.Errors { - log.Warn("attachment error: %v", err) - } - file := utils.NewFile(attachment.FileName, attachment.Content) - files = append(files, file) - } - - return files -} diff --git a/utils/email.go b/utils/email.go index 34b03e3..dda0025 100644 --- a/utils/email.go +++ b/utils/email.go @@ -34,6 +34,8 @@ type Email struct { References string From string To string + RcptTo string + CC string Subject string Text string HTML string @@ -56,6 +58,8 @@ type ContentOptions struct { SubjectKey string FromKey string ToKey string + CcKey string + RcptToKey string } // AddressValid checks if email address is valid @@ -64,6 +68,16 @@ func AddressValid(email string) bool { return err == nil } +// EmailDate returns Date in RFC1123 with numeric timezone +func EmailDate(original ...time.Time) string { + now := time.Now().UTC() + if len(original) > 0 && !original[0].IsZero() { + now = original[0] + } + + return now.Format(time.RFC1123Z) +} + // MessageID generates email Message-Id from matrix event ID func MessageID(eventID id.EventID, domain string) string { return fmt.Sprintf("<%s@%s>", eventID, domain) @@ -72,12 +86,13 @@ func MessageID(eventID id.EventID, domain string) string { // NewEmail constructs Email object func NewEmail(messageID, inReplyTo, references, subject, from, to, text, html string, files []*File) *Email { email := &Email{ - Date: time.Now().UTC().Format(time.RFC1123Z), + Date: EmailDate(), MessageID: messageID, InReplyTo: inReplyTo, References: references, From: from, To: to, + RcptTo: to, Subject: subject, Text: text, HTML: html, @@ -92,10 +107,46 @@ func NewEmail(messageID, inReplyTo, references, subject, from, to, text, html st return email } +func FromEnvelope(rcptto string, eml *enmime.Envelope) *Email { + datetime, _ := eml.Date() //nolint:errcheck // handled in EmailDate() + date := EmailDate(datetime) + + var html string + if eml.HTML != "" { + html = styleRegex.ReplaceAllString(eml.HTML, "") + } + + files := make([]*File, 0, len(eml.Attachments)) + for _, attachment := range eml.Attachments { + for _, err := range attachment.Errors { + log.Warn("attachment error: %v", err) + } + file := NewFile(attachment.FileName, attachment.Content) + files = append(files, file) + } + + email := &Email{ + Date: date, + MessageID: eml.GetHeader("Message-Id"), + InReplyTo: eml.GetHeader("In-Reply-To"), + References: eml.GetHeader("References"), + From: eml.GetHeader("From"), + To: eml.GetHeader("To"), + RcptTo: rcptto, + CC: eml.GetHeader("Cc"), + Subject: eml.GetHeader("Subject"), + Text: eml.Text, + HTML: html, + Files: files, + } + + return email +} + // Mailbox returns postmoogle's mailbox, parsing it from FROM (if incoming=false) or TO (incoming=true) func (e *Email) Mailbox(incoming bool) string { if incoming { - return Mailbox(e.To) + return Mailbox(e.RcptTo) } return Mailbox(e.From) } @@ -133,8 +184,10 @@ func (e *Email) Content(threadID id.EventID, options *ContentOptions) *event.Con options.InReplyToKey: e.InReplyTo, options.ReferencesKey: e.References, options.SubjectKey: e.Subject, + options.RcptToKey: e.RcptTo, options.FromKey: e.From, options.ToKey: e.To, + options.CcKey: e.CC, }, Parsed: &parsed, }