72 lines
1.6 KiB
Go
72 lines
1.6 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"net/mail"
|
|
"strings"
|
|
|
|
"archivmail/pkg/mailparser"
|
|
)
|
|
|
|
// emailsFromHeader is fail-closed: an unparseable (possibly attacker-crafted)
|
|
// header yields no addresses and therefore grants no access.
|
|
func emailsFromHeader(header string) []string {
|
|
addrs, err := mail.ParseAddressList(header)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
out := make([]string, len(addrs))
|
|
for i, a := range addrs {
|
|
out[i] = strings.ToLower(a.Address)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// sourceType must be the stored emails.source_type of the mail: delivery
|
|
// headers only count for trusted pull sources (see mailparser).
|
|
func mailBelongsToUser(pm *mailparser.ParsedMail, userEmail, sourceType string) bool {
|
|
target := strings.ToLower(strings.TrimSpace(userEmail))
|
|
if pm == nil || target == "" {
|
|
return false
|
|
}
|
|
|
|
checkHeader := func(header string) bool {
|
|
for _, addr := range emailsFromHeader(header) {
|
|
if addr == target {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
if checkHeader(pm.From) {
|
|
return true
|
|
}
|
|
for _, to := range pm.To {
|
|
if checkHeader(to) {
|
|
return true
|
|
}
|
|
}
|
|
for _, cc := range pm.CC {
|
|
if checkHeader(cc) {
|
|
return true
|
|
}
|
|
}
|
|
for _, addr := range mailparser.TrustedDeliveryAddresses(pm, sourceType) {
|
|
if addr == target {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// A failed source lookup only drops the delivery-header path; From/To/CC
|
|
// still apply, so the result can never be more permissive than before.
|
|
func (s *Server) userOwnsMail(ctx context.Context, id string, pm *mailparser.ParsedMail, userEmail string) bool {
|
|
src, err := s.store.GetSourceType(ctx, id)
|
|
if err != nil {
|
|
src = ""
|
|
}
|
|
return mailBelongsToUser(pm, userEmail, src)
|
|
}
|