icydns/src/utility/dns/validator.ts

65 lines
1.6 KiB
TypeScript

import * as fs from 'fs/promises';
import { exec } from 'child_process';
import { join } from 'path';
import { CachedZone, DNSRecord } from 'src/types/dns.interfaces';
import { writeZoneFile } from './writer';
// TODO: in-depth validation for record types
const forbiddenCharacters = ['\n', '\r'];
const forbiddenOutsideStr = ['$', ';'];
export function validateRecord(record: DNSRecord): boolean {
for (const char of forbiddenCharacters) {
if (record.name.includes(char) || record.value.includes(char)) {
return false;
}
}
for (const char of forbiddenOutsideStr) {
if (record.name.includes(char)) {
return false;
}
}
return true;
}
export async function validateZonefile(
domain: string,
file: string,
): Promise<string> {
return new Promise((resolve, reject) => {
exec(`named-checkzone ${domain} ${file}`, (error, stdout) => {
if (error) {
const errorFull = stdout.split('\n')[0].split(':');
reject(
new Error(
`Validation error: ${errorFull[0]}: ${errorFull
.slice(2)
.join(':')}`,
),
);
return;
}
resolve(stdout);
});
});
}
export async function validateAndSave(
name: string,
zone: CachedZone,
): Promise<void> {
const tempfile = join(process.cwd(), `.${name}-${Date.now()}.zone`);
await writeZoneFile(zone.zone, tempfile);
try {
await validateZonefile(name, tempfile);
} catch (e) {
await fs.unlink(tempfile);
throw e;
}
// TODO: cross-device move
await fs.rename(tempfile, zone.file);
}