diff --git a/services/ptr.go b/services/ptr.go new file mode 100644 index 0000000..ee05e31 --- /dev/null +++ b/services/ptr.go @@ -0,0 +1,91 @@ +// This file is part of the happyDomain (R) project. +// Copyright (c) 2020-2024 happyDomain +// Authors: Pierre-Olivier Mercier, et al. +// +// This program is offered under a commercial and under the AGPL license. +// For commercial licensing, contact us at . +// +// For AGPL licensing: +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package svcs + +import ( + "strings" + + "github.com/StackExchange/dnscontrol/v4/models" + "github.com/miekg/dns" + + "git.happydns.org/happyDomain/model" + "git.happydns.org/happyDomain/utils" +) + +type PTR struct { + Target string +} + +func (s *PTR) GetNbResources() int { + return 1 +} + +func (s *PTR) GenComment(origin string) string { + return strings.TrimSuffix(s.Target, "."+origin) +} + +func (s *PTR) GenRRs(domain string, ttl uint32, origin string) (rrs models.Records) { + rr := utils.NewRecordConfig(domain, "PTR", ttl, origin) + rr.SetTarget(utils.DomainFQDN(s.Target, origin)) + rrs = append(rrs, rr) + return +} + +func pointer_analyze(a *Analyzer) error { + for _, record := range a.SearchRR(AnalyzerRecordFilter{Type: dns.TypePTR}) { + if record.Type == "PTR" { + newrr := &PTR{ + Target: strings.TrimSuffix(record.String(), "."+a.origin), + } + + a.UseRR(record, record.NameFQDN, newrr) + } + } + return nil +} + +func init() { + RegisterService( + func() happydns.Service { + return &PTR{} + }, + pointer_analyze, + ServiceInfos{ + Name: "Pointer", + Description: "A pointer to another domain.", + Categories: []string{ + "internal", + }, + RecordTypes: []uint16{ + dns.TypePTR, + }, + Restrictions: ServiceRestrictions{ + Alone: true, + Single: true, + NeedTypes: []uint16{ + dns.TypePTR, + }, + }, + }, + 99999998, + ) +} diff --git a/ui/src/lib/components/domains/SubdomainItem.svelte b/ui/src/lib/components/domains/SubdomainItem.svelte index d067817..176d51a 100644 --- a/ui/src/lib/components/domains/SubdomainItem.svelte +++ b/ui/src/lib/components/domains/SubdomainItem.svelte @@ -34,7 +34,7 @@ import { deleteZoneService } from '$lib/api/zone'; import Service from '$lib/components/domains/Service.svelte'; - import { fqdn } from '$lib/dns'; + import { fqdn, isReverseZone, unreverseDomain } from '$lib/dns'; import type { Domain, DomainInList } from '$lib/model/domain'; import type { ServiceCombined } from '$lib/model/service'; import { ZoneViewGrid } from '$lib/model/usersettings'; @@ -49,12 +49,19 @@ export let services: Array; export let zoneId: string; + let reverseZone = false; + $: reverseZone = isReverseZone(origin.domain); + let showResources = true; function isCNAME(services: Array) { return services.length === 1 && services[0]._svctype === 'svcs.CNAME'; } + function isPTR(services: Array) { + return services.length === 1 && services[0]._svctype === 'svcs.PTR'; + } + let deleteServiceInProgress = false; function deleteCNAME() { deleteServiceInProgress = true; @@ -75,19 +82,33 @@ } -{#if isCNAME(services)} +{#if isCNAME(services) || isPTR(services)}

- + {#if isPTR(services)} + + {:else} + + {/if} - {fqdn(dn, origin.domain)} + {#if reverseZone} + {unreverseDomain(fqdn(dn, origin.domain))} + {:else} + {fqdn(dn, origin.domain)} + {/if} @@ -131,7 +152,11 @@ {:else} {/if} - {$t('domains.drop-alias')} + {#if isPTR(services)} + {$t('domains.drop-pointer')} + {:else} + {$t('domains.drop-alias')} + {/if}

@@ -143,7 +168,7 @@ >

showResources = !showResources} on:keypress={() => showResources = !showResources} > @@ -156,7 +181,11 @@ class="font-monospace" title={fqdn(dn, origin.domain)} > - {fqdn(dn, origin.domain)} + {#if reverseZone} + {unreverseDomain(fqdn(dn, origin.domain))} + {:else} + {fqdn(dn, origin.domain)} + {/if}

{#if aliases.length != 0} diff --git a/ui/src/lib/dns.ts b/ui/src/lib/dns.ts index 62dc25b..6dfa0aa 100644 --- a/ui/src/lib/dns.ts +++ b/ui/src/lib/dns.ts @@ -242,3 +242,55 @@ export function validateDomain(dn: string, origin: string = "", hostname: boolea return ret; } + +export function isReverseZone(fqdn: string) { + return fqdn.endsWith('in-addr.arpa.') || fqdn.endsWith('ip6.arpa.') +} + +export function reverseDomain(ip: string) { + let suffix = 'in-addr.arpa.'; + + let fields: Array; + if (ip.indexOf(":") > 0) { + suffix = 'ip6.arpa.'; + + fields = ip.split(':'); + + fields = fields.map((e) => { + let exp_len = 4; + if (e.length == 0) { + exp_len = 4 * (7 - fields.length); + } + while (e.length < exp_len) { + e = '0' + e; + } + return e; + }); + } else { + fields = ip.split('.'); + while (fields.length < 4) { + const last = fields.pop(); + fields.push('0', last); + } + } + + return fields.reduce((a, v) => v.replace(/^0*(0|[^0].*)$/, '$1') + '.' + a, suffix); +} + +export function unreverseDomain(dn: string) { + let split_char = '.'; + let group = 1; + + if (dn.endsWith('ip6.arpa.')) { + split_char = ':'; + group = 4; + dn = dn.substring(0, dn.indexOf('.ip6.arpa.')) + } else { + dn = dn.substring(0, dn.indexOf('.in-addr.arpa.')) + } + + const fields = dn.split('.'); + let ip = fields.reduce((a, v, i) => v + (i % group == 0 ? split_char : '') + a, ''); + ip = ip.substring(0, ip.length - 1); + return ip.replace(/:(0000:)+/, '::').replace(/:0+/g, ':'); +} diff --git a/ui/src/lib/locales/en.json b/ui/src/lib/locales/en.json index 4b89df8..346122a 100644 --- a/ui/src/lib/locales/en.json +++ b/ui/src/lib/locales/en.json @@ -102,6 +102,7 @@ "create-new-key": "Create new {{id}} key", "discard": "Discard", "drop-alias": "Drop alias", + "drop-pointer": "Drop pointer", "edit-target": "Edit target", "give-explicit-name": "Give an explicit name in order to easily find this service.", "history": "History", diff --git a/ui/src/lib/locales/fr.json b/ui/src/lib/locales/fr.json index 80b079e..740ef83 100644 --- a/ui/src/lib/locales/fr.json +++ b/ui/src/lib/locales/fr.json @@ -102,6 +102,7 @@ "create-new-key": "Créer une nouvelle clé {{id}}", "discard": "Supprimer", "drop-alias": "Supprimer l'alias", + "drop-pointer": "Supprimer le pointeur", "edit-target": "Éditer la destination", "give-explicit-name": "Donnez un nom explicite afin de trouver facilement ce service.", "history": "Historique",