134 lines
3.4 KiB
TypeScript
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;
|
|
}
|