Skip to content

Instantly share code, notes, and snippets.

@otherjoel
Last active July 2, 2024 06:35
Show Gist options
  • Save otherjoel/6b8bf02f6db6e0c47ba6bca72ecf1c3e to your computer and use it in GitHub Desktop.
Save otherjoel/6b8bf02f6db6e0c47ba6bca72ecf1c3e to your computer and use it in GitHub Desktop.
Generate a form email to explain to someone at another company that their SPF is broken
#lang racket/base
;; Generate a form email to let someone know their SPF records are misconfigured for their current email provider.
;;
;; Run (fill-report "domain.com" "1.2.3.4") where the 2nd arg is the sending email server's IP address.
;; It will copy the completed report to the clipboard for you.
;; Only works on Windows for now.
(require net/dns
racket/class
racket/gui/base
racket/list
racket/match
racket/port
racket/string
racket/system)
(provide (all-defined-out))
(struct provider (name spf doc-link))
(define my-company-name "My Co")
(define my-direct-phone "800-488-8888")
;; Common email providers and their required SPF directives. Will build this up over time.
(define server-providers
(hash
"google\\.com" (provider "Google Domains"
"include:_spf.google.com"
"https://support.google.com/a/answer/10685031")
"pp(?:e\\-)hosted\\.com" (provider "Proofpoint"
"a:dispatch-us.ppe-hosted.com"
"https://help.proofpoint.com/Proofpoint_Essentials/Email_Security/Administrator_Topics/000_gettingstarted/020_connectiondetails")
"pobox\\.com" (provider "Pobox"
"include:pobox.com"
"https://www.pobox.help/hc/en-us/articles/360060504613-Setting-up-SPF-for-your-domain")
"livemail\\.co\\.uk" (provider "Fasthosts Livemail"
"a ip4:213.171.216.0/24 ip4:77.68.64.0/27 mx"
"https://help.fasthosts.co.uk/app/answers/detail/a_id/519/~/adding-a-spf-record-to-your-domains-dns")
"yandex\\.net" (provider "Yandex"
"include:_spf.yandex.net"
"https://yandex.com/support/connect/dns/spf.html")
"outlook\\.com" (provider "Microsoft 365"
"include:spf.protection.outlook.com"
"https://docs.microsoft.com/en-us/microsoft-365/security/office-365-security/set-up-spf-in-office-365-to-help-prevent-spoofing?view=o365-worldwide")
"protection\\.office365\\.us" (provider "Microsoft 365 GCC High"
"include:spf.protection.office365.us"
"https://docs.microsoft.com/en-us/microsoft-365/enterprise/dns-records-for-office-365-gcc-high")
"securence\\.com" (provider "Securence"
"include:spf.securence.com"
"This is based on recent SPF records from other companies that use Securence; you should confirm this with Securence support.")
"cloudfilter\\.net" (provider "BlueHost"
"include:bluehost.com"
"https://www.bluehost.com/help/article/dns-spf")
"\\.qq\\.com" (provider "QQ.com"
"include:spf.mail.qq.com"
"https://service.mail.qq.com/cgi-bin/help?subtype=1&no=1001505&id=16"))
)
(define (server->provider domain)
(for/or ([pat (in-hash-keys server-providers)])
(and (regexp-match? (regexp pat) domain) (hash-ref server-providers pat))))
(define (run+parse executable args pat [selector-proc car])
(define exe-path
(match (find-executable-path executable #f)
[(? path? p) p]
[_ (raise-user-error 'run+parse "could not find path for executable ~a" executable)]))
(define cmd (format "~a ~a" exe-path args))
(define result
(parameterize ([current-error-port (open-output-string)]) ; throw away any error output
(with-output-to-string (lambda () (system cmd)))))
(regexp-match* pat result #:match-select selector-proc))
; This only works on Windows for now
(define (get-spf domain)
(run+parse "nslookup.exe" (format "-type=txt ~a" domain) #rx"(v=spf1[^\"]+)"))
(define (get-mx-service domain)
(server->provider (string-downcase (dns-get-mail-exchanger (dns-find-nameserver) domain))))
(define (ipv4->provider ip-str)
(define server (reverse-lookup ip-str))
(or (server->provider server)
(provider (format "Unknown: ~a" server) "[unknown]" "[unknown]")))
(define (get-registrar domain)
(match (run+parse "whois.exe" (format "-v ~a" domain) #rx"Registrar: (?m:(.+)$)" last)
[(? list? results) (map string-trim results)]
[(var results) results]))
(define (reverse-lookup ip-addr)
(dns-get-name (dns-find-nameserver) ip-addr))
(define (fill-report domain sender-ip)
(define spf (car (get-spf domain)))
(match-define (provider mail-provider-name provider-spf provider-docs) (ipv4->provider sender-ip))
(define registrar (last (get-registrar domain)))
(define report-fields
(hash "{COMPANY}" my-company-name
"{MY-PHONE}" my-direct-phone
"{DOMAIN}" domain
"{SENDER-IP}" sender-ip
"{SPFRECORD}" spf
"{SERVICE}" mail-provider-name
"{DIRECTIVE}" provider-spf
"{DOCS}" provider-docs
"{REGISTRAR}" registrar))
(define filled-report
(regexp-replace* (regexp (string-join (hash-keys report-fields) "|")) report
(lambda (match-str) (hash-ref report-fields match-str match-str))))
(display filled-report)
(send the-clipboard set-clipboard-string filled-report 0))
(define report #<<REPORT
Hello,
I’m the IT admin at {COMPANY}. We’re having trouble receiving emails from your organization; they are being sent from a server that {DOMAIN} does not list as one of its legitimate servers. When this happens, the emails are marked as Fraud and cannot be released for delivery by anyone except a system administrator.
I’ve included some details about the problem and how to fix it below. If you do not have access to your company’s DNS records, please forward this email to someone in an IT role. If anyone would like to contact me directly, I would be happy to help: my direct number is {MY-PHONE}. (A phone call might be best in this case — because of this email issue.)
This problem is likely hurting the deliverability of your emails to other companies as well. SPF checks are one of the basic ways that companies are increasingly using to combat spam and phishing threats.
More details:
Some or all of the emails being sent from @{DOMAIN} to {COMPANY} are being delivered by a server with the IP address {SENDER-IP}, but this address is not listed as a legitimate server in the SPF records for {DOMAIN}.
{DOMAIN}'s current SPF record is: {SPFRECORD}
Your email headers indicate that your organization is using {SERVICE} to send your emails, and indeed, your SPF record shown above is missing some or all of the servers used by {SERVICE}.
Suggested actions:
Since your organization sends email via {SERVICE}, you MUST include the directive {DIRECTIVE} in your SPF record. ({DOCS})
The {DOMAIN} domain appears to be registered through “{REGISTRAR}”. The person at your organization with access to the {REGISTRAR} account for the {DOMAIN} domain should log in and update the above SPF record.
REPORT
)

Blue Oak Model License

Version 1.0.0

Purpose

This license gives everyone as much permission to work with this software as possible, while protecting contributors from liability.

Acceptance

In order to receive this license, you must agree to its rules. The rules of this license are both obligations under that agreement and conditions to your license. You must not do anything with this software that triggers a rule that you cannot or will not follow.

Copyright

Each contributor licenses you to do everything with this software that would otherwise infringe that contributor's copyright in it.

Notices

You must ensure that everyone who gets a copy of any part of this software from you, with or without changes, also gets the text of this license or a link to https://blueoakcouncil.org/license/1.0.0.

Excuse

If anyone notifies you in writing that you have not complied with Notices, you can keep your license by taking all practical steps to comply within 30 days after the notice. If you do not do so, your license ends immediately.

Patent

Each contributor licenses you to do everything with this software that would otherwise infringe any patent claims they can license or become able to license.

Reliability

No contributor can revoke this license.

No Liability

As far as the law allows, this software comes as is, without any warranty or condition, and no contributor will be liable to anyone for any damages related to this software or this license, under any kind of legal claim.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment