65 lines
1.6 KiB
TypeScript
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);
|
||
|
}
|