spf

package module
v1.0.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 8, 2025 License: MIT Imports: 8 Imported by: 0

README

SPF Validation Library for Go

This library provides SPF (Sender Policy Framework) validation functionality according to RFC 7208. It allows you to check if an IP address is authorized to send emails from a specific domain by validating against the domain's SPF records.

Features

  • Complete implementation of RFC 7208 SPF validation
  • Support for all SPF mechanisms: all, ip4, ip6, a, mx, include, exists, ptr
  • Support for all qualifiers: + (pass), - (fail), ~ (softfail), ? (neutral)
  • Recursive resolution of include: directives
  • Support for redirect modifier
  • Multiple DNS server support for lookups
  • Optional Memcache caching for DNS responses (TXT, A, MX)
  • Proper handling of DNS lookup limits (max 10 lookups per check)
  • Debug mode for hierarchical mechanism evaluation output
  • Clear result codes as defined in RFC 7208

Installation

go get github.com/jwillberg/spf

Usage

Basic Usage
package main

import (
    "fmt"
    "github.com/jwillberg/spf"
)

func main() {
    // Check if IP 192.168.1.1 is authorized to send emails from example.com
    result, err := spf.SPFCheck("192.168.1.1", "example.com")
    if err != nil {
        fmt.Printf("Error: %v\n", err)
        return
    }

    fmt.Printf("SPF check result: %s\n", result)

    // Handle different result types
    switch result {
    case spf.ResultPass:
        fmt.Println("The IP is authorized to send mail from this domain")
    case spf.ResultFail:
        fmt.Println("The IP is NOT authorized to send mail from this domain")
    case spf.ResultSoftFail:
        fmt.Println("The IP is probably not authorized (weak policy)")
    case spf.ResultNeutral:
        fmt.Println("The domain explicitly states no assertion about this IP")
    case spf.ResultNone:
        fmt.Println("No SPF record found or no valid domain")
    case spf.ResultTempError:
        fmt.Println("Temporary error occurred during SPF check")
    case spf.ResultPermError:
        fmt.Println("Permanent error occurred during SPF check")
    }
}
Optional Memcache Caching

This library can optionally cache DNS lookups (TXT, A, MX) in Memcache. If you provide a Memcache address when creating the DNSResolver, DNS responses are stored temporarily according to their DNS TTL. Subsequent queries for the same record(s) use cached data until the TTL expires.

// Example: create a resolver with Memcache caching
memcacheAddr := "127.0.0.1:11211" // or "" to disable caching
resolver := spf.NewDNSResolver(spfDNSServers, memcacheAddr)

// Then use the resolver in your SPFCheck or custom flow...
Result Types

The library returns one of the following result types as defined in RFC 7208 section 2.6:

  • none: No SPF records found or no valid domain
  • neutral: Domain explicitly states no assertion
  • pass: IP is authorized to send mail from this domain
  • fail: IP is not authorized to send mail from this domain
  • softfail: IP is probably not authorized (weak policy)
  • temperror: Temporary error (generally DNS) while performing the check
  • permerror: Permanent error (invalid SPF record) that requires DNS operator intervention

Command Line Tool

The library includes a simple command-line tool to test SPF validation:

go run main.go [--debug] <ip> <domain>

Example with debug output:

go run main.go --debug 52.103.0.1 willberg.me
Example Debug Output
Checking SPF for IP 52.103.0.1 and domain willberg.me
  ↳ SPF: v=spf1 a mx ip4:37.97.172.152 ip6:2a01:7c8:aac0:79:5054:ff:fea1:15a0 ip4:217.77.192.9 ip4:217.77.193.9 include:spf.protection.outlook.com ~all
    ↳ a → not matched
    ↳ mx → not matched
    ↳ ip4:37.97.172.152/32 → not matched
    ↳ ip6:2a01:7c8:aac0:79:5054:ff:fea1:15a0/128 → not matched
    ↳ ip4:217.77.192.9/32 → not matched
    ↳ ip4:217.77.193.9/32 → not matched
  ↳ include:spf.protection.outlook.com
  Checking SPF for IP 52.103.0.1 and domain spf.protection.outlook.com
    ↳ SPF: v=spf1 ip4:40.92.0.0/15 ip4:40.107.0.0/16 ip4:52.100.0.0/15 ip4:52.102.0.0/16 ip4:52.103.0.0/17 ip4:104.47.0.0/17 ip6:2a01:111:f400::/48 ip6:2a01:111:f403::/49 ip6:2a01:111:f403:8000::/51 ip6:2a01:111:f403:c000::/51 ip6:2a01:111:f403:f000::/52 -all
      ↳ ip4:40.92.0.0/15 → not matched
      ↳ ip4:40.107.0.0/16 → not matched
      ↳ ip4:52.100.0.0/15 → not matched
      ↳ ip4:52.102.0.0/16 → not matched
      ↳ ip4:52.103.0.0/17 → MATCHED (pass)
    ↳ include:spf.protection.outlook.com → MATCHED (pass)
SPF check result for IP 52.103.0.1 sending from domain willberg.me: pass

Implementation Details

The library uses multiple DNS servers for lookups:

var spfDNSServers = []string{"8.8.8.8", "1.1.1.1", "9.9.9.9", "208.67.222.222"}

It implements all SPF mechanisms as specified in RFC 7208:

  • all: Matches always
  • ip4: Matches IPv4 addresses and networks
  • ip6: Matches IPv6 addresses and networks
  • a: Matches if the IP is one of the A records for the domain
  • mx: Matches if the IP is one of the mail exchangers for the domain
  • include: Includes another domain's SPF policy
  • exists: Matches if the specified domain exists
  • ptr: Matches if a PTR record for the IP resolves to the domain (deprecated)
  • redirect: Redirects evaluation to another domain

License

MIT License

Documentation

Index

Constants

View Source
const (
	ResultNone      = "none"      // No SPF records found or no valid domain
	ResultNeutral   = "neutral"   // Domain explicitly states no assertion (?)
	ResultPass      = "pass"      // IP is authorized to send mail from this domain
	ResultFail      = "fail"      // IP is not authorized to send mail from this domain
	ResultSoftFail  = "softfail"  // IP is probably not authorized (weak policy)
	ResultTempError = "temperror" // Temporary error (DNS issues, etc.)
	ResultPermError = "permerror" // Permanent error (invalid SPF record, etc.)
)

SPF result types as defined in RFC 7208

Variables

View Source
var ErrNXDOMAIN = errors.New("nxdomain")

ErrNXDOMAIN is used to indicate that the domain does not exist (NXDOMAIN).

Functions

func EnableDebug

func EnableDebug()

EnableDebug enables debug output

func SPFCheck

func SPFCheck(ip string, domain string, memcacheAddr string) (string, error)

SPFCheck validates if an IP address is authorized to send emails from a specific domain Returns one of the following results as defined in RFC 7208 section 2.6: - "none": No SPF records found or no valid domain - "neutral": Domain explicitly states no assertion - "pass": IP is authorized to send mail from this domain - "fail": IP is not authorized to send mail from this domain - "softfail": IP is probably not authorized (weak policy) - "temperror": Temporary error (DNS issues, etc.) - "permerror": Permanent error (invalid SPF record, etc.)

Types

type DNSResolver

type DNSResolver struct {
	// contains filtered or unexported fields
}

DNSResolver handles DNS lookups for SPF validation

func NewDNSResolver

func NewDNSResolver(servers []string, memcacheAddr string) *DNSResolver

NewDNSResolver creates a new DNS resolver with the specified servers If memcacheAddr is an empty string, no memcache usage

type Mechanism

type Mechanism struct {
	Type      MechanismType
	Qualifier Qualifier
	Domain    string
	IP        net.IP
	IPNet     *net.IPNet
	Prefix    int
}

Mechanism represents an SPF mechanism with its qualifier and parameters

type MechanismType

type MechanismType string

MechanismType represents the type of SPF mechanism

const (
	MechanismAll     MechanismType = "all"
	MechanismIP4     MechanismType = "ip4"
	MechanismIP6     MechanismType = "ip6"
	MechanismA       MechanismType = "a"
	MechanismMX      MechanismType = "mx"
	MechanismPTR     MechanismType = "ptr"
	MechanismExists  MechanismType = "exists"
	MechanismInclude MechanismType = "include"
)

type Qualifier

type Qualifier string

Qualifier represents the qualifier for an SPF mechanism

const (
	QualifierPass     Qualifier = "+"
	QualifierFail     Qualifier = "-"
	QualifierSoftFail Qualifier = "~"
	QualifierNeutral  Qualifier = "?"
)

type SPFEvaluator

type SPFEvaluator struct {
	// contains filtered or unexported fields
}

SPFEvaluator handles the evaluation of SPF records

func NewSPFEvaluator

func NewSPFEvaluator(resolver *DNSResolver) *SPFEvaluator

NewSPFEvaluator creates a new SPF evaluator

func (*SPFEvaluator) EvaluateSPF

func (e *SPFEvaluator) EvaluateSPF(ip net.IP, domain string, record *SPFRecord) (string, error)

EvaluateSPF evaluates an SPF record for a given IP and domain

type SPFRecord

type SPFRecord struct {
	Raw        string
	Mechanisms []Mechanism
	Modifiers  map[string]string
}

SPFRecord represents a parsed SPF record

func ParseSPFRecord

func ParseSPFRecord(record string) (*SPFRecord, error)

ParseSPFRecord parses an SPF record string into an SPFRecord struct

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL