split long TXT records

This commit is contained in:
Evert Prants 2021-05-15 20:33:29 +03:00
parent 10ac1b0d80
commit 8caefdbf13
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
4 changed files with 99 additions and 35 deletions

View File

@ -1,10 +1,13 @@
{
"name": "icy-dyndns",
"name": "icydns",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"test": "echo \"Error: no test specified\" && exit 1",
"build": "tsc",
"watch": "tsc -w",
"start": "node src/index.js"
},
"keywords": [],
"author": "",

View File

@ -1,4 +1,4 @@
import { CachedZone, SOARecord } from '../models/interfaces';
import { CachedZone, DNSRecord, SOARecord } from '../models/interfaces';
import { readZoneFile } from './reader';
import { DNSRecordType } from './records';
import { ReloadExecutor } from './rndc';
@ -17,6 +17,37 @@ export class DNSCache {
return this.cached[name] != null;
}
search(
cached: CachedZone,
name?: string,
type?: DNSRecordType,
value?: string,
strict = false
): DNSRecord[] {
return cached.zone.records.filter((zone) => {
if (type && zone.type !== type) {
return false;
}
if (name && zone.name !== name) {
return false;
}
if (value && ((!strict && !zone.value.includes(value as string)) ||
(strict && zone.value !== value))) {
return false;
}
return true;
}).map((record) => {
const inx = cached.zone.records.indexOf(record);
return {
...record,
index: inx
}
})
}
async get(name: string): Promise<CachedZone | null> {
const cached = this.cached[name];
if (!cached) {

View File

@ -1,13 +1,21 @@
import * as fs from 'fs/promises';
import { DNSZone, SOARecord } from '../models/interfaces';
import { DNSRecord, DNSZone, SOARecord } from '../models/interfaces';
import { DNSRecordType } from './records';
const maxStrLength = 255;
const magicPadding = 3;
/**
* Splits and comments SOA record
* @param record
* @param padI
* @param padJ
* @returns new lines
*/
function createSOAString(record: SOARecord, padI: number, padJ: number): string[] {
const name = record.name.padEnd(padI, ' ');
const type = record.type.toString().padEnd(padJ, ' ');
const padK = ' '.padStart(
padI + 3
);
const padK = ' '.padStart(padI + magicPadding);
const padL = ['serial', 'refresh', 'retry', 'expire', 'minimum']
.reduce((previous, current) => {
const len = `${record[current]}`.length;
@ -24,10 +32,44 @@ function createSOAString(record: SOARecord, padI: number, padJ: number): string[
];
}
export function createZoneFile(zone: DNSZone, preferredLineLength = 120): string[] {
/**
* Splits very long TXT records into multiple lines.
* Mandatory for DKIM keys, for example.
* @param record
* @param padI
* @param padJ
* @returns new lines
*/
function splitTXTString(record: DNSRecord, padI: number, padJ: number): string[] {
const name = record.name.padEnd(padI, ' ');
const type = record.type.toString().padEnd(padJ, ' ');
const strLen = maxStrLength - padI - magicPadding;
const padK = ' '.padStart(padI + magicPadding);
const splitStrings = [];
if (record.value.length < strLen) {
return [`${name} IN ${type} ${record.value}`];
}
let temporary = record.value.replace(/"/g, '');
while (temporary.length > strLen) {
splitStrings.push(temporary.substr(0, strLen));
temporary = temporary.substr(strLen);
}
splitStrings.push(temporary);
return [
`${name} IN ${type} (`,
...splitStrings.map((str) => `${padK} "${str}"`),
`)`
]
}
export function createZoneFile(zone: DNSZone): string[] {
const file: string[] = [];
file.push(`$TTL ${zone.ttl}`);
file.push(`; GENERATED BY icy-dyndns`);
file.push(`; GENERATED BY ICYDNS`);
let longestName = 0;
let longestType = 0;
@ -49,6 +91,11 @@ export function createZoneFile(zone: DNSZone, preferredLineLength = 120): string
return;
}
if (record.type === DNSRecordType.TXT) {
file.push(...splitTXTString(record, longestName, longestType));
return;
}
const name = record.name.padEnd(longestName, ' ');
const type = record.type.toString().padEnd(longestType, ' ');
file.push(`${name} IN ${type} ${record.value}`);

View File

@ -18,6 +18,7 @@ const app = express();
const api = express.Router();
app.use(express.json());
app.enable('trust proxy');
const keys = new Keys();
const rndc = ReloadExecutor.fromEnvironment();
@ -84,35 +85,13 @@ api.get('/zone/records/:domain', domainAuthorization, async (req, res) => {
const domain = req.params.domain;
const cached = await getOrLoad(domain);
const type = req.query.type;
const name = req.query.name;
const value = req.query.value;
const type = req.query.type as DNSRecordType;
const name = req.query.name as string;
const value = req.query.value as string;
if (type || name || value) {
const results = cached.zone.records.filter((zone) => {
if (type && zone.type !== type) {
return false;
}
if (name && zone.name !== name) {
return false;
}
if (value && !zone.value.includes(value as string)) {
return false;
}
return true;
}).map((record) => {
const inx = cached.zone.records.indexOf(record);
return {
...record,
index: inx
}
});
res.json({
records: results,
records: cache.search(cached, name, type, value),
});
return;
@ -241,6 +220,10 @@ api.put('/zone/records/:domain', domainAuthorization, async (req, res) => {
throw new Error('Validation error: Invalid characters');
}
if (cache.search(cached, name, upperType, value, true).length) {
throw new Error('Exact same record already exists. No need to duplicate records!');
}
zone.records.push(newRecord);
await cache.update(domain, cached);