icydns/src/utility/dns/writer.ts

134 lines
3.4 KiB
TypeScript

import * as fs from 'fs/promises';
import { DNSRecordType } from 'src/types/dns.enum';
import { SOARecord, DNSRecord, DNSZone } from 'src/types/dns.interfaces';
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 + magicPadding);
const padL =
['serial', 'refresh', 'retry', 'expire', 'minimum'].reduce(
(previous, current) => {
const len = `${record[current]}`.length;
return previous > len ? previous : len;
},
0,
) + 1;
return [
`${name} IN ${type} ${record.nameserver} ${record.email} (`,
`${padK} ${record.serial.toString().padEnd(padL, ' ')} ; Serial`,
`${padK} ${record.refresh.toString().padEnd(padL, ' ')} ; Refresh`,
`${padK} ${record.retry.toString().padEnd(padL, ' ')} ; Retry`,
`${padK} ${record.expire.toString().padEnd(padL, ' ')} ; Expire`,
`${padK} ${record.minimum.toString().padEnd(padL, ' ')} ; Minimum`,
`)`,
];
}
/**
* 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 ICYDNS`);
let longestName = 0;
let longestType = 0;
// First pass: for nice alignments
zone.records.forEach((record) => {
if (record.name.length > longestName) {
longestName = record.name.length;
}
if (record.type.toString().length > longestType) {
longestType = record.type.toString().length;
}
});
zone.records.forEach((record) => {
if (record.type === DNSRecordType.SOA) {
file.push(
...createSOAString(record as SOARecord, longestName, longestType),
);
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}`);
});
zone.includes.forEach((include) => {
file.push(`$INCLUDE ${include}`);
});
file.push('');
return file;
}
export async function writeZoneFile(
zone: DNSZone,
file: string,
): Promise<string[]> {
const fullText = createZoneFile(zone);
await fs.writeFile(file, fullText.join('\n'));
return fullText;
}