mirror of
https://github.com/emersion/go-msgauth
synced 2026-07-03 13:08:35 +00:00
262 lines
5.4 KiB
Go
262 lines
5.4 KiB
Go
package authres
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
"unicode"
|
|
)
|
|
|
|
// ResultValue is an authentication result value, as defined in RFC 5451 section
|
|
// 6.3.
|
|
type ResultValue string
|
|
|
|
const (
|
|
ResultNone ResultValue = "none"
|
|
ResultPass = "pass"
|
|
ResultFail = "fail"
|
|
ResultPolicy = "policy"
|
|
ResultNeutral = "neutral"
|
|
ResultTempError = "temperror"
|
|
ResultPermError = "permerror"
|
|
ResultHardFail = "hardfail"
|
|
ResultSoftFail = "softfail"
|
|
)
|
|
|
|
// Result is an authentication result.
|
|
type Result interface {
|
|
parse(value ResultValue, params map[string]string)
|
|
format() (value ResultValue, params map[string]string)
|
|
}
|
|
|
|
type AuthResult struct {
|
|
Value ResultValue
|
|
Auth string
|
|
}
|
|
|
|
func (r *AuthResult) parse(value ResultValue, params map[string]string) {
|
|
r.Value = value
|
|
r.Auth = params["smtp.auth"]
|
|
}
|
|
|
|
func (r *AuthResult) format() (ResultValue, map[string]string) {
|
|
return r.Value, map[string]string{"smtp.auth": r.Auth}
|
|
}
|
|
|
|
type DKIMResult struct {
|
|
Value ResultValue
|
|
Domain string
|
|
Identifier string
|
|
}
|
|
|
|
func (r *DKIMResult) parse(value ResultValue, params map[string]string) {
|
|
r.Value = value
|
|
r.Domain = params["header.d"]
|
|
r.Identifier = params["header.i"]
|
|
}
|
|
|
|
func (r *DKIMResult) format() (ResultValue, map[string]string) {
|
|
return r.Value, map[string]string{
|
|
"header.d": r.Domain,
|
|
"header.i": r.Identifier,
|
|
}
|
|
}
|
|
|
|
type DomainKeysResult struct {
|
|
Value ResultValue
|
|
Domain string
|
|
From string
|
|
Sender string
|
|
}
|
|
|
|
func (r *DomainKeysResult) parse(value ResultValue, params map[string]string) {
|
|
r.Value = value
|
|
r.Domain = params["header.d"]
|
|
r.From = params["header.from"]
|
|
r.Sender = params["header.sender"]
|
|
}
|
|
|
|
func (r *DomainKeysResult) format() (ResultValue, map[string]string) {
|
|
return r.Value, map[string]string{
|
|
"header.d": r.Domain,
|
|
"header.from": r.From,
|
|
"header.sender": r.Sender,
|
|
}
|
|
}
|
|
|
|
type IPRevResult struct {
|
|
Value ResultValue
|
|
IP string
|
|
}
|
|
|
|
func (r *IPRevResult) parse(value ResultValue, params map[string]string) {
|
|
r.Value = value
|
|
r.IP = params["policy.iprev"]
|
|
}
|
|
|
|
func (r *IPRevResult) format() (ResultValue, map[string]string) {
|
|
return r.Value, map[string]string{"policy.iprev": r.IP}
|
|
}
|
|
|
|
type SenderIDResult struct {
|
|
Value ResultValue
|
|
HeaderKey string
|
|
HeaderValue string
|
|
}
|
|
|
|
func (r *SenderIDResult) parse(value ResultValue, params map[string]string) {
|
|
r.Value = value
|
|
|
|
for k, v := range params {
|
|
if strings.HasPrefix(k, "header.") {
|
|
r.HeaderKey = strings.TrimPrefix(k, "header.")
|
|
r.HeaderValue = v
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
func (r *SenderIDResult) format() (value ResultValue, params map[string]string) {
|
|
return r.Value, map[string]string{
|
|
"header."+strings.ToLower(r.HeaderKey): r.HeaderValue,
|
|
}
|
|
}
|
|
|
|
type SPFResult struct {
|
|
Value ResultValue
|
|
From string
|
|
Helo string
|
|
}
|
|
|
|
func (r *SPFResult) parse(value ResultValue, params map[string]string) {
|
|
r.Value = value
|
|
r.From = params["smtp.mailfrom"]
|
|
r.Helo = params["smtp.helo"]
|
|
}
|
|
|
|
func (r *SPFResult) format() (ResultValue, map[string]string) {
|
|
return r.Value, map[string]string{
|
|
"smtp.mailfrom": r.From,
|
|
"smtp.helo": r.Helo,
|
|
}
|
|
}
|
|
|
|
type GenericResult struct {
|
|
Method string
|
|
Value ResultValue
|
|
Params map[string]string
|
|
}
|
|
|
|
func (r *GenericResult) parse(value ResultValue, params map[string]string) {
|
|
r.Value = value
|
|
r.Params = params
|
|
}
|
|
|
|
func (r *GenericResult) format() (ResultValue, map[string]string) {
|
|
return r.Value, r.Params
|
|
}
|
|
|
|
type newResultFunc func() Result
|
|
|
|
var results = map[string]newResultFunc{
|
|
"auth": func() Result {
|
|
return new(AuthResult)
|
|
},
|
|
"dkim": func() Result {
|
|
return new(DKIMResult)
|
|
},
|
|
"domainkeys": func() Result {
|
|
return new(DomainKeysResult)
|
|
},
|
|
"iprev": func() Result {
|
|
return new(IPRevResult)
|
|
},
|
|
"sender-id": func() Result {
|
|
return new(SenderIDResult)
|
|
},
|
|
"spf": func() Result {
|
|
return new(SPFResult)
|
|
},
|
|
}
|
|
|
|
// Parse parses the provided Authentication-Results header field. It returns the
|
|
// authentication service identifier and authentication results.
|
|
func Parse(v string) (identifier string, results []Result, err error) {
|
|
parts := strings.Split(v, ";")
|
|
|
|
identifier = strings.TrimSpace(parts[0])
|
|
i := strings.IndexFunc(identifier, unicode.IsSpace)
|
|
if i > 0 {
|
|
version := strings.TrimSpace(identifier[i:])
|
|
if version != "1" {
|
|
return "", nil, errors.New("msgauth: unsupported version")
|
|
}
|
|
|
|
identifier = identifier[:i]
|
|
}
|
|
|
|
for i := 1; i < len(parts); i++ {
|
|
s := strings.TrimSpace(parts[i])
|
|
if s == "" {
|
|
continue
|
|
}
|
|
|
|
result, err := parseResult(s)
|
|
if err != nil {
|
|
return identifier, results, err
|
|
}
|
|
if result != nil {
|
|
results = append(results, result)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func parseResult(s string) (Result, error) {
|
|
// TODO: ignore header comments in parenthesis
|
|
|
|
parts := strings.Fields(s)
|
|
if len(parts) == 0 || parts[0] == "none" {
|
|
return nil, nil
|
|
}
|
|
|
|
k, v, err := parseParam(parts[0])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
method, value := k, ResultValue(strings.ToLower(v))
|
|
|
|
params := make(map[string]string)
|
|
for i := 1; i < len(parts); i++ {
|
|
k, v, err := parseParam(parts[i])
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
params[k] = v
|
|
}
|
|
|
|
newResult, ok := results[method]
|
|
|
|
var r Result
|
|
if ok {
|
|
r = newResult()
|
|
} else {
|
|
r = &GenericResult{
|
|
Method: method,
|
|
Value: value,
|
|
Params: params,
|
|
}
|
|
}
|
|
|
|
r.parse(value, params)
|
|
return r, nil
|
|
}
|
|
|
|
func parseParam(s string) (k string, v string, err error) {
|
|
kv := strings.SplitN(s, "=", 2)
|
|
if len(kv) != 2 {
|
|
return "", "", errors.New("msgauth: malformed authentication method and value")
|
|
}
|
|
return strings.ToLower(strings.TrimSpace(kv[0])), strings.TrimSpace(kv[1]), nil
|
|
}
|