297 lines
9.1 KiB
TypeScript
297 lines
9.1 KiB
TypeScript
// This file is part of the happyDomain (R) project.
|
|
// Copyright (c) 2022-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 <contact@happydomain.org>.
|
|
//
|
|
// 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 <https://www.gnu.org/licenses/>.
|
|
|
|
import type { Domain } from '$lib/model/domain';
|
|
|
|
export const dns_common_types: Array<string> = ['ANY', 'A', 'AAAA', 'NS', 'SRV', 'MX', 'TXT', 'SOA'];
|
|
|
|
export function fqdn(input: string, origin: string) {
|
|
if (input[-1] === '.') {
|
|
return input
|
|
} else if (input === '') {
|
|
return origin
|
|
} else {
|
|
return input + '.' + origin
|
|
}
|
|
}
|
|
|
|
export function domainCompare (a: string | Domain, b: string | Domain) {
|
|
// Convert to string if Domain
|
|
if (typeof a === "object" && a.domain) a = a.domain;
|
|
if (typeof b === "object" && b.domain) b = b.domain;
|
|
|
|
const as = a.split('.').reverse();
|
|
const bs = b.split('.').reverse();
|
|
|
|
// Remove first item if empty
|
|
if (!as[0].length) as.shift();
|
|
if (!bs[0].length) bs.shift();
|
|
|
|
const maxDepth = Math.min(as.length, bs.length)
|
|
for (let i = 0; i < maxDepth; i++) {
|
|
const cmp = as[i].localeCompare(bs[i])
|
|
if (cmp !== 0) {
|
|
return cmp;
|
|
}
|
|
}
|
|
|
|
return as.length - bs.length
|
|
}
|
|
|
|
export function fqdnCompare (a: string | Domain, b: string | Domain) {
|
|
// Convert to string if Domain
|
|
if (typeof a === "object" && a.domain) a = a.domain;
|
|
if (typeof b === "object" && b.domain) b = b.domain;
|
|
|
|
const as = a.split('.').reverse();
|
|
const bs = b.split('.').reverse();
|
|
|
|
// Remove first item if empty
|
|
if (!as[0].length) as.shift();
|
|
if (!bs[0].length) bs.shift();
|
|
|
|
const maxDepth = Math.min(as.length, bs.length)
|
|
for (let i = Math.min(maxDepth, 1); i < maxDepth; i++) {
|
|
const cmp = as[i].localeCompare(bs[i])
|
|
if (cmp !== 0) {
|
|
return cmp;
|
|
} else if (i == 1) {
|
|
const cmp = as[0].localeCompare(bs[0]);
|
|
if (cmp !== 0) {
|
|
return cmp;
|
|
}
|
|
}
|
|
}
|
|
|
|
return as.length - bs.length
|
|
}
|
|
|
|
export function nsclass(input: number): string {
|
|
switch (input) {
|
|
case 1:
|
|
return 'IN'
|
|
case 3:
|
|
return 'CH'
|
|
case 4:
|
|
return 'HS'
|
|
case 254:
|
|
return 'NONE'
|
|
default:
|
|
return '##'
|
|
}
|
|
}
|
|
|
|
export function nsttl(input: number): string {
|
|
let ret = '';
|
|
|
|
if (input / 86400 >= 1) {
|
|
ret = Math.floor(input / 86400) + 'd '
|
|
input = input % 86400
|
|
}
|
|
if (input / 3600 >= 1) {
|
|
ret = Math.floor(input / 3600) + 'h '
|
|
input = input % 3600
|
|
}
|
|
if (input / 60 >= 1) {
|
|
ret = Math.floor(input / 60) + 'm '
|
|
input = input % 60
|
|
}
|
|
if (input >= 1) {
|
|
ret = Math.floor(input) + 's'
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
export function nsrrtype(input: number | string): string {
|
|
switch (input) {
|
|
case '1': case 1: return 'A'
|
|
case '2': case 2: return 'NS'
|
|
case '3': case 3: return 'MD'
|
|
case '4': case 4: return 'MF'
|
|
case '5': case 5: return 'CNAME'
|
|
case '6': case 6: return 'SOA'
|
|
case '7': case 7: return 'MB'
|
|
case '8': case 8: return 'MG'
|
|
case '9': case 9: return 'MR'
|
|
case '10': case 10: return 'NULL'
|
|
case '11': case 11: return 'WKS'
|
|
case '12': case 12: return 'PTR'
|
|
case '13': case 13: return 'HINFO'
|
|
case '14': case 14: return 'MINFO'
|
|
case '15': case 15: return 'MX'
|
|
case '16': case 16: return 'TXT'
|
|
case '17': case 17: return 'RP'
|
|
case '18': case 18: return 'AFSDB'
|
|
case '19': case 19: return 'X25'
|
|
case '20': case 20: return 'ISDN'
|
|
case '21': case 21: return 'RT'
|
|
case '22': case 22: return 'NSAP'
|
|
case '23': case 23: return 'NSAP-PTR'
|
|
case '24': case 24: return 'SIG'
|
|
case '25': case 25: return 'KEY'
|
|
case '26': case 26: return 'PX'
|
|
case '27': case 27: return 'GPOS'
|
|
case '28': case 28: return 'AAAA'
|
|
case '29': case 29: return 'LOC'
|
|
case '30': case 30: return 'NXT'
|
|
case '31': case 31: return 'EID'
|
|
case '32': case 32: return 'NIMLOC'
|
|
case '33': case 33: return 'SRV'
|
|
case '34': case 34: return 'ATMA'
|
|
case '35': case 35: return 'NAPTR'
|
|
case '36': case 36: return 'KX'
|
|
case '37': case 37: return 'CERT'
|
|
case '38': case 38: return 'A6'
|
|
case '39': case 39: return 'DNAME'
|
|
case '40': case 40: return 'SINK'
|
|
case '41': case 41: return 'OPT'
|
|
case '42': case 42: return 'APL'
|
|
case '43': case 43: return 'DS'
|
|
case '44': case 44: return 'SSHFP'
|
|
case '45': case 45: return 'IPSECKEY'
|
|
case '46': case 46: return 'RRSIG'
|
|
case '47': case 47: return 'NSEC'
|
|
case '48': case 48: return 'DNSKEY'
|
|
case '49': case 49: return 'DHCID'
|
|
case '50': case 50: return 'NSEC3'
|
|
case '51': case 51: return 'NSEC3PARAM'
|
|
case '52': case 52: return 'TLSA'
|
|
case '53': case 53: return 'SMIMEA'
|
|
case '55': case 55: return 'HIP'
|
|
case '56': case 56: return 'NINFO'
|
|
case '57': case 57: return 'RKEY'
|
|
case '58': case 58: return 'TALINK'
|
|
case '59': case 59: return 'CDS'
|
|
case '60': case 60: return 'CDNSKEY'
|
|
case '61': case 61: return 'OPENPGPKEY'
|
|
case '62': case 62: return 'CSYNC'
|
|
case '63': case 63: return 'ZONEMD'
|
|
case '99': case 99: return 'SPF'
|
|
case '100': case 100: return 'UINFO'
|
|
case '101': case 101: return 'UID'
|
|
case '102': case 102: return 'GID'
|
|
case '103': case 103: return 'UNSPEC'
|
|
case '104': case 104: return 'NID'
|
|
case '105': case 105: return 'L32'
|
|
case '106': case 106: return 'L64'
|
|
case '107': case 107: return 'LP'
|
|
case '108': case 108: return 'EUI48'
|
|
case '109': case 109: return 'EUI64'
|
|
case '249': case 249: return 'TKEY'
|
|
case '250': case 250: return 'TSIG'
|
|
case '251': case 251: return 'IXFR'
|
|
case '252': case 252: return 'AXFR'
|
|
case '253': case 253: return 'MAILB'
|
|
case '254': case 254: return 'MAILA'
|
|
case '256': case 256: return 'URI'
|
|
case '257': case 257: return 'CAA'
|
|
case '258': case 258: return 'AVC'
|
|
case '259': case 259: return 'DOA'
|
|
case '260': case 260: return 'AMTRELAY'
|
|
case '32768': case 32768: return 'TA'
|
|
case '32769': case 32769: return 'DLV'
|
|
default: return '#'
|
|
}
|
|
}
|
|
|
|
export function validateDomain(dn: string, origin: string = "", hostname: boolean = false): boolean | undefined {
|
|
let ret: boolean | undefined = undefined;
|
|
if (dn.length !== 0) {
|
|
dn = fqdn(dn, origin);
|
|
if (!dn.endsWith(origin)) {
|
|
return false;
|
|
}
|
|
|
|
ret = dn.length >= 1 && dn.length <= 254;
|
|
|
|
if (ret) {
|
|
const domains = dn.split('.');
|
|
|
|
// Remove the last . if any, it's ok
|
|
if (domains[domains.length - 1] === '') {
|
|
domains.pop();
|
|
}
|
|
|
|
let newDomainState: boolean = ret
|
|
domains.forEach(function (domain) {
|
|
newDomainState = newDomainState && domain.length >= 1 && domain.length <= 63;
|
|
newDomainState = newDomainState && (!hostname || /^(\*|_?[a-zA-Z0-9]([a-zA-Z0-9-]?[a-zA-Z0-9])*)$/.test(domain));
|
|
})
|
|
ret = newDomainState;
|
|
}
|
|
}
|
|
|
|
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<String>;
|
|
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, ':');
|
|
}
|