├── .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 |
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 |
--------------------------------------------------------------------------------