From afe76e2bea387197312df5e19d4b1e35be2a987d Mon Sep 17 00:00:00 2001 From: nicobo Date: Fri, 23 Aug 2024 12:23:38 +0200 Subject: [PATCH] Added -E option to preserve the domain name in the final SPF record --- README.md | 31 +++++++++++++++++++++++++++++-- gandi-flatten-spf.py | 29 +++++++++++++++++------------ 2 files changed, 46 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 8e95a09..25c03b7 100644 --- a/README.md +++ b/README.md @@ -16,11 +16,38 @@ Other useful resources on SPF : - [SPF Record Syntax](https://dmarcian.com/spf-syntax-table/) - [Can I have a TXT or SPF record longer than 255 characters?](https://kb.isc.org/docs/aa-00356) +## Current manual + + usage: gandi-flatten-spf.py [-h] -d DOMAIN [DOMAIN ...] -e EMAIL_PROVIDERS [EMAIL_PROVIDERS ...] -E EMAIL_PROVIDERS_AS_IS [EMAIL_PROVIDERS_AS_IS ...] [-k API_KEY] [-r DNS [DNS ...]] [-l LOG_LEVEL] + [-L LOAD] [-K] + + Flatten SPF records using Gandi's Live DNS API + + options: + -h, --help show this help message and exit + -d DOMAIN [DOMAIN ...], --domain DOMAIN [DOMAIN ...] + Domains you own from which to update the TXT record for SPF + -e EMAIL_PROVIDERS [EMAIL_PROVIDERS ...], --email-providers EMAIL_PROVIDERS [EMAIL_PROVIDERS ...] + E-mail providers' SPF domains to add to the TXT record AFTER CONVERSION to a list of IP addresses + -E EMAIL_PROVIDERS_AS_IS [EMAIL_PROVIDERS_AS_IS ...], --email-providers-as-is EMAIL_PROVIDERS_AS_IS [EMAIL_PROVIDERS_AS_IS ...] + E-mail providers' SPF domains to add to the TXT record AS-IS (no IP conversion) + -k API_KEY, --api-key API_KEY + Your Gandi API key (otherwise looks for the 'GANDI_APIKEY' environment variable) + -r DNS [DNS ...], --dns DNS [DNS ...] + DNS servers to use to resolve into IP addresses + -l LOG_LEVEL, --log-level LOG_LEVEL + Log level + -L LOAD, --load LOAD A JSON file to load the result from Gandi's API instead of calling the API + -K, --dry-run Dry-run mode (will not change the records, only print) + +The `-E` option was added because some providers only check that their domain appears in the DNS entries, not the corresponding IP (wrongly, in my opinion), so you can preserve the domains in the final TXT record. + + ## Example - python gandi-flatten-spf.py -d mydomain.com -e _spf.mailfence.com _spf.google.com _spf.mail.yahoo.com _mailcust.gandi.net _spf.protonmail.ch -l DEBUG + python gandi-flatten-spf.py -d mydomain.com -e _spf.google.com _spf.mail.yahoo.com _mailcust.gandi.net _spf.protonmail.ch -E _spf.mailfence.com -l DEBUG -Run without arguments to show the full syntax (including how to pass your Gandi API key). +Run without arguments to show the full syntax for your version (including how to pass your Gandi API key). Put in a *cron job* to run on a regular basis and check if there was any change in the IP addresses of the email providers. diff --git a/gandi-flatten-spf.py b/gandi-flatten-spf.py index d9b96f6..06f2ebc 100755 --- a/gandi-flatten-spf.py +++ b/gandi-flatten-spf.py @@ -48,23 +48,27 @@ def spf2ips(records, domain, resolvers): """ Builds a valid TXT record for SPF by resolving DNS into IP addresses - _emailProviders: list of SPF domains of the email providers you want to add to SPF + _emailProvidersToIp: list of SPF domains to convert to a list of IPs (of the email providers you want to add to SPF) + _emailProvidersToInclude: : list of SPF domains NOT to convert to IP _nameServers: list of DNS you want to use to resolve domains into IP addresses Returns the TXT record as a string """ -def createFlatSpfRecord( _emailProviders, _nameServers ): +def createFlatSpfRecord( _emailProvidersToIp, _emailProvidersToInclude, _nameServers ): dnsResolver = resolver.Resolver() dnsResolver.nameservers = _nameServers - ips = [] - for emailProvider in _emailProviders: + parts = [] - ips = ips + spf2ips({emailProvider: 'txt'}, emailProvider, dnsResolver) + for emailProvider in _emailProvidersToIp: + parts = parts + spf2ips({emailProvider: 'txt'}, emailProvider, dnsResolver) - logging.debug("Flattened IPs : %s",ips) - return SPF_TEMPLATE.format( includes=" ".join(ips) ) + for emailProvider in _emailProvidersToInclude: + parts = parts + ["include:" + emailProvider] + + logging.debug("Flattened IPs : %s",parts) + return SPF_TEMPLATE.format( includes=" ".join(parts) ) @@ -187,8 +191,9 @@ def updateDns( _domain, _apikey, _txtRecords, _dryRun=False ): parser = argparse.ArgumentParser(description="Flatten SPF records using Gandi's Live DNS API") parser.add_argument('-d', '--domain', nargs='+', required=True, help="Domains you own from which to update the TXT record for SPF") -parser.add_argument('-e', '--email-providers', nargs='+', required=True, help="E-mail providers' SPF domains to add to the TXT record") -parser.add_argument('-k', '--apikey', help="Your Gandi API key (otherwise looks for the 'GANDI-APIKEY' environment variable)") +parser.add_argument('-e', '--email-providers', nargs='+', required=True, help="E-mail providers' SPF domains to add to the TXT record AFTER CONVERSION to a list of IP addresses") +parser.add_argument('-E', '--email-providers-as-is', nargs='+', required=True, help="E-mail providers' SPF domains to add to the TXT record AS-IS (no IP conversion)") +parser.add_argument('-k', '--api-key', help="Your Gandi API key (otherwise looks for the 'GANDI_APIKEY' environment variable)") parser.add_argument('-r', '--dns', default=DEFAULT_DNS, nargs='+', help="DNS servers to use to resolve into IP addresses") parser.add_argument('-l', '--log-level', default=DEFAULT_LOGLEVEL, help="Log level") parser.add_argument('-L', '--load', help="A JSON file to load the result from Gandi's API instead of calling the API") @@ -199,14 +204,14 @@ logging.basicConfig(level=logging.getLevelName(args.log_level)) logging.debug(repr(args)) -if args.apikey: - apikey = args.apikey +if args.api_key: + apikey = args.api_key else : apikey = os.environ['GANDI_APIKEY'] -flatSpf = createFlatSpfRecord(args.email_providers,args.dns) +flatSpf = createFlatSpfRecord(args.email_providers,args.email_providers_as_is,args.dns) for domain in args.domain: if args.load: txts = file_getTxtRecords(args.load)