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 { 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 { 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); }