split long TXT records
This commit is contained in:
parent
10ac1b0d80
commit
8caefdbf13
@ -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": "",
|
||||
|
@ -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) {
|
||||
|
@ -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}`);
|
||||
|
35
src/index.ts
35
src/index.ts
@ -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);
|
||||
|
Reference in New Issue
Block a user