├── .nvmrc ├── docs ├── CNAME ├── css │ └── site.css └── _includes │ └── feature.html ├── integrationTest ├── config │ └── .gitkeep └── zones │ └── .gitkeep ├── staticcheck.conf ├── .golangci.yml ├── pkg ├── js │ └── parse_tests │ │ ├── 008-import.js │ │ ├── 009-reverse.js │ │ ├── 051-HASH.js │ │ ├── 023-ignored-glob-records │ │ └── foo.com.zone │ │ ├── 020-complexRequire.js │ │ ├── import.js │ │ ├── 001-basic │ │ └── foo.com.zone │ │ ├── 002-ttl │ │ └── foo.com.zone │ │ ├── 003-meta │ │ └── foo.com.zone │ │ ├── 008-import │ │ └── foo.com.zone │ │ ├── 013-mx.js │ │ ├── 013-mx │ │ └── foo.com.zone │ │ ├── 046-DHCID.js │ │ ├── 010-alias.js │ │ ├── 024-json-import │ │ └── foo.com.zone │ │ ├── 047-DNAME.js │ │ ├── complexImports │ │ ├── a │ │ │ ├── a.js │ │ │ └── c │ │ │ │ └── c.js │ │ ├── b │ │ │ ├── d │ │ │ │ └── d.js │ │ │ └── b.js │ │ └── base.js │ │ ├── 037-splithor │ │ ├── example.com!outside.zone │ │ ├── example.com.zone │ │ ├── example-b.net.zone │ │ ├── example.com!inside.zone │ │ ├── example.net.zone │ │ ├── empty.example.net.zone │ │ ├── example.net!inside.zone │ │ └── example.net!outside.zone │ │ ├── domain-ip-map.json │ │ ├── 023-ignored-glob-records.js │ │ ├── 048-DNSKEY.js │ │ ├── 043-safety.js │ │ ├── 040-cfWorkerRoute.js │ │ ├── 040-cfWorkerRoute │ │ └── foo.com.zone │ │ ├── 038-soa │ │ └── foo.com.zone │ │ ├── 028-dextend │ │ ├── bar.foo.com.zone │ │ ├── foo.com.zone │ │ └── foo.edu.zone │ │ ├── 034-nameserver-ttl.js │ │ ├── 038-soa.js │ │ ├── 027-ds.js │ │ ├── 027-ds │ │ └── foo.com.zone │ │ ├── 033-revextend │ │ └── 3.1.in-addr.arpa.zone │ │ ├── 004-ips │ │ └── foo.com.zone │ │ ├── 006-transforms │ │ └── foo.com.zone │ │ ├── 015-tlsa │ │ └── foo.com.zone │ │ ├── 025-autodnssec.js │ │ ├── 029-dextendsub │ │ ├── bar.foo.tld.zone │ │ ├── foo.tld.zone │ │ ├── foo.help.zone │ │ ├── bar.foo.help.zone │ │ ├── example.tld.zone │ │ ├── example.com.zone │ │ ├── foo.here.zone │ │ ├── foo.net.zone │ │ ├── xn--tda.example.net.zone │ │ └── xn--dsseldorf-q9a.example.net.zone │ │ ├── domain-ip-map.json5 │ │ ├── 011-cfRedirect.js │ │ ├── 047-SVCB.js │ │ ├── 015-tlsa.js │ │ ├── 024-json-import.js │ │ ├── 044-ensureabsent.js │ │ ├── 049-json5-require.js │ │ ├── 003-meta.js │ │ ├── 001-basic.js │ │ ├── 002-ttl.js │ │ ├── 026-azure-alias.js │ │ ├── 017-txt.js │ │ ├── 055-b3550-ipv6ptr │ │ ├── d.c.b.a.1.1.0.2.ip6.arpa.zone │ │ └── 8.b.d.0.1.0.0.2.ip6.arpa.zone │ │ ├── 012-duration │ │ └── foo.com.zone │ │ ├── 040-r53-zone.js │ │ ├── 057-smimea.js │ │ ├── 020-complexRequire │ │ └── foo.com.zone │ │ ├── 012-duration.js │ │ ├── 017-txt │ │ └── foo.com.zone │ │ ├── 004-ips.js │ │ ├── 021-srv.js │ │ ├── 021-srv │ │ └── foo.com.zone │ │ ├── 031-dextendnames │ │ ├── sub.domain.tld.zone │ │ └── domain.tld.zone │ │ ├── 014-caa │ │ └── foo.com.zone │ │ ├── 060-rawmetas.js │ │ ├── 039-include.js │ │ ├── 035-naptr.js │ │ ├── 035-naptr │ │ └── foo.com.zone │ │ ├── 058-ignore-external-dns.js │ │ ├── 018-dkim │ │ └── foo.com.zone │ │ ├── 032-reverseip │ │ └── 3.2.1.in-addr.arpa.zone │ │ ├── 009-reverse.json │ │ ├── 011-cfRedirect │ │ └── foo.com.zone │ │ ├── 018-dkim.js │ │ ├── 036-dextendcf.js │ │ ├── 050-cfSingleRedirect.js │ │ ├── 030-dextenddoc │ │ └── domain.tld.zone │ │ ├── 059-rawttls │ │ └── example.com.zone │ │ ├── 050-cfSingleRedirect │ │ └── foo.com.zone │ │ ├── 051-HASH.json │ │ ├── 028-dextend.js │ │ ├── 006-transforms.js │ │ ├── 055-b3550-ipv6ptr.js │ │ ├── 008-import.json │ │ ├── 046-DHCID.json │ │ ├── 007-importTransformTTL.js │ │ ├── 010-alias.json │ │ ├── 024-json-import.json │ │ ├── 047-DNAME.json │ │ ├── 049-json5-require.json │ │ ├── 023-ignored-glob-records.json │ │ ├── 036-dextendcf │ │ └── foo.com.zone │ │ ├── 033-revextend.js │ │ ├── 013-mx.json │ │ ├── 014-caa.js │ │ ├── 022-sshfp.js │ │ ├── 022-sshfp │ │ └── foo.com.zone │ │ ├── 054-b3487_d_extend_rev │ │ └── 6.10.in-addr.arpa.zone │ │ ├── 019-r53-alias.js │ │ ├── 040-cfWorkerRoute.json │ │ ├── 005-ignored-records.js │ │ ├── 048-DNSKEY.json │ │ ├── 003-meta.json │ │ ├── 038-soa.json │ │ ├── 057-smimea.json │ │ ├── 015-tlsa.json │ │ ├── 059-rawttls.js │ │ ├── 034-nameserver-ttl.json │ │ ├── 043-safety.json │ │ ├── 001-basic.json │ │ ├── 002-ttl.json │ │ ├── 041-newstyleproviders.js │ │ ├── 044-ensureabsent.json │ │ ├── 054-b3487_d_extend_rev.js │ │ ├── 047-SVCB.json │ │ ├── 018-dkim.json │ │ ├── 027-ds.json │ │ ├── 030-dextenddoc.js │ │ ├── 032-reverseip.js │ │ └── 031-dextendnames.js ├── diff2 │ ├── flag.go │ ├── highest.go │ ├── verb_string.go │ └── ordering.go ├── dnssort │ └── result.go ├── soautil │ ├── soautil.go │ └── soautil_test.go ├── rtypecontrol │ └── stringify.go ├── rejectif │ ├── naptr.go │ ├── label.go │ ├── mx.go │ ├── ns.go │ ├── ultimate.go │ └── srv.go ├── dnsgraph │ ├── dependencies.go │ ├── testutils │ │ └── stubrecords.go │ └── graphable.go ├── bindserial │ └── main.go ├── txtutil │ ├── txtutil.go │ ├── state_string.go │ └── txtutil_test.go ├── notifications │ ├── notifications_test.go │ └── shoutrrr.go ├── rfc4183 │ └── ipv6.go ├── rtypeinfo │ └── rtypeinfo.go ├── recorddb │ └── recorddb.go └── domaintags │ └── idn.go ├── .prettierrc ├── providers ├── rwth │ ├── registrar.go │ ├── listzones.go │ └── auditrecords.go ├── cscglobal │ └── listzones.go ├── porkbun │ ├── listzones.go │ └── auditrecords.go ├── bind │ └── auditrecords.go ├── desec │ └── auditrecords.go ├── packetframe │ └── auditrecords.go ├── softlayer │ └── auditrecords.go ├── hetznerv2 │ └── auditrecords.go ├── domainnameshop │ ├── auditrecords.go │ └── dns_test.go ├── gandiv5 │ └── auditrecords.go ├── cnr │ ├── error.go │ └── auditrecords.go ├── hexonet │ ├── error.go │ └── auditrecords.go ├── joker │ └── nameservers.go ├── realtimeregister │ ├── realtimeregisterProvider_test.go │ └── auditrecords.go ├── linode │ ├── linodeProvider_test.go │ └── auditrecords.go ├── powerdns │ ├── listzones.go │ └── auditrecords.go ├── gcore │ └── auditrecords.go ├── ns1 │ └── auditrecords.go ├── luadns │ └── auditrecords.go ├── ovh │ ├── auditrecords.go │ └── ovhProvider_test.go ├── hedns │ └── auditrecords.go ├── gcloud │ └── auditrecords.go ├── hostingde │ └── auditrecords.go ├── azureprivatedns │ └── auditrecords.go ├── dnsmadeeasy │ └── auditrecords.go ├── hetzner │ └── auditrecords.go ├── namecheap │ └── auditrecords.go ├── autodns │ └── auditrecords.go ├── namedotcom │ └── zones.go ├── netcup │ └── auditrecords.go ├── netlify │ └── auditrecords.go ├── azuredns │ └── auditrecords.go ├── bunnydns │ ├── auditrecords.go │ ├── dnssec.go │ └── listzones.go ├── akamaiedgedns │ └── auditrecords.go ├── cloudflare │ └── auditrecords.go ├── inwx │ └── auditrecords.go ├── oracle │ └── auditrecords.go ├── axfrddns │ ├── auditrecords.go │ └── md5Provider.go ├── huaweicloud │ └── auditrecords.go ├── mythicbeasts │ └── auditrecords.go ├── dnsimple │ └── auditrecords.go ├── exoscale │ └── auditrecords.go ├── vultr │ └── auditrecords.go ├── doh │ └── api.go ├── cloudns │ └── auditrecords.go ├── alidns │ └── pagination.go ├── sakuracloud │ ├── listzones.go │ └── convert.go ├── loopia │ └── auditrecords.go ├── route53 │ └── auditrecords.go └── transip │ └── auditrecords.go ├── commands ├── test_data │ ├── bind-creds.json │ ├── ds.com.zone.tsv │ ├── ds.com.zone │ ├── ds.com.zone.zone │ ├── apex.com.zone.tsv │ ├── ds.com.zone.djs │ ├── ds.com.zone.js │ ├── apex.com.zone │ ├── apex.com.zone.zone │ ├── apex.com.zone.js │ └── apex.com.zone.djs ├── ultimate.go ├── types │ └── base-types.d.ts ├── cmdzonecache.go └── completion-scripts │ ├── completion.zsh.gotmpl │ └── completion.bash.gotmpl ├── .git-blame-ignore-revs ├── package.json ├── documentation ├── assets │ ├── ci-cd-gitlab │ │ ├── ci-cd-pipelines-new.png │ │ ├── settings-ci-cd-variables.png │ │ ├── settings-ci-cd-variables-insert.png │ │ ├── ci-cd-job-output-dnscontrol-push.png │ │ └── ci-cd-job-output-dnscontrol-preview.png │ ├── providers │ │ ├── vercel │ │ │ ├── vercel-team-id-slug.png │ │ │ └── vercel-account-switcher.png │ │ └── cloudflareapi │ │ │ └── example-permissions-configuration.png │ ├── styleguide-doc │ │ └── pull-request-preview.webp │ ├── gcloud │ │ └── create-credentials-service-account-key.png │ ├── getting-started │ │ ├── creds.json │ │ └── dnsconfig.js │ └── 1password │ │ └── creds.json ├── markdown-examples │ ├── hint │ │ ├── hint-success.md │ │ ├── hint-info.md │ │ ├── hint-warning.md │ │ └── hint-danger.md │ └── code │ │ ├── dnsconfig-code-example-without-filename.md │ │ └── dnsconfig-code-example-with-filename.md ├── language-reference │ ├── domain-modifiers │ │ ├── AUTODNSSEC_OFF.md │ │ ├── IGNORE_NAME.md │ │ ├── CLOUDNS_WR.md │ │ ├── IGNORE_TARGET.md │ │ ├── AKAMAICDN.md │ │ ├── DNAME.md │ │ ├── DHCID.md │ │ ├── INCLUDE.md │ │ ├── RP.md │ │ ├── URL.md │ │ ├── FRAME.md │ │ ├── URL301.md │ │ ├── DS.md │ │ ├── DISABLE_IGNORE_SAFETY_CHECK.md │ │ ├── DNSKEY.md │ │ ├── ADGUARDHOME_A_PASSTHROUGH.md │ │ ├── ADGUARDHOME_AAAA_PASSTHROUGH.md │ │ ├── TLSA.md │ │ ├── MX.md │ │ ├── SRV.md │ │ ├── AAAA.md │ │ ├── A.md │ │ ├── CNAME.md │ │ ├── PURGE.md │ │ ├── SSHFP.md │ │ ├── DefaultTTL.md │ │ └── IMPORT_TRANSFORM_STRIP.md │ ├── js.md │ ├── record-modifiers │ │ ├── R53_EVALUATE_TARGET_HEALTH.md │ │ └── R53_ZONE.md │ └── top-level-functions │ │ ├── PANIC.md │ │ └── IP.md ├── commands │ └── get-certs.md ├── provider │ ├── opensrs.md │ ├── exoscale.md │ ├── ns1.md │ ├── vultr.md │ ├── packetframe.md │ ├── autodns.md │ ├── netcup.md │ ├── rwth.md │ ├── netlify.md │ ├── internetbs.md │ └── domainnameshop.md └── advanced-features │ ├── bug-triage.md │ └── dual-host.md ├── .github ├── ISSUE_TEMPLATE │ ├── custom.md │ ├── config.yml │ ├── feature_request.md │ └── bug_report.md ├── pull_request_template.md ├── workflows │ └── pr_check_git_status.yml └── dependabot.yml ├── models ├── unknown.go ├── tdwarn.go ├── recordtype.go ├── rawrecord.go ├── provider.go ├── recorddb.go └── t_mx.go ├── bin ├── generate-all.sh └── fix_js_parse_tests.sh ├── Dockerfile ├── .linkspector.yml ├── .gitattributes ├── .gitignore ├── SECURITY.md ├── main.go └── LICENSE /.nvmrc: -------------------------------------------------------------------------------- 1 | lts/* 2 | -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | dnscontrol.org -------------------------------------------------------------------------------- /integrationTest/config/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /integrationTest/zones/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /staticcheck.conf: -------------------------------------------------------------------------------- 1 | checks = ["all", "-ST1000"] 2 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters: 2 | disable: 3 | - errcheck 4 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/008-import.js: -------------------------------------------------------------------------------- 1 | require("./import.js"); 2 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/009-reverse.js: -------------------------------------------------------------------------------- 1 | D(REV("1.2.0.0/16"), "none"); 2 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/051-HASH.js: -------------------------------------------------------------------------------- 1 | D(HASH("SHA1", "abc"), "reg"); 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | tabWidth: 4 2 | singleQuote: true 3 | trailingComma: es5 4 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/023-ignored-glob-records/foo.com.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/020-complexRequire.js: -------------------------------------------------------------------------------- 1 | require('./complexImports/base.js'); 2 | -------------------------------------------------------------------------------- /providers/rwth/registrar.go: -------------------------------------------------------------------------------- 1 | package rwth 2 | 3 | // No registrar functionality 4 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/import.js: -------------------------------------------------------------------------------- 1 | D("foo.com", "none", 2 | A("@", "1.2.3.4"), 3 | ); 4 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/001-basic/foo.com.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | @ IN A 1.2.3.4 3 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/002-ttl/foo.com.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | @ 42 IN A 1.2.3.4 3 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/003-meta/foo.com.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | @ IN A 1.2.3.4 3 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/008-import/foo.com.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | @ IN A 1.2.3.4 3 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/013-mx.js: -------------------------------------------------------------------------------- 1 | D("foo.com", "none", 2 | MX("@", 15, "foo.com."), 3 | ); 4 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/013-mx/foo.com.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | @ IN MX 15 foo.com. 3 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/046-DHCID.js: -------------------------------------------------------------------------------- 1 | D("foo.com", "none", 2 | DHCID("@", "Test") 3 | ); 4 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/010-alias.js: -------------------------------------------------------------------------------- 1 | D("foo.com", "none", 2 | ALIAS("@", "foo.com."), 3 | ); 4 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/024-json-import/foo.com.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | @ IN A 1.1.1.1 3 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/047-DNAME.js: -------------------------------------------------------------------------------- 1 | D("foo.com", "none", 2 | DNAME("@", "bar.com."), 3 | ); 4 | -------------------------------------------------------------------------------- /commands/test_data/bind-creds.json: -------------------------------------------------------------------------------- 1 | { 2 | "bind": { 3 | "directory": "test_data" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/complexImports/a/a.js: -------------------------------------------------------------------------------- 1 | function a() { 2 | return CNAME("A", "foo.com.") 3 | } 4 | -------------------------------------------------------------------------------- /docs/css/site.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-bottom: 50px; 3 | } 4 | 5 | .fa { 6 | font-size: 150%; 7 | } 8 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/037-splithor/example.com!outside.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | main IN A 8.8.8.8 3 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/complexImports/b/d/d.js: -------------------------------------------------------------------------------- 1 | function d() { 2 | return CNAME("D", "foo.com.") 3 | } 4 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/domain-ip-map.json: -------------------------------------------------------------------------------- 1 | { 2 | "bar.com": "5.5.5.5", 3 | "foo.com": "1.1.1.1" 4 | } 5 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/023-ignored-glob-records.js: -------------------------------------------------------------------------------- 1 | D("foo.com", "none", 2 | IGNORE("\\*.testignore"), 3 | ); 4 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/048-DNSKEY.js: -------------------------------------------------------------------------------- 1 | D("foo.com", "none", 2 | DNSKEY("@", 257, 3, 13, "AABBCCDD"), 3 | ); 4 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/043-safety.js: -------------------------------------------------------------------------------- 1 | D("unsafe.com", "none", DISABLE_IGNORE_SAFETY_CHECK); 2 | D("safe.com", "none"); 3 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # 2025-01-03: Reformat parse_tests .js and .json 2 | a85c498477a9998360a86ec11966ad548cc4022e 3 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/040-cfWorkerRoute.js: -------------------------------------------------------------------------------- 1 | D("foo.com", "none", 2 | CF_WORKER_ROUTE("test.foo.com", "test-worker") 3 | ); 4 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/040-cfWorkerRoute/foo.com.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | ;@ IN CF_WORKER_ROUTE test.foo.com,test-worker 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@umbrelladocs/linkspector": "^0.3.13", 4 | "prettier": "^3.7.4" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/038-soa/foo.com.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | @ IN SOA ns1.foo.com. admin.foo.com 0 3600 900 604800 86400 3 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/028-dextend/bar.foo.com.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | @ IN A 10.3.3.3 3 | www IN A 10.4.4.4 4 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/028-dextend/foo.com.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | @ IN A 10.1.1.1 3 | www IN A 10.2.2.2 4 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/034-nameserver-ttl.js: -------------------------------------------------------------------------------- 1 | D("foo.com", "none", NAMESERVER_TTL("1d")); 2 | D("bar.com", "none", NAMESERVER_TTL(300)); 3 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/038-soa.js: -------------------------------------------------------------------------------- 1 | D("foo.com", "none", 2 | SOA('@', 'ns1.foo.com.', 'admin.foo.com', 3600, 900, 604800, 86400), 3 | ); 4 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/027-ds.js: -------------------------------------------------------------------------------- 1 | D("foo.com", "none", 2 | DS("@", 1000, 13, 2, "AABBCCDDEEFF"), 3 | DS("@", 1, 1, 1, "FFFF"), 4 | ); 5 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/037-splithor/example.com.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | main IN A 3.3.3.3 3 | www IN A 33.33.33.33 4 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/027-ds/foo.com.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | @ IN DS 1 1 1 FFFF 3 | IN DS 1000 13 2 AABBCCDDEEFF 4 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/037-splithor/example-b.net.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | main IN A 203.0.113.12 3 | www IN A 203.0.113.1 4 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/037-splithor/example.com!inside.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | main IN A 1.1.1.1 3 | IN A 11.11.11.11 4 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/037-splithor/example.net.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | main IN A 203.0.113.12 3 | www IN A 203.0.113.1 4 | -------------------------------------------------------------------------------- /pkg/diff2/flag.go: -------------------------------------------------------------------------------- 1 | package diff2 2 | 3 | // DisableOrdering can be set to true to disable the reordering of the changes 4 | var DisableOrdering bool 5 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/037-splithor/empty.example.net.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | main IN A 203.0.113.22 3 | www IN A 203.0.113.2 4 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/033-revextend/3.1.in-addr.arpa.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | 1 IN NS ns1.example.com. 3 | 2 IN NS ns2.example.org. 4 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/004-ips/foo.com.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | @ IN A 1.2.3.4 3 | p1 IN A 1.2.3.5 4 | p255 IN A 1.2.4.3 5 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/006-transforms/foo.com.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | @ IN A 3.3.3.3 3 | IN A 4.4.4.4 4 | IN A 5.5.5.5 5 | -------------------------------------------------------------------------------- /documentation/assets/ci-cd-gitlab/ci-cd-pipelines-new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StackExchange/dnscontrol/HEAD/documentation/assets/ci-cd-gitlab/ci-cd-pipelines-new.png -------------------------------------------------------------------------------- /documentation/markdown-examples/hint/hint-success.md: -------------------------------------------------------------------------------- 1 | {% hint style="success" %} 2 | **Success hints** are good for showing positive actions or achievements. 3 | {% endhint %} 4 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/complexImports/a/c/c.js: -------------------------------------------------------------------------------- 1 | require('../a.js'); 2 | 3 | function c() { 4 | return [ 5 | a(), 6 | CNAME("C", "foo.com.") 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /documentation/assets/providers/vercel/vercel-team-id-slug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StackExchange/dnscontrol/HEAD/documentation/assets/providers/vercel/vercel-team-id-slug.png -------------------------------------------------------------------------------- /documentation/assets/styleguide-doc/pull-request-preview.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StackExchange/dnscontrol/HEAD/documentation/assets/styleguide-doc/pull-request-preview.webp -------------------------------------------------------------------------------- /pkg/js/parse_tests/015-tlsa/foo.com.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | _443._tcp IN TLSA 3 1 1 mdfiytq3mtljodbinmzlotexyja5mwe3yza1mti0yjy0zwvly2u5njrlmdljmdu4zwy4zjk4mdvkywnhntq2yiaglqo= 3 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/025-autodnssec.js: -------------------------------------------------------------------------------- 1 | D("nothing.com", "none"); 2 | D("with.com", "none", 3 | AUTODNSSEC_ON 4 | ); 5 | D("without.com", "none", 6 | AUTODNSSEC_OFF, 7 | ); 8 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/029-dextendsub/bar.foo.tld.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | @ IN A 30.7.7.7 3 | a IN A 30.9.9.9 4 | www IN A 30.8.8.8 5 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/029-dextendsub/foo.tld.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | @ IN A 20.5.5.5 3 | a IN A 20.10.10.10 4 | www IN A 20.6.6.6 5 | -------------------------------------------------------------------------------- /documentation/assets/ci-cd-gitlab/settings-ci-cd-variables.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StackExchange/dnscontrol/HEAD/documentation/assets/ci-cd-gitlab/settings-ci-cd-variables.png -------------------------------------------------------------------------------- /documentation/markdown-examples/hint/hint-info.md: -------------------------------------------------------------------------------- 1 | {% hint style="info" %} 2 | **Info hints** are great for showing general information, or providing tips and tricks. 3 | {% endhint %} 4 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/complexImports/base.js: -------------------------------------------------------------------------------- 1 | require('./a/c/c.js'); 2 | require('./b/b.js'); 3 | 4 | D("sortfoo.com", "none", 5 | A("@", "1.2.3.4"), 6 | c(), 7 | b() 8 | ); 9 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/domain-ip-map.json5: -------------------------------------------------------------------------------- 1 | // This is a comment. 2 | { 3 | "foo.com": "1.1.1.1", 4 | "bar.com": "5.5.5.5", 5 | } 6 | 7 | // The "bar.com" line has a comma at the end. 8 | -------------------------------------------------------------------------------- /documentation/assets/providers/vercel/vercel-account-switcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StackExchange/dnscontrol/HEAD/documentation/assets/providers/vercel/vercel-account-switcher.png -------------------------------------------------------------------------------- /documentation/markdown-examples/hint/hint-warning.md: -------------------------------------------------------------------------------- 1 | {% hint style="warning" %} 2 | **Warning hints** are good for showing important information or non-critical warnings. 3 | {% endhint %} 4 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/011-cfRedirect.js: -------------------------------------------------------------------------------- 1 | D("foo.com", "none", 2 | CF_REDIRECT("test1.foo.com", "https://goo.com/$1"), 3 | CF_TEMP_REDIRECT("test2.foo.com", "https://goo.com/$1"), 4 | ); 5 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/037-splithor/example.net!inside.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | main IN A 192.0.2.1 3 | IN A 203.0.113.12 4 | www IN A 203.0.113.1 5 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/047-SVCB.js: -------------------------------------------------------------------------------- 1 | D("foo.com", "none", 2 | SVCB("@", 1, ".", ""), 3 | HTTPS("@", 2, ".", 'alpn="h3,h2" port=443 ipv4hint=123.123.123.123 ipv6hint=dead::beaf'), 4 | ); 5 | -------------------------------------------------------------------------------- /documentation/assets/ci-cd-gitlab/settings-ci-cd-variables-insert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StackExchange/dnscontrol/HEAD/documentation/assets/ci-cd-gitlab/settings-ci-cd-variables-insert.png -------------------------------------------------------------------------------- /pkg/js/parse_tests/015-tlsa.js: -------------------------------------------------------------------------------- 1 | D("foo.com", "none", 2 | TLSA("_443._tcp", 3, 1, 1, "MDFiYTQ3MTljODBiNmZlOTExYjA5MWE3YzA1MTI0YjY0ZWVlY2U5NjRlMDljMDU4ZWY4Zjk4MDVkYWNhNTQ2YiAgLQo="), 3 | ); 4 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/037-splithor/example.net!outside.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | main IN A 203.0.113.1 3 | IN A 203.0.113.12 4 | www IN A 203.0.113.1 5 | -------------------------------------------------------------------------------- /documentation/assets/ci-cd-gitlab/ci-cd-job-output-dnscontrol-push.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StackExchange/dnscontrol/HEAD/documentation/assets/ci-cd-gitlab/ci-cd-job-output-dnscontrol-push.png -------------------------------------------------------------------------------- /documentation/assets/gcloud/create-credentials-service-account-key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StackExchange/dnscontrol/HEAD/documentation/assets/gcloud/create-credentials-service-account-key.png -------------------------------------------------------------------------------- /documentation/assets/ci-cd-gitlab/ci-cd-job-output-dnscontrol-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StackExchange/dnscontrol/HEAD/documentation/assets/ci-cd-gitlab/ci-cd-job-output-dnscontrol-preview.png -------------------------------------------------------------------------------- /pkg/js/parse_tests/024-json-import.js: -------------------------------------------------------------------------------- 1 | var domains = require('./domain-ip-map.json'); 2 | 3 | var domain = "foo.com"; 4 | var ip = domains["foo.com"]; 5 | 6 | D(domain, "none", 7 | A("@", ip), 8 | ); 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /documentation/markdown-examples/hint/hint-danger.md: -------------------------------------------------------------------------------- 1 | {% hint style="danger" %} 2 | **Danger hints** are good for highlighting destructive actions or raising attention to critical information. 3 | {% endhint %} 4 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/028-dextend/foo.edu.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | @ IN A 10.5.5.5 3 | more1 IN A 10.7.7.7 4 | more2 IN A 10.8.8.8 5 | www IN A 10.6.6.6 6 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/044-ensureabsent.js: -------------------------------------------------------------------------------- 1 | D("example.com", "none", 2 | A("normal", "1.1.1.1"), 3 | A("helper", "2.2.2.2", ENSURE_ABSENT_REC()), 4 | //ENSURE_ABSENT(A("wrapped", "3.3.3.3")), 5 | ); 6 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/049-json5-require.js: -------------------------------------------------------------------------------- 1 | var domains = require('./domain-ip-map.json5'); 2 | 3 | var domain = "foo.com"; 4 | var ip = domains["foo.com"]; 5 | 6 | D(domain, "none", 7 | A("@", ip), 8 | ); 9 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/complexImports/b/b.js: -------------------------------------------------------------------------------- 1 | require('pkg/js/parse_tests/complexImports/b/d/d.js'); 2 | 3 | function b() { 4 | return [ 5 | d(), 6 | CNAME("B", "foo.com.") 7 | ]; 8 | } 9 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/003-meta.js: -------------------------------------------------------------------------------- 1 | var CLOUDFLARE = NewRegistrar("Cloudflare", "CLOUDFLAREAPI"); 2 | 3 | D("foo.com", CLOUDFLARE, 4 | A("@", "1.2.3.4", { 5 | "cloudflare_proxy": "ON" 6 | }), 7 | ); 8 | -------------------------------------------------------------------------------- /providers/cscglobal/listzones.go: -------------------------------------------------------------------------------- 1 | package cscglobal 2 | 3 | // ListZones returns all the zones in an account 4 | func (client *providerClient) ListZones() ([]string, error) { 5 | return client.getDomains() 6 | } 7 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/001-basic.js: -------------------------------------------------------------------------------- 1 | var REG = NewRegistrar("Third-Party", "NONE"); 2 | var CF = NewDnsProvider("Cloudflare", "CLOUDFLAREAPI"); 3 | 4 | D("foo.com", REG, DnsProvider(CF), 5 | A("@", "1.2.3.4") 6 | ); 7 | -------------------------------------------------------------------------------- /documentation/assets/providers/cloudflareapi/example-permissions-configuration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StackExchange/dnscontrol/HEAD/documentation/assets/providers/cloudflareapi/example-permissions-configuration.png -------------------------------------------------------------------------------- /pkg/js/parse_tests/002-ttl.js: -------------------------------------------------------------------------------- 1 | var REG = NewRegistrar("Third-Party", "NONE"); 2 | var CF = NewDnsProvider("Cloudflare", "CLOUDFLAREAPI"); 3 | 4 | D("foo.com", REG, DnsProvider(CF), 5 | A("@", "1.2.3.4", TTL(42)), 6 | ); 7 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/026-azure-alias.js: -------------------------------------------------------------------------------- 1 | D("foo.com", "none", 2 | AZURE_ALIAS("atest", "A", "foo.com."), 3 | AZURE_ALIAS("aaaatest", "AAAA", "foo.com."), 4 | AZURE_ALIAS("cnametest", "CNAME", "foo.com."), 5 | ); 6 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/029-dextendsub/foo.help.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | @ IN A 40.12.12.12 3 | morty IN A 40.17.17.17 4 | www.morty IN A 40.18.18.18 5 | www IN A 40.12.12.12 6 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/017-txt.js: -------------------------------------------------------------------------------- 1 | D("foo.com", "none", 2 | TXT("a", "simple"), 3 | TXT("b", "ws at end "), 4 | TXT("c", ["one"]), 5 | TXT("d", ["bonie", "clyde"]), 6 | TXT("e", ["straw", "wood", "brick"]), 7 | ); 8 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/029-dextendsub/bar.foo.help.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | @ IN A 50.13.13.13 3 | www IN A 50.14.14.14 4 | zip IN A 50.15.15.15 5 | www.zip IN A 50.16.16.16 6 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/055-b3550-ipv6ptr/d.c.b.a.1.1.0.2.ip6.arpa.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | 1.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0 IN PTR host11.example.com. 3 | 2.2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0 IN PTR host22.example.com. 4 | -------------------------------------------------------------------------------- /commands/test_data/ds.com.zone.tsv: -------------------------------------------------------------------------------- 1 | ds.com @ 300 IN SOA ns3.serverfault.com. sysadmin.stackoverflow.com. 2020022300 3600 600 604800 1440 2 | geo.ds.com geo 300 IN DS 14480 13 2 BB1C4B615CDED2B34347CF23710471934D972F1E34F53B54ED8D5F786202C73B 3 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/012-duration/foo.com.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | @ IN A 1.2.3.4 3 | a IN A 1.2.3.5 4 | b 180 IN A 1.2.3.6 5 | c 10800 IN A 1.2.3.7 6 | d 259200 IN A 1.2.3.8 7 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/040-r53-zone.js: -------------------------------------------------------------------------------- 1 | D('foo.com', 'none', R53_ZONE('Z2FTEDLFRTZ')); 2 | D( 3 | 'foo.com!internal', 4 | 'none', 5 | R53_ZONE('Z2FTEDLFRTF'), 6 | R53_ALIAS('atest', 'A', 'foo.com.', R53_ZONE('Z2FTEDLFRTZ')) 7 | ); 8 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/057-smimea.js: -------------------------------------------------------------------------------- 1 | D("foo.com", "none", 2 | SMIMEA("f10e7de079689f55c0cdd6782e4dd1448c84006962a4bd832e8eff73", 3, 0, 0, "MDFiYTQ3MTljODBiNmZlOTExYjA5MWE3YzA1MTI0YjY0ZWVlY2U5NjRlMDljMDU4ZWY4Zjk4MDVkYWNhNTQ2YiAgLQo="), 3 | ); 4 | -------------------------------------------------------------------------------- /providers/porkbun/listzones.go: -------------------------------------------------------------------------------- 1 | package porkbun 2 | 3 | func (c *porkbunProvider) ListZones() ([]string, error) { 4 | zones, err := c.listAllDomains() 5 | if err != nil { 6 | return nil, err 7 | } 8 | return zones, err 9 | } 10 | -------------------------------------------------------------------------------- /commands/test_data/ds.com.zone: -------------------------------------------------------------------------------- 1 | $ORIGIN ds.com. 2 | $TTL 300 3 | @ IN SOA ns3.serverfault.com. sysadmin.stackoverflow.com. 2020022300 3600 600 604800 1440 4 | geo IN DS 14480 13 2 BB1C4B615CDED2B34347CF23710471934D972F1E34F53B54ED8D5F786202C73B 5 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/029-dextendsub/example.tld.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | a.sub IN CNAME b.sub.example.tld. 3 | b.sub IN CNAME sub.example.tld. 4 | c.sub IN CNAME sub.example.tld. 5 | e.sub IN CNAME otherdomain.tld. 6 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/020-complexRequire/foo.com.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | @ IN A 1.2.3.4 3 | a IN CNAME foo.com. 4 | b IN CNAME foo.com. 5 | c IN CNAME foo.com. 6 | d IN CNAME foo.com. 7 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/012-duration.js: -------------------------------------------------------------------------------- 1 | D("foo.com", "none", 2 | A("@", "1.2.3.4", TTL("300s")), 3 | A("a", "1.2.3.5", TTL("300")), 4 | A("b", "1.2.3.6", TTL("3m")), 5 | A("c", "1.2.3.7", TTL("3h")), 6 | A("d", "1.2.3.8", TTL("3d")), 7 | ); 8 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/017-txt/foo.com.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | a IN TXT "simple" 3 | b IN TXT "ws at end " 4 | c IN TXT "one" 5 | d IN TXT "bonieclyde" 6 | e IN TXT "strawwoodbrick" 7 | -------------------------------------------------------------------------------- /pkg/diff2/highest.go: -------------------------------------------------------------------------------- 1 | package diff2 2 | 3 | // highest returns the highest valid index for an array. The equiv of len(s)-1, but with 4 | // less likelihood that you'll commit an off-by-one error. 5 | func highest[S ~[]T, T any](s S) int { 6 | return len(s) - 1 7 | } 8 | -------------------------------------------------------------------------------- /documentation/language-reference/domain-modifiers/AUTODNSSEC_OFF.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: AUTODNSSEC_OFF 3 | --- 4 | 5 | `AUTODNSSEC_OFF` tells the provider to disable AutoDNSSEC. It takes no 6 | parameters. 7 | 8 | See [`AUTODNSSEC_ON`](AUTODNSSEC_ON.md) for further details. 9 | -------------------------------------------------------------------------------- /documentation/commands/get-certs.md: -------------------------------------------------------------------------------- 1 | # *Let's Encrypt* Certificate generation 2 | 3 | {% hint style="warning" %} 4 | **WARNING**: This feature last existed in v4.21.0. 5 | See discussion in [issues/1400](https://github.com/StackExchange/dnscontrol/issues/1400) 6 | {% endhint %} 7 | -------------------------------------------------------------------------------- /commands/test_data/ds.com.zone.zone: -------------------------------------------------------------------------------- 1 | $ORIGIN ds.com. 2 | $TTL 300 3 | @ IN SOA ns3.serverfault.com. sysadmin.stackoverflow.com. 2020022300 3600 600 604800 1440 4 | geo IN DS 14480 13 2 BB1C4B615CDED2B34347CF23710471934D972F1E34F53B54ED8D5F786202C73B 5 | 6 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/029-dextendsub/example.com.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | @ IN A 10.0.0.1 3 | düsseldorf IN A 10.0.0.3 4 | www.düsseldorf IN A 10.0.0.4 5 | www IN A 10.0.0.2 6 | ü IN A 10.0.0.5 7 | www.ü IN A 10.0.0.6 8 | -------------------------------------------------------------------------------- /documentation/assets/getting-started/creds.json: -------------------------------------------------------------------------------- 1 | { 2 | "bind": { 3 | "TYPE": "BIND" 4 | }, 5 | "r53_ACCOUNTNAME": { 6 | "KeyId": "change_to_your_keyid", 7 | "SecretKey": "change_to_your_secretkey", 8 | "TYPE": "ROUTE53", 9 | "Token": "optional_sts_token" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/029-dextendsub/foo.here.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | @ IN A 60.19.19.19 3 | bar IN A 60.21.21.21 4 | baz.bar IN A 60.23.23.23 5 | www.baz.bar IN A 60.24.24.24 6 | www.bar IN A 60.22.22.22 7 | www IN A 60.20.20.20 8 | -------------------------------------------------------------------------------- /models/unknown.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | // MakeUnknown turns an RecordConfig into an UNKNOWN type. 4 | func MakeUnknown(rc *RecordConfig, rtype string, contents string, origin string) error { 5 | rc.Type = "UNKNOWN" 6 | rc.UnknownTypeName = rtype 7 | rc.target = contents 8 | 9 | return nil 10 | } 11 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/029-dextendsub/foo.net.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | @ IN A 10.1.1.1 3 | bar IN A 10.3.3.3 4 | www.bar IN A 10.4.4.4 5 | a.long.path.of.sub.domains IN A 10.25.25.25 6 | www.a.long.path.of.sub.domains IN A 10.26.26.26 7 | www IN A 10.2.2.2 8 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/055-b3550-ipv6ptr/8.b.d.0.1.0.0.2.ip6.arpa.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | 1.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0 IN PTR server11.example.com. 3 | 2.2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0 IN PTR server22.example.com. 4 | d.c.b.a.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0 IN PTR abcd.example.com. 5 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/004-ips.js: -------------------------------------------------------------------------------- 1 | var REG = NewRegistrar("Third-Party", "NONE"); 2 | var CF = NewDnsProvider("Cloudflare", "CLOUDFLAREAPI"); 3 | 4 | var BASE = IP("1.2.3.4"); 5 | 6 | D("foo.com", REG, DnsProvider(CF, 0), 7 | A("@", BASE), 8 | A("p1", BASE + 1), 9 | A("p255", BASE + 255), 10 | ); 11 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/021-srv.js: -------------------------------------------------------------------------------- 1 | D("foo.com", "none", 2 | SRV('_ntp._udp', 1, 100, 123, 'one.foo.com.'), 3 | SRV('_ntp._udp', 2, 100, 123, 'two'), 4 | SRV('_ntp._udp', 3, 100, 123, 'localhost'), 5 | SRV('_ntp._udp', 4, 100, 123, 'three.example.com.'), 6 | SRV('_ntp._udp', 0, 0, 1, 'zeros'), 7 | ); 8 | -------------------------------------------------------------------------------- /docs/_includes/feature.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 |

{{include.text}}

7 |
8 |
9 | -------------------------------------------------------------------------------- /documentation/markdown-examples/code/dnsconfig-code-example-without-filename.md: -------------------------------------------------------------------------------- 1 | {% code %} 2 | ```javascript 3 | var REG_NONE = NewRegistrar("none"); 4 | var DNS_BIND = NewDnsProvider("bind"); 5 | 6 | D("example.com", REG_NONE, DnsProvider(DNS_BIND), 7 | A("@", "1.2.3.4") 8 | ); 9 | ``` 10 | {% endcode %} 11 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/021-srv/foo.com.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | _ntp._udp IN SRV 0 0 1 zeros.foo.com. 3 | IN SRV 1 100 123 one.foo.com. 4 | IN SRV 2 100 123 two.foo.com. 5 | IN SRV 3 100 123 localhost.foo.com. 6 | IN SRV 4 100 123 three.example.com. 7 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/031-dextendnames/sub.domain.tld.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | @ IN A 127.0.1.1 3 | IN A 127.0.1.3 4 | aa IN A 127.0.1.2 5 | bb IN CNAME cc.sub.domain.tld. 6 | dd IN A 127.0.1.4 7 | ee IN CNAME ff.sub.domain.tld. 8 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/014-caa/foo.com.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | @ IN CAA 128 iodef "https://example.com" 3 | IN CAA 128 iodef "mailto:test@example.com" 4 | IN CAA 0 iodef "http://example.com" 5 | IN CAA 0 issue "letsencrypt.org" 6 | IN CAA 0 issuewild ";" 7 | -------------------------------------------------------------------------------- /documentation/markdown-examples/code/dnsconfig-code-example-with-filename.md: -------------------------------------------------------------------------------- 1 | {% code title="dnsconfig.js" %} 2 | ```javascript 3 | var REG_NONE = NewRegistrar("none"); 4 | var DNS_BIND = NewDnsProvider("bind"); 5 | 6 | D("example.com", REG_NONE, DnsProvider(DNS_BIND), 7 | A("@", "1.2.3.4"), 8 | ); 9 | ``` 10 | {% endcode %} 11 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/060-rawmetas.js: -------------------------------------------------------------------------------- 1 | // This tests whether or not metadata gets passed to RecordConfig v2. 2 | 3 | D("bar.com", "none", 4 | RP("foo.bar.com", "user2.example.com.", "mytxt.example.com.", DISABLE_REPEATED_DOMAIN_CHECK), 5 | RP("bar.com", "user2.example.com.", "mytxt.example.com.", DISABLE_REPEATED_DOMAIN_CHECK), 6 | ) 7 | -------------------------------------------------------------------------------- /documentation/language-reference/domain-modifiers/IGNORE_NAME.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: IGNORE_NAME 3 | parameters: 4 | - pattern 5 | - rTypes 6 | parameter_types: 7 | pattern: string 8 | rTypes: string? 9 | --- 10 | 11 | `IGNORE_NAME(a)` is the same as `IGNORE(a, "*", "*")`. 12 | 13 | `IGNORE_NAME(a, b)` is the same as `IGNORE(a, b, "*")`. 14 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/039-include.js: -------------------------------------------------------------------------------- 1 | var REG = NewRegistrar("Third-Party", "NONE"); 2 | var CF = NewDnsProvider("Cloudflare", "CLOUDFLAREAPI"); 3 | 4 | D("foo.com!external", REG, DnsProvider(CF), 5 | A("@", "1.2.3.4"), 6 | ); 7 | 8 | D("foo.com!internal", REG, DnsProvider(CF), 9 | INCLUDE("foo.com!external"), 10 | A("local", "127.0.0.1"), 11 | ); 12 | -------------------------------------------------------------------------------- /providers/rwth/listzones.go: -------------------------------------------------------------------------------- 1 | package rwth 2 | 3 | // ListZones lists the zones on this account. 4 | func (api *rwthProvider) ListZones() ([]string, error) { 5 | if err := api.getAllZones(); err != nil { 6 | return nil, err 7 | } 8 | var zones []string 9 | for i := range api.zones { 10 | zones = append(zones, i) 11 | } 12 | return zones, nil 13 | } 14 | -------------------------------------------------------------------------------- /documentation/language-reference/domain-modifiers/CLOUDNS_WR.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: CLOUDNS_WR 3 | parameters: 4 | - name 5 | - target 6 | - modifiers... 7 | provider: CLOUDNS 8 | parameter_types: 9 | name: string 10 | target: string 11 | "modifiers...": RecordModifier[] 12 | --- 13 | 14 | {% hint style="info" %} 15 | Documentation needed. 16 | {% endhint %} 17 | -------------------------------------------------------------------------------- /documentation/language-reference/js.md: -------------------------------------------------------------------------------- 1 | # JavaScript DSL 2 | 3 | DNSControl uses JavaScript as its primary input language to provide power and flexibility to configure your domains. The ultimate purpose of the JavaScript is to construct a 4 | [DNSConfig](https://pkg.go.dev/github.com/StackExchange/dnscontrol/models#DNSConfig) object that will be passed to the go backend and operated on. 5 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/029-dextendsub/xn--tda.example.net.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | @ IN A 10.0.0.15 3 | düsseldorf IN A 10.0.0.19 4 | www.düsseldorf IN A 10.0.0.20 5 | subdomain IN A 10.0.0.17 6 | www.subdomain IN A 10.0.0.18 7 | www IN A 10.0.0.16 8 | ü IN A 10.0.0.21 9 | www.ü IN A 10.0.0.22 10 | -------------------------------------------------------------------------------- /documentation/language-reference/domain-modifiers/IGNORE_TARGET.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: IGNORE_TARGET 3 | parameters: 4 | - pattern 5 | - rType 6 | parameter_types: 7 | pattern: string 8 | rType: string 9 | --- 10 | 11 | `IGNORE_TARGET_NAME(target)` is the same as `IGNORE("*", "*", target)`. 12 | 13 | `IGNORE_TARGET_NAME(target, rtype)` is the same as `IGNORE("*", rtype, target)`. 14 | -------------------------------------------------------------------------------- /pkg/dnssort/result.go: -------------------------------------------------------------------------------- 1 | package dnssort 2 | 3 | import "github.com/StackExchange/dnscontrol/v4/pkg/dnsgraph" 4 | 5 | // SortResult is the result of a sort function. 6 | type SortResult[T dnsgraph.Graphable] struct { 7 | // SortedRecords is the sorted records. 8 | SortedRecords []T 9 | // UnresolvedRecords is the records that could not be resolved. 10 | UnresolvedRecords []T 11 | } 12 | -------------------------------------------------------------------------------- /providers/bind/auditrecords.go: -------------------------------------------------------------------------------- 1 | package bind 2 | 3 | import "github.com/StackExchange/dnscontrol/v4/models" 4 | 5 | // AuditRecords returns a list of errors corresponding to the records 6 | // that aren't supported by this provider. If all records are 7 | // supported, an empty list is returned. 8 | func AuditRecords(records []*models.RecordConfig) []error { 9 | return nil 10 | } 11 | -------------------------------------------------------------------------------- /providers/desec/auditrecords.go: -------------------------------------------------------------------------------- 1 | package desec 2 | 3 | import "github.com/StackExchange/dnscontrol/v4/models" 4 | 5 | // AuditRecords returns a list of errors corresponding to the records 6 | // that aren't supported by this provider. If all records are 7 | // supported, an empty list is returned. 8 | func AuditRecords(records []*models.RecordConfig) []error { 9 | return nil 10 | } 11 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/029-dextendsub/xn--dsseldorf-q9a.example.net.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | @ IN A 10.0.0.7 3 | düsseltal IN A 10.0.0.11 4 | www.düsseltal IN A 10.0.0.12 5 | subdomain IN A 10.0.0.9 6 | www.subdomain IN A 10.0.0.10 7 | www IN A 10.0.0.8 8 | ü IN A 10.0.0.13 9 | www.ü IN A 10.0.0.14 10 | -------------------------------------------------------------------------------- /documentation/assets/getting-started/dnsconfig.js: -------------------------------------------------------------------------------- 1 | /* 2 | dnsconfig.js: dnscontrol configuration file for ORGANIZATION NAME. 3 | */ 4 | 5 | // Providers: 6 | 7 | var REG_NONE = NewRegistrar("none"); // No registrar. 8 | var DNS_BIND = NewDnsProvider("bind"); // ISC BIND. 9 | 10 | // Domains: 11 | 12 | D("example.com", REG_NONE, DnsProvider(DNS_BIND), 13 | A("@", "1.2.3.4") 14 | ); 15 | -------------------------------------------------------------------------------- /providers/packetframe/auditrecords.go: -------------------------------------------------------------------------------- 1 | package packetframe 2 | 3 | import "github.com/StackExchange/dnscontrol/v4/models" 4 | 5 | // AuditRecords returns a list of errors corresponding to the records 6 | // that aren't supported by this provider. If all records are 7 | // supported, an empty list is returned. 8 | func AuditRecords(records []*models.RecordConfig) []error { 9 | return nil 10 | } 11 | -------------------------------------------------------------------------------- /providers/softlayer/auditrecords.go: -------------------------------------------------------------------------------- 1 | package softlayer 2 | 3 | import "github.com/StackExchange/dnscontrol/v4/models" 4 | 5 | // AuditRecords returns a list of errors corresponding to the records 6 | // that aren't supported by this provider. If all records are 7 | // supported, an empty list is returned. 8 | func AuditRecords(records []*models.RecordConfig) []error { 9 | return nil 10 | } 11 | -------------------------------------------------------------------------------- /pkg/soautil/soautil.go: -------------------------------------------------------------------------------- 1 | package soautil 2 | 3 | import "strings" 4 | 5 | // RFC5322MailToBind converts a user@host email address to BIND format. 6 | func RFC5322MailToBind(rfc5322Mail string) string { 7 | res := strings.SplitN(rfc5322Mail, "@", 2) 8 | user, domain := res[0], res[1] 9 | // RFC-1035 [Section-8] 10 | user = strings.ReplaceAll(user, ".", "\\.") 11 | return user + "." + domain 12 | } 13 | -------------------------------------------------------------------------------- /providers/hetznerv2/auditrecords.go: -------------------------------------------------------------------------------- 1 | package hetznerv2 2 | 3 | import ( 4 | "github.com/StackExchange/dnscontrol/v4/models" 5 | ) 6 | 7 | // AuditRecords returns a list of errors corresponding to the records 8 | // that aren't supported by this provider. If all records are 9 | // supported, an empty list is returned. 10 | func AuditRecords(_ []*models.RecordConfig) []error { 11 | return nil 12 | } 13 | -------------------------------------------------------------------------------- /pkg/rtypecontrol/stringify.go: -------------------------------------------------------------------------------- 1 | package rtypecontrol 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | func StringifyQuoted(args []any) string { 9 | if len(args) == 0 { 10 | return "" 11 | } 12 | 13 | var sb strings.Builder 14 | sb.WriteString(fmt.Sprintf("%q", args[0])) 15 | for _, arg := range args[1:] { 16 | sb.WriteString(fmt.Sprintf(" %q", arg)) 17 | } 18 | return sb.String() 19 | } 20 | -------------------------------------------------------------------------------- /providers/domainnameshop/auditrecords.go: -------------------------------------------------------------------------------- 1 | package domainnameshop 2 | 3 | import "github.com/StackExchange/dnscontrol/v4/models" 4 | 5 | // AuditRecords returns a list of errors corresponding to the records 6 | // that aren't supported by this provider. If all records are 7 | // supported, an empty list is returned. 8 | func AuditRecords(records []*models.RecordConfig) []error { 9 | return nil 10 | } 11 | -------------------------------------------------------------------------------- /providers/gandiv5/auditrecords.go: -------------------------------------------------------------------------------- 1 | package gandiv5 2 | 3 | import ( 4 | "github.com/StackExchange/dnscontrol/v4/models" 5 | ) 6 | 7 | // AuditRecords returns a list of errors corresponding to the records 8 | // that aren't supported by this provider. If all records are 9 | // supported, an empty list is returned. 10 | func AuditRecords(records []*models.RecordConfig) []error { 11 | return nil 12 | } 13 | -------------------------------------------------------------------------------- /documentation/assets/1password/creds.json: -------------------------------------------------------------------------------- 1 | { 2 | "bind": { 3 | "TYPE": "BIND" 4 | }, 5 | "cloudflare": { 6 | "TYPE": "CLOUDFLAREAPI", 7 | "accountid": "op://Secrets/Cloudflare DNSControl/username", 8 | "apitoken": "op://Secrets/Cloudflare DNSControl/credential" 9 | }, 10 | "linode": { 11 | "TYPE": "LINODE", 12 | "token": "op://Secrets/Linode DNSControl/credential" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /models/tdwarn.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "fmt" 4 | 5 | var dotwarned = map[string]bool{} 6 | 7 | // WarnNameserverDot prints a warning about issue 491 never more than once. 8 | func WarnNameserverDot(p, w string) { 9 | if dotwarned[p] { 10 | return 11 | } 12 | fmt.Printf("Warning: provider %s could be improved. See https://github.com/StackExchange/dnscontrol/issues/491 (%s)\n", p, w) 13 | dotwarned[p] = true 14 | } 15 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/035-naptr.js: -------------------------------------------------------------------------------- 1 | D("foo.com", "none", 2 | NAPTR("@", 100, 10, "U", "E2U+sip", "!^.*$!sip:customer-service@example.com!", "example"), 3 | NAPTR("@", 102, 10, "U", "E2U+email", "!^.*$!mailto:information@example.com!", "example"), 4 | NAPTR("@", 103, 10, "U", "E2U+email", "!^.*$!mailto:information@example.com!", ""), 5 | NAPTR("@", 104, 10, "U", "E2U+email", "!^.*$!mailto:information@example.com!", "."), 6 | ); 7 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/035-naptr/foo.com.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | @ IN NAPTR 100 10 "U" "E2U+sip" "!^.*$!sip:customer-service@example.com!" example 3 | IN NAPTR 102 10 "U" "E2U+email" "!^.*$!mailto:information@example.com!" example 4 | IN NAPTR 103 10 "U" "E2U+email" "!^.*$!mailto:information@example.com!" . 5 | IN NAPTR 104 10 "U" "E2U+email" "!^.*$!mailto:information@example.com!" . 6 | -------------------------------------------------------------------------------- /pkg/rejectif/naptr.go: -------------------------------------------------------------------------------- 1 | package rejectif 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/StackExchange/dnscontrol/v4/models" 7 | ) 8 | 9 | // Keep these in alphabetical order. 10 | 11 | // NaptrHasEmptyTarget detects NAPTR records with empty targets. 12 | func NaptrHasEmptyTarget(rc *models.RecordConfig) error { 13 | if rc.GetTargetField() == "" { 14 | return errors.New("naptr has empty target") 15 | } 16 | return nil 17 | } 18 | -------------------------------------------------------------------------------- /providers/cnr/error.go: -------------------------------------------------------------------------------- 1 | package cnr 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/centralnicgroup-opensource/rtldev-middleware-go-sdk/v5/response" 7 | ) 8 | 9 | // GetAPIError returns an error including API error code and error description. 10 | func (n *Client) GetAPIError(format string, objectid string, r *response.Response) error { 11 | return fmt.Errorf(format+" %q. [%v %s]", objectid, r.GetCode(), r.GetDescription()) 12 | } 13 | -------------------------------------------------------------------------------- /providers/hexonet/error.go: -------------------------------------------------------------------------------- 1 | package hexonet 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/centralnicgroup-opensource/rtldev-middleware-go-sdk/v4/response" 7 | ) 8 | 9 | // GetHXApiError returns an error including API error code and error description. 10 | func (n *HXClient) GetHXApiError(format string, objectid string, r *response.Response) error { 11 | return fmt.Errorf(format+" %q. [%v %s]", objectid, r.GetCode(), r.GetDescription()) 12 | } 13 | -------------------------------------------------------------------------------- /providers/joker/nameservers.go: -------------------------------------------------------------------------------- 1 | package joker 2 | 3 | import "github.com/StackExchange/dnscontrol/v4/models" 4 | 5 | // GetNameservers returns the nameservers for a domain. 6 | func (api *jokerProvider) GetNameservers(domain string) ([]*models.Nameserver, error) { 7 | // For DNS-only providers like Joker, we can return an empty list 8 | // since nameserver management is typically handled separately 9 | return []*models.Nameserver{}, nil 10 | } 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: I have a question or need support 4 | url: https://docs.dnscontrol.org/ 5 | about: We use GitHub for tracking bugs, check our website for resources on getting help. 6 | - name: I'm unsure where to go 7 | url: https://groups.google.com/g/dnscontrol-discuss 8 | about: If you are unsure where to go, then joining our mailing list is recommended; Just ask! 9 | -------------------------------------------------------------------------------- /commands/test_data/apex.com.zone.tsv: -------------------------------------------------------------------------------- 1 | apex.com @ 300 IN SOA ns3.serverfault.com. sysadmin.stackoverflow.com. 2020022300 3600 600 604800 1440 2 | apex.com @ 172800 IN NS ns-1313.awsdns-36.org. 3 | apex.com @ 172800 IN NS ns-736.awsdns-28.net. 4 | apex.com @ 172800 IN NS ns-cloud-c1.googledomains.com. 5 | apex.com @ 172800 IN NS ns-cloud-c2.googledomains.com. 6 | apex.com @ 300 IN CNAME cnametest1.example.com. 7 | www.apex.com www 300 IN CNAME cnametest2.example.com. 8 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/058-ignore-external-dns.js: -------------------------------------------------------------------------------- 1 | // Test IGNORE_EXTERNAL_DNS domain modifier 2 | // Default usage (no prefix) 3 | D("extdns-default.com", "none", IGNORE_EXTERNAL_DNS()); 4 | 5 | // With custom prefix 6 | D("extdns-custom.com", "none", IGNORE_EXTERNAL_DNS("extdns-")); 7 | 8 | // Combined with other records 9 | D("extdns-combined.com", "none", 10 | IGNORE_EXTERNAL_DNS(), 11 | A("www", "1.2.3.4"), 12 | CNAME("api", "www") 13 | ); 14 | -------------------------------------------------------------------------------- /pkg/rejectif/label.go: -------------------------------------------------------------------------------- 1 | package rejectif 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/StackExchange/dnscontrol/v4/models" 7 | ) 8 | 9 | // Keep these in alphabetical order. 10 | 11 | // LabelNotApex detects use not at apex. Use this when a record type 12 | // is only permitted at the apex. 13 | func LabelNotApex(rc *models.RecordConfig) error { 14 | if rc.GetLabel() != "@" { 15 | return errors.New("use not at apex") 16 | } 17 | return nil 18 | } 19 | -------------------------------------------------------------------------------- /bin/generate-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | go fmt ./... 4 | 5 | if [[ -x node_modules/.bin/prettier ]]; then 6 | node_modules/.bin/prettier --write pkg/js/helpers.js 7 | fi 8 | 9 | # JSON 10 | bin/fmtjson $(find . -path ./.vscode -prune -o -type f -name "*.json" ! -name "package-lock.json" -print) 11 | 12 | # dnsconfig.js-compatible files: 13 | for i in pkg/js/parse_tests/*.js ; do dnscontrol fmt -i $i -o $i ; done 14 | 15 | go generate ./... 16 | 17 | go mod tidy 18 | -------------------------------------------------------------------------------- /pkg/rejectif/mx.go: -------------------------------------------------------------------------------- 1 | package rejectif 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/StackExchange/dnscontrol/v4/models" 7 | ) 8 | 9 | // Keep these in alphabetical order. 10 | 11 | // MxNull detects MX records that are a "null MX". 12 | // This is needed by providers that don't support RFC 7505. 13 | func MxNull(rc *models.RecordConfig) error { 14 | if rc.GetTargetField() == "." { 15 | return errors.New("mx has null target") 16 | } 17 | return nil 18 | } 19 | -------------------------------------------------------------------------------- /pkg/dnsgraph/dependencies.go: -------------------------------------------------------------------------------- 1 | package dnsgraph 2 | 3 | // CreateDependencies creates a dependency list from a list of FQDNs and a DepenendyType. 4 | func CreateDependencies(dependencyFQDNs []string, dependencyType DependencyType) []Dependency { 5 | var dependencies []Dependency 6 | 7 | for _, dependency := range dependencyFQDNs { 8 | dependencies = append(dependencies, Dependency{NameFQDN: dependency, Type: dependencyType}) 9 | } 10 | 11 | return dependencies 12 | } 13 | -------------------------------------------------------------------------------- /providers/realtimeregister/realtimeregisterProvider_test.go: -------------------------------------------------------------------------------- 1 | package realtimeregister 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestRemoveEscapeChars(t *testing.T) { 10 | cleanedString := removeEscapeChars("\\\\\\\"") 11 | assert.Equal(t, "\\\"", cleanedString) 12 | } 13 | 14 | func TestAddEscapeChars(t *testing.T) { 15 | addedString := addEscapeChars("\\\"") 16 | assert.Equal(t, "\\\\\\\"", addedString) 17 | } 18 | -------------------------------------------------------------------------------- /pkg/bindserial/main.go: -------------------------------------------------------------------------------- 1 | package bindserial 2 | 3 | // NB(tlim): Yes, its gross to use a global variable for this. 4 | // However there's no cleaner way to do it. Ideally we'd add a way to 5 | // have per-provider flags or settings on the command line. At least 6 | // by isolating it to this file we limit the blast radius of this bad 7 | // decision. 8 | 9 | // ForcedValue if non-zero, BIND will generate SOA serial numbers 10 | // using this value. 11 | var ForcedValue int64 12 | -------------------------------------------------------------------------------- /commands/test_data/ds.com.zone.djs: -------------------------------------------------------------------------------- 1 | // generated by get-zones. This is 'a decent first draft' and requires editing. 2 | 3 | var DSP_BIND = NewDnsProvider("bind", "BIND"); 4 | var REG_CHANGEME = NewRegistrar("none"); 5 | 6 | D("ds.com", REG_CHANGEME 7 | , DnsProvider(DSP_BIND) 8 | //, SOA("@", "ns3.serverfault.com.", "sysadmin.stackoverflow.com.", 3600, 600, 604800, 1440) 9 | , DS("geo", 14480, 13, 2, "BB1C4B615CDED2B34347CF23710471934D972F1E34F53B54ED8D5F786202C73B") 10 | ) 11 | 12 | -------------------------------------------------------------------------------- /commands/test_data/ds.com.zone.js: -------------------------------------------------------------------------------- 1 | // generated by get-zones. This is 'a decent first draft' and requires editing. 2 | 3 | var DSP_BIND = NewDnsProvider("bind", "BIND"); 4 | var REG_CHANGEME = NewRegistrar("none"); 5 | 6 | D("ds.com", REG_CHANGEME, 7 | DnsProvider(DSP_BIND), 8 | //SOA("@", "ns3.serverfault.com.", "sysadmin.stackoverflow.com.", 3600, 600, 604800, 1440), 9 | DS("geo", 14480, 13, 2, "BB1C4B615CDED2B34347CF23710471934D972F1E34F53B54ED8D5F786202C73B"), 10 | ); 11 | 12 | -------------------------------------------------------------------------------- /documentation/language-reference/record-modifiers/R53_EVALUATE_TARGET_HEALTH.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: R53_EVALUATE_TARGET_HEALTH 3 | parameters: 4 | - enabled 5 | parameter_types: 6 | enabled: boolean 7 | ts_return: RecordModifier 8 | provider: ROUTE53 9 | --- 10 | 11 | `R53_EVALUATE_TARGET_HEALTH` lets you enable target health evaluation for a [`R53_ALIAS()`](../domain-modifiers/R53_ALIAS.md) record. Omitting `R53_EVALUATE_TARGET_HEALTH()` from `R53_ALIAS()` set the behavior to false. 12 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/018-dkim/foo.com.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | dkimtest2 IN TXT "this string is 255 bytes long.hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnKZogtjOlHoeY8iZ5o5brlPOsj/a2Q9Bopu1kHxlxrdw7tZVL9FzUMngiIYGrl8dbP7Rvk7TLMoxHxVkRZPBtIpsKIab/gOUoPLQVYbrAmzyguHYBwAApi3H/pvjUsK8+XF0dKY17AR96lokAPqvfBaUb+DSx8zNw2hrYWYVqvCtnxHUGEUhT1bTlEZBptH3j" "this is the remainder. it is 156 bytes long.mOhl2JmbsFKy+RoMTwbkk0/meRvcEFWLHkr4MSgbnie6OpQvM4Y51+kO6DUVr3rwjrdVO9wpFt+n/hdQ92TNif17RMJtE5AGaQ6BN3yJQIDAQAB;" 3 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/032-reverseip/3.2.1.in-addr.arpa.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | 1 IN PTR foo.example.com. 3 | 2 IN PTR bar.example.com. 4 | 3 IN PTR baz.example.com. 5 | 4 IN PTR silly.example.com. 6 | 5 IN PTR willy.example.com. 7 | 6 IN PTR billy.example.com. 8 | 7 IN PTR my.example.com. 9 | 8 IN PTR fair.example.com. 10 | 9 IN PTR lady.example.com. 11 | -------------------------------------------------------------------------------- /pkg/rejectif/ns.go: -------------------------------------------------------------------------------- 1 | package rejectif 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/StackExchange/dnscontrol/v4/models" 7 | ) 8 | 9 | // Keep these in alphabetical order. 10 | 11 | // NsAtApex detects NS records at the apex/root domain. 12 | // Use this when a provider doesn't support custom NS records at the apex. 13 | func NsAtApex(rc *models.RecordConfig) error { 14 | if rc.GetLabel() == "" { 15 | return errors.New("NS records not supported at apex") 16 | } 17 | return nil 18 | } 19 | -------------------------------------------------------------------------------- /commands/test_data/apex.com.zone: -------------------------------------------------------------------------------- 1 | $ORIGIN apex.com. 2 | $TTL 300 3 | @ IN SOA ns3.serverfault.com. sysadmin.stackoverflow.com. 2020022300 3600 600 604800 1440 4 | 172800 IN NS ns-1313.awsdns-36.org. 5 | 172800 IN NS ns-736.awsdns-28.net. 6 | 172800 IN NS ns-cloud-c1.googledomains.com. 7 | 172800 IN NS ns-cloud-c2.googledomains.com. 8 | IN CNAME cnametest1.example.com. 9 | www IN CNAME cnametest2.example.com. 10 | -------------------------------------------------------------------------------- /documentation/provider/opensrs.md: -------------------------------------------------------------------------------- 1 | ## Configuration 2 | 3 | To use this provider, add an entry to `creds.json` with `TYPE` set to `OpenSRS` 4 | along with your OpenSRS credentials. 5 | 6 | ## Usage 7 | 8 | An example configuration: 9 | 10 | {% code title="dnsconfig.js" %} 11 | ```javascript 12 | var REG_NONE = NewRegistrar("none"); 13 | var DSP_OPENSRS = NewDnsProvider("opensrs"); 14 | 15 | D("example.com", REG_NONE, DnsProvider(DSP_OPENSRS), 16 | A("test", "1.2.3.4"), 17 | ); 18 | ``` 19 | {% endcode %} 20 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/009-reverse.json: -------------------------------------------------------------------------------- 1 | { 2 | "dns_providers": [], 3 | "domains": [ 4 | { 5 | "dnsProviders": {}, 6 | "meta": { 7 | "dnscontrol_nameraw": "2.1.in-addr.arpa", 8 | "dnscontrol_nameunicode": "2.1.in-addr.arpa", 9 | "dnscontrol_uniquename": "2.1.in-addr.arpa" 10 | }, 11 | "name": "2.1.in-addr.arpa", 12 | "records": [], 13 | "registrar": "none", 14 | "uniquename": "2.1.in-addr.arpa" 15 | } 16 | ], 17 | "registrars": [] 18 | } 19 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/011-cfRedirect/foo.com.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | ;@ 1 IN CLOUDFLAREAPI_SINGLE_REDIRECT name=(301,test1.foo.com,https://goo.com/$1) code=(301) when=(http.host eq "test1.foo.com" and http.request.uri.path eq "/") then=(concat("https://goo.com", http.request.uri.path)) 3 | ;@ 1 IN CLOUDFLAREAPI_SINGLE_REDIRECT name=(302,test2.foo.com,https://goo.com/$1) code=(302) when=(http.host eq "test2.foo.com" and http.request.uri.path eq "/") then=(concat("https://goo.com", http.request.uri.path)) 4 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/018-dkim.js: -------------------------------------------------------------------------------- 1 | D("foo.com", "none", 2 | TXT("dkimtest2", 3 | DKIM("this string is 255 bytes long.hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnKZogtjOlHoeY8iZ5o5brlPOsj/a2Q9Bopu1kHxlxrdw7tZVL9FzUMngiIYGrl8dbP7Rvk7TLMoxHxVkRZPBtIpsKIab/gOUoPLQVYbrAmzyguHYBwAApi3H/pvjUsK8+XF0dKY17AR96lokAPqvfBaUb+DSx8zNw2hrYWYVqvCtnxHUGEUhT1bTlEZBptH3jthis is the remainder. it is 156 bytes long.mOhl2JmbsFKy+RoMTwbkk0/meRvcEFWLHkr4MSgbnie6OpQvM4Y51+kO6DUVr3rwjrdVO9wpFt+n/hdQ92TNif17RMJtE5AGaQ6BN3yJQIDAQAB;"), 4 | ), 5 | ); 6 | -------------------------------------------------------------------------------- /commands/test_data/apex.com.zone.zone: -------------------------------------------------------------------------------- 1 | $ORIGIN apex.com. 2 | $TTL 300 3 | @ IN SOA ns3.serverfault.com. sysadmin.stackoverflow.com. 2020022300 3600 600 604800 1440 4 | 172800 IN NS ns-1313.awsdns-36.org. 5 | 172800 IN NS ns-736.awsdns-28.net. 6 | 172800 IN NS ns-cloud-c1.googledomains.com. 7 | 172800 IN NS ns-cloud-c2.googledomains.com. 8 | IN CNAME cnametest1.example.com. 9 | www IN CNAME cnametest2.example.com. 10 | 11 | -------------------------------------------------------------------------------- /documentation/provider/exoscale.md: -------------------------------------------------------------------------------- 1 | ## Configuration 2 | 3 | To use this provider, add an entry to `creds.json` with `TYPE` set to `EXOSCALE` 4 | along with your Exoscale credentials. 5 | 6 | ## Usage 7 | 8 | An example configuration: 9 | 10 | {% code title="dnsconfig.js" %} 11 | ```javascript 12 | var REG_NONE = NewRegistrar("none"); 13 | var DSP_EXOSCALE = NewDnsProvider("exoscale"); 14 | 15 | D("example.com", REG_NONE, DnsProvider(DSP_EXOSCALE), 16 | A("test", "1.2.3.4"), 17 | ); 18 | ``` 19 | {% endcode %} 20 | -------------------------------------------------------------------------------- /models/recordtype.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | // ChangeType converts rc to an rc of type newType. This is only needed when 4 | // converting from one type to another. Do not use this when initializing a new 5 | // record. 6 | // 7 | // Typically this is used to convert an ALIAS to a CNAME, or SPF to TXT. Using 8 | // this function future-proofs the code since eventually such changes will 9 | // require extra steps. 10 | func (rc *RecordConfig) ChangeType(newType string, _ string) { 11 | 12 | rc.Type = newType 13 | 14 | } 15 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/036-dextendcf.js: -------------------------------------------------------------------------------- 1 | var REG = NewRegistrar("Third-Party", "NONE"); 2 | var CF = NewDnsProvider("Cloudflare", "CLOUDFLAREAPI"); 3 | 4 | D("foo.com", REG, DnsProvider(CF)); 5 | D_EXTEND("sub.foo.com", 6 | A("test1.foo.com", "10.2.3.1"), 7 | A("test2.foo.com", "10.2.3.2"), 8 | A("test3.foo.com", "10.2.3.3"), 9 | CF_REDIRECT("test1.foo.com", "https://goo.com/$1"), 10 | CF_TEMP_REDIRECT("test2.foo.com", "https://goo.com/$1"), 11 | CF_WORKER_ROUTE("test3.foo.com", "test-worker"), 12 | ); 13 | -------------------------------------------------------------------------------- /documentation/language-reference/domain-modifiers/AKAMAICDN.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: AKAMAICDN 3 | parameters: 4 | - name 5 | - target 6 | - modifiers... 7 | provider: AKAMAIEDGEDNS 8 | parameter_types: 9 | name: string 10 | target: string 11 | "modifiers...": RecordModifier[] 12 | --- 13 | 14 | AKAMAICDN is a proprietary record type that is used to configure [Zone Apex Mapping](https://www.akamai.com/blog/security/edge-dns--zone-apex-mapping---dnssec). 15 | The AKAMAICDN target must be preconfigured in the Akamai network. 16 | -------------------------------------------------------------------------------- /providers/linode/linodeProvider_test.go: -------------------------------------------------------------------------------- 1 | package linode 2 | 3 | import "testing" 4 | 5 | func TestFixTTL(t *testing.T) { 6 | for i, test := range []struct { 7 | given, expected uint32 8 | }{ 9 | {0, 0}, 10 | {1, 300}, 11 | {299, 300}, 12 | {300, 300}, 13 | {301, 3600}, 14 | {2419202, 2419200}, 15 | {600, 3600}, 16 | {3600, 3600}, 17 | } { 18 | found := fixTTL(test.given) 19 | if found != test.expected { 20 | t.Errorf("Test %d: Expected %d, but was %d", i, test.expected, found) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax = docker/dockerfile:1.4 2 | 3 | FROM alpine:3.22.2@sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2ad8dd412 as RUN 4 | 5 | # Add runtime dependencies 6 | # - tzdata: Go time required external dependency eg: TRANSIP and possibly others 7 | # - ca-certificates: Needed for https to work properly 8 | RUN apk update && apk add --no-cache tzdata ca-certificates && update-ca-certificates 9 | 10 | COPY dnscontrol /usr/local/bin/ 11 | 12 | WORKDIR /dns 13 | 14 | ENTRYPOINT ["/usr/local/bin/dnscontrol"] 15 | -------------------------------------------------------------------------------- /documentation/language-reference/top-level-functions/PANIC.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: PANIC 3 | parameters: 4 | - message 5 | parameter_types: 6 | message: string 7 | ts_return: never 8 | --- 9 | 10 | `PANIC` terminates the script and therefore DNSControl with an exit code of 1. This should be used if your script cannot gather enough information to generate records, for example when a HTTP request failed. 11 | 12 | {% code title="dnsconfig.js" %} 13 | ```javascript 14 | PANIC("Something really bad has happened"); 15 | ``` 16 | {% endcode %} 17 | -------------------------------------------------------------------------------- /providers/powerdns/listzones.go: -------------------------------------------------------------------------------- 1 | package powerdns 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | ) 7 | 8 | // ListZones returns all the zones in an account 9 | func (dsp *powerdnsProvider) ListZones() ([]string, error) { 10 | var result []string 11 | myZones, err := dsp.client.Zones().ListZones(context.Background(), dsp.ServerName) 12 | if err != nil { 13 | return result, err 14 | } 15 | for _, zone := range myZones { 16 | result = append(result, strings.TrimSuffix(zone.Name, ".")) 17 | } 18 | return result, nil 19 | } 20 | -------------------------------------------------------------------------------- /documentation/language-reference/domain-modifiers/DNAME.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: DNAME 3 | parameters: 4 | - name 5 | - target 6 | - modifiers... 7 | parameter_types: 8 | name: string 9 | target: string 10 | "modifiers...": RecordModifier[] 11 | --- 12 | 13 | DNAME adds a DNAME record to the domain. 14 | 15 | Target should be a string. 16 | 17 | {% code title="dnsconfig.js" %} 18 | ```javascript 19 | D("example.com", REG_MY_PROVIDER, DnsProvider(DSP_MY_PROVIDER), 20 | DNAME("sub", "example.net."), 21 | ); 22 | ``` 23 | {% endcode %} 24 | -------------------------------------------------------------------------------- /documentation/language-reference/domain-modifiers/DHCID.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: DHCID 3 | parameters: 4 | - name 5 | - digest 6 | - modifiers... 7 | parameter_types: 8 | name: string 9 | digest: string 10 | "modifiers...": RecordModifier[] 11 | --- 12 | 13 | DHCID adds a DHCID record to the domain. 14 | 15 | Digest should be a string. 16 | 17 | {% code title="dnsconfig.js" %} 18 | ```javascript 19 | D("example.com", REG_MY_PROVIDER, DnsProvider(DSP_MY_PROVIDER), 20 | DHCID("example.com", "ABCDEFG"), 21 | ); 22 | ``` 23 | {% endcode %} 24 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/050-cfSingleRedirect.js: -------------------------------------------------------------------------------- 1 | D("foo.com", "none", 2 | A("name1", "1.2.3.4", { 3 | meta: "value" 4 | }), 5 | CF_SINGLE_REDIRECT("name1", 301, "when1", "then1"), 6 | CF_SINGLE_REDIRECT("name2", 302, "when2", "then2"), 7 | CF_SINGLE_REDIRECT("name3", "301", "when3", "then3"), 8 | CF_SINGLE_REDIRECT("namettl", 302, "whenttl", "thenttl", TTL(999)), 9 | CF_SINGLE_REDIRECT("namemeta", 302, "whenmeta", "thenmeta", { 10 | metastr: "stringy" 11 | }, { 12 | metanum: 22 13 | }), 14 | ); 15 | -------------------------------------------------------------------------------- /providers/gcore/auditrecords.go: -------------------------------------------------------------------------------- 1 | package gcore 2 | 3 | import ( 4 | "github.com/StackExchange/dnscontrol/v4/models" 5 | "github.com/StackExchange/dnscontrol/v4/pkg/rejectif" 6 | ) 7 | 8 | // AuditRecords returns a list of errors corresponding to the records 9 | // that aren't supported by this provider. If all records are 10 | // supported, an empty list is returned. 11 | func AuditRecords(records []*models.RecordConfig) []error { 12 | a := rejectif.Auditor{} 13 | a.Add("SRV", rejectif.SrvHasNullTarget) 14 | return a.Audit(records) 15 | } 16 | -------------------------------------------------------------------------------- /providers/ns1/auditrecords.go: -------------------------------------------------------------------------------- 1 | package ns1 2 | 3 | import ( 4 | "github.com/StackExchange/dnscontrol/v4/models" 5 | "github.com/StackExchange/dnscontrol/v4/pkg/rejectif" 6 | ) 7 | 8 | // AuditRecords returns a list of errors corresponding to the records 9 | // that aren't supported by this provider. If all records are 10 | // supported, an empty list is returned. 11 | func AuditRecords(records []*models.RecordConfig) []error { 12 | a := rejectif.Auditor{} 13 | 14 | a.Add("TXT", rejectif.TxtIsEmpty) 15 | 16 | return a.Audit(records) 17 | } 18 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/031-dextendnames/domain.tld.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | @ IN A 127.0.0.1 3 | IN A 127.0.0.3 4 | a IN A 127.0.0.2 5 | b IN CNAME c.domain.tld. 6 | d IN A 127.0.0.4 7 | e IN CNAME f.domain.tld. 8 | ssub IN A 127.0.0.7 9 | j.ssub IN A 127.0.0.8 10 | k.ssub IN CNAME l.ssub.domain.tld. 11 | ub IN A 127.0.0.5 12 | g.ub IN A 127.0.0.6 13 | h.ub IN CNAME i.ub.domain.tld. 14 | -------------------------------------------------------------------------------- /pkg/txtutil/txtutil.go: -------------------------------------------------------------------------------- 1 | package txtutil 2 | 3 | // ToChunks returns the string as chunks of 255-octet strings (the last string being the remainder). 4 | func ToChunks(s string) []string { 5 | return splitChunks(s, 255) 6 | } 7 | 8 | func splitChunks(buf string, lim int) []string { 9 | var chunk string 10 | chunks := make([]string, 0, len(buf)/lim+1) 11 | for len(buf) >= lim { 12 | chunk, buf = buf[:lim], buf[lim:] 13 | chunks = append(chunks, chunk) 14 | } 15 | if len(buf) > 0 { 16 | chunks = append(chunks, buf) 17 | } 18 | return chunks 19 | } 20 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/030-dextenddoc/domain.tld.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | @ IN A 127.0.0.1 3 | a IN CNAME b.domain.tld. 4 | aaa IN A 127.0.0.3 5 | c IN CNAME d.domain.tld. 6 | sub IN A 127.0.0.7 7 | bbb.sub IN A 127.0.0.4 8 | ccc.sub IN A 127.0.0.5 9 | e.sub IN CNAME f.sub.domain.tld. 10 | i.sub IN CNAME j.sub.domain.tld. 11 | ddd.sub.sub IN A 127.0.0.6 12 | g.sub.sub IN CNAME h.sub.sub.domain.tld. 13 | www IN A 127.0.0.2 14 | -------------------------------------------------------------------------------- /providers/luadns/auditrecords.go: -------------------------------------------------------------------------------- 1 | package luadns 2 | 3 | import ( 4 | "github.com/StackExchange/dnscontrol/v4/models" 5 | "github.com/StackExchange/dnscontrol/v4/pkg/rejectif" 6 | ) 7 | 8 | // AuditRecords returns a list of errors corresponding to the records 9 | // that aren't supported by this provider. If all records are 10 | // supported, an empty list is returned. 11 | func AuditRecords(records []*models.RecordConfig) []error { 12 | a := rejectif.Auditor{} 13 | a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2023-03-03 14 | return a.Audit(records) 15 | } 16 | -------------------------------------------------------------------------------- /providers/ovh/auditrecords.go: -------------------------------------------------------------------------------- 1 | package ovh 2 | 3 | import ( 4 | "github.com/StackExchange/dnscontrol/v4/models" 5 | "github.com/StackExchange/dnscontrol/v4/pkg/rejectif" 6 | ) 7 | 8 | // AuditRecords returns a list of errors corresponding to the records 9 | // that aren't supported by this provider. If all records are 10 | // supported, an empty list is returned. 11 | func AuditRecords(records []*models.RecordConfig) []error { 12 | a := rejectif.Auditor{} 13 | 14 | a.Add("MX", rejectif.MxNull) // Last verified 2020-12-28 15 | 16 | return a.Audit(records) 17 | } 18 | -------------------------------------------------------------------------------- /providers/hedns/auditrecords.go: -------------------------------------------------------------------------------- 1 | package hedns 2 | 3 | import ( 4 | "github.com/StackExchange/dnscontrol/v4/models" 5 | "github.com/StackExchange/dnscontrol/v4/pkg/rejectif" 6 | ) 7 | 8 | // AuditRecords returns a list of errors corresponding to the records 9 | // that aren't supported by this provider. If all records are 10 | // supported, an empty list is returned. 11 | func AuditRecords(records []*models.RecordConfig) []error { 12 | a := rejectif.Auditor{} 13 | 14 | a.Add("MX", rejectif.MxNull) // Last verified 2020-12-28 15 | 16 | return a.Audit(records) 17 | } 18 | -------------------------------------------------------------------------------- /documentation/language-reference/domain-modifiers/INCLUDE.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: INCLUDE 3 | parameters: 4 | - domain 5 | parameter_types: 6 | domain: string 7 | --- 8 | 9 | Includes all records from a given domain 10 | 11 | 12 | {% code title="dnsconfig.js" %} 13 | ```javascript 14 | D("example.com!external", REG_MY_PROVIDER, DnsProvider(DSP_MY_PROVIDER), 15 | A("test", "8.8.8.8"), 16 | ); 17 | 18 | D("example.com!internal", REG_MY_PROVIDER, DnsProvider(DSP_MY_PROVIDER), 19 | INCLUDE("example.com!external"), 20 | A("home", "127.0.0.1"), 21 | ); 22 | ``` 23 | {% endcode %} 24 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/059-rawttls/example.com.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | @ IN RP user.example.com. mytxt.example.com. 3 | IN RP user2.example.com. mytxt.example.com. 4 | aaa300 IN RP user.example.com. mytxt.example.com. 5 | bbb1 1111 IN RP user.example.com. mytxt.example.com. 6 | ccc2 2222 IN RP user.example.com. mytxt.example.com. 7 | ddd1 1111 IN RP user.example.com. mytxt.example.com. 8 | eee3 3333 IN RP user.example.com. mytxt.example.com. 9 | mytxt IN TXT "Do not call me on my phone" 10 | -------------------------------------------------------------------------------- /documentation/advanced-features/bug-triage.md: -------------------------------------------------------------------------------- 1 | # Who to assign bugs to? 2 | 3 | If an issue is related to a particular provider, assign it to 4 | the person responsible for the provider, as listed in 5 | [Providers](../provider/index.md)'s "Maintainers of 6 | contributed providers". 7 | 8 | Otherwise leave it unassigned until someone grabs it. 9 | 10 | 11 | # How bugs are classified 12 | 13 | labels: 14 | 15 | * enhancement: New feature of improvement of existing feature 16 | * bug: feature works wrong or not as expected 17 | 18 | priority: 19 | 20 | * maybe someday: Low priority 21 | -------------------------------------------------------------------------------- /providers/gcloud/auditrecords.go: -------------------------------------------------------------------------------- 1 | package gcloud 2 | 3 | import ( 4 | "github.com/StackExchange/dnscontrol/v4/models" 5 | "github.com/StackExchange/dnscontrol/v4/pkg/rejectif" 6 | ) 7 | 8 | // AuditRecords returns a list of errors corresponding to the records 9 | // that aren't supported by this provider. If all records are 10 | // supported, an empty list is returned. 11 | func AuditRecords(records []*models.RecordConfig) []error { 12 | a := rejectif.Auditor{} 13 | 14 | a.Add("ALIAS", rejectif.LabelNotApex) // Last verified 2023-03-24 15 | 16 | return a.Audit(records) 17 | } 18 | -------------------------------------------------------------------------------- /providers/hostingde/auditrecords.go: -------------------------------------------------------------------------------- 1 | package hostingde 2 | 3 | import ( 4 | "github.com/StackExchange/dnscontrol/v4/models" 5 | "github.com/StackExchange/dnscontrol/v4/pkg/rejectif" 6 | ) 7 | 8 | // AuditRecords returns a list of errors corresponding to the records 9 | // that aren't supported by this provider. If all records are 10 | // supported, an empty list is returned. 11 | func AuditRecords(records []*models.RecordConfig) []error { 12 | a := rejectif.Auditor{} 13 | 14 | a.Add("SRV", rejectif.SrvHasNullTarget) // Last verified 2023-01-19 15 | 16 | return a.Audit(records) 17 | } 18 | -------------------------------------------------------------------------------- /providers/azureprivatedns/auditrecords.go: -------------------------------------------------------------------------------- 1 | package azureprivatedns 2 | 3 | import ( 4 | "github.com/StackExchange/dnscontrol/v4/models" 5 | "github.com/StackExchange/dnscontrol/v4/pkg/rejectif" 6 | ) 7 | 8 | // AuditRecords returns a list of errors corresponding to the records 9 | // that aren't supported by this provider. If all records are 10 | // supported, an empty list is returned. 11 | func AuditRecords(records []*models.RecordConfig) []error { 12 | a := rejectif.Auditor{} 13 | 14 | a.Add("MX", rejectif.MxNull) // Last verified 2020-12-28 15 | 16 | return a.Audit(records) 17 | } 18 | -------------------------------------------------------------------------------- /providers/dnsmadeeasy/auditrecords.go: -------------------------------------------------------------------------------- 1 | package dnsmadeeasy 2 | 3 | import ( 4 | "github.com/StackExchange/dnscontrol/v4/models" 5 | "github.com/StackExchange/dnscontrol/v4/pkg/rejectif" 6 | ) 7 | 8 | // AuditRecords returns a list of errors corresponding to the records 9 | // that aren't supported by this provider. If all records are 10 | // supported, an empty list is returned. 11 | func AuditRecords(records []*models.RecordConfig) []error { 12 | a := rejectif.Auditor{} 13 | 14 | a.Add("TXT", rejectif.TxtHasDoubleQuotes) // Last verified 2021-03-11 15 | 16 | return a.Audit(records) 17 | } 18 | -------------------------------------------------------------------------------- /providers/hetzner/auditrecords.go: -------------------------------------------------------------------------------- 1 | package hetzner 2 | 3 | import ( 4 | "github.com/StackExchange/dnscontrol/v4/models" 5 | "github.com/StackExchange/dnscontrol/v4/pkg/rejectif" 6 | ) 7 | 8 | // AuditRecords returns a list of errors corresponding to the records 9 | // that aren't supported by this provider. If all records are 10 | // supported, an empty list is returned. 11 | func AuditRecords(records []*models.RecordConfig) []error { 12 | a := rejectif.Auditor{} 13 | 14 | a.Add("CAA", rejectif.CaaTargetContainsWhitespace) // Last verified 2023-04-01 15 | 16 | return a.Audit(records) 17 | } 18 | -------------------------------------------------------------------------------- /providers/namecheap/auditrecords.go: -------------------------------------------------------------------------------- 1 | package namecheap 2 | 3 | import ( 4 | "github.com/StackExchange/dnscontrol/v4/models" 5 | "github.com/StackExchange/dnscontrol/v4/pkg/rejectif" 6 | ) 7 | 8 | // AuditRecords returns a list of errors corresponding to the records 9 | // that aren't supported by this provider. If all records are 10 | // supported, an empty list is returned. 11 | func AuditRecords(records []*models.RecordConfig) []error { 12 | a := rejectif.Auditor{} 13 | 14 | a.Add("MX", rejectif.MxNull) 15 | 16 | a.Add("TXT", rejectif.TxtHasDoubleQuotes) 17 | 18 | return a.Audit(records) 19 | } 20 | -------------------------------------------------------------------------------- /providers/rwth/auditrecords.go: -------------------------------------------------------------------------------- 1 | package rwth 2 | 3 | import ( 4 | "github.com/StackExchange/dnscontrol/v4/models" 5 | "github.com/StackExchange/dnscontrol/v4/pkg/rejectif" 6 | ) 7 | 8 | // AuditRecords returns a list of errors corresponding to the records 9 | // that aren't supported by this provider. If all records are 10 | // supported, an empty list is returned. 11 | func AuditRecords(records []*models.RecordConfig) []error { 12 | a := rejectif.Auditor{} 13 | 14 | a.Add("TXT", rejectif.TxtHasTrailingSpace) 15 | 16 | a.Add("TXT", rejectif.TxtIsEmpty) 17 | 18 | return a.Audit(records) 19 | } 20 | -------------------------------------------------------------------------------- /providers/domainnameshop/dns_test.go: -------------------------------------------------------------------------------- 1 | package domainnameshop 2 | 3 | import "testing" 4 | 5 | func TestFixTTL(t *testing.T) { 6 | for i, test := range []struct { 7 | given, expected uint32 8 | }{ 9 | {1, minAllowedTTL}, 10 | {multiplierTTL*5 - 1, multiplierTTL * 4}, 11 | {maxAllowedTTL + 1, maxAllowedTTL}, 12 | {0, 60}, 13 | {59, 60}, 14 | {60, 60}, 15 | {61, 60}, 16 | {119, 60}, 17 | {120, 120}, 18 | {121, 120}, 19 | } { 20 | found := fixTTL(test.given) 21 | if found != test.expected { 22 | t.Errorf("Test %d: Expected %d, but was %d", i, test.expected, found) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /documentation/language-reference/domain-modifiers/RP.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: RP 3 | parameters: 4 | - name 5 | - mbox 6 | - txt 7 | - modifiers... 8 | parameter_types: 9 | name: string 10 | mbox: string 11 | txt: string 12 | "modifiers...": RecordModifier[] 13 | --- 14 | 15 | `RP()` adds an RP record to a domain. 16 | 17 | The RP implementation in DNSControl is still experimental and may change. 18 | 19 | {% code title="dnsconfig.js" %} 20 | ```javascript 21 | D("example.com", REG_MY_PROVIDER, DnsProvider(DSP_MY_PROVIDER), 22 | RP("@", "user.example.com.", "example.com."), 23 | ); 24 | ``` 25 | {% endcode %} 26 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/050-cfSingleRedirect/foo.com.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | ;@ 1 IN CLOUDFLAREAPI_SINGLE_REDIRECT name=(name1) code=(301) when=(when1) then=(then1) 3 | ;@ 1 IN CLOUDFLAREAPI_SINGLE_REDIRECT name=(name2) code=(302) when=(when2) then=(then2) 4 | ;@ 1 IN CLOUDFLAREAPI_SINGLE_REDIRECT name=(name3) code=(301) when=(when3) then=(then3) 5 | ;@ 1 IN CLOUDFLAREAPI_SINGLE_REDIRECT name=(namemeta) code=(302) when=(whenmeta) then=(thenmeta) 6 | ;@ 1 IN CLOUDFLAREAPI_SINGLE_REDIRECT name=(namettl) code=(302) when=(whenttl) then=(thenttl) 7 | name1 IN A 1.2.3.4 8 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/051-HASH.json: -------------------------------------------------------------------------------- 1 | { 2 | "dns_providers": [], 3 | "domains": [ 4 | { 5 | "dnsProviders": {}, 6 | "meta": { 7 | "dnscontrol_nameraw": "a9993e364706816aba3e25717850c26c9cd0d89d", 8 | "dnscontrol_nameunicode": "a9993e364706816aba3e25717850c26c9cd0d89d", 9 | "dnscontrol_uniquename": "a9993e364706816aba3e25717850c26c9cd0d89d" 10 | }, 11 | "name": "a9993e364706816aba3e25717850c26c9cd0d89d", 12 | "records": [], 13 | "registrar": "reg", 14 | "uniquename": "a9993e364706816aba3e25717850c26c9cd0d89d" 15 | } 16 | ], 17 | "registrars": [] 18 | } 19 | -------------------------------------------------------------------------------- /models/rawrecord.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | // RawRecordConfig stores the user-input from dnsconfig.js for a DNS 4 | // Record. This is later processed (in Go) to become a RecordConfig. 5 | // NOTE: Only newer rtypes are processed this way. Eventually the 6 | // legacy types will be converted. 7 | type RawRecordConfig struct { 8 | Type string `json:"type"` 9 | Args []any `json:"args,omitempty"` 10 | Metas []map[string]any `json:"metas,omitempty"` 11 | TTL uint32 `json:"ttl,omitempty"` 12 | FilePos string `json:"filepos"` // Where in the file this record was defined. 13 | } 14 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/028-dextend.js: -------------------------------------------------------------------------------- 1 | var REG = NewRegistrar("Third-Party", "NONE"); 2 | var CF = NewDnsProvider("Cloudflare", "CLOUDFLAREAPI"); 3 | 4 | // Zone and subdomain Zone: 5 | D("foo.com", REG, DnsProvider(CF), 6 | A("@", "10.1.1.1"), 7 | A("www", "10.2.2.2"), 8 | ); 9 | D("bar.foo.com", REG, DnsProvider(CF), 10 | A("@", "10.3.3.3"), 11 | A("www", "10.4.4.4"), 12 | ); 13 | 14 | // Zone that gets extended 15 | D("foo.edu", REG, DnsProvider(CF), 16 | A("@", "10.5.5.5"), 17 | A("www", "10.6.6.6"), 18 | ); 19 | D_EXTEND("foo.edu", 20 | A("more1", "10.7.7.7"), 21 | A("more2", "10.8.8.8"), 22 | ); 23 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/006-transforms.js: -------------------------------------------------------------------------------- 1 | var REG = NewRegistrar("Third-Party", "NONE"); 2 | var CF = NewDnsProvider("Cloudflare", "CLOUDFLAREAPI"); 3 | 4 | // This is a cloudflare-specific transform. It is no longer used because 5 | // IMPORT_TRANSFORM works for all providers. 6 | 7 | var TRANSFORM_INT = [{ 8 | low: "0.0.0.0", 9 | high: "1.1.1.0", 10 | newBase: "2.2.2.2" 11 | }, { 12 | low: "1.1.1.1", 13 | high: IP("2.2.2.2"), 14 | newIP: ["3.3.3.3", "4.4.4.4", IP("5.5.5.5")] 15 | }]; 16 | 17 | D("foo.com", REG, DnsProvider(CF), 18 | A("@", "1.2.3.4", { 19 | transform: TRANSFORM_INT 20 | }), 21 | ); 22 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/055-b3550-ipv6ptr.js: -------------------------------------------------------------------------------- 1 | var REG_NONE = NewRegistrar("none"); 2 | var DSP_BIND = NewDnsProvider("bind", "BIND"); 3 | 4 | D(REV("2011:abcd::/32"), REG_NONE, DnsProvider(DSP_BIND), 5 | PTR("2011:abcd::11", "host11.example.com."), 6 | PTR(REV("2011:abcd::22"), "host22.example.com."), 7 | ); 8 | 9 | D(REV("2001:db8::/32"), REG_NONE, DnsProvider(DSP_BIND), 10 | PTR("2001:db8::11", "server11.example.com."), 11 | PTR(REV("2001:db8::22"), "server22.example.com."), 12 | ); 13 | 14 | D_EXTEND("d.c.b.a.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa", 15 | PTR("2001:db8::abcd", "abcd.example.com.") 16 | ); 17 | -------------------------------------------------------------------------------- /providers/autodns/auditrecords.go: -------------------------------------------------------------------------------- 1 | package autodns 2 | 3 | import ( 4 | "github.com/StackExchange/dnscontrol/v4/models" 5 | "github.com/StackExchange/dnscontrol/v4/pkg/rejectif" 6 | ) 7 | 8 | // AuditRecords returns a list of errors corresponding to the records 9 | // that aren't supported by this provider. If all records are 10 | // supported, an empty list is returned. 11 | func AuditRecords(records []*models.RecordConfig) []error { 12 | a := rejectif.Auditor{} 13 | 14 | a.Add("MX", rejectif.MxNull) // Last verified 2022-03-25 15 | a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2025-05-13 16 | 17 | return a.Audit(records) 18 | } 19 | -------------------------------------------------------------------------------- /providers/namedotcom/zones.go: -------------------------------------------------------------------------------- 1 | package namedotcom 2 | 3 | import "github.com/namedotcom/go/namecom" 4 | 5 | // ListZones returns all the zones in an account 6 | func (n *namedotcomProvider) ListZones() ([]string, error) { 7 | var names []string 8 | var page int32 9 | 10 | for { 11 | response, err := n.client.ListDomains(&namecom.ListDomainsRequest{Page: page}) 12 | if err != nil { 13 | return nil, err 14 | } 15 | page = response.NextPage 16 | 17 | for _, j := range response.Domains { 18 | names = append(names, j.DomainName) 19 | } 20 | 21 | if page == 0 { 22 | break 23 | } 24 | } 25 | 26 | return names, nil 27 | } 28 | -------------------------------------------------------------------------------- /providers/netcup/auditrecords.go: -------------------------------------------------------------------------------- 1 | package netcup 2 | 3 | import ( 4 | "github.com/StackExchange/dnscontrol/v4/models" 5 | "github.com/StackExchange/dnscontrol/v4/pkg/rejectif" 6 | ) 7 | 8 | // AuditRecords returns a list of errors corresponding to the records 9 | // that aren't supported by this provider. If all records are 10 | // supported, an empty list is returned. 11 | func AuditRecords(records []*models.RecordConfig) []error { 12 | a := rejectif.Auditor{} 13 | 14 | a.Add("MX", rejectif.MxNull) // Last verified 2020-12-28 15 | 16 | a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2021-03-01 17 | 18 | return a.Audit(records) 19 | } 20 | -------------------------------------------------------------------------------- /providers/netlify/auditrecords.go: -------------------------------------------------------------------------------- 1 | package netlify 2 | 3 | import ( 4 | "github.com/StackExchange/dnscontrol/v4/models" 5 | "github.com/StackExchange/dnscontrol/v4/pkg/rejectif" 6 | ) 7 | 8 | // AuditRecords returns a list of errors corresponding to the records 9 | // that aren't supported by this provider. If all records are 10 | // supported, an empty list is returned. 11 | func AuditRecords(records []*models.RecordConfig) []error { 12 | a := rejectif.Auditor{} 13 | 14 | a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2023-01-19 15 | 16 | a.Add("MX", rejectif.MxNull) // Last verified 2023-01-19 17 | 18 | return a.Audit(records) 19 | } 20 | -------------------------------------------------------------------------------- /providers/azuredns/auditrecords.go: -------------------------------------------------------------------------------- 1 | package azuredns 2 | 3 | import ( 4 | "github.com/StackExchange/dnscontrol/v4/models" 5 | "github.com/StackExchange/dnscontrol/v4/pkg/rejectif" 6 | ) 7 | 8 | // AuditRecords returns a list of errors corresponding to the records 9 | // that aren't supported by this provider. If all records are 10 | // supported, an empty list is returned. 11 | func AuditRecords(records []*models.RecordConfig) []error { 12 | a := rejectif.Auditor{} 13 | 14 | a.Add("MX", rejectif.MxNull) // Last verified 2020-12-28 15 | 16 | a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2023-11-11 17 | 18 | return a.Audit(records) 19 | } 20 | -------------------------------------------------------------------------------- /providers/bunnydns/auditrecords.go: -------------------------------------------------------------------------------- 1 | package bunnydns 2 | 3 | import ( 4 | "github.com/StackExchange/dnscontrol/v4/models" 5 | "github.com/StackExchange/dnscontrol/v4/pkg/rejectif" 6 | ) 7 | 8 | // AuditRecords returns a list of errors corresponding to the records 9 | // that aren't supported by this provider. If all records are 10 | // supported, an empty list is returned. 11 | func AuditRecords(records []*models.RecordConfig) []error { 12 | a := rejectif.Auditor{} 13 | a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2024-01-02 14 | a.Add("SRV", rejectif.SrvHasNullTarget) // Last verified 2024-01-02 15 | 16 | return a.Audit(records) 17 | } 18 | -------------------------------------------------------------------------------- /bin/fix_js_parse_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # fix_js_parse_tests.sh -- Fix up the pkg/js/parse_tests "want" files. 4 | # 5 | # DO NOT use this without carefully checking the output. You could 6 | # accidentally codify bad test data and commit it to the repo. 7 | # 8 | # Useful bash/zsh alias: 9 | # alias fixjsparse='"$(git rev-parse --show-toplevel)/bin/fix_js_parse_tests.sh"' 10 | 11 | set -e 12 | 13 | cd $(git rev-parse --show-toplevel) 14 | cd pkg/js 15 | find . -type f -name \*.ACTUAL -print -delete 16 | go test -count=1 ./... || true 17 | cd parse_tests 18 | fmtjson *.json *.json.ACTUAL 19 | for i in *.ACTUAL ; do f=$(basename $i .ACTUAL) ; mv $i $f ; done 20 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/008-import.json: -------------------------------------------------------------------------------- 1 | { 2 | "dns_providers": [], 3 | "domains": [ 4 | { 5 | "dnsProviders": {}, 6 | "meta": { 7 | "dnscontrol_nameraw": "foo.com", 8 | "dnscontrol_nameunicode": "foo.com", 9 | "dnscontrol_uniquename": "foo.com" 10 | }, 11 | "name": "foo.com", 12 | "records": [ 13 | { 14 | "filepos": "[line:1:1]", 15 | "name": "@", 16 | "target": "1.2.3.4", 17 | "ttl": 300, 18 | "type": "A" 19 | } 20 | ], 21 | "registrar": "none", 22 | "uniquename": "foo.com" 23 | } 24 | ], 25 | "registrars": [] 26 | } 27 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/046-DHCID.json: -------------------------------------------------------------------------------- 1 | { 2 | "dns_providers": [], 3 | "domains": [ 4 | { 5 | "dnsProviders": {}, 6 | "meta": { 7 | "dnscontrol_nameraw": "foo.com", 8 | "dnscontrol_nameunicode": "foo.com", 9 | "dnscontrol_uniquename": "foo.com" 10 | }, 11 | "name": "foo.com", 12 | "records": [ 13 | { 14 | "filepos": "[line:2:5]", 15 | "name": "@", 16 | "target": "Test", 17 | "ttl": 300, 18 | "type": "DHCID" 19 | } 20 | ], 21 | "registrar": "none", 22 | "uniquename": "foo.com" 23 | } 24 | ], 25 | "registrars": [] 26 | } 27 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/007-importTransformTTL.js: -------------------------------------------------------------------------------- 1 | D("foo1.com", "reg", 2 | A("bar", "1.1.1.1"), 3 | A("foo", "5.5.5.5"), 4 | ); 5 | 6 | var TRANSFORM_BASE = [{ 7 | low: "1.1.1.0", 8 | high: "1.1.1.100", 9 | newBase: "4.4.4.100" 10 | }, { 11 | low: "5.5.5.2", 12 | high: "5.5.5.100", 13 | newBase: "6.6.6.0" 14 | }, ]; 15 | 16 | D("inny", "reg", 17 | IMPORT_TRANSFORM(TRANSFORM_BASE, "foo1.com", 60), 18 | ); 19 | 20 | var TRANSFORM_NEWIP = [{ 21 | low: "5.5.5.0", 22 | high: "6.0.0.0", 23 | newIP: "7.7.7.7" 24 | }]; 25 | 26 | D("com.inny", "reg", 27 | IMPORT_TRANSFORM_STRIP(TRANSFORM_NEWIP, "foo1.com", 99, "com"), 28 | ); 29 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/010-alias.json: -------------------------------------------------------------------------------- 1 | { 2 | "dns_providers": [], 3 | "domains": [ 4 | { 5 | "dnsProviders": {}, 6 | "meta": { 7 | "dnscontrol_nameraw": "foo.com", 8 | "dnscontrol_nameunicode": "foo.com", 9 | "dnscontrol_uniquename": "foo.com" 10 | }, 11 | "name": "foo.com", 12 | "records": [ 13 | { 14 | "filepos": "[line:2:5]", 15 | "name": "@", 16 | "target": "foo.com.", 17 | "ttl": 300, 18 | "type": "ALIAS" 19 | } 20 | ], 21 | "registrar": "none", 22 | "uniquename": "foo.com" 23 | } 24 | ], 25 | "registrars": [] 26 | } 27 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/024-json-import.json: -------------------------------------------------------------------------------- 1 | { 2 | "dns_providers": [], 3 | "domains": [ 4 | { 5 | "dnsProviders": {}, 6 | "meta": { 7 | "dnscontrol_nameraw": "foo.com", 8 | "dnscontrol_nameunicode": "foo.com", 9 | "dnscontrol_uniquename": "foo.com" 10 | }, 11 | "name": "foo.com", 12 | "records": [ 13 | { 14 | "filepos": "[line:7:5]", 15 | "name": "@", 16 | "target": "1.1.1.1", 17 | "ttl": 300, 18 | "type": "A" 19 | } 20 | ], 21 | "registrar": "none", 22 | "uniquename": "foo.com" 23 | } 24 | ], 25 | "registrars": [] 26 | } 27 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/047-DNAME.json: -------------------------------------------------------------------------------- 1 | { 2 | "dns_providers": [], 3 | "domains": [ 4 | { 5 | "dnsProviders": {}, 6 | "meta": { 7 | "dnscontrol_nameraw": "foo.com", 8 | "dnscontrol_nameunicode": "foo.com", 9 | "dnscontrol_uniquename": "foo.com" 10 | }, 11 | "name": "foo.com", 12 | "records": [ 13 | { 14 | "filepos": "[line:2:5]", 15 | "name": "@", 16 | "target": "bar.com.", 17 | "ttl": 300, 18 | "type": "DNAME" 19 | } 20 | ], 21 | "registrar": "none", 22 | "uniquename": "foo.com" 23 | } 24 | ], 25 | "registrars": [] 26 | } 27 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/049-json5-require.json: -------------------------------------------------------------------------------- 1 | { 2 | "dns_providers": [], 3 | "domains": [ 4 | { 5 | "dnsProviders": {}, 6 | "meta": { 7 | "dnscontrol_nameraw": "foo.com", 8 | "dnscontrol_nameunicode": "foo.com", 9 | "dnscontrol_uniquename": "foo.com" 10 | }, 11 | "name": "foo.com", 12 | "records": [ 13 | { 14 | "filepos": "[line:7:5]", 15 | "name": "@", 16 | "target": "1.1.1.1", 17 | "ttl": 300, 18 | "type": "A" 19 | } 20 | ], 21 | "registrar": "none", 22 | "uniquename": "foo.com" 23 | } 24 | ], 25 | "registrars": [] 26 | } 27 | -------------------------------------------------------------------------------- /providers/akamaiedgedns/auditrecords.go: -------------------------------------------------------------------------------- 1 | package akamaiedgedns 2 | 3 | import ( 4 | "github.com/StackExchange/dnscontrol/v4/models" 5 | "github.com/StackExchange/dnscontrol/v4/pkg/rejectif" 6 | ) 7 | 8 | // AuditRecords returns a list of errors corresponding to the records 9 | // that aren't supported by this provider. If all records are 10 | // supported, an empty list is returned. 11 | func AuditRecords(records []*models.RecordConfig) []error { 12 | a := rejectif.Auditor{} 13 | a.Add("TXT", rejectif.TxtHasDoubleQuotes) // Last verified 2023-12-12 14 | a.Add("TXT", rejectif.TxtHasBackslash) // Last verified 2023-12-12 15 | 16 | return a.Audit(records) 17 | } 18 | -------------------------------------------------------------------------------- /providers/cloudflare/auditrecords.go: -------------------------------------------------------------------------------- 1 | package cloudflare 2 | 3 | import ( 4 | "github.com/StackExchange/dnscontrol/v4/models" 5 | "github.com/StackExchange/dnscontrol/v4/pkg/rejectif" 6 | ) 7 | 8 | // AuditRecords returns a list of errors corresponding to the records 9 | // that aren't supported by this provider. If all records are 10 | // supported, an empty list is returned. 11 | func AuditRecords(records []*models.RecordConfig) []error { 12 | a := rejectif.Auditor{} 13 | 14 | a.Add("TXT", rejectif.TxtHasTrailingSpace) // Last verified 2022-06-18 15 | 16 | a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2022-06-18 17 | 18 | return a.Audit(records) 19 | } 20 | -------------------------------------------------------------------------------- /providers/powerdns/auditrecords.go: -------------------------------------------------------------------------------- 1 | package powerdns 2 | 3 | import ( 4 | "github.com/StackExchange/dnscontrol/v4/models" 5 | "github.com/StackExchange/dnscontrol/v4/pkg/rejectif" 6 | ) 7 | 8 | // AuditRecords returns a list of errors corresponding to the records 9 | // that aren't supported by this provider. If all records are 10 | // supported, an empty list is returned. 11 | func AuditRecords(records []*models.RecordConfig) []error { 12 | a := rejectif.Auditor{} 13 | 14 | a.Add("TXT", rejectif.TxtHasDoubleQuotes) // Last verified 2023-11-11 15 | a.Add("TXT", rejectif.TxtHasBackslash) // Last verified 2023-11-11 16 | 17 | return a.Audit(records) 18 | } 19 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/023-ignored-glob-records.json: -------------------------------------------------------------------------------- 1 | { 2 | "dns_providers": [], 3 | "domains": [ 4 | { 5 | "dnsProviders": {}, 6 | "meta": { 7 | "dnscontrol_nameraw": "foo.com", 8 | "dnscontrol_nameunicode": "foo.com", 9 | "dnscontrol_uniquename": "foo.com" 10 | }, 11 | "name": "foo.com", 12 | "records": [], 13 | "registrar": "none", 14 | "uniquename": "foo.com", 15 | "unmanaged": [ 16 | { 17 | "label_pattern": "\\*.testignore", 18 | "rType_pattern": "*", 19 | "target_pattern": "*" 20 | } 21 | ] 22 | } 23 | ], 24 | "registrars": [] 25 | } 26 | -------------------------------------------------------------------------------- /providers/linode/auditrecords.go: -------------------------------------------------------------------------------- 1 | package linode 2 | 3 | import ( 4 | "github.com/StackExchange/dnscontrol/v4/models" 5 | "github.com/StackExchange/dnscontrol/v4/pkg/rejectif" 6 | ) 7 | 8 | // AuditRecords returns a list of errors corresponding to the records 9 | // that aren't supported by this provider. If all records are 10 | // supported, an empty list is returned. 11 | func AuditRecords(records []*models.RecordConfig) []error { 12 | a := rejectif.Auditor{} 13 | 14 | a.Add("CAA", rejectif.CaaFlagIsNonZero) // Last verified 2022-03-25 15 | 16 | a.Add("CAA", rejectif.CaaTargetContainsWhitespace) // Last verified 2023-01-15 17 | 18 | return a.Audit(records) 19 | } 20 | -------------------------------------------------------------------------------- /.linkspector.yml: -------------------------------------------------------------------------------- 1 | useGitIgnore: true 2 | dirs: 3 | - documentation 4 | files: 5 | - README.md 6 | - SECURITY.md 7 | ignorePatterns: 8 | - pattern: '.*#opinion-3-dnsconfig.js-are-not-zonefiles$' 9 | - pattern: '.*?plain=1$' 10 | - pattern: '.*.md#$' 11 | - pattern: '^https://cloud.digitalocean.com/account/api/tokens' 12 | - pattern: '^https://cloud.digitalocean.com/settings/applications' 13 | - pattern: '^https://dns.hetzner.com/settings/api-token' 14 | - pattern: '^https://github.com/StackExchange/dnscontrol/settings/.*$' 15 | - pattern: '^https://gitlab.com/cafferata/dnscontrol/-/pipelines/new.*$' 16 | - pattern: '^https://my.easyname.com/en/account/api' 17 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/036-dextendcf/foo.com.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | ;@ IN CF_WORKER_ROUTE test3.foo.com,test-worker 3 | ;@ 1 IN CLOUDFLAREAPI_SINGLE_REDIRECT name=(301,test1.foo.com,https://goo.com/$1) code=(301) when=(http.host eq "test1.foo.com" and http.request.uri.path eq "/") then=(concat("https://goo.com", http.request.uri.path)) 4 | ;@ 1 IN CLOUDFLAREAPI_SINGLE_REDIRECT name=(302,test2.foo.com,https://goo.com/$1) code=(302) when=(http.host eq "test2.foo.com" and http.request.uri.path eq "/") then=(concat("https://goo.com", http.request.uri.path)) 5 | test1.foo.com.sub IN A 10.2.3.1 6 | test2.foo.com.sub IN A 10.2.3.2 7 | test3.foo.com.sub IN A 10.2.3.3 8 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/033-revextend.js: -------------------------------------------------------------------------------- 1 | // This tests D_EXTEND() in reverse and forward domains. 2 | var REGISTRAR = NewRegistrar("none", "NONE"); // No registrar. 3 | var BIND = NewDnsProvider("bind", "BIND"); 4 | 5 | // Delegating reverse zones 6 | D(REV("9.8.0.0/16"), REGISTRAR, 7 | DnsProvider(BIND), 8 | NS(REV("9.8.2.1"), "ns1.example.com."), 9 | ); 10 | D_EXTEND(REV("9.8.7.0/24"), 11 | NS(REV("9.8.7.6"), "ns2.example.org."), 12 | ); 13 | 14 | // Forward zone 15 | D("example.com", REGISTRAR, 16 | DnsProvider(BIND), 17 | NS("foo", "ns1.fooexample.com."), 18 | ); 19 | D_EXTEND("lego.example.com", 20 | NS("more", "ns1.example.com."), 21 | NS("short", "ns1"), 22 | ); 23 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/013-mx.json: -------------------------------------------------------------------------------- 1 | { 2 | "dns_providers": [], 3 | "domains": [ 4 | { 5 | "dnsProviders": {}, 6 | "meta": { 7 | "dnscontrol_nameraw": "foo.com", 8 | "dnscontrol_nameunicode": "foo.com", 9 | "dnscontrol_uniquename": "foo.com" 10 | }, 11 | "name": "foo.com", 12 | "records": [ 13 | { 14 | "filepos": "[line:2:5]", 15 | "mxpreference": 15, 16 | "name": "@", 17 | "target": "foo.com.", 18 | "ttl": 300, 19 | "type": "MX" 20 | } 21 | ], 22 | "registrar": "none", 23 | "uniquename": "foo.com" 24 | } 25 | ], 26 | "registrars": [] 27 | } 28 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/014-caa.js: -------------------------------------------------------------------------------- 1 | D("foo.com", "none", 2 | // Allow letsencrypt to issue certificate for this domain 3 | CAA("@", "issue", "letsencrypt.org"), 4 | // Allow no CA to issue wildcard certificate for this domain 5 | CAA("@", "issuewild", ";"), 6 | // Report all violation to test@example.com. If CA does not support 7 | // this record then refuse to issue any certificate 8 | CAA("@", "iodef", "mailto:test@example.com", CAA_CRITICAL), 9 | // Optionally report violation to http://example.com 10 | CAA("@", "iodef", "http://example.com"), 11 | // Report violation to https://example.com 12 | CAA("@", "iodef", "https://example.com", CAA_CRITICAL), 13 | ); 14 | -------------------------------------------------------------------------------- /providers/realtimeregister/auditrecords.go: -------------------------------------------------------------------------------- 1 | package realtimeregister 2 | 3 | import ( 4 | "github.com/StackExchange/dnscontrol/v4/models" 5 | "github.com/StackExchange/dnscontrol/v4/pkg/rejectif" 6 | ) 7 | 8 | // AuditRecords returns a list of errors corresponding to the records 9 | // that aren't supported by this provider. If all records are 10 | // supported, an empty list is returned. 11 | func AuditRecords(records []*models.RecordConfig) []error { 12 | auditor := rejectif.Auditor{} 13 | 14 | auditor.Add("TXT", rejectif.TxtHasTrailingSpace) // Last verified 2024-01-03 15 | 16 | auditor.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2024-01-03 17 | 18 | return auditor.Audit(records) 19 | } 20 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/022-sshfp.js: -------------------------------------------------------------------------------- 1 | D("foo.com", "none", 2 | SSHFP("@", 1, 1, "66c7d5540b7d75a1fb4c84febfa178ad99bdd67c"), 3 | SSHFP("@", 1, 2, "745a635bc46a397a5c4f21d437483005bcc40d7511ff15fbfafe913a081559bc"), 4 | SSHFP("@", 2, 1, "66c7d5540b7d75a1fb4c84febfa178ad99bdd67c"), 5 | SSHFP("@", 2, 2, "745a635bc46a397a5c4f21d437483005bcc40d7511ff15fbfafe913a081559bc"), 6 | SSHFP("@", 3, 1, "66c7d5540b7d75a1fb4c84febfa178ad99bdd67c"), 7 | SSHFP("@", 3, 2, "745a635bc46a397a5c4f21d437483005bcc40d7511ff15fbfafe913a081559bc"), 8 | SSHFP("@", 4, 1, "66c7d5540b7d75a1fb4c84febfa178ad99bdd67c"), 9 | SSHFP("@", 4, 2, "745a635bc46a397a5c4f21d437483005bcc40d7511ff15fbfafe913a081559bc"), 10 | ); 11 | -------------------------------------------------------------------------------- /providers/bunnydns/dnssec.go: -------------------------------------------------------------------------------- 1 | package bunnydns 2 | 3 | import "github.com/StackExchange/dnscontrol/v4/models" 4 | 5 | func (b *bunnydnsProvider) getDNSSECCorrections(dc *models.DomainConfig, zone *zone) ([]*models.Correction, error) { 6 | if zone.HasDNSSEC && dc.AutoDNSSEC == "off" { 7 | return []*models.Correction{ 8 | {Msg: "Disable DNSSEC", F: func() error { 9 | return b.disableDNSSEC(zone.ID) 10 | }}, 11 | }, nil 12 | } 13 | 14 | if !zone.HasDNSSEC && dc.AutoDNSSEC == "on" { 15 | return []*models.Correction{ 16 | {Msg: "Enable DNSSEC", F: func() error { 17 | return b.enableDNSSEC(zone.ID) 18 | }}, 19 | }, nil 20 | } 21 | 22 | return []*models.Correction{}, nil 23 | } 24 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | 3 | # Git itself 4 | .gitattributes eol=lf 5 | .gitignore eol=lf 6 | OWNERS eol=lf 7 | 8 | # dnscontrol files are unix line-endings: 9 | *.md text eol=lf 10 | *.js text eol=lf 11 | *.json text eol=lf 12 | *.yaml text eol=lf 13 | *.zone text eol=lf 14 | *.tsv text eol=lf 15 | 16 | # shell scripts are unix line-endings: 17 | *.sh text eol=lf 18 | 19 | # powershell scripts are DOS line-endings: 20 | *.ps1 text eol=crlf 21 | *.nuspec text eol=crlf 22 | 23 | # Go code are unix line-endings: 24 | *.go text eol=lf 25 | go.mod text eol=lf 26 | go.sum text eol=lf 27 | 28 | # Graphics are binary: 29 | *.jpg binary 30 | *.png 31 | *.svg 32 | 33 | pkg/cloudflare-go/.changelog text eol=crlf 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /providers/inwx/auditrecords.go: -------------------------------------------------------------------------------- 1 | package inwx 2 | 3 | import ( 4 | "github.com/StackExchange/dnscontrol/v4/models" 5 | "github.com/StackExchange/dnscontrol/v4/pkg/rejectif" 6 | ) 7 | 8 | // AuditRecords returns a list of errors corresponding to the records 9 | // that aren't supported by this provider. If all records are 10 | // supported, an empty list is returned. 11 | func AuditRecords(records []*models.RecordConfig) []error { 12 | a := rejectif.Auditor{} 13 | a.Add("TXT", rejectif.TxtHasBackticks) // Last verified 2021-03-01 14 | a.Add("TXT", rejectif.TxtHasTrailingSpace) // Last verified 2021-03-01 15 | a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2021-03-01 16 | return a.Audit(records) 17 | } 18 | -------------------------------------------------------------------------------- /commands/test_data/apex.com.zone.js: -------------------------------------------------------------------------------- 1 | // generated by get-zones. This is 'a decent first draft' and requires editing. 2 | 3 | var DSP_BIND = NewDnsProvider("bind", "BIND"); 4 | var REG_CHANGEME = NewRegistrar("none"); 5 | 6 | D("apex.com", REG_CHANGEME, 7 | DnsProvider(DSP_BIND), 8 | //SOA("@", "ns3.serverfault.com.", "sysadmin.stackoverflow.com.", 3600, 600, 604800, 1440), 9 | //NAMESERVER("ns-1313.awsdns-36.org."), 10 | //NAMESERVER("ns-736.awsdns-28.net."), 11 | //NAMESERVER("ns-cloud-c1.googledomains.com."), 12 | //NAMESERVER("ns-cloud-c2.googledomains.com."), 13 | // NOTE: CNAME at apex may require manual editing. 14 | CNAME("@", "cnametest1.example.com."), 15 | CNAME("www", "cnametest2.example.com."), 16 | ); 17 | 18 | -------------------------------------------------------------------------------- /documentation/language-reference/domain-modifiers/URL.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: URL 3 | parameters: 4 | - name 5 | - target 6 | - modifiers... 7 | parameter_types: 8 | name: string 9 | target: string 10 | "modifiers...": RecordModifier[] 11 | --- 12 | 13 | {% hint style="info" %} 14 | This is provider specific type of record and not a DNS standard. It may behave differently for each provider that handles it. 15 | {% endhint %} 16 | 17 | ### Namecheap 18 | 19 | This is a URL Redirect record with a type of "Unmasked", it creates a 302 redirect to the target. 20 | 21 | You can read more at the [Namecheap documentation](https://www.namecheap.com/support/knowledgebase/article.aspx/385/2237/how-to-set-up-a-url-redirect-for-a-domain/) 22 | -------------------------------------------------------------------------------- /providers/cnr/auditrecords.go: -------------------------------------------------------------------------------- 1 | package cnr 2 | 3 | import ( 4 | "github.com/StackExchange/dnscontrol/v4/models" 5 | "github.com/StackExchange/dnscontrol/v4/pkg/rejectif" 6 | ) 7 | 8 | // AuditRecords returns a list of errors corresponding to the records 9 | // that aren't supported by this provider. If all records are 10 | // supported, an empty list is returned. 11 | func AuditRecords(records []*models.RecordConfig) []error { 12 | a := rejectif.Auditor{} 13 | 14 | a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2021-10-01 15 | 16 | a.Add("TXT", rejectif.TxtHasDoubleQuotes) // Last verified 2023-11-30 17 | 18 | a.Add("SRV", rejectif.SrvHasNullTarget) // Last verified 2020-12-28 19 | 20 | return a.Audit(records) 21 | } 22 | -------------------------------------------------------------------------------- /commands/test_data/apex.com.zone.djs: -------------------------------------------------------------------------------- 1 | // generated by get-zones. This is 'a decent first draft' and requires editing. 2 | 3 | var DSP_BIND = NewDnsProvider("bind", "BIND"); 4 | var REG_CHANGEME = NewRegistrar("none"); 5 | 6 | D("apex.com", REG_CHANGEME 7 | , DnsProvider(DSP_BIND) 8 | //, SOA("@", "ns3.serverfault.com.", "sysadmin.stackoverflow.com.", 3600, 600, 604800, 1440) 9 | //, NAMESERVER("ns-1313.awsdns-36.org.") 10 | //, NAMESERVER("ns-736.awsdns-28.net.") 11 | //, NAMESERVER("ns-cloud-c1.googledomains.com.") 12 | //, NAMESERVER("ns-cloud-c2.googledomains.com.") 13 | // NOTE: CNAME at apex may require manual editing. 14 | , CNAME("@", "cnametest1.example.com.") 15 | , CNAME("www", "cnametest2.example.com.") 16 | ) 17 | 18 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/022-sshfp/foo.com.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | @ IN SSHFP 1 1 66C7D5540B7D75A1FB4C84FEBFA178AD99BDD67C 3 | IN SSHFP 1 2 745A635BC46A397A5C4F21D437483005BCC40D7511FF15FBFAFE913A081559BC 4 | IN SSHFP 2 1 66C7D5540B7D75A1FB4C84FEBFA178AD99BDD67C 5 | IN SSHFP 2 2 745A635BC46A397A5C4F21D437483005BCC40D7511FF15FBFAFE913A081559BC 6 | IN SSHFP 3 1 66C7D5540B7D75A1FB4C84FEBFA178AD99BDD67C 7 | IN SSHFP 3 2 745A635BC46A397A5C4F21D437483005BCC40D7511FF15FBFAFE913A081559BC 8 | IN SSHFP 4 1 66C7D5540B7D75A1FB4C84FEBFA178AD99BDD67C 9 | IN SSHFP 4 2 745A635BC46A397A5C4F21D437483005BCC40D7511FF15FBFAFE913A081559BC 10 | -------------------------------------------------------------------------------- /pkg/rejectif/ultimate.go: -------------------------------------------------------------------------------- 1 | package rejectif 2 | 3 | /* 4 | I proposed that Go add something like "len()" that returns the highest 5 | index. This would avoid off-by-one errors. The proposed names include 6 | ultimate(), ult(), high(), highest(). 7 | 8 | Nay-sayers said I should implement this as a function and see if I 9 | actually used it. (I suspect the nay-sayers are perfect people that 10 | never make off-by-one errors.) 11 | 12 | That's what this file is about. It should be exactly the same (except 13 | the first line) anywhere this is needed. After a few years I'll be 14 | able to report if it actually helped. 15 | 16 | Go will in-line this function. 17 | */ 18 | 19 | func ultimate(s string) int { 20 | return len(s) - 1 21 | } 22 | -------------------------------------------------------------------------------- /providers/oracle/auditrecords.go: -------------------------------------------------------------------------------- 1 | package oracle 2 | 3 | import ( 4 | "github.com/StackExchange/dnscontrol/v4/models" 5 | "github.com/StackExchange/dnscontrol/v4/pkg/rejectif" 6 | ) 7 | 8 | // AuditRecords returns a list of errors corresponding to the records 9 | // that aren't supported by this provider. If all records are 10 | // supported, an empty list is returned. 11 | func AuditRecords(records []*models.RecordConfig) []error { 12 | a := rejectif.Auditor{} 13 | 14 | a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2024-08-21 15 | a.Add("TXT", rejectif.TxtHasBackslash) // Last verified 2024-08-21 16 | a.Add("TXT", rejectif.TxtHasDoubleQuotes) // Last verified 2024-08-21 17 | 18 | return a.Audit(records) 19 | } 20 | -------------------------------------------------------------------------------- /documentation/language-reference/domain-modifiers/FRAME.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: FRAME 3 | parameters: 4 | - name 5 | - target 6 | - modifiers... 7 | parameter_types: 8 | name: string 9 | target: string 10 | "modifiers...": RecordModifier[] 11 | --- 12 | 13 | {% hint style="info" %} 14 | This is provider specific type of record and not a DNS standard. It may behave differently for each provider that handles it. 15 | {% endhint %} 16 | 17 | ### Namecheap 18 | 19 | This is a URL Redirect record with a type of "Masked", it creates a framed HTML page to the target. 20 | 21 | You can read more at the [Namecheap documentation](https://www.namecheap.com/support/knowledgebase/article.aspx/385/2237/how-to-set-up-a-url-redirect-for-a-domain/). 22 | -------------------------------------------------------------------------------- /documentation/language-reference/domain-modifiers/URL301.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: URL301 3 | parameters: 4 | - name 5 | - target 6 | - modifiers... 7 | parameter_types: 8 | name: string 9 | target: string 10 | "modifiers...": RecordModifier[] 11 | --- 12 | 13 | {% hint style="info" %} 14 | This is provider specific type of record and not a DNS standard. It may behave differently for each provider that handles it. 15 | {% endhint %} 16 | 17 | ### Namecheap 18 | 19 | This is a URL Redirect record with a type of "Permanent", it creates a 301 redirect to the target. 20 | 21 | You can read more at the [Namecheap documentation](https://www.namecheap.com/support/knowledgebase/article.aspx/385/2237/how-to-set-up-a-url-redirect-for-a-domain/). 22 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/054-b3487_d_extend_rev/6.10.in-addr.arpa.zone: -------------------------------------------------------------------------------- 1 | $TTL 300 2 | 31.104 IN PTR example.site.com. 3 | 206.104 IN PTR example2.site.com. 4 | 0.119 IN PTR ip-10-6-119-0.example.com. 5 | 1.119 IN PTR ip-10-6-119-1.example.com. 6 | 2.119 IN PTR ip-10-6-119-2.example.com. 7 | 3.119 IN PTR ip-10-6-119-3.example.com. 8 | 50.200 IN PTR ip-10-6-200-50.example.com. 9 | 51.200 IN PTR ip-10-6-200-51.example.com. 10 | 52.200 IN PTR ip-10-6-200-52.example.com. 11 | 53.200 IN PTR ip-10-6-200-53.example.com. 12 | 20.220 IN PTR ip-10-6-220-20.example.com. 13 | 30.230 IN PTR ip-10-6-230-30.example.com. 14 | -------------------------------------------------------------------------------- /providers/axfrddns/auditrecords.go: -------------------------------------------------------------------------------- 1 | package axfrddns 2 | 3 | import ( 4 | "github.com/StackExchange/dnscontrol/v4/models" 5 | "github.com/StackExchange/dnscontrol/v4/pkg/rejectif" 6 | ) 7 | 8 | // AuditRecords returns a list of errors corresponding to the records 9 | // that aren't supported by this provider. If all records are 10 | // supported, an empty list is returned. 11 | func AuditRecords(records []*models.RecordConfig) []error { 12 | a := rejectif.Auditor{} 13 | 14 | a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2025-01-01 15 | 16 | a.Add("TXT", rejectif.TxtHasBackslash) // Last verified 2025-01-01 17 | 18 | a.Add("TXT", rejectif.TxtHasDoubleQuotes) // Last verified 2025-01-01 19 | 20 | return a.Audit(records) 21 | } 22 | -------------------------------------------------------------------------------- /providers/hexonet/auditrecords.go: -------------------------------------------------------------------------------- 1 | package hexonet 2 | 3 | import ( 4 | "github.com/StackExchange/dnscontrol/v4/models" 5 | "github.com/StackExchange/dnscontrol/v4/pkg/rejectif" 6 | ) 7 | 8 | // AuditRecords returns a list of errors corresponding to the records 9 | // that aren't supported by this provider. If all records are 10 | // supported, an empty list is returned. 11 | func AuditRecords(records []*models.RecordConfig) []error { 12 | a := rejectif.Auditor{} 13 | 14 | a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2021-10-01 15 | 16 | a.Add("TXT", rejectif.TxtHasDoubleQuotes) // Last verified 2023-11-30 17 | 18 | a.Add("SRV", rejectif.SrvHasNullTarget) // Last verified 2020-12-28 19 | 20 | return a.Audit(records) 21 | } 22 | -------------------------------------------------------------------------------- /providers/huaweicloud/auditrecords.go: -------------------------------------------------------------------------------- 1 | package huaweicloud 2 | 3 | import ( 4 | "github.com/StackExchange/dnscontrol/v4/models" 5 | "github.com/StackExchange/dnscontrol/v4/pkg/rejectif" 6 | ) 7 | 8 | // AuditRecords returns a list of errors corresponding to the records 9 | // that aren't supported by this provider. If all records are 10 | // supported, an empty list is returned. 11 | func AuditRecords(records []*models.RecordConfig) []error { 12 | a := rejectif.Auditor{} 13 | a.Add("MX", rejectif.MxNull) // Last verified 2024-06-14 14 | a.Add("TXT", rejectif.TxtHasBackslash) // Last verified 2024-06-14 15 | a.Add("TXT", rejectif.TxtHasDoubleQuotes) // Last verified 2024-06-14 16 | 17 | return a.Audit(records) 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /tmp 3 | /commands/types/dnscontrol.d.ts 4 | dnscontrol-Darwin 5 | dnscontrol-Linux 6 | dnscontrol.exe 7 | dnscontrol 8 | /build/generate/generate 9 | /dnsconfig.js 10 | /creds.json 11 | ExternalDNS 12 | powershell.log 13 | integrationTest/env.sh 14 | integrationTest/.env 15 | /integrationTest/zones/*.zone 16 | /integrationTest/config/*.yaml 17 | /integrationTest/.hedns-session 18 | /pkg/js/parse_tests/*.ACTUAL 19 | env.sh 20 | certs/ 21 | spfcache* 22 | adzonedump.* 23 | /zones/ 24 | stack.sh 25 | .idea/ 26 | *.nupkg 27 | .DS_Store 28 | .vscode 29 | *.sw[a-p] 30 | .jekyll-cache 31 | types-dnscontrol.d.ts 32 | 33 | dist/ 34 | node_modules/ 35 | 36 | // Golang 37 | __debug_bin* 38 | 39 | // Test artifact: 40 | *.ACTUAL 41 | -------------------------------------------------------------------------------- /providers/mythicbeasts/auditrecords.go: -------------------------------------------------------------------------------- 1 | package mythicbeasts 2 | 3 | import ( 4 | "github.com/StackExchange/dnscontrol/v4/models" 5 | "github.com/StackExchange/dnscontrol/v4/pkg/rejectif" 6 | ) 7 | 8 | // AuditRecords returns a list of errors corresponding to the records 9 | // that aren't supported by this provider. If all records are 10 | // supported, an empty list is returned. 11 | func AuditRecords(records []*models.RecordConfig) []error { 12 | a := rejectif.Auditor{} 13 | 14 | a.Add("TXT", rejectif.TxtHasBackslash) // Last verified 2024-12-29 15 | 16 | a.Add("TXT", rejectif.TxtHasDoubleQuotes) // Last verified 2024-12-29 17 | 18 | a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2024-12-29 19 | 20 | return a.Audit(records) 21 | } 22 | -------------------------------------------------------------------------------- /providers/porkbun/auditrecords.go: -------------------------------------------------------------------------------- 1 | package porkbun 2 | 3 | import ( 4 | "github.com/StackExchange/dnscontrol/v4/models" 5 | "github.com/StackExchange/dnscontrol/v4/pkg/rejectif" 6 | ) 7 | 8 | // AuditRecords returns a list of errors corresponding to the records 9 | // that aren't supported by this provider. If all records are 10 | // supported, an empty list is returned. 11 | func AuditRecords(records []*models.RecordConfig) []error { 12 | a := rejectif.Auditor{} 13 | 14 | a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2022-11-19 15 | 16 | a.Add("TXT", rejectif.TxtHasTrailingSpace) // Last verified 2022-11-19 17 | 18 | a.Add("CAA", rejectif.CaaTargetContainsWhitespace) // Last verified 2024-11-11 19 | 20 | return a.Audit(records) 21 | } 22 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 21 | -------------------------------------------------------------------------------- /pkg/notifications/notifications_test.go: -------------------------------------------------------------------------------- 1 | package notifications 2 | 3 | import "testing" 4 | 5 | func Test_stripAnsiColorsValid(t *testing.T) { 6 | coloredStr := "\x1b[0133myellow\x1b[0m" // 33 == yellow 7 | nonColoredStr := "yellow" 8 | 9 | s := stripAnsiColors(coloredStr) 10 | if s != nonColoredStr { 11 | t.Errorf("stripAnsiColors() stripped %q different from %q", coloredStr, nonColoredStr) 12 | } 13 | } 14 | 15 | func Test_stripAnsiColorsInvalid(t *testing.T) { 16 | coloredStr := "\x1b[01AAmyellow\x1b[0m" // AA not a real color 17 | nonColoredStr := "yellow" 18 | 19 | s := stripAnsiColors(coloredStr) 20 | if s == nonColoredStr { 21 | t.Errorf("stripAnsiColors() stripped %q should be different from %q", coloredStr, nonColoredStr) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /pkg/soautil/soautil_test.go: -------------------------------------------------------------------------------- 1 | package soautil 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func Test_RFC5322MailToBind(t *testing.T) { 9 | tests := []struct { 10 | name string 11 | rfc5322Mail string 12 | bindMail string 13 | }{ 14 | {"0", "hostmaster@example.com", "hostmaster.example.com"}, 15 | {"1", "admin.dns@example.com", "admin\\.dns.example.com"}, 16 | {"2", "hostmaster@sub.example.com", "hostmaster.sub.example.com"}, 17 | } 18 | for _, tt := range tests { 19 | t.Run(tt.name, func(t *testing.T) { 20 | if got := RFC5322MailToBind(tt.rfc5322Mail); !reflect.DeepEqual(got, tt.bindMail) { 21 | t.Errorf("RFC5322MailToBind(%v) = %v, want %v", tt.rfc5322Mail, got, tt.bindMail) 22 | } 23 | }) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /pkg/rfc4183/ipv6.go: -------------------------------------------------------------------------------- 1 | package rfc4183 2 | 3 | import "errors" 4 | 5 | // reverseIPv6 returns the ipv6.arpa string suitable for reverse DNS lookups. 6 | func reverseIPv6(ip []byte, maskbits int) (arpa string, err error) { 7 | // Must be IPv6 8 | if len(ip) != 16 { 9 | return "", errors.New("not IPv6") 10 | } 11 | 12 | buf := []byte("x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.ip6.arpa") 13 | // Poke hex digits into the template 14 | pos := 128/4*2 - 2 // Position of the last "x" 15 | for _, v := range ip { 16 | buf[pos] = hexDigit[v>>4] 17 | buf[pos-2] = hexDigit[v&0xF] 18 | pos = pos - 4 19 | } 20 | // Return only the parts without x's 21 | return string(buf[(128-maskbits)/4*2:]), nil 22 | } 23 | 24 | const hexDigit = "0123456789abcdef" 25 | -------------------------------------------------------------------------------- /documentation/language-reference/domain-modifiers/DS.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: DS 3 | parameters: 4 | - name 5 | - keytag 6 | - algorithm 7 | - digesttype 8 | - digest 9 | - modifiers... 10 | parameter_types: 11 | name: string 12 | keytag: number 13 | algorithm: number 14 | digesttype: number 15 | digest: string 16 | "modifiers...": RecordModifier[] 17 | --- 18 | 19 | DS adds a DS record to the domain. 20 | 21 | Key Tag should be a number. 22 | 23 | Algorithm should be a number. 24 | 25 | Digest Type must be a number. 26 | 27 | Digest must be a string. 28 | 29 | {% code title="dnsconfig.js" %} 30 | ```javascript 31 | D("example.com", REG_MY_PROVIDER, DnsProvider(DSP_MY_PROVIDER), 32 | DS("example.com", 2371, 13, 2, "ABCDEF"), 33 | ); 34 | ``` 35 | {% endcode %} 36 | -------------------------------------------------------------------------------- /documentation/provider/ns1.md: -------------------------------------------------------------------------------- 1 | ## Configuration 2 | 3 | To use this provider, add an entry to `creds.json` with `TYPE` set to `NS1` 4 | along with your NS1 api key. 5 | 6 | Example: 7 | 8 | {% code title="creds.json" %} 9 | ```json 10 | { 11 | "ns1": { 12 | "TYPE": "NS1", 13 | "api_token": "your-ns1-token" 14 | } 15 | } 16 | ``` 17 | {% endcode %} 18 | 19 | ## Metadata 20 | This provider does not recognize any special metadata fields unique to NS1. 21 | 22 | ## Usage 23 | An example configuration: 24 | 25 | {% code title="dnsconfig.js" %} 26 | ```javascript 27 | var REG_NONE = NewRegistrar("none"); 28 | var DSP_NS1 = NewDnsProvider("ns1"); 29 | 30 | D("example.com", REG_NONE, DnsProvider(DSP_NS1), 31 | A("test", "1.2.3.4"), 32 | ); 33 | ``` 34 | {% endcode %} 35 | -------------------------------------------------------------------------------- /pkg/diff2/verb_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=Verb"; DO NOT EDIT. 2 | 3 | package diff2 4 | 5 | import "strconv" 6 | 7 | func _() { 8 | // An "invalid array index" compiler error signifies that the constant values have changed. 9 | // Re-run the stringer command to generate them again. 10 | var x [1]struct{} 11 | _ = x[CREATE-1] 12 | _ = x[CHANGE-2] 13 | _ = x[DELETE-3] 14 | _ = x[REPORT-4] 15 | } 16 | 17 | const _Verb_name = "CREATECHANGEDELETEREPORT" 18 | 19 | var _Verb_index = [...]uint8{0, 6, 12, 18, 24} 20 | 21 | func (i Verb) String() string { 22 | idx := int(i) - 1 23 | if i < 1 || idx >= len(_Verb_index)-1 { 24 | return "Verb(" + strconv.FormatInt(int64(i), 10) + ")" 25 | } 26 | return _Verb_name[_Verb_index[idx]:_Verb_index[idx+1]] 27 | } 28 | -------------------------------------------------------------------------------- /documentation/language-reference/domain-modifiers/DISABLE_IGNORE_SAFETY_CHECK.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: DISABLE_IGNORE_SAFETY_CHECK 3 | --- 4 | 5 | `DISABLE_IGNORE_SAFETY_CHECK()` disables the safety check. Normally it is an 6 | error to insert records that match an `IGNORE()` pattern. This disables that 7 | safety check for the entire domain. 8 | 9 | It replaces the per-record `IGNORE_NAME_DISABLE_SAFETY_CHECK()` which is 10 | deprecated as of DNSControl v4.0.0.0. 11 | 12 | See [`IGNORE()`](../domain-modifiers/IGNORE.md) for more information. 13 | 14 | ## Syntax 15 | 16 | ```javascript 17 | D("example.com", REG_MY_PROVIDER, DnsProvider(DSP_MY_PROVIDER), 18 | DISABLE_IGNORE_SAFETY_CHECK, 19 | ... 20 | TXT("myhost", "mytext"), 21 | IGNORE("myhost", "*", "*"), 22 | ... 23 | ``` 24 | -------------------------------------------------------------------------------- /documentation/language-reference/domain-modifiers/DNSKEY.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: DNSKEY 3 | parameters: 4 | - name 5 | - flags 6 | - protocol 7 | - algorithm 8 | - publicKey 9 | - modifiers... 10 | parameter_types: 11 | name: string 12 | flags: number 13 | protocol: number 14 | algorithm: number 15 | publicKey: string 16 | "modifiers...": RecordModifier[] 17 | --- 18 | 19 | DNSKEY adds a DNSKEY record to the domain. 20 | 21 | Flags should be a number. 22 | 23 | Protocol should be a number. 24 | 25 | Algorithm must be a number. 26 | 27 | Public key must be a string. 28 | 29 | {% code title="dnsconfig.js" %} 30 | ```javascript 31 | D("example.com", REG_MY_PROVIDER, DnsProvider(DSP_MY_PROVIDER), 32 | DNSKEY("@", 257, 3, 13, "AABBCCDD"), 33 | ); 34 | ``` 35 | {% endcode %} 36 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/019-r53-alias.js: -------------------------------------------------------------------------------- 1 | D("foo.com", "none", 2 | R53_ALIAS("mxtest", "MX", "foo.com."), 3 | R53_ALIAS("atest", "A", "foo.com."), 4 | R53_ALIAS("atest", "A", "foo.com.", R53_ZONE("Z2FTEDLFRTF")), 5 | R53_ALIAS("aevaltargethealthtest", "A", "foo.com.", R53_EVALUATE_TARGET_HEALTH(true)), 6 | R53_ALIAS("aaaatest", "AAAA", "foo.com."), 7 | R53_ALIAS("aaaatest", "AAAA", "foo.com.", R53_ZONE("ERERTFGFGF")), 8 | R53_ALIAS("cnametest", "CNAME", "foo.com."), 9 | R53_ALIAS("ptrtest", "PTR", "foo.com."), 10 | R53_ALIAS("txttest", "TXT", "foo.com."), 11 | R53_ALIAS("srvtest", "SRV", "foo.com."), 12 | R53_ALIAS("spftest", "SPF", "foo.com."), 13 | R53_ALIAS("caatest", "CAA", "foo.com."), 14 | R53_ALIAS("naptrtest", "NAPTR", "foo.com.") 15 | ); 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | NOTE: Have a general question? You'll get a better response on the [dnscontrol-discuss](https://groups.google.com/g/dnscontrol-discuss) email list! 11 | 12 | **Describe the bug** 13 | A clear and concise description of what the bug is. 14 | 15 | **To Reproduce** 16 | Steps to reproduce the behavior: 17 | 1. Go to '...' 18 | 2. Click on '....' 19 | 3. Scroll down to '....' 20 | 4. See error 21 | 22 | **Expected behavior** 23 | A clear and concise description of what you expected to happen. 24 | 25 | **DNS Provider** 26 | - FILL IN 27 | 28 | **Additional context** 29 | Add any other context about the problem here. 30 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/040-cfWorkerRoute.json: -------------------------------------------------------------------------------- 1 | { 2 | "dns_providers": [], 3 | "domains": [ 4 | { 5 | "dnsProviders": {}, 6 | "meta": { 7 | "dnscontrol_nameraw": "foo.com", 8 | "dnscontrol_nameunicode": "foo.com", 9 | "dnscontrol_uniquename": "foo.com" 10 | }, 11 | "name": "foo.com", 12 | "records": [ 13 | { 14 | "filepos": "[line:2:5]", 15 | "meta": { 16 | "orig_custom_type": "CF_WORKER_ROUTE" 17 | }, 18 | "name": "@", 19 | "target": "test.foo.com,test-worker", 20 | "ttl": 300, 21 | "type": "CF_WORKER_ROUTE" 22 | } 23 | ], 24 | "registrar": "none", 25 | "uniquename": "foo.com" 26 | } 27 | ], 28 | "registrars": [] 29 | } 30 | -------------------------------------------------------------------------------- /documentation/language-reference/domain-modifiers/ADGUARDHOME_A_PASSTHROUGH.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: ADGUARDHOME_A_PASSTHROUGH 3 | parameters: 4 | - source 5 | - destination 6 | provider: ADGUARDHOME 7 | parameter_types: 8 | source: string 9 | destination: string 10 | --- 11 | 12 | `ADGUARDHOME_A_PASSTHROUGH` represents the literal 'A'. AdGuardHome uses this to passthrough 13 | the original values of a record type. 14 | 15 | The second argument to this record type must be empty. 16 | 17 | See [this](https://github.com/AdguardTeam/Adguardhome/wiki/Configuration) page for 18 | more information. 19 | 20 | {% code title="dnsconfig.js" %} 21 | ```javascript 22 | D("example.com", REG_MY_PROVIDER, DnsProvider(DSP_MY_PROVIDER), 23 | ADGUARDHOME_A_PASSTHROUGH("foo", ""), 24 | ); 25 | ``` 26 | {% endcode %} 27 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/005-ignored-records.js: -------------------------------------------------------------------------------- 1 | D("foo.com", "none", 2 | IGNORE_NAME("testignore"), 3 | IGNORE_NAME("testignore2", "A"), 4 | IGNORE_NAME("testignore3", "A, CNAME, TXT"), 5 | IGNORE_NAME("testignore4", "*"), 6 | IGNORE_TARGET("testtarget", "CNAME"), 7 | IGNORE("legacyignore"), 8 | IGNORE_NAME("@"), 9 | IGNORE_TARGET("@", "CNAME"), 10 | ); 11 | 12 | D("diff2.com", "none", 13 | IGNORE("mylabel"), 14 | IGNORE("mylabel2", ""), 15 | IGNORE("mylabel3", "", ""), 16 | IGNORE("", "A"), 17 | IGNORE("", "A,AAAA"), 18 | IGNORE("", "", "mytarget"), 19 | IGNORE("labelc", "CNAME", "targetc"), 20 | // Compatibility mode: 21 | IGNORE_NAME("nametest"), 22 | IGNORE_TARGET("targettest1"), 23 | IGNORE_TARGET("targettest2", "A"), 24 | ); 25 | -------------------------------------------------------------------------------- /commands/ultimate.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import "github.com/StackExchange/dnscontrol/v4/models" 4 | 5 | /* 6 | I proposed that Go add something like "len()" that returns the highest 7 | index. This would avoid off-by-one errors. The proposed names include 8 | ultimate(), ult(), high(), highest(). 9 | 10 | Nay-sayers said I should implement this as a function and see if I 11 | actually used it. (I suspect the nay-sayers are perfect people that 12 | never make off-by-one errors.) 13 | 14 | That's what this file is about. It should be exactly the same (except 15 | the first line) anywhere this is needed. After a few years I'll be 16 | able to report if it actually helped. 17 | 18 | Go will in-line this function. 19 | */ 20 | 21 | func ultimate(s []*models.DomainConfig) int { 22 | return len(s) - 1 23 | } 24 | -------------------------------------------------------------------------------- /documentation/language-reference/domain-modifiers/ADGUARDHOME_AAAA_PASSTHROUGH.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: ADGUARDHOME_AAAA_PASSTHROUGH 3 | parameters: 4 | - source 5 | - destination 6 | provider: ADGUARDHOME 7 | parameter_types: 8 | source: string 9 | destination: string 10 | --- 11 | 12 | `ADGUARDHOME_AAAA_PASSTHROUGH` represents the literal 'A'. AdGuardHome uses this to passthrough 13 | the original values of a record type. 14 | 15 | The second argument to this record type must be empty. 16 | 17 | See [this](https://github.com/AdguardTeam/Adguardhome/wiki/Configuration) page for 18 | more information. 19 | 20 | {% code title="dnsconfig.js" %} 21 | ```javascript 22 | D("example.com", REG_MY_PROVIDER, DnsProvider(DSP_MY_PROVIDER), 23 | ADGUARDHOME_AAAA_PASSTHROUGH("foo", ""), 24 | ); 25 | ``` 26 | {% endcode %} 27 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/048-DNSKEY.json: -------------------------------------------------------------------------------- 1 | { 2 | "dns_providers": [], 3 | "domains": [ 4 | { 5 | "dnsProviders": {}, 6 | "meta": { 7 | "dnscontrol_nameraw": "foo.com", 8 | "dnscontrol_nameunicode": "foo.com", 9 | "dnscontrol_uniquename": "foo.com" 10 | }, 11 | "name": "foo.com", 12 | "records": [ 13 | { 14 | "dnskeyalgorithm": 13, 15 | "dnskeyflags": 257, 16 | "dnskeyprotocol": 3, 17 | "dnskeypublickey": "AABBCCDD", 18 | "filepos": "[line:2:5]", 19 | "name": "@", 20 | "target": "", 21 | "ttl": 300, 22 | "type": "DNSKEY" 23 | } 24 | ], 25 | "registrar": "none", 26 | "uniquename": "foo.com" 27 | } 28 | ], 29 | "registrars": [] 30 | } 31 | -------------------------------------------------------------------------------- /providers/dnsimple/auditrecords.go: -------------------------------------------------------------------------------- 1 | package dnsimple 2 | 3 | import ( 4 | "github.com/StackExchange/dnscontrol/v4/models" 5 | "github.com/StackExchange/dnscontrol/v4/pkg/rejectif" 6 | ) 7 | 8 | // AuditRecords returns a list of errors corresponding to the records 9 | // that aren't supported by this provider. If all records are 10 | // supported, an empty list is returned. 11 | func AuditRecords(records []*models.RecordConfig) []error { 12 | a := rejectif.Auditor{} 13 | 14 | a.Add("TXT", rejectif.TxtLongerThan(1000)) // Last verified 2023-12 15 | 16 | a.Add("TXT", rejectif.TxtHasTrailingSpace) // Last verified 2023-03 17 | 18 | a.Add("TXT", rejectif.TxtHasUnpairedDoubleQuotes) // Last verified 2023-03 19 | 20 | a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2023-03 21 | 22 | return a.Audit(records) 23 | } 24 | -------------------------------------------------------------------------------- /documentation/advanced-features/dual-host.md: -------------------------------------------------------------------------------- 1 | # Dual Host 2 | 3 | The dual hosting feature of DNSControl provides 4 | for the ability to use multiple DNS providers simultaneously. 5 | Consult your provider docs to ensure that **both** of them support this feature. 6 | 7 | ✅ - A checkmark means "this has been tested, and the provider works with dual hosting". 8 | ❔ - The questionmark means "it hasn't been tested, safety unknown" 9 | ❌ - The red "X" means "this has been tested, and it does _not_ work currently". 10 | 11 | ## Source reference 12 | 13 | [The source](https://github.com/StackExchange/dnscontrol/blob/cdbd54016f93140548d846842b0d7575603069c8/providers/capabilities.go#L93) 14 | states that this flag 15 | 16 | > provider allows full management of apex NS records, so we can safely dual-host with another provider 17 | -------------------------------------------------------------------------------- /documentation/language-reference/domain-modifiers/TLSA.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: TLSA 3 | parameters: 4 | - name 5 | - usage 6 | - selector 7 | - type 8 | - certificate 9 | - modifiers... 10 | parameter_types: 11 | name: string 12 | usage: number 13 | selector: number 14 | type: number 15 | certificate: string 16 | "modifiers...": RecordModifier[] 17 | --- 18 | 19 | `TLSA` adds a `TLSA` record to a domain. The name should be the relative label for the record. 20 | 21 | Usage, selector, and type are ints. 22 | 23 | Certificate is a hex string. 24 | 25 | {% code title="dnsconfig.js" %} 26 | ```javascript 27 | D("example.com", REG_MY_PROVIDER, DnsProvider(DSP_MY_PROVIDER), 28 | // Create TLSA record for certificate used on TCP port 443 29 | TLSA("_443._tcp", 3, 1, 1, "abcdef0"), 30 | ); 31 | ``` 32 | {% endcode %} 33 | -------------------------------------------------------------------------------- /providers/exoscale/auditrecords.go: -------------------------------------------------------------------------------- 1 | package exoscale 2 | 3 | import ( 4 | "github.com/StackExchange/dnscontrol/v4/models" 5 | "github.com/StackExchange/dnscontrol/v4/pkg/rejectif" 6 | ) 7 | 8 | // AuditRecords returns a list of errors corresponding to the records 9 | // that aren't supported by this provider. If all records are 10 | // supported, an empty list is returned. 11 | func AuditRecords(records []*models.RecordConfig) []error { 12 | a := rejectif.Auditor{} 13 | 14 | a.Add("CAA", rejectif.CaaTargetContainsWhitespace) // Last verified 2022-07-11 15 | 16 | a.Add("MX", rejectif.MxNull) // Last verified 2022-07-11 17 | 18 | a.Add("SRV", rejectif.SrvHasNullTarget) // Last verified 2020-12-28 19 | 20 | a.Add("TXT", rejectif.TxtHasUnpairedDoubleQuotes) // Last verified 2022-09-14 21 | 22 | return a.Audit(records) 23 | } 24 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/003-meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "dns_providers": [], 3 | "domains": [ 4 | { 5 | "dnsProviders": {}, 6 | "meta": { 7 | "dnscontrol_nameraw": "foo.com", 8 | "dnscontrol_nameunicode": "foo.com", 9 | "dnscontrol_uniquename": "foo.com" 10 | }, 11 | "name": "foo.com", 12 | "records": [ 13 | { 14 | "filepos": "[line:4:5]", 15 | "meta": { 16 | "cloudflare_proxy": "ON" 17 | }, 18 | "name": "@", 19 | "target": "1.2.3.4", 20 | "ttl": 300, 21 | "type": "A" 22 | } 23 | ], 24 | "registrar": "Cloudflare", 25 | "uniquename": "foo.com" 26 | } 27 | ], 28 | "registrars": [ 29 | { 30 | "name": "Cloudflare", 31 | "type": "CLOUDFLAREAPI" 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/038-soa.json: -------------------------------------------------------------------------------- 1 | { 2 | "dns_providers": [], 3 | "domains": [ 4 | { 5 | "dnsProviders": {}, 6 | "meta": { 7 | "dnscontrol_nameraw": "foo.com", 8 | "dnscontrol_nameunicode": "foo.com", 9 | "dnscontrol_uniquename": "foo.com" 10 | }, 11 | "name": "foo.com", 12 | "records": [ 13 | { 14 | "filepos": "[line:2:5]", 15 | "name": "@", 16 | "soaexpire": 604800, 17 | "soambox": "admin.foo.com", 18 | "soaminttl": 86400, 19 | "soarefresh": 3600, 20 | "soaretry": 900, 21 | "target": "ns1.foo.com.", 22 | "ttl": 300, 23 | "type": "SOA" 24 | } 25 | ], 26 | "registrar": "none", 27 | "uniquename": "foo.com" 28 | } 29 | ], 30 | "registrars": [] 31 | } 32 | -------------------------------------------------------------------------------- /providers/bunnydns/listzones.go: -------------------------------------------------------------------------------- 1 | package bunnydns 2 | 3 | import "github.com/StackExchange/dnscontrol/v4/pkg/printer" 4 | 5 | func (b *bunnydnsProvider) ListZones() ([]string, error) { 6 | zones, err := b.getAllZones() 7 | if err != nil { 8 | return nil, err 9 | } 10 | 11 | zoneNames := make([]string, 0, len(zones)) 12 | for _, zone := range zones { 13 | zoneNames = append(zoneNames, zone.Domain) 14 | } 15 | 16 | return zoneNames, nil 17 | } 18 | 19 | func (b *bunnydnsProvider) EnsureZoneExists(domain string, metadata map[string]string) error { 20 | _, err := b.findZoneByDomain(domain) 21 | if err == nil { 22 | return nil 23 | } 24 | 25 | zone, err := b.createZone(domain) 26 | if err != nil { 27 | return err 28 | } 29 | 30 | printer.Warnf("BUNNY_DNS: Added zone %s with ID %d", domain, zone.ID) 31 | return nil 32 | } 33 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | DNSControl is a command-line tool and therefore has a different (limited) attack surface as compared to a web app or other system. 4 | 5 | ## Supported Versions 6 | 7 | Only the most recent release is supported with security updates. 8 | 9 | When a major version is incremented, we'll support the previous major version for 6 months. For example, when v4.0 is released, we will support the most recent v3.x release for 6 months. 10 | 11 | ## Reporting a Vulnerability 12 | 13 | To report a vulnerability please [create a new GitHub "issue"](https://github.com/StackExchange/dnscontrol/issues/new/choose). 14 | 15 | We will respond in a best-effort manner, usually within 1 week. We will communciate via the GitHub issue unless we need to communicate privately, in which case we'll arrange a way to communicate directly. 16 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/057-smimea.json: -------------------------------------------------------------------------------- 1 | { 2 | "dns_providers": [], 3 | "domains": [ 4 | { 5 | "dnsProviders": {}, 6 | "meta": { 7 | "dnscontrol_nameraw": "foo.com", 8 | "dnscontrol_nameunicode": "foo.com", 9 | "dnscontrol_uniquename": "foo.com" 10 | }, 11 | "name": "foo.com", 12 | "records": [ 13 | { 14 | "filepos": "[line:2:5]", 15 | "name": "f10e7de079689f55c0cdd6782e4dd1448c84006962a4bd832e8eff73._smimecert", 16 | "smimeausage": 3, 17 | "target": "mdfiytq3mtljodbinmzlotexyja5mwe3yza1mti0yjy0zwvly2u5njrlmdljmdu4zwy4zjk4mdvkywnhntq2yiaglqo=", 18 | "ttl": 300, 19 | "type": "SMIMEA" 20 | } 21 | ], 22 | "registrar": "none", 23 | "uniquename": "foo.com" 24 | } 25 | ], 26 | "registrars": [] 27 | } 28 | -------------------------------------------------------------------------------- /documentation/language-reference/top-level-functions/IP.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: IP 3 | parameters: 4 | - ip 5 | parameter_types: 6 | ip: string 7 | return: number 8 | --- 9 | 10 | Converts an IPv4 address from string to an integer. This allows performing mathematical operations with the IP address. 11 | 12 | {% code title="dnsconfig.js" %} 13 | ```javascript 14 | var addrA = IP("1.2.3.4") 15 | var addrB = addrA + 1 16 | // addrB = 1.2.3.5 17 | ``` 18 | {% endcode %} 19 | 20 | {% hint style="info" %} 21 | **NOTE**: `IP()` does not accept IPv6 addresses (PRs gladly accepted!). IPv6 addresses are simply strings: 22 | {% endhint %} 23 | 24 | {% code title="dnsconfig.js" %} 25 | ```javascript 26 | // IPv4 Var 27 | var addrA1 = IP("1.2.3.4"); 28 | var addrA2 = "1.2.3.4"; 29 | 30 | // IPv6 Var 31 | var addrAAAA = "0:0:0:0:0:0:0:0"; 32 | ``` 33 | {% endcode %} 34 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/015-tlsa.json: -------------------------------------------------------------------------------- 1 | { 2 | "dns_providers": [], 3 | "domains": [ 4 | { 5 | "dnsProviders": {}, 6 | "meta": { 7 | "dnscontrol_nameraw": "foo.com", 8 | "dnscontrol_nameunicode": "foo.com", 9 | "dnscontrol_uniquename": "foo.com" 10 | }, 11 | "name": "foo.com", 12 | "records": [ 13 | { 14 | "filepos": "[line:2:5]", 15 | "name": "_443._tcp", 16 | "target": "mdfiytq3mtljodbinmzlotexyja5mwe3yza1mti0yjy0zwvly2u5njrlmdljmdu4zwy4zjk4mdvkywnhntq2yiaglqo=", 17 | "tlsamatchingtype": 1, 18 | "tlsaselector": 1, 19 | "tlsausage": 3, 20 | "ttl": 300, 21 | "type": "TLSA" 22 | } 23 | ], 24 | "registrar": "none", 25 | "uniquename": "foo.com" 26 | } 27 | ], 28 | "registrars": [] 29 | } 30 | -------------------------------------------------------------------------------- /providers/vultr/auditrecords.go: -------------------------------------------------------------------------------- 1 | package vultr 2 | 3 | import ( 4 | "github.com/StackExchange/dnscontrol/v4/models" 5 | "github.com/StackExchange/dnscontrol/v4/pkg/rejectif" 6 | ) 7 | 8 | // AuditRecords returns a list of errors corresponding to the records 9 | // that aren't supported by this provider. If all records are 10 | // supported, an empty list is returned. 11 | func AuditRecords(records []*models.RecordConfig) []error { 12 | a := rejectif.Auditor{} 13 | 14 | a.Add("MX", rejectif.MxNull) // Last verified 2020-12-28 15 | 16 | a.Add("TXT", rejectif.TxtHasDoubleQuotes) // Last verified 2021-03-02 17 | // Needs investigation. Could be a dnscontrol issue or 18 | // the provider doesn't support double quotes. 19 | 20 | a.Add("CAA", rejectif.CaaTargetContainsWhitespace) // Last verified 2023-01-19 21 | 22 | return a.Audit(records) 23 | } 24 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/059-rawttls.js: -------------------------------------------------------------------------------- 1 | D("example.com", "none", 2 | 3 | TXT("mytxt", "Do not call me on my phone"), 4 | 5 | // Test at the apex two ways: 6 | RP("@", "user.example.com.", "mytxt.example.com."), 7 | RP("example.com.", "user2.example.com.", "mytxt.example.com."), 8 | 9 | // Test the default TTL 10 | RP("aaa300", "user.example.com.", "mytxt.example.com."), 11 | 12 | // Test DefaultTTL() 13 | DefaultTTL(1111), 14 | RP("bbb1", "user.example.com.", "mytxt.example.com."), 15 | 16 | // Test TTL() 17 | RP("ccc2", "user.example.com.", "mytxt.example.com.", TTL(2222)), 18 | 19 | // Test the default TTL 20 | RP("ddd1", "user.example.com.", "mytxt.example.com."), 21 | 22 | // Test a second DefaultTTL() 23 | DefaultTTL(3333), 24 | RP("eee3", "user.example.com.", "mytxt.example.com."), 25 | ); 26 | -------------------------------------------------------------------------------- /documentation/language-reference/domain-modifiers/MX.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: MX 3 | parameters: 4 | - name 5 | - priority 6 | - target 7 | - modifiers... 8 | parameter_types: 9 | name: string 10 | priority: number 11 | target: string 12 | "modifiers...": RecordModifier[] 13 | --- 14 | 15 | MX adds an MX record to the domain. 16 | 17 | Priority should be a number. 18 | 19 | Target should be a string representing the MX target. If it is a single label we will assume it is a relative name on the current domain. If it contains *any* dots, it should be a fully qualified domain name, ending with a `.`. 20 | 21 | {% code title="dnsconfig.js" %} 22 | ```javascript 23 | D("example.com", REG_MY_PROVIDER, DnsProvider(DSP_MY_PROVIDER), 24 | MX("@", 5, "mail"), // mx example.com -> mail.example.com 25 | MX("sub", 10, "mail.foo.com."), 26 | ); 27 | ``` 28 | {% endcode %} 29 | -------------------------------------------------------------------------------- /documentation/provider/vultr.md: -------------------------------------------------------------------------------- 1 | ## Configuration 2 | 3 | To use this provider, add an entry to `creds.json` with `TYPE` set to `VULTR` 4 | along with a Vultr personal access token. 5 | 6 | Example: 7 | 8 | {% code title="creds.json" %} 9 | ```json 10 | { 11 | "vultr": { 12 | "TYPE": "VULTR", 13 | "token": "your-vultr-personal-access-token" 14 | } 15 | } 16 | ``` 17 | {% endcode %} 18 | 19 | ## Metadata 20 | 21 | This provider does not recognize any special metadata fields unique to Vultr. 22 | 23 | ## Usage 24 | 25 | An example configuration: 26 | 27 | {% code title="dnsconfig.js" %} 28 | ```javascript 29 | var DSP_VULTR = NewDnsProvider("vultr"); 30 | 31 | D("example.com", REG_DNSIMPLE, DnsProvider(DSP_VULTR), 32 | A("test", "1.2.3.4"), 33 | ); 34 | ``` 35 | {% endcode %} 36 | 37 | ## Activation 38 | 39 | Vultr depends on a Vultr personal access token. 40 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/034-nameserver-ttl.json: -------------------------------------------------------------------------------- 1 | { 2 | "dns_providers": [], 3 | "domains": [ 4 | { 5 | "dnsProviders": {}, 6 | "meta": { 7 | "dnscontrol_nameraw": "foo.com", 8 | "dnscontrol_nameunicode": "foo.com", 9 | "dnscontrol_uniquename": "foo.com", 10 | "ns_ttl": "86400" 11 | }, 12 | "name": "foo.com", 13 | "records": [], 14 | "registrar": "none", 15 | "uniquename": "foo.com" 16 | }, 17 | { 18 | "dnsProviders": {}, 19 | "meta": { 20 | "dnscontrol_nameraw": "bar.com", 21 | "dnscontrol_nameunicode": "bar.com", 22 | "dnscontrol_uniquename": "bar.com", 23 | "ns_ttl": "300" 24 | }, 25 | "name": "bar.com", 26 | "records": [], 27 | "registrar": "none", 28 | "uniquename": "bar.com" 29 | } 30 | ], 31 | "registrars": [] 32 | } 33 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/043-safety.json: -------------------------------------------------------------------------------- 1 | { 2 | "dns_providers": [], 3 | "domains": [ 4 | { 5 | "dnsProviders": {}, 6 | "meta": { 7 | "dnscontrol_nameraw": "unsafe.com", 8 | "dnscontrol_nameunicode": "unsafe.com", 9 | "dnscontrol_uniquename": "unsafe.com" 10 | }, 11 | "name": "unsafe.com", 12 | "records": [], 13 | "registrar": "none", 14 | "uniquename": "unsafe.com", 15 | "unmanaged_disable_safety_check": true 16 | }, 17 | { 18 | "dnsProviders": {}, 19 | "meta": { 20 | "dnscontrol_nameraw": "safe.com", 21 | "dnscontrol_nameunicode": "safe.com", 22 | "dnscontrol_uniquename": "safe.com" 23 | }, 24 | "name": "safe.com", 25 | "records": [], 26 | "registrar": "none", 27 | "uniquename": "safe.com" 28 | } 29 | ], 30 | "registrars": [] 31 | } 32 | -------------------------------------------------------------------------------- /.github/workflows/pr_check_git_status.yml: -------------------------------------------------------------------------------- 1 | name: "PR: Check git status" 2 | on: 3 | push: 4 | branches: 5 | - 'tlim_testpr' 6 | pull_request: 7 | workflow_dispatch: 8 | 9 | jobs: 10 | check-git-status: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v6 14 | with: 15 | repository: ${{ github.event.pull_request.head.repo.full_name }} 16 | ref: ${{ github.event.pull_request.head.ref }} 17 | - uses: actions/setup-go@v6 18 | with: 19 | go-version: stable 20 | - run: go install golang.org/x/tools/cmd/stringer@latest 21 | - run: go fmt ./... 22 | - run: bin/fmtjson $(find . -type f -name "*.json" ! -name "package-lock.json" -print) 23 | - run: go mod tidy 24 | - run: go generate ./... 25 | - uses: CatChen/check-git-status-action@v1 26 | with: 27 | fail-if-not-clean: true 28 | -------------------------------------------------------------------------------- /documentation/language-reference/domain-modifiers/SRV.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: SRV 3 | parameters: 4 | - name 5 | - priority 6 | - weight 7 | - port 8 | - target 9 | - modifiers... 10 | parameter_types: 11 | name: string 12 | priority: number 13 | weight: number 14 | port: number 15 | target: string 16 | "modifiers...": RecordModifier[] 17 | --- 18 | 19 | `SRV` adds a `SRV` record to a domain. The name should be the relative label for the record. 20 | 21 | Priority, weight, and port are ints. 22 | 23 | {% code title="dnsconfig.js" %} 24 | ```javascript 25 | D("example.com", REG_MY_PROVIDER, DnsProvider(DSP_MY_PROVIDER), 26 | // Create SRV records for a a SIP service: 27 | // pr w port, target 28 | SRV("_sip._tcp", 10, 60, 5060, "bigbox.example.com."), 29 | SRV("_sip._tcp", 10, 20, 5060, "smallbox1.example.com."), 30 | ); 31 | ``` 32 | {% endcode %} 33 | -------------------------------------------------------------------------------- /pkg/txtutil/state_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=State"; DO NOT EDIT. 2 | 3 | package txtutil 4 | 5 | import "strconv" 6 | 7 | func _() { 8 | // An "invalid array index" compiler error signifies that the constant values have changed. 9 | // Re-run the stringer command to generate them again. 10 | var x [1]struct{} 11 | _ = x[StateStart-0] 12 | _ = x[StateUnquoted-1] 13 | _ = x[StateQuoted-2] 14 | _ = x[StateBackslash-3] 15 | _ = x[StateWantSpace-4] 16 | } 17 | 18 | const _State_name = "StateStartStateUnquotedStateQuotedStateBackslashStateWantSpace" 19 | 20 | var _State_index = [...]uint8{0, 10, 23, 34, 48, 62} 21 | 22 | func (i State) String() string { 23 | idx := int(i) - 0 24 | if i < 0 || idx >= len(_State_index)-1 { 25 | return "State(" + strconv.FormatInt(int64(i), 10) + ")" 26 | } 27 | return _State_name[_State_index[idx]:_State_index[idx+1]] 28 | } 29 | -------------------------------------------------------------------------------- /commands/types/base-types.d.ts: -------------------------------------------------------------------------------- 1 | interface Domain { 2 | name: string; 3 | subdomain: string; 4 | registrar: unknown; 5 | meta: Record; 6 | records: DNSRecord[]; 7 | dnsProviders: Record; 8 | defaultTTL: number; 9 | nameservers: unknown[]; 10 | ignored_names: unknown[]; 11 | ignored_targets: unknown[]; 12 | [key: string]: unknown; 13 | } 14 | 15 | interface DNSRecord { 16 | type: string; 17 | meta: Record; 18 | ttl: number; 19 | } 20 | 21 | type DomainModifier = 22 | | ((domain: Domain) => void) 23 | | Partial 24 | | DomainModifier[]; 25 | 26 | type RecordModifier = 27 | | ((record: DNSRecord) => void) 28 | | Partial; 29 | 30 | type Duration = 31 | | `${number}${'s' | 'm' | 'h' | 'd' | 'w' | 'n' | 'y' | ''}` 32 | | number /* seconds */; 33 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/001-basic.json: -------------------------------------------------------------------------------- 1 | { 2 | "dns_providers": [ 3 | { 4 | "name": "Cloudflare", 5 | "type": "CLOUDFLAREAPI" 6 | } 7 | ], 8 | "domains": [ 9 | { 10 | "dnsProviders": { 11 | "Cloudflare": -1 12 | }, 13 | "meta": { 14 | "dnscontrol_nameraw": "foo.com", 15 | "dnscontrol_nameunicode": "foo.com", 16 | "dnscontrol_uniquename": "foo.com" 17 | }, 18 | "name": "foo.com", 19 | "records": [ 20 | { 21 | "filepos": "[line:5:5]", 22 | "name": "@", 23 | "target": "1.2.3.4", 24 | "ttl": 300, 25 | "type": "A" 26 | } 27 | ], 28 | "registrar": "Third-Party", 29 | "uniquename": "foo.com" 30 | } 31 | ], 32 | "registrars": [ 33 | { 34 | "name": "Third-Party", 35 | "type": "NONE" 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/002-ttl.json: -------------------------------------------------------------------------------- 1 | { 2 | "dns_providers": [ 3 | { 4 | "name": "Cloudflare", 5 | "type": "CLOUDFLAREAPI" 6 | } 7 | ], 8 | "domains": [ 9 | { 10 | "dnsProviders": { 11 | "Cloudflare": -1 12 | }, 13 | "meta": { 14 | "dnscontrol_nameraw": "foo.com", 15 | "dnscontrol_nameunicode": "foo.com", 16 | "dnscontrol_uniquename": "foo.com" 17 | }, 18 | "name": "foo.com", 19 | "records": [ 20 | { 21 | "filepos": "[line:5:5]", 22 | "name": "@", 23 | "target": "1.2.3.4", 24 | "ttl": 42, 25 | "type": "A" 26 | } 27 | ], 28 | "registrar": "Third-Party", 29 | "uniquename": "foo.com" 30 | } 31 | ], 32 | "registrars": [ 33 | { 34 | "name": "Third-Party", 35 | "type": "NONE" 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/041-newstyleproviders.js: -------------------------------------------------------------------------------- 1 | // Test old-style and new-style New*() functions. 2 | 3 | var REG1 = NewRegistrar("foo1"); 4 | var CF1 = NewDnsProvider("dns1"); 5 | 6 | var REG2a = NewRegistrar("foo2a", "NONE"); 7 | var CF2a = NewDnsProvider("dns2a", "CLOUDFLAREAPI"); 8 | 9 | var REG2b = NewRegistrar("foo2b", { 10 | regmetakey: "reg2b" 11 | }); 12 | var CF2b = NewDnsProvider("dns2b", { 13 | dnsmetakey: "dns2b" 14 | }); 15 | 16 | var REG3 = NewRegistrar("foo3", "MANUAL", { 17 | regmetakey: "reg3" 18 | }); 19 | var CF3 = NewDnsProvider("dns3", "CLOUDFLAREAPI", { 20 | dnsmetakey: "dns3" 21 | }); 22 | 23 | var REG1h = NewRegistrar("foo1h", "-"); 24 | var CF1h = NewDnsProvider("dns1h", "-"); 25 | 26 | var REG2bh = NewRegistrar("foo2bh", "-", { 27 | regmetakey: "reg2bh" 28 | }); 29 | var CF2bh = NewDnsProvider("dns2bh", "-", { 30 | dnsmetakey: "dns2bh" 31 | }); 32 | -------------------------------------------------------------------------------- /providers/doh/api.go: -------------------------------------------------------------------------------- 1 | package doh 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | 7 | "github.com/babolivier/go-doh-client" 8 | ) 9 | 10 | type dohProvider struct { 11 | host string 12 | } 13 | 14 | func (c *dohProvider) getNameservers(domain string) ([]string, error) { 15 | resolver := doh.Resolver{ 16 | Host: c.host, 17 | Class: doh.IN, 18 | } 19 | 20 | // Perform a NS lookup 21 | nss, _, err := resolver.LookupNS(domain) 22 | if err != nil { 23 | return nil, fmt.Errorf("failed fetching nameservers list (DNS-over-HTTPS): %w", err) 24 | } 25 | 26 | ns := []string{} 27 | for _, res := range nss { 28 | ns = append(ns, res.Host) 29 | } 30 | sort.Strings(ns) 31 | return ns, nil 32 | } 33 | 34 | func (c *dohProvider) updateNameservers(domain string) error { 35 | return fmt.Errorf("DNS-over-HTTPS 'Registrar' is read only, changes must be applied to %s manually", domain) 36 | } 37 | -------------------------------------------------------------------------------- /commands/cmdzonecache.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import "github.com/StackExchange/dnscontrol/v4/pkg/providers" 4 | 5 | // FYI(tlim): This file was originally called zonecache.go. To remove any 6 | // confusion between it and pkg/zonecache, we've renamed it. We've also added 7 | // "cmd" or "Cmd" to various labels too. 8 | 9 | // NewCmdZoneCache creates a zoneCache. 10 | func NewCmdZoneCache() *cmdZoneCache { 11 | return &cmdZoneCache{} 12 | } 13 | 14 | func (zc *cmdZoneCache) zoneList(name string, lister providers.ZoneLister) (*[]string, error) { 15 | zc.Lock() 16 | defer zc.Unlock() 17 | 18 | if zc.cache == nil { 19 | zc.cache = map[string]*[]string{} 20 | } 21 | 22 | if v, ok := zc.cache[name]; ok { 23 | return v, nil 24 | } 25 | 26 | zones, err := lister.ListZones() 27 | if err != nil { 28 | return nil, err 29 | } 30 | zc.cache[name] = &zones 31 | return &zones, nil 32 | } 33 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/044-ensureabsent.json: -------------------------------------------------------------------------------- 1 | { 2 | "dns_providers": [], 3 | "domains": [ 4 | { 5 | "dnsProviders": {}, 6 | "meta": { 7 | "dnscontrol_nameraw": "example.com", 8 | "dnscontrol_nameunicode": "example.com", 9 | "dnscontrol_uniquename": "example.com" 10 | }, 11 | "name": "example.com", 12 | "records": [ 13 | { 14 | "filepos": "[line:2:5]", 15 | "name": "normal", 16 | "target": "1.1.1.1", 17 | "ttl": 300, 18 | "type": "A" 19 | } 20 | ], 21 | "recordsabsent": [ 22 | { 23 | "filepos": "[line:3:5]", 24 | "name": "helper", 25 | "target": "2.2.2.2", 26 | "type": "A" 27 | } 28 | ], 29 | "registrar": "none", 30 | "uniquename": "example.com" 31 | } 32 | ], 33 | "registrars": [] 34 | } 35 | -------------------------------------------------------------------------------- /providers/cloudns/auditrecords.go: -------------------------------------------------------------------------------- 1 | package cloudns 2 | 3 | import ( 4 | "github.com/StackExchange/dnscontrol/v4/models" 5 | "github.com/StackExchange/dnscontrol/v4/pkg/rejectif" 6 | ) 7 | 8 | // AuditRecords returns a list of errors corresponding to the records 9 | // that aren't supported by this provider. If all records are 10 | // supported, an empty list is returned. 11 | func AuditRecords(records []*models.RecordConfig) []error { 12 | a := rejectif.Auditor{} 13 | 14 | a.Add("TXT", rejectif.TxtHasBackticks) // Last verified 2021-03-01 15 | 16 | a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2021-03-01 17 | 18 | a.Add("TXT", rejectif.TxtHasTrailingSpace) // Last verified 2021-03-01 19 | 20 | a.Add("TXT", rejectif.TxtHasDoubleQuotes) // Last verified 2021-03-01 21 | 22 | a.Add("SRV", rejectif.SrvHasNullTarget) // Last verified 2023-03-30 23 | 24 | return a.Audit(records) 25 | } 26 | -------------------------------------------------------------------------------- /pkg/rejectif/srv.go: -------------------------------------------------------------------------------- 1 | package rejectif 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/StackExchange/dnscontrol/v4/models" 7 | ) 8 | 9 | // Keep these in alphabetical order. 10 | 11 | // SrvHasNullTarget detects SRV records that has a null target. 12 | func SrvHasNullTarget(rc *models.RecordConfig) error { 13 | if rc.GetTargetField() == "." { 14 | return errors.New("srv has null target") 15 | } 16 | return nil 17 | } 18 | 19 | // SrvHasEmptyTarget detects SRV records with empty targets. 20 | func SrvHasEmptyTarget(rc *models.RecordConfig) error { 21 | if rc.GetTargetField() == "" { 22 | return errors.New("srv has empty target") 23 | } 24 | return nil 25 | } 26 | 27 | // SrvHasZeroPort detects SRV records with port set to zero. 28 | func SrvHasZeroPort(rc *models.RecordConfig) error { 29 | if rc.SrvPort == 0 { 30 | return errors.New("srv has zero port") 31 | } 32 | return nil 33 | } 34 | -------------------------------------------------------------------------------- /documentation/provider/packetframe.md: -------------------------------------------------------------------------------- 1 | ## Configuration 2 | 3 | To use this provider, add an entry to `creds.json` with `TYPE` set to `PACKETFRAME` 4 | along with your Packetframe Token which can be extracted from the `token` cookie on packetframe.com 5 | 6 | Example: 7 | 8 | {% code title="creds.json" %} 9 | ```json 10 | { 11 | "packetframe": { 12 | "TYPE": "PACKETFRAME", 13 | "token": "your-packetframe-token" 14 | } 15 | } 16 | ``` 17 | {% endcode %} 18 | 19 | ## Metadata 20 | This provider does not recognize any special metadata fields unique to Packetframe. 21 | 22 | ## Usage 23 | An example configuration: 24 | 25 | {% code title="dnsconfig.js" %} 26 | ```javascript 27 | var REG_NONE = NewRegistrar("none"); 28 | var DSP_PACKETFRAME = NewDnsProvider("packetframe"); 29 | 30 | D("example.com", REG_NONE, DnsProvider(DSP_PACKETFRAME), 31 | A("test", "1.2.3.4"), 32 | ); 33 | ``` 34 | {% endcode %} 35 | -------------------------------------------------------------------------------- /commands/completion-scripts/completion.zsh.gotmpl: -------------------------------------------------------------------------------- 1 | #compdef {{.App.Name}} 2 | 3 | _dnscontrol() { 4 | local -a opts 5 | local cur 6 | cur=${words[-1]} 7 | if [[ "$cur" == "-"* ]]; then 8 | opts=("${(@f)$(${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}") 9 | else 10 | opts=("${(@f)$(${words[@]:0:#words[@]-1} --generate-bash-completion)}") 11 | fi 12 | 13 | if [[ "${opts[1]}" != "" ]]; then 14 | _describe 'values' opts 15 | else 16 | _files 17 | fi 18 | } 19 | 20 | # Run the function the first time we are auto-loaded, otherwise the very first 21 | # complete wouldn't work in each shell session 22 | # Otherwise assume we are directly sourced and register the completion 23 | # (This is done by the #compdef directive in the autoloaded case.) 24 | if [[ "${zsh_eval_context[-1]}" == "loadautofunc" ]]; then 25 | _dnscontrol "$@" 26 | else 27 | compdef "_dnscontrol" "dnscontrol" 28 | fi 29 | -------------------------------------------------------------------------------- /providers/alidns/pagination.go: -------------------------------------------------------------------------------- 1 | package alidns 2 | 3 | // paginateAll is a small generic paginator helper. The caller provides a 4 | // fetch function that requests a single page (pageNumber,pageSize) and 5 | // returns the items for that page, the total number of items available, 6 | // and an error if any. paginateAll will iterate pages until it has 7 | // collected all items or an error occurs. 8 | func paginateAll[T any](fetch func(pageNumber, pageSize int) ([]T, int, error), maxPageSize int) ([]T, error) { 9 | page := 1 10 | pageSize := maxPageSize 11 | var out []T 12 | 13 | for { 14 | items, total, err := fetch(page, pageSize) 15 | if err != nil { 16 | return nil, err 17 | } 18 | out = append(out, items...) 19 | 20 | // If we've collected all items, or the page returned nothing, stop. 21 | if len(out) >= total || len(items) == 0 { 22 | break 23 | } 24 | page++ 25 | } 26 | return out, nil 27 | } 28 | -------------------------------------------------------------------------------- /documentation/provider/autodns.md: -------------------------------------------------------------------------------- 1 | ## Configuration 2 | 3 | To use this provider, add an entry to `creds.json` with `TYPE` set to `AUTODNS` along with 4 | [username, password and a context](https://help.internetx.com/display/APIXMLEN/Authentication#Authentication-AuthenticationviaCredentials(username/password/context)). 5 | 6 | Example: 7 | 8 | {% code title="creds.json" %} 9 | ```json 10 | { 11 | "autodns": { 12 | "TYPE": "AUTODNS", 13 | "username": "autodns.service-account@example.com", 14 | "password": "[***]", 15 | "context": "33004" 16 | } 17 | } 18 | ``` 19 | {% endcode %} 20 | 21 | ## Usage 22 | 23 | An example configuration: 24 | 25 | {% code title="dnsconfig.js" %} 26 | ```javascript 27 | var REG_NONE = NewRegistrar("none"); 28 | var DSP_AUTODNS = NewDnsProvider("autodns"); 29 | 30 | D("example.com", REG_NONE, DnsProvider(DSP_AUTODNS), 31 | A("test", "1.2.3.4"), 32 | ); 33 | ``` 34 | {% endcode %} 35 | -------------------------------------------------------------------------------- /documentation/language-reference/record-modifiers/R53_ZONE.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: R53_ZONE 3 | parameters: 4 | - zone_id 5 | parameter_types: 6 | zone_id: string 7 | ts_return: DomainModifier & RecordModifier 8 | provider: ROUTE53 9 | --- 10 | 11 | `R53_ZONE` lets you specify the AWS Zone ID for an entire domain ([`D()`](../top-level-functions/D.md)) or a specific [`R53_ALIAS()`](../domain-modifiers/R53_ALIAS.md) record. 12 | 13 | When used with [`D()`](../top-level-functions/D.md), it sets the zone id of the domain. This can be used to differentiate between split horizon domains in public and private zones. See this [example](../../provider/route53.md#split-horizon) in the [Amazon Route 53 provider page](../../provider/route53.md). 14 | 15 | When used with [`R53_ALIAS()`](../domain-modifiers/R53_ALIAS.md) it sets the required Route53 hosted zone id in a R53_ALIAS record. See [`R53_ALIAS()`](../domain-modifiers/R53_ALIAS.md) documentation for details. 16 | -------------------------------------------------------------------------------- /pkg/rtypeinfo/rtypeinfo.go: -------------------------------------------------------------------------------- 1 | package rtypeinfo 2 | 3 | import "github.com/StackExchange/dnscontrol/v4/pkg/rtypecontrol" 4 | 5 | // IsModernType returns true if the given record type is implemented in the new 6 | // ("Modern") way. (i.e. uses the RecordConfig .F field to store the record's 7 | // rdata). 8 | // 9 | // This does NOT rely on .F, which makes this function useful before the 10 | // RecordConfig is fully populated. 11 | // 12 | // NOTE: Do not confuse this with RecordConfig.IsModernType() which provides 13 | // similar functionality. The difference is that this function receives the 14 | // type as a string, while RecordConfig.IsModernType() is a method on 15 | // RecordConfig that reveals if that specific RecordConfig instance is modern. 16 | // 17 | // FUTURE(tlim): Once all record types have been migrated to use ".F", this function can be removed. 18 | func IsModernType(t string) bool { 19 | _, ok := rtypecontrol.Func[t] 20 | return ok 21 | } 22 | -------------------------------------------------------------------------------- /providers/sakuracloud/listzones.go: -------------------------------------------------------------------------------- 1 | package sakuracloud 2 | 3 | import "github.com/StackExchange/dnscontrol/v4/pkg/printer" 4 | 5 | // ListZones return all the zones in the account 6 | func (s *sakuracloudProvider) ListZones() ([]string, error) { 7 | itemMap, err := s.api.GetCommonServiceItemMap() 8 | if err != nil { 9 | return nil, err 10 | } 11 | 12 | var zones []string 13 | for _, item := range itemMap { 14 | zones = append(zones, item.Status.Zone) 15 | } 16 | return zones, nil 17 | } 18 | 19 | // EnsureZoneExists creates a zone if it does not exist 20 | func (s *sakuracloudProvider) EnsureZoneExists(domain string, metadata map[string]string) error { 21 | itemMap, err := s.api.GetCommonServiceItemMap() 22 | if err != nil { 23 | return err 24 | } 25 | 26 | if _, ok := itemMap[domain]; ok { 27 | return nil 28 | } 29 | 30 | printer.Printf("Adding zone for %s to Sakura Cloud account\n", domain) 31 | return s.api.CreateZone(domain) 32 | } 33 | -------------------------------------------------------------------------------- /pkg/dnsgraph/testutils/stubrecords.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import "github.com/StackExchange/dnscontrol/v4/pkg/dnsgraph" 4 | 5 | // StubRecord stub 6 | type StubRecord struct { 7 | NameFQDN string 8 | Dependencies []dnsgraph.Dependency 9 | Type dnsgraph.NodeType 10 | } 11 | 12 | // GetType stub 13 | func (record StubRecord) GetType() dnsgraph.NodeType { 14 | return record.Type 15 | } 16 | 17 | // GetName stub 18 | func (record StubRecord) GetName() string { 19 | return record.NameFQDN 20 | } 21 | 22 | // GetDependencies stub 23 | func (record StubRecord) GetDependencies() []dnsgraph.Dependency { 24 | return record.Dependencies 25 | } 26 | 27 | // StubRecordsAsGraphable stub 28 | func StubRecordsAsGraphable(records []StubRecord) []dnsgraph.Graphable { 29 | sortableRecords := make([]dnsgraph.Graphable, len(records)) 30 | 31 | for iX := range records { 32 | sortableRecords[iX] = records[iX] 33 | } 34 | 35 | return sortableRecords 36 | } 37 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/054-b3487_d_extend_rev.js: -------------------------------------------------------------------------------- 1 | var DSP_BIND = NewDnsProvider("bind"); 2 | var REG_CHANGEME = NewRegistrar("none"); 3 | D("6.10.in-addr.arpa", REG_CHANGEME, 4 | DnsProvider(DSP_BIND), 5 | PTR("31.104", "example.site.com."), 6 | PTR("206.104", "example2.site.com."), 7 | ); 8 | 9 | D_EXTEND(REV("10.6.200.0/24"), 10 | PTR("50", "ip-10-6-200-50.example.com."), 11 | PTR("51", "ip-10-6-200-51.example.com."), 12 | PTR("52", "ip-10-6-200-52.example.com."), 13 | PTR("53", "ip-10-6-200-53.example.com."), 14 | ) 15 | 16 | D_EXTEND(REV("10.6.119.0/27"), 17 | PTR("0", "ip-10-6-119-0.example.com."), 18 | PTR("1", "ip-10-6-119-1.example.com."), 19 | PTR("2", "ip-10-6-119-2.example.com."), 20 | PTR("3", "ip-10-6-119-3.example.com."), 21 | ) 22 | 23 | D_EXTEND("220.6.10.in-addr.arpa", 24 | PTR("20", "ip-10-6-220-20.example.com."), 25 | ) 26 | 27 | D_EXTEND("230.6.10.in-addr.arpa", 28 | PTR("30", "ip-10-6-230-30.example.com."), 29 | ) 30 | -------------------------------------------------------------------------------- /documentation/language-reference/domain-modifiers/AAAA.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: AAAA 3 | parameters: 4 | - name 5 | - address 6 | - modifiers... 7 | parameter_types: 8 | name: string 9 | address: string 10 | "modifiers...": RecordModifier[] 11 | --- 12 | 13 | AAAA adds an AAAA record To a domain. The name should be the relative label for the record. Use `@` for the domain apex. 14 | 15 | The address should be an IPv6 address as a string. 16 | 17 | Modifiers can be any number of [record modifiers](https://docs.dnscontrol.org/language-reference/record-modifiers) or JSON objects, which will be merged into the record's metadata. 18 | 19 | {% code title="dnsconfig.js" %} 20 | ```javascript 21 | var addrV6 = "2001:0db8:85a3:0000:0000:8a2e:0370:7334" 22 | 23 | D("example.com", REG_MY_PROVIDER, DnsProvider(DSP_MY_PROVIDER), 24 | AAAA("@", addrV6), 25 | AAAA("foo", addrV6), 26 | AAAA("test.foo", addrV6, TTL(5000)), 27 | AAAA("*", addrV6, {foo: 42}), 28 | ); 29 | ``` 30 | {% endcode %} 31 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "runtime/debug" 7 | 8 | "github.com/StackExchange/dnscontrol/v4/commands" 9 | _ "github.com/StackExchange/dnscontrol/v4/pkg/providers/_all" 10 | _ "github.com/StackExchange/dnscontrol/v4/pkg/rtype" 11 | "github.com/StackExchange/dnscontrol/v4/pkg/version" 12 | "github.com/fatih/color" 13 | ) 14 | 15 | //go:generate go run build/generate/generate.go build/generate/featureMatrix.go build/generate/functionTypes.go build/generate/dtsFile.go build/generate/ownersFile.go 16 | 17 | func main() { 18 | if os.Getenv("CI") == "true" { 19 | color.NoColor = false 20 | } 21 | if info, ok := debug.ReadBuildInfo(); !ok && info == nil { 22 | fmt.Fprint(os.Stderr, "Warning: dnscontrol was built without Go modules. See https://docs.dnscontrol.org/getting-started/getting-started#source for more information on how to build dnscontrol correctly.\n\n") 23 | } 24 | os.Exit(commands.Run("DNSControl version " + version.Version())) 25 | } 26 | -------------------------------------------------------------------------------- /providers/loopia/auditrecords.go: -------------------------------------------------------------------------------- 1 | package loopia 2 | 3 | import ( 4 | "github.com/StackExchange/dnscontrol/v4/models" 5 | "github.com/StackExchange/dnscontrol/v4/pkg/rejectif" 6 | ) 7 | 8 | // AuditRecords returns a list of errors corresponding to the records 9 | // that aren't supported by this provider. If all records are 10 | // supported, an empty list is returned. 11 | func AuditRecords(records []*models.RecordConfig) []error { 12 | a := rejectif.Auditor{} 13 | 14 | a.Add("CAA", rejectif.CaaTargetContainsWhitespace) // Last verified 2025-07-24: Loopia returns 404 15 | 16 | a.Add("MX", rejectif.MxNull) // Last verified 2025-07-24: Loopia returns 404 17 | 18 | a.Add("SRV", rejectif.SrvHasNullTarget) // Last verified 2025-07-24: Loopia returns 404 19 | 20 | a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2025-07-24: Loopia returns 404 21 | 22 | a.Add("TXT", rejectif.TxtLongerThan(450)) // Last verified 2025-07-24: Loopia returns 404 23 | 24 | return a.Audit(records) 25 | } 26 | -------------------------------------------------------------------------------- /documentation/language-reference/domain-modifiers/A.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: A 3 | parameters: 4 | - name 5 | - address 6 | - modifiers... 7 | parameter_types: 8 | name: string 9 | address: string | number 10 | "modifiers...": RecordModifier[] 11 | --- 12 | 13 | A adds an A record To a domain. The name should be the relative label for the record. Use `@` for the domain apex. 14 | 15 | The address should be an ip address, either a string, or a numeric value obtained via [IP](../top-level-functions/IP.md). 16 | 17 | Modifiers can be any number of [record modifiers](https://docs.dnscontrol.org/language-reference/record-modifiers) or JSON objects, which will be merged into the record's metadata. 18 | 19 | {% code title="dnsconfig.js" %} 20 | ```javascript 21 | D("example.com", REG_MY_PROVIDER, DnsProvider(DSP_MY_PROVIDER), 22 | A("@", "1.2.3.4"), 23 | A("foo", "2.3.4.5"), 24 | A("test.foo", IP("1.2.3.4"), TTL(5000)), 25 | A("*", "1.2.3.4", {foo: 42}), 26 | ); 27 | ``` 28 | {% endcode %} 29 | -------------------------------------------------------------------------------- /pkg/notifications/shoutrrr.go: -------------------------------------------------------------------------------- 1 | package notifications 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/nicholas-fedor/shoutrrr" 7 | ) 8 | 9 | func init() { 10 | initers = append(initers, func(cfg map[string]string) Notifier { 11 | if url, ok := cfg["shoutrrr_url"]; ok { 12 | return shoutrrrNotifier(url) 13 | } 14 | return nil 15 | }) 16 | } 17 | 18 | type shoutrrrNotifier string 19 | 20 | func (b shoutrrrNotifier) Notify(domain, provider, msg string, err error, preview bool) error { 21 | var payload string 22 | if preview { 23 | payload = fmt.Sprintf("DNSControl preview: %s[%s]:\n%s", domain, provider, msg) 24 | } else if err != nil { 25 | payload = fmt.Sprintf("DNSControl ERROR running correction on %s[%s]:\n%s\nError: %s", domain, provider, msg, err) 26 | } else { 27 | payload = fmt.Sprintf("DNSControl successfully ran correction for %s[%s]:\n%s", domain, provider, msg) 28 | } 29 | return shoutrrr.Send(string(b), payload) 30 | } 31 | 32 | func (b shoutrrrNotifier) Done() {} 33 | -------------------------------------------------------------------------------- /pkg/recorddb/recorddb.go: -------------------------------------------------------------------------------- 1 | package recorddb 2 | 3 | import "github.com/StackExchange/dnscontrol/v4/models" 4 | 5 | // Functions that make it easier to deal with 6 | // a group of records. 7 | // 8 | 9 | // RecordDB is a container of many model.RecordConfig 10 | type RecordDB = struct { 11 | labelAndTypeMap map[models.RecordKey]struct{} 12 | } 13 | 14 | // NewFromRecords creates a RecordDB from a list of model.RecordConfig. 15 | func NewFromRecords(recs models.Records) *RecordDB { 16 | result := &RecordDB{} 17 | 18 | result.labelAndTypeMap = make(map[models.RecordKey]struct{}, len(recs)) 19 | for _, rec := range recs { 20 | result.labelAndTypeMap[rec.Key()] = struct{}{} 21 | } 22 | 23 | return result 24 | } 25 | 26 | // ContainsLT returns true if recdb contains rec. Matching is done 27 | // on the record's label and type (i.e. the RecordKey) 28 | // func (recdb RecordDB) ContainsLT(rec *models.RecordConfig) bool { 29 | // _, ok := recdb.labelAndTypeMap[rec.Key()] 30 | // return ok 31 | //} 32 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/047-SVCB.json: -------------------------------------------------------------------------------- 1 | { 2 | "dns_providers": [], 3 | "domains": [ 4 | { 5 | "dnsProviders": {}, 6 | "meta": { 7 | "dnscontrol_nameraw": "foo.com", 8 | "dnscontrol_nameunicode": "foo.com", 9 | "dnscontrol_uniquename": "foo.com" 10 | }, 11 | "name": "foo.com", 12 | "records": [ 13 | { 14 | "filepos": "[line:3:5]", 15 | "name": "@", 16 | "svcparams": "alpn=\"h3,h2\" port=443 ipv4hint=123.123.123.123 ipv6hint=dead::beaf", 17 | "svcpriority": 2, 18 | "target": ".", 19 | "ttl": 300, 20 | "type": "HTTPS" 21 | }, 22 | { 23 | "filepos": "[line:2:5]", 24 | "name": "@", 25 | "svcpriority": 1, 26 | "target": ".", 27 | "ttl": 300, 28 | "type": "SVCB" 29 | } 30 | ], 31 | "registrar": "none", 32 | "uniquename": "foo.com" 33 | } 34 | ], 35 | "registrars": [] 36 | } 37 | -------------------------------------------------------------------------------- /models/provider.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | // DNSProvider is an interface for DNS Provider plug-ins. 4 | type DNSProvider interface { 5 | GetNameservers(domain string) ([]*Nameserver, error) 6 | GetZoneRecords(domain string, meta map[string]string) (Records, error) 7 | GetZoneRecordsCorrections(dc *DomainConfig, existing Records) ([]*Correction, int, error) 8 | } 9 | 10 | // Registrar is an interface for Registrar plug-ins. 11 | type Registrar interface { 12 | GetRegistrarCorrections(dc *DomainConfig) ([]*Correction, error) 13 | } 14 | 15 | // ProviderBase describes providers. 16 | type ProviderBase struct { 17 | Name string 18 | IsDefault bool 19 | ProviderType string 20 | } 21 | 22 | // RegistrarInstance is a single registrar. 23 | type RegistrarInstance struct { 24 | ProviderBase 25 | Driver Registrar 26 | } 27 | 28 | // DNSProviderInstance is a single DNS provider. 29 | type DNSProviderInstance struct { 30 | ProviderBase 31 | Driver DNSProvider 32 | NumberOfNameservers int 33 | } 34 | -------------------------------------------------------------------------------- /pkg/txtutil/txtutil_test.go: -------------------------------------------------------------------------------- 1 | package txtutil 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func Test_splitChunks(t *testing.T) { 9 | type args struct { 10 | buf string 11 | lim int 12 | } 13 | tests := []struct { 14 | name string 15 | args args 16 | want []string 17 | }{ 18 | {"0", args{"", 3}, []string{}}, 19 | {"1", args{"a", 3}, []string{"a"}}, 20 | {"2", args{"ab", 3}, []string{"ab"}}, 21 | {"3", args{"abc", 3}, []string{"abc"}}, 22 | {"4", args{"abcd", 3}, []string{"abc", "d"}}, 23 | {"5", args{"abcde", 3}, []string{"abc", "de"}}, 24 | {"6", args{"abcdef", 3}, []string{"abc", "def"}}, 25 | {"7", args{"abcdefg", 3}, []string{"abc", "def", "g"}}, 26 | {"8", args{"abcdefgh", 3}, []string{"abc", "def", "gh"}}, 27 | } 28 | for _, tt := range tests { 29 | t.Run(tt.name, func(t *testing.T) { 30 | if got := splitChunks(tt.args.buf, tt.args.lim); !reflect.DeepEqual(got, tt.want) { 31 | t.Errorf("splitChunks() = %v, want %v", got, tt.want) 32 | } 33 | }) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /providers/route53/auditrecords.go: -------------------------------------------------------------------------------- 1 | package route53 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/StackExchange/dnscontrol/v4/models" 7 | "github.com/StackExchange/dnscontrol/v4/pkg/rejectif" 8 | ) 9 | 10 | // AuditRecords returns a list of errors corresponding to the records 11 | // that aren't supported by this provider. If all records are 12 | // supported, an empty list is returned. 13 | func AuditRecords(records []*models.RecordConfig) []error { 14 | a := rejectif.Auditor{} 15 | 16 | a.Add("R53_ALIAS", rejectifTargetEqualsLabel) // Last verified 2023-03-01 17 | 18 | return a.Audit(records) 19 | } 20 | 21 | // Normally this kind of function would be put in `pkg/rejectif` but 22 | // since this is ROUTE53-specific, we'll include it here. 23 | 24 | // rejectifTargetEqualsLabel rejects an ALIAS that would create a loop. 25 | 26 | func rejectifTargetEqualsLabel(rc *models.RecordConfig) error { 27 | if (rc.GetLabelFQDN() + ".") == rc.GetTargetField() { 28 | return errors.New("alias target loop") 29 | } 30 | return nil 31 | } 32 | -------------------------------------------------------------------------------- /documentation/language-reference/domain-modifiers/CNAME.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: CNAME 3 | parameters: 4 | - name 5 | - target 6 | - modifiers... 7 | parameter_types: 8 | name: string 9 | target: string 10 | "modifiers...": RecordModifier[] 11 | --- 12 | 13 | CNAME adds a CNAME record to the domain. The name should be the relative label for the domain. 14 | Using `@` or `*` for CNAME records is not recommended, as different providers support them differently. 15 | 16 | Target should be a string representing the CNAME target. If it is a single label we will assume it is a relative name on the current domain. If it contains *any* dots, it should be a fully qualified domain name, ending with a `.`. 17 | 18 | {% code title="dnsconfig.js" %} 19 | ```javascript 20 | D("example.com", REG_MY_PROVIDER, DnsProvider(DSP_MY_PROVIDER), 21 | CNAME("foo", "google.com."), // foo.example.com -> google.com 22 | CNAME("abc", "@"), // abc.example.com -> example.com 23 | CNAME("def", "test"), // def.example.com -> test.example.com 24 | ); 25 | ``` 26 | {% endcode %} 27 | -------------------------------------------------------------------------------- /documentation/provider/netcup.md: -------------------------------------------------------------------------------- 1 | ## Configuration 2 | 3 | To use this provider, add an entry to `creds.json` with `TYPE` set to `NETCUP` 4 | along with your [api key, password and your customer number](https://www.netcup-wiki.de/wiki/CCP_API#Authentifizierung). 5 | 6 | Example: 7 | 8 | {% code title="creds.json" %} 9 | ```json 10 | { 11 | "netcup": { 12 | "TYPE": "NETCUP", 13 | "api-key": "abc12345", 14 | "api-password": "abc12345", 15 | "customer-number": "123456" 16 | } 17 | } 18 | ``` 19 | {% endcode %} 20 | 21 | ## Usage 22 | An example configuration: 23 | 24 | {% code title="dnsconfig.js" %} 25 | ```javascript 26 | var REG_NONE = NewRegistrar("none"); 27 | var DSP_NETCUP = NewDnsProvider("netcup"); 28 | 29 | D("example.com", REG_NONE, DnsProvider(DSP_NETCUP), 30 | A("test", "1.2.3.4"), 31 | ); 32 | ``` 33 | {% endcode %} 34 | 35 | ## Caveats 36 | Netcup does not allow any TTLs to be set for individual records. Thus in 37 | the diff/preview it will always show a TTL of 0. `NS` records are also 38 | not currently supported. 39 | -------------------------------------------------------------------------------- /models/recorddb.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | // Functions that make it easier to deal with a group of records. 4 | 5 | // RecordDB is a container of many RecordConfig, queryable by various methods. 6 | // The first to be implemented is as a hash with label:type as the index. 7 | type RecordDB struct { 8 | labelAndTypeMap map[RecordKey]struct{} 9 | } 10 | 11 | // NewRecordDBFromRecords creates a RecordDB from a list of RecordConfig. 12 | func NewRecordDBFromRecords(recs Records, zone string) *RecordDB { 13 | result := &RecordDB{} 14 | 15 | result.labelAndTypeMap = make(map[RecordKey]struct{}, len(recs)) 16 | for _, rec := range recs { 17 | result.labelAndTypeMap[rec.Key()] = struct{}{} 18 | } 19 | // fmt.Printf("DEBUG: BUILDING RecordDB: DONE!\n") 20 | 21 | return result 22 | } 23 | 24 | // ContainsLT returns true if recdb contains rec. Matching is done 25 | // on the record's label and type (i.e. the RecordKey) 26 | func (recdb *RecordDB) ContainsLT(rec *RecordConfig) bool { 27 | _, ok := recdb.labelAndTypeMap[rec.Key()] 28 | return ok 29 | } 30 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/018-dkim.json: -------------------------------------------------------------------------------- 1 | { 2 | "dns_providers": [], 3 | "domains": [ 4 | { 5 | "dnsProviders": {}, 6 | "meta": { 7 | "dnscontrol_nameraw": "foo.com", 8 | "dnscontrol_nameunicode": "foo.com", 9 | "dnscontrol_uniquename": "foo.com" 10 | }, 11 | "name": "foo.com", 12 | "records": [ 13 | { 14 | "filepos": "[line:2:5]", 15 | "name": "dkimtest2", 16 | "target": "this string is 255 bytes long.hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnKZogtjOlHoeY8iZ5o5brlPOsj/a2Q9Bopu1kHxlxrdw7tZVL9FzUMngiIYGrl8dbP7Rvk7TLMoxHxVkRZPBtIpsKIab/gOUoPLQVYbrAmzyguHYBwAApi3H/pvjUsK8+XF0dKY17AR96lokAPqvfBaUb+DSx8zNw2hrYWYVqvCtnxHUGEUhT1bTlEZBptH3jthis is the remainder. it is 156 bytes long.mOhl2JmbsFKy+RoMTwbkk0/meRvcEFWLHkr4MSgbnie6OpQvM4Y51+kO6DUVr3rwjrdVO9wpFt+n/hdQ92TNif17RMJtE5AGaQ6BN3yJQIDAQAB;", 17 | "ttl": 300, 18 | "type": "TXT" 19 | } 20 | ], 21 | "registrar": "none", 22 | "uniquename": "foo.com" 23 | } 24 | ], 25 | "registrars": [] 26 | } 27 | -------------------------------------------------------------------------------- /providers/axfrddns/md5Provider.go: -------------------------------------------------------------------------------- 1 | package axfrddns 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/md5" //#nosec 6 | "encoding/base64" 7 | "encoding/hex" 8 | 9 | "github.com/miekg/dns" 10 | ) 11 | 12 | type md5Provider string 13 | 14 | func fromBase64(s []byte) (buf []byte, err error) { 15 | buflen := base64.StdEncoding.DecodedLen(len(s)) 16 | buf = make([]byte, buflen) 17 | n, err := base64.StdEncoding.Decode(buf, s) 18 | buf = buf[:n] 19 | return 20 | } 21 | 22 | func (key md5Provider) Generate(msg []byte, _ *dns.TSIG) ([]byte, error) { 23 | rawsecret, err := fromBase64([]byte(key)) 24 | if err != nil { 25 | return nil, err 26 | } 27 | h := hmac.New(md5.New, rawsecret) 28 | 29 | h.Write(msg) 30 | return h.Sum(nil), nil 31 | } 32 | 33 | func (key md5Provider) Verify(msg []byte, t *dns.TSIG) error { 34 | b, err := key.Generate(msg, t) 35 | if err != nil { 36 | return err 37 | } 38 | mac, err := hex.DecodeString(t.MAC) 39 | if err != nil { 40 | return err 41 | } 42 | if !hmac.Equal(b, mac) { 43 | return dns.ErrSig 44 | } 45 | return nil 46 | } 47 | -------------------------------------------------------------------------------- /providers/sakuracloud/convert.go: -------------------------------------------------------------------------------- 1 | package sakuracloud 2 | 3 | import ( 4 | "github.com/StackExchange/dnscontrol/v4/models" 5 | ) 6 | 7 | const defaultTTL = uint32(3600) 8 | 9 | func toRc(domain string, r domainRecord) (*models.RecordConfig, error) { 10 | rc := &models.RecordConfig{ 11 | Type: r.Type, 12 | TTL: r.TTL, 13 | Original: r, 14 | } 15 | if r.TTL == 0 { 16 | rc.TTL = defaultTTL 17 | } 18 | 19 | rc.SetLabel(r.Name, domain) 20 | 21 | var err error 22 | switch r.Type { 23 | case "TXT": 24 | // TXT records are stored verbatim; no quoting/escaping to parse. 25 | err = rc.SetTargetTXT(r.RData) 26 | default: 27 | err = rc.PopulateFromString(r.Type, r.RData, domain) 28 | } 29 | return rc, err 30 | } 31 | 32 | func toNative(rc *models.RecordConfig) domainRecord { 33 | rr := domainRecord{ 34 | Name: rc.GetLabel(), 35 | Type: rc.Type, 36 | RData: rc.String(), 37 | } 38 | if rc.TTL != defaultTTL { 39 | rr.TTL = rc.TTL 40 | } 41 | 42 | switch rc.Type { 43 | case "TXT": 44 | rr.RData = rc.GetTargetTXTJoined() 45 | } 46 | return rr 47 | } 48 | -------------------------------------------------------------------------------- /documentation/language-reference/domain-modifiers/PURGE.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: PURGE 3 | --- 4 | 5 | `PURGE` is the default setting for all domains. Therefore `PURGE` is 6 | a no-op. It is included for completeness only. 7 | 8 | A domain with a mixture of `NO_PURGE` and `PURGE` parameters will abide 9 | by the last one. 10 | 11 | These three examples all are equivalent. 12 | 13 | `PURGE` is the default: 14 | 15 | {% code title="dnsconfig.js" %} 16 | ```javascript 17 | D("example.com", REG_MY_PROVIDER, DnsProvider(DSP_MY_PROVIDER), 18 | ); 19 | ``` 20 | {% endcode %} 21 | 22 | Purge is the default, but we set it anyway: 23 | 24 | {% code title="dnsconfig.js" %} 25 | ```javascript 26 | D("example.com", REG_MY_PROVIDER, DnsProvider(DSP_MY_PROVIDER), 27 | PURGE, 28 | ); 29 | ``` 30 | {% endcode %} 31 | 32 | Since the "last command wins", this is the same as `PURGE`: 33 | 34 | {% code title="dnsconfig.js" %} 35 | ```javascript 36 | D("example.com", REG_MY_PROVIDER, DnsProvider(DSP_MY_PROVIDER), 37 | PURGE, 38 | NO_PURGE, 39 | PURGE, 40 | NO_PURGE, 41 | PURGE, 42 | ); 43 | ``` 44 | {% endcode %} 45 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/027-ds.json: -------------------------------------------------------------------------------- 1 | { 2 | "dns_providers": [], 3 | "domains": [ 4 | { 5 | "dnsProviders": {}, 6 | "meta": { 7 | "dnscontrol_nameraw": "foo.com", 8 | "dnscontrol_nameunicode": "foo.com", 9 | "dnscontrol_uniquename": "foo.com" 10 | }, 11 | "name": "foo.com", 12 | "records": [ 13 | { 14 | "dsalgorithm": 1, 15 | "dsdigest": "FFFF", 16 | "dsdigesttype": 1, 17 | "dskeytag": 1, 18 | "filepos": "[line:3:5]", 19 | "name": "@", 20 | "target": "", 21 | "ttl": 300, 22 | "type": "DS" 23 | }, 24 | { 25 | "dsalgorithm": 13, 26 | "dsdigest": "AABBCCDDEEFF", 27 | "dsdigesttype": 2, 28 | "dskeytag": 1000, 29 | "filepos": "[line:2:5]", 30 | "name": "@", 31 | "target": "", 32 | "ttl": 300, 33 | "type": "DS" 34 | } 35 | ], 36 | "registrar": "none", 37 | "uniquename": "foo.com" 38 | } 39 | ], 40 | "registrars": [] 41 | } 42 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/030-dextenddoc.js: -------------------------------------------------------------------------------- 1 | var REG = NewRegistrar("Third-Party", "NONE"); 2 | var DNS = NewDnsProvider("Cloudflare", "CLOUDFLAREAPI"); 3 | 4 | // The example from documentation/language-reference/top-level-functions/D_EXTEND.md 5 | 6 | D("domain.tld", REG, DnsProvider(DNS), 7 | A("@", "127.0.0.1"), // domain.tld 8 | A("www", "127.0.0.2"), // www.domain.tld 9 | CNAME("a", "b"), // a.domain.tld -> b.domain.tld 10 | ); 11 | D_EXTEND("domain.tld", 12 | A("aaa", "127.0.0.3"), // aaa.domain.tld 13 | CNAME("c", "d"), // c.domain.tld -> d.domain.tld 14 | ); 15 | D_EXTEND("sub.domain.tld", 16 | A("bbb", "127.0.0.4"), // bbb.sub.domain.tld 17 | A("ccc", "127.0.0.5"), // ccc.sub.domain.tld 18 | CNAME("e", "f"), // e.sub.domain.tld -> f.sub.domain.tld 19 | ); 20 | D_EXTEND("sub.sub.domain.tld", 21 | A("ddd", "127.0.0.6"), // ddd.sub.sub.domain.tld 22 | CNAME("g", "h"), // g.sub.domain.tld -> h.sub.domain.tld 23 | ); 24 | D_EXTEND("sub.domain.tld", 25 | A("@", "127.0.0.7"), // sub.domain.tld 26 | CNAME("i", "j"), // i.sub.domain.tld -> j.sub.domain.tld 27 | ); 28 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/032-reverseip.js: -------------------------------------------------------------------------------- 1 | // This tests PTR records, REV(), and PTR label magic. 2 | // This tests D_EXTEND()'s ability to generate proper labels when REV() is used as a label. 3 | var REGISTRAR = NewRegistrar('none', 'NONE'); // No registrar. 4 | var BIND = NewDnsProvider('bind', 'BIND'); 5 | 6 | D(REV('1.2.3.0/24'), REGISTRAR, DnsProvider(BIND), 7 | PTR("1", 'foo.example.com.'), 8 | PTR("1.2.3.2", 'bar.example.com.'), 9 | PTR(REV("1.2.3.3"), 'baz.example.com.', { 10 | skip_fqdn_check: "true" 11 | }), 12 | ); 13 | D_EXTEND(REV("1.2.3.4"), 14 | PTR("@", "silly.example.com."), 15 | ); 16 | D_EXTEND(REV("1.2.3.5/32"), 17 | PTR("1.2.3.5", "willy.example.com."), 18 | ); 19 | D_EXTEND(REV("1.2.3.6"), 20 | PTR(REV("1.2.3.6"), "billy.example.com."), 21 | ); 22 | 23 | D_EXTEND(REV("1.2.3.0/24"), 24 | PTR("7", "my.example.com."), 25 | ); 26 | D_EXTEND(REV("1.2.3.0/24"), 27 | PTR("1.2.3.8", "fair.example.com."), 28 | ); 29 | D_EXTEND(REV("1.2.3.0/24"), 30 | PTR(REV("1.2.3.9/32"), "lady.example.com.", { 31 | skip_fqdn_check: "true" 32 | }), 33 | ); 34 | -------------------------------------------------------------------------------- /documentation/language-reference/domain-modifiers/SSHFP.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: SSHFP 3 | parameters: 4 | - name 5 | - algorithm 6 | - type 7 | - value 8 | - modifiers... 9 | parameter_types: 10 | name: string 11 | algorithm: 0 | 1 | 2 | 3 | 4 12 | type: 0 | 1 | 2 13 | value: string 14 | "modifiers...": RecordModifier[] 15 | --- 16 | 17 | `SSHFP` contains a fingerprint of a SSH server which can be validated before SSH clients are establishing the connection. 18 | 19 | **Algorithm** (type of the key) 20 | 21 | | ID | Algorithm | 22 | |----|-----------| 23 | | 0 | reserved | 24 | | 1 | RSA | 25 | | 2 | DSA | 26 | | 3 | ECDSA | 27 | | 4 | ED25519 | 28 | 29 | **Type** (fingerprint format) 30 | 31 | | ID | Algorithm | 32 | |----|-----------| 33 | | 0 | reserved | 34 | | 1 | SHA-1 | 35 | | 2 | SHA-256 | 36 | 37 | `value` is the fingerprint as a string. 38 | 39 | {% code title="dnsconfig.js" %} 40 | ```javascript 41 | D("example.com", REG_MY_PROVIDER, DnsProvider(DSP_MY_PROVIDER), 42 | SSHFP("@", 1, 1, "00yourAmazingFingerprint00"), 43 | ); 44 | ``` 45 | {% endcode %} 46 | -------------------------------------------------------------------------------- /pkg/js/parse_tests/031-dextendnames.js: -------------------------------------------------------------------------------- 1 | var REG = NewRegistrar("Third-Party", "NONE"); 2 | var DNS = NewDnsProvider("Cloudflare", "CLOUDFLAREAPI"); 3 | 4 | // Test the name matching algorithm 5 | 6 | D("domain.tld", REG, DnsProvider(DNS), 7 | A("@", "127.0.0.1"), 8 | A("a", "127.0.0.2"), 9 | CNAME("b", "c"), 10 | ); 11 | 12 | D("sub.domain.tld", REG, DnsProvider(DNS), 13 | A("@", "127.0.1.1"), 14 | A("aa", "127.0.1.2"), 15 | CNAME("bb", "cc"), 16 | ); 17 | 18 | 19 | // Should match domain.tld 20 | D_EXTEND("domain.tld", 21 | A("@", "127.0.0.3"), 22 | A("d", "127.0.0.4"), 23 | CNAME("e", "f"), 24 | ); 25 | 26 | // Should match domain.tld 27 | D_EXTEND("ub.domain.tld", 28 | A("@", "127.0.0.5"), 29 | A("g", "127.0.0.6"), 30 | CNAME("h", "i"), 31 | ); 32 | 33 | // Should match sub.domain.tld 34 | D_EXTEND("sub.domain.tld", 35 | A("@", "127.0.1.3"), 36 | A("dd", "127.0.1.4"), 37 | CNAME("ee", "ff"), 38 | ); 39 | 40 | // Should match domain.tld 41 | D_EXTEND("ssub.domain.tld", 42 | A("@", "127.0.0.7"), 43 | A("j", "127.0.0.8"), 44 | CNAME("k", "l"), 45 | ); 46 | -------------------------------------------------------------------------------- /providers/transip/auditrecords.go: -------------------------------------------------------------------------------- 1 | package transip 2 | 3 | import ( 4 | "github.com/StackExchange/dnscontrol/v4/models" 5 | "github.com/StackExchange/dnscontrol/v4/pkg/rejectif" 6 | ) 7 | 8 | // AuditRecords returns a list of errors corresponding to the records 9 | // that aren't supported by this provider. If all records are 10 | // supported, an empty list is returned. 11 | func AuditRecords(records []*models.RecordConfig) []error { 12 | a := rejectif.Auditor{} 13 | 14 | a.Add("ALIAS", rejectif.LabelNotApex) // Last verified 2024-01-11 15 | 16 | a.Add("MX", rejectif.MxNull) // Last verified 2023-12-04 17 | 18 | a.Add("TXT", rejectif.TxtHasBackticks) // Last verified 2024-01-11 19 | 20 | a.Add("TXT", rejectif.TxtHasBackslash) // Last verified 2024-01-11 21 | 22 | a.Add("TXT", rejectif.TxtStartsOrEndsWithSpaces) // Last verified 2024-01-11 23 | 24 | a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2024-01-11 25 | 26 | a.Add("TXT", rejectif.TxtLongerThan(1024)) // Last verified 2024-01-11 27 | 28 | a.Add("TXT", rejectif.TxtHasTrailingSpace) // Last verified 2024-01-11 29 | 30 | return a.Audit(records) 31 | } 32 | -------------------------------------------------------------------------------- /pkg/diff2/ordering.go: -------------------------------------------------------------------------------- 1 | package diff2 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/StackExchange/dnscontrol/v4/pkg/dnsgraph" 7 | "github.com/StackExchange/dnscontrol/v4/pkg/dnssort" 8 | ) 9 | 10 | func orderByDependencies(changes ChangeList) ChangeList { 11 | if DisableOrdering { 12 | log.Println("[Info: ordering of the changes has been disabled.]") 13 | return changes 14 | } 15 | 16 | a := dnssort.SortUsingGraph(changes) 17 | 18 | if len(a.UnresolvedRecords) > 0 { 19 | log.Printf("Warning: Found unresolved records %v.\n"+ 20 | "This can indicate a circular dependency, please ensure all targets from given records exist and no circular dependencies exist in the changeset. "+ 21 | "These unresolved records are still added as changes and pushed to the provider, but will cause issues if and when the provider checks the changes.\n"+ 22 | "For more information and how to disable the reordering please consolidate our documentation at https://docs.dnscontrol.org/developer-info/ordering\n", 23 | dnsgraph.GetRecordsNamesForGraphables(a.UnresolvedRecords), 24 | ) 25 | } 26 | 27 | return a.SortedRecords 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Stack Overflow 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /documentation/provider/rwth.md: -------------------------------------------------------------------------------- 1 | ## Configuration 2 | 3 | To use this provider, add an entry to `creds.json` with `TYPE` set to `RWTH` 4 | along with your API Token (which you can create via noc-portal.rz.rwth-aachen.de/dns-admin/en/api_tokens). 5 | 6 | The provider may only be used from within the intranet. 7 | 8 | Example: 9 | 10 | {% code title="creds.json" %} 11 | ```json 12 | { 13 | "rwth": { 14 | "TYPE": "RWTH", 15 | "api_token": "bQGz0DOi0AkTzG...=" 16 | } 17 | } 18 | ``` 19 | {% endcode %} 20 | 21 | ## Metadata 22 | This provider does not recognize any special metadata fields unique to it. 23 | 24 | ## Usage 25 | An example configuration: 26 | 27 | {% code title="dnsconfig.js" %} 28 | ```javascript 29 | var REG_NONE = NewRegistrar("none"); 30 | var DSP_RWTH = NewDnsProvider("rwth"); 31 | 32 | D("example.rwth-aachen.de", REG_NONE, DnsProvider(DSP_RWTH), 33 | A("test", "1.2.3.4"), 34 | ); 35 | ``` 36 | {% endcode %} 37 | 38 | ## Caveats 39 | The default TTL is not automatically fetched, as the API does not provide such an endpoint. 40 | 41 | The RWTH deploys zones every 15 minutes, so it might take some time for changes to take effect. 42 | -------------------------------------------------------------------------------- /pkg/domaintags/idn.go: -------------------------------------------------------------------------------- 1 | package domaintags 2 | 3 | import ( 4 | "strings" 5 | 6 | "golang.org/x/net/idna" 7 | ) 8 | 9 | // EfficientToASCII converts a domain name to its ASCII representation using 10 | // IDNA, on error returns the original name, and avoids allocating new memory 11 | // when possible. The final string is lowercased. 12 | func EfficientToASCII(name string) string { 13 | nameIDN, err := idna.ToASCII(name) 14 | if err != nil { 15 | return name // Fallback to raw name on error. 16 | } 17 | nameIDN = strings.ToLower(nameIDN) 18 | 19 | // Avoid pointless duplication. 20 | if nameIDN == name { 21 | return name 22 | } 23 | return nameIDN 24 | } 25 | 26 | // EfficientToUnicode converts a domain name to its Unicode representation 27 | // using IDNA, on error returns the original name, and avoids allocating new 28 | // memory when possible. 29 | func EfficientToUnicode(name string) string { 30 | nameUnicode, err := idna.ToUnicode(name) 31 | if err != nil { 32 | return name // Fallback to raw name on error. 33 | } 34 | // Avoid pointless duplication. 35 | if nameUnicode == name { 36 | return name 37 | } 38 | return nameUnicode 39 | } 40 | -------------------------------------------------------------------------------- /providers/ovh/ovhProvider_test.go: -------------------------------------------------------------------------------- 1 | package ovh 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ovh/go-ovh/ovh" 7 | ) 8 | 9 | func Test_getOVHEndpoint(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | endpoint string 13 | want string 14 | }{ 15 | { 16 | "default to EU", "", ovh.OvhEU, 17 | }, 18 | { 19 | "default to EU if omitted", "omitted", ovh.OvhEU, 20 | }, 21 | { 22 | "set to EU", "eu", ovh.OvhEU, 23 | }, 24 | { 25 | "set to CA", "ca", ovh.OvhCA, 26 | }, 27 | { 28 | "set to US", "us", ovh.OvhUS, 29 | }, 30 | { 31 | "case insensitive", "Eu", ovh.OvhEU, 32 | }, 33 | { 34 | "case insensitive ca", "CA", ovh.OvhCA, 35 | }, 36 | { 37 | "arbitratry", "https://blah", "https://blah", 38 | }, 39 | } 40 | for _, tt := range tests { 41 | t.Run(tt.name, func(t *testing.T) { 42 | params := make(map[string]string) 43 | if tt.endpoint != "" && tt.endpoint != "omitted" { 44 | params["endpoint"] = tt.endpoint 45 | } 46 | if got := getOVHEndpoint(params); got != tt.want { 47 | t.Errorf("getOVHEndpoint() = %v, want %v", got, tt.want) 48 | } 49 | }) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | # Maintain dependencies for GitHub Actions 9 | - package-ecosystem: "github-actions" 10 | directory: "/" 11 | schedule: 12 | interval: "weekly" 13 | 14 | # Maintain dependencies for Go 15 | - package-ecosystem: "gomod" 16 | directory: "/" 17 | schedule: 18 | interval: "monthly" 19 | ignore: 20 | - dependency-name: "github.com/billputer/go-namecheap" 21 | - dependency-name: "github.com/dnsimple/dnsimple-go" 22 | - dependency-name: "github.com/exoscale/egoscale" 23 | - dependency-name: "github.com/ovh/go-ovh" 24 | - dependency-name: "github.com/vultr/govultr" 25 | 26 | # Maintain dependencies for Docker 27 | - package-ecosystem: "docker" 28 | directory: / 29 | schedule: 30 | interval: "monthly" 31 | -------------------------------------------------------------------------------- /documentation/provider/netlify.md: -------------------------------------------------------------------------------- 1 | ## Configuration 2 | 3 | To use this provider, add an entry to `creds.json` with `TYPE` set to `NETLIFY` 4 | along with a Netlify account personal access token. You can also optionally add an 5 | account slug. This is _typically_ your username on Netlify. 6 | 7 | Examples: 8 | 9 | {% code title="creds.json" %} 10 | ```json 11 | { 12 | "netlify": { 13 | "TYPE": "NETLIFY", 14 | "token": "your-netlify-account-access-token", 15 | "slug": "account-slug" // this is optional 16 | } 17 | } 18 | ``` 19 | {% endcode %} 20 | 21 | ## Metadata 22 | This provider does not recognize any special metadata fields unique to Netlify. 23 | 24 | ## Usage 25 | An example configuration: 26 | 27 | {% code title="dnsconfig.js" %} 28 | ```javascript 29 | var REG_NETLIFY = NewRegistrar("netlify"); 30 | var DSP_NETLIFY = NewDnsProvider("netlify"); 31 | 32 | D("example.com", REG_NETLIFY, DnsProvider(DSP_NETLIFY), 33 | A("test", "1.2.3.4"), 34 | ); 35 | ``` 36 | {% endcode %} 37 | 38 | ## Activation 39 | DNSControl depends on a Netlify account personal access token. 40 | 41 | ## Caveats 42 | Empty MX records are not supported. 43 | 44 | 45 | -------------------------------------------------------------------------------- /pkg/dnsgraph/graphable.go: -------------------------------------------------------------------------------- 1 | package dnsgraph 2 | 3 | // NodeType enumerates the node types. 4 | type NodeType uint8 5 | 6 | const ( 7 | // Change is the type of change. 8 | Change NodeType = iota 9 | // Report is a Report. 10 | Report 11 | ) 12 | 13 | // DependencyType enumerates the dependency types. 14 | type DependencyType uint8 15 | 16 | const ( 17 | // ForwardDependency is a forward dependency. 18 | ForwardDependency DependencyType = iota 19 | // BackwardDependency is a backwards dependency. 20 | BackwardDependency 21 | ) 22 | 23 | // Dependency is a dependency. 24 | type Dependency struct { 25 | NameFQDN string 26 | Type DependencyType 27 | } 28 | 29 | // Graphable is an interface for things that can be in a graph. 30 | type Graphable interface { 31 | GetType() NodeType 32 | GetName() string 33 | GetDependencies() []Dependency 34 | } 35 | 36 | // GetRecordsNamesForGraphables returns names in a graph. 37 | func GetRecordsNamesForGraphables[T Graphable](graphables []T) []string { 38 | var names []string 39 | 40 | for _, graphable := range graphables { 41 | names = append(names, graphable.GetName()) 42 | } 43 | 44 | return names 45 | } 46 | -------------------------------------------------------------------------------- /commands/completion-scripts/completion.bash.gotmpl: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | : "{{.App.Name}}" 4 | 5 | # Macs have bash3 for which the bash-completion package doesn't include 6 | # _init_completion. This is a minimal version of that function. 7 | _dnscontrol_init_completion() { 8 | COMPREPLY=() 9 | _get_comp_words_by_ref "$@" cur prev words cword 10 | } 11 | 12 | _dnscontrol() { 13 | if [[ "${COMP_WORDS[0]}" != "source" ]]; then 14 | local cur opts base words 15 | COMPREPLY=() 16 | cur="${COMP_WORDS[COMP_CWORD]}" 17 | if declare -F _init_completion >/dev/null 2>&1; then 18 | _init_completion -n "=:" || return 19 | else 20 | _dnscontrol_init_completion -n "=:" || return 21 | fi 22 | words=("${words[@]:0:$cword}") 23 | if [[ "$cur" == "-"* ]]; then 24 | requestComp="${words[*]} ${cur} --generate-bash-completion" 25 | else 26 | requestComp="${words[*]} --generate-bash-completion" 27 | fi 28 | opts=$(eval "${requestComp}" 2>/dev/null) 29 | COMPREPLY=($(compgen -W "${opts}" -- ${cur})) 30 | return 0 31 | fi 32 | } 33 | 34 | complete -o bashdefault -o default -o nospace -F "_dnscontrol" "{{.App.Name}}" 35 | -------------------------------------------------------------------------------- /documentation/provider/internetbs.md: -------------------------------------------------------------------------------- 1 | DNSControl's Internet.bs provider supports being a Registrar. Support for being a DNS Provider is not included, but could be added in the future. 2 | 3 | ## Configuration 4 | 5 | To use this provider, add an entry to `creds.json` with `TYPE` set to `INTERNETBS` 6 | along with an API key and account password. 7 | 8 | Example: 9 | 10 | {% code title="creds.json" %} 11 | ```json 12 | { 13 | "internetbs": { 14 | "TYPE": "INTERNETBS", 15 | "api-key": "your-api-key", 16 | "password": "account-password" 17 | } 18 | } 19 | ``` 20 | {% endcode %} 21 | 22 | ## Metadata 23 | This provider does not recognize any special metadata fields unique to Internet.bs. 24 | 25 | ## Usage 26 | An example configuration: 27 | 28 | {% code title="dnsconfig.js" %} 29 | ```javascript 30 | var REG_INTERNETBS = NewRegistrar("internetbs"); 31 | 32 | D("example.com", REG_INTERNETBS, 33 | NAMESERVER("ns1.example.com."), 34 | NAMESERVER("ns2.example.com."), 35 | ); 36 | ``` 37 | {% endcode %} 38 | 39 | ## Activation 40 | 41 | Pay attention, you need to define white list of IP for API. But you always can change it on `My Profile > Reseller Settings` 42 | -------------------------------------------------------------------------------- /models/t_mx.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | // SetTargetMX sets the MX fields. 10 | func (rc *RecordConfig) SetTargetMX(pref uint16, target string) error { 11 | rc.MxPreference = pref 12 | if err := rc.SetTarget(target); err != nil { 13 | return err 14 | } 15 | if rc.Type == "" { 16 | rc.Type = "MX" 17 | } 18 | if rc.Type != "MX" { 19 | panic("assertion failed: SetTargetMX called when .Type is not MX") 20 | } 21 | return nil 22 | } 23 | 24 | // SetTargetMXStrings is like SetTargetMX but accepts strings. 25 | func (rc *RecordConfig) SetTargetMXStrings(pref, target string) error { 26 | u64pref, err := strconv.ParseUint(pref, 10, 16) 27 | if err != nil { 28 | return fmt.Errorf("can't parse MX data: %w", err) 29 | } 30 | return rc.SetTargetMX(uint16(u64pref), target) 31 | } 32 | 33 | // SetTargetMXString is like SetTargetMX but accepts one big string. 34 | func (rc *RecordConfig) SetTargetMXString(s string) error { 35 | part := strings.Fields(s) 36 | if len(part) != 2 { 37 | return fmt.Errorf("MX value does not contain 2 fields: (%#v)", s) 38 | } 39 | return rc.SetTargetMXStrings(part[0], part[1]) 40 | } 41 | -------------------------------------------------------------------------------- /documentation/language-reference/domain-modifiers/DefaultTTL.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: DefaultTTL 3 | parameters: 4 | - ttl 5 | parameter_types: 6 | ttl: Duration 7 | --- 8 | 9 | DefaultTTL sets the TTL for all subsequent records following it in a domain that do not explicitly set one with [`TTL`](../record-modifiers/TTL.md). If neither `DefaultTTL` or `TTL` exist for a record, 10 | the record will inherit the DNSControl global internal default of 300 seconds. See also [`DEFAULTS`](../top-level-functions/DEFAULTS.md) to override the internal defaults. 11 | 12 | NS records are currently a special case, and do not inherit from `DefaultTTL`. See [`NAMESERVER_TTL`](../domain-modifiers/NAMESERVER_TTL.md) to set a default TTL for all NS records. 13 | 14 | 15 | {% code title="dnsconfig.js" %} 16 | ```javascript 17 | D("example.com", REG_MY_PROVIDER, DnsProvider(DSP_MY_PROVIDER), 18 | DefaultTTL("4h"), 19 | A("@","1.2.3.4"), // uses default 20 | A("foo", "2.3.4.5", TTL(600)), // overrides default 21 | ); 22 | ``` 23 | {% endcode %} 24 | 25 | The DefaultTTL duration is the same format as [`TTL`](../record-modifiers/TTL.md), an integer number of seconds 26 | or a string with a unit such as `"4d"`. 27 | -------------------------------------------------------------------------------- /documentation/provider/domainnameshop.md: -------------------------------------------------------------------------------- 1 | ## Configuration 2 | 3 | To use this provider, add an entry to `creds.json` with `TYPE` set to `DOMAINNAMESHOP` 4 | along with your [Domainnameshop Token and Secret](https://www.domeneshop.no/admin?view=api). 5 | 6 | Example: 7 | 8 | {% code title="creds.json" %} 9 | ```json 10 | { 11 | "mydomainnameshop": { 12 | "TYPE": "DOMAINNAMESHOP", 13 | "token": "your-domainnameshop-token", 14 | "secret": "your-domainnameshop-secret" 15 | } 16 | } 17 | ``` 18 | {% endcode %} 19 | 20 | ## Metadata 21 | This provider does not recognize any special metadata fields unique to Domainnameshop. 22 | 23 | ## Usage 24 | An example configuration: 25 | 26 | {% code title="dnsconfig.js" %} 27 | ```javascript 28 | var REG_NONE = NewRegistrar("none"); 29 | var DSP_DOMAINNAMESHOP = NewDnsProvider("mydomainnameshop"); 30 | 31 | D("example.com", REG_NONE, DnsProvider(DSP_DOMAINNAMESHOP), 32 | A("test", "1.2.3.4"), 33 | ); 34 | ``` 35 | {% endcode %} 36 | 37 | ## Activation 38 | [Create API Token and secret](https://www.domeneshop.no/admin?view=api) 39 | 40 | ## Limitations 41 | 42 | - Domainnameshop DNS only supports TTLs which are a multiple of 60. 43 | -------------------------------------------------------------------------------- /documentation/language-reference/domain-modifiers/IMPORT_TRANSFORM_STRIP.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: IMPORT_TRANSFORM_STRIP 3 | parameters: 4 | - transform table 5 | - domain 6 | - ttl 7 | - suffixstrip 8 | - modifiers... 9 | ts_ignore: true 10 | --- 11 | 12 | {% hint style="warning" %} 13 | Don't use this feature. It was added for a very specific situation at Stack Overflow. 14 | {% endhint %} 15 | 16 | `IMPORT_TRANSFORM_STRIP` is the same as `IMPORT_TRANSFORM` with an additional parameter: `suffixstrip`. 17 | 18 | When `IMPORT_TRANSFORM_STRIP` is generating the label for new records, it 19 | checks the label. If the label ends with `.` + `suffixstrip`, that suffix is removed. 20 | If the label does not end with `suffixstrip`, an error is returned. 21 | 22 | For CNAMEs, the `suffixstrip` is stripped from the beginning (prefix) of the target domain. 23 | 24 | For example, if the domain is `com.extra` and the label is `foo.com`, 25 | `IMPORT_TRANSFORM` would generate a label `foo.com.com.extra`. 26 | `IMPORT_TRANSFORM_STRIP(... , 'com')` would generate 27 | the label `foo.com.extra` instead. 28 | 29 | In the case of a CNAME, if the target is `foo.com.`, the new target would be `foo.com.extra`. 30 | --------------------------------------------------------------------------------