func sanitizeAncestorIDs(headers mail.Header) EmailAddresses { inReplyToStr := headers.Get("In-Reply-To") referencesStr := headers.Get("References") inReplyTo := ParseAngledEmailAddressesSmart(inReplyToStr) references := ParseAngledEmailAddressesSmart(referencesStr) if len(inReplyTo) > 0 && (len(references) == 0 || references[len(references)-1] != inReplyTo[0]) { references = append(references, inReplyTo[0]) } return references }
// IsAttachment returns true, if the given header defines an attachment. // First it checks, if the Content-Disposition header defines an attachement. // If this test is false, the Content-Type header is checked. // // Valid Attachment-Headers: // // Content-Disposition: attachment; filename="frog.jpg" // Content-Type: attachment; filename="frog.jpg" // func IsAttachment(header mail.Header) bool { mediatype, _, _ := mime.ParseMediaType(header.Get("Content-Disposition")) if strings.ToLower(mediatype) == "attachment" { return true } mediatype, _, _ = mime.ParseMediaType(header.Get("Content-Type")) if strings.ToLower(mediatype) == "attachment" { return true } return false }
// SpamScore finds the spam score given the email headers func SpamScore(headers mail.Header) float64 { score := headers.Get("X-Spam-Score") if score == "" { return .0 } scoreNum, err := strconv.ParseFloat(score, 64) if err != nil { return .0 } return scoreNum }
// parseBody will accept a a raw body, break it into all its parts and then convert the // message to UTF-8 from whatever charset it may have. func parseBody(header mail.Header, body []byte) (html []byte, text []byte, isMultipart bool, err error) { var mediaType string var params map[string]string mediaType, params, err = mime.ParseMediaType(header.Get("Content-Type")) if err != nil { return } if strings.HasPrefix(mediaType, "multipart/") { isMultipart = true mr := multipart.NewReader(bytes.NewReader(body), params["boundary"]) for { p, err := mr.NextPart() if err == io.EOF { break } if err != nil { break } slurp, err := ioutil.ReadAll(p) if err != nil { break } partMediaType, partParams, err := mime.ParseMediaType(p.Header.Get("Content-Type")) if err != nil { break } var htmlT, textT []byte htmlT, textT, err = parsePart(partMediaType, partParams["charset"], p.Header.Get("Content-Transfer-Encoding"), slurp) if len(htmlT) > 0 { html = htmlT } else { text = textT } } } else { splitBody := bytes.SplitN(body, headerSplitter, 2) if len(splitBody) < 2 { err = errors.New("unexpected email format. (single part and no \\r\\n\\r\\n separating headers/body") return } body = splitBody[1] html, text, err = parsePart(mediaType, params["charset"], header.Get("Content-Transfer-Encoding"), body) } return }
// headToAddy parses a header containing email addresses func headToAddy(h mail.Header, header string) (addys []string) { _, exists := h[header] if !exists { return } addyList, err := h.AddressList(header) if err != nil { Warn.Printf("Failed to parse header: %s", header) } for _, addy := range addyList { addys = append(addys, addy.Address) } return }
// IsPlain returns true, if the the mime headers define a valid // 'text/plain' or 'text/html part'. Ff emptyContentTypeIsPlain is set // to true, a missing Content-Type header will result in a positive // plain part detection. func IsPlain(header mail.Header, emptyContentTypeIsPlain bool) bool { ctype := header.Get("Content-Type") if ctype == "" && emptyContentTypeIsPlain { return true } mediatype, _, err := mime.ParseMediaType(ctype) if err != nil { return false } switch mediatype { case "text/plain", "text/html": return true } return false }
// parseFrom takes a mail address of the format Name <name@foo> and validates // it. If custom From headers are not allowed, it will be tweaked to conform // with the Remailer's configuration. func parseFrom(h mail.Header) []string { from, err := h.AddressList("From") if err != nil { // The supplied address is invalid. Use defaults instead. return []string{fmt.Sprintf( "%s <%s>", cfg.Mail.OutboundName, cfg.Mail.OutboundAddy, )} } if len(from) == 0 { // The address list is empty so return defaults return []string{fmt.Sprintf( "%s <%s>", cfg.Mail.OutboundName, cfg.Mail.OutboundAddy, )} } if cfg.Mail.CustomFrom { // Accept whatever was provided (it's already been validated by // AddressList). return []string{fmt.Sprintf( "%s <%s>", from[0].Name, from[0].Address, )} } if len(from[0].Name) == 0 { return []string{fmt.Sprintf( "%s <%s>", cfg.Mail.OutboundName, cfg.Mail.OutboundAddy, )} } return []string{fmt.Sprintf( "%s <%s>", from[0].Name, cfg.Mail.OutboundAddy, )} }
// processMail reads the Remailer's Maildir and processes the content func processMail(secret *keymgr.Secring) (err error) { dir := maildir.Dir(cfg.Files.Maildir) // Get a list of Maildir keys from the directory keys, err := dir.Unseen() if err != nil { return } newMsgs := len(keys) if newMsgs == 0 { // Nothing to do, move along! return } Trace.Printf( "Reading %d messages from %s\n", newMsgs, cfg.Files.Maildir, ) // Increment inbound Email counter stats.inMail += newMsgs // Fetch headers for each Maildir key var head mail.Header for _, key := range keys { head, err = dir.Header(key) if err != nil { Warn.Printf("%s: Getting headers failed with: %s", key, err) continue } // The Subject determines if the message needs remailer-foo handling subject := strings.TrimSpace(strings.ToLower(head.Get("Subject"))) if strings.HasPrefix(subject, "remailer-") { // It's a remailer-foo request err = remailerFoo(subject, head.Get("From")) if err == nil { // Increments stats counter stats.inRemFoo++ } else { Info.Println(err) } err = dir.Purge(key) if err != nil { Warn.Printf( "Cannot delete remailer-foo mail: %s", err, ) } // Nothing else to do, move on to the next message continue } // It's not a remailer-foo request so assume a remailer message var mailMsg *mail.Message mailMsg, err := dir.Message(key) if err != nil { Warn.Printf( "%s: Reading message failed with: %s", key, err, ) continue } var msg []byte // Convert the armored Yamn message to its byte components msg, err = stripArmor(mailMsg.Body) if err != nil { Info.Println(err) continue } if msg == nil { Warn.Println("Dearmor returned zero bytes") continue } err = decodeMsg(msg, secret) if err != nil { Info.Println(err) } err = dir.Purge(key) if err != nil { Warn.Printf("Cannot delete mail: %s", err) } } // Maildir keys loop return }