UNPKG

ts-zone-file

Version:

This module is designed for manaing a BIND name server.

139 lines (138 loc) 5.82 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const VALUEXP = /\s+(NS|CNAME|TXT|PTR|A|AAAA|DNAME)\s+/; const VALUETST = /(NS|CNAME|TXT|PTR|A|AAAA|IN|DNAME)/; const PREFTST = /\s+(MX|CAA)\s+/; let curhost; /** * Takes BIND9 Zonefile as string, parses it and returns an object of the values within the zone * @param zone Valid BIND9 Zonefile as a string * @example * ```typescript * import { parseZoneFile } from 'ts-zone-file'; * import { readFile } from 'fs-extra'; * const file = await readFile('/zones/example.com'); * const zone = await parseZoneFile(file.toString()); * if (zone.a) zone.a.map((RR) => console.log(RR.host, RR.ttl, RR.value)); * ``` */ exports.parseZoneFile = async (zone) => { // Async Interface for line by line processing const rl = zone.split('\n'); // @ts-ignore let Zone = { $origin: '', soa: {} }; let soa = ''; let SOASEC; // Iterate through file async line by line for (let line of rl) { // Remove comments from current line line = line.replace(/(^|[^\\]);.*/g, ''); // Convert current line to uppercase const uLine = line.toUpperCase(); // IF upercase line has $ORIGIN then set Zone Origin to current line if (uLine.indexOf('$ORIGIN') === 0) Zone.$origin = line.split(/\s+/g)[1]; // ZONE TTL if (uLine.indexOf('$TTL') === 0) Zone.$ttl = parseInt(line.split(/\s+/g)[1], 10); // If line is the first line of a SOA line enable SOA Parser if (/\s+SOA\s+/.test(uLine)) SOASEC = true; // Append data to SOA String if (SOASEC && /(?<=\s+)\S+/.test(line)) soa = soa + line; // If final line of SOA then disable SOA Mode if (SOASEC && /(?<=\s{10})\S+\s+\)|^\s+\)/.test(line)) SOASEC = false; // SRV Record // Test for value record and process with value extractor if (VALUEXP.test(uLine)) Zone[VALUEXP.exec(line)[1].toLowerCase()] ? Zone[VALUEXP.exec(line)[1].toLowerCase()].push({ ...(await exports.ProcessValueRecord(line)) }) : (Zone[VALUEXP.exec(line)[1].toLowerCase()] = [{ ...(await exports.ProcessValueRecord(line)) }]); else if (/\s+SRV\s+/.test(uLine)) Zone.srv ? Zone.srv.push(await exports.ProcessSRV(line)) : (Zone.srv = [{ ...(await exports.ProcessSRV(line)) }]); else if (/\s+(MX)\s+/.test(uLine)) Zone.mx ? Zone.mx.push(await exports.ProcessPref(line)) : (Zone.mx = [{ ...(await exports.ProcessPref(line)) }]); else if (/\s+(CAA)\s/.test(uLine)) Zone.caa ? Zone.caa.push(await exports.ProcessCAA(line)) : (Zone.caa = [{ ...(await exports.ProcessCAA(line)) }]); } // If their is no SOA at this point it is an INVALID Zone File if (soa.length === 0) throw new Error('INVALID ZONE FILE'); // Extract the SOA Information into a line of the info soa = /((?<=SOA\s+\S+\s)|(\())[\s\S]*?\)/gim .exec(soa)[0] .replace(/\s+/gm, ' ') .replace(/\(|\)/g, '') .trim(); // Extract all values with named capture groups Zone.soa = { ...RegExp(/(?<contact>^\S+)\s{1,2}(?<serial>\d+)\s(?<refresh>\d+)\s(?<retry>\d+)\s(?<expire>\d+)\s(?<mttl>\d+)/g).exec(soa).groups, }; return Zone; }; /** * Extracts TTL, host, and value from a zonefile line * @param line line of Zonefile * @exmaple * ```typescript * const line = 'example.com. 300 IN NS ns1.exmaple.xyz.' * const { ttl, host, value } = await ProcessValueRecord(value) * ``` */ exports.ProcessValueRecord = async (line) => { let [host, ...rrRecord] = line.trim().split(/\s+/g); if (rrRecord.length < 1) throw new Error('INVALID Record'); if (rrRecord.length == 1 && VALUETST.test(host)) host = '@'; if (curhost && VALUETST.test(host)) host = curhost; else curhost = host; let returnObj = { host: host, value: '' }; if (!isNaN(parseInt(rrRecord[0])) && rrRecord.length > 1) returnObj.ttl = parseInt(rrRecord[0]); returnObj.value = rrRecord.length > 3 ? rrRecord .filter((a, b) => b < rrRecord.length && ((!/^(NS|CNAME|TXT|PTR|A|AAAA|IN|DNAME)$/.test(a) && b > 1) || b > 2) && a !== (returnObj.ttl ? returnObj.ttl.toString() : undefined)) .join(',') .replace(/,/g, ' ') .replace(/\"/g, '') : rrRecord[rrRecord.length - 1].replace(/\"/g, ''); return returnObj; }; exports.ProcessSRV = async (line) => { const [hostRR, ...rr] = line.trim().split(/\s+/g); const [, service, protocol, host] = /^(_\w+).(_\w+).(\S+)/.exec(hostRR); let returnObj = { host, service, protocol, priority: parseInt(rr[rr.length - 4]), weight: parseInt(rr[rr.length - 3]), port: parseInt(rr[rr.length - 2]), target: rr[rr.length - 1], }; if (!isNaN(parseInt(rr[0])) && rr.length > 1) returnObj.ttl = parseInt(rr[0]); return returnObj; }; exports.ProcessPref = async (line) => { const [hostRR, ...rr] = line.trim().split(/\s+/g); let returnObj = { host: hostRR, preference: parseInt(rr[rr.length - 2]), value: rr[rr.length - 1] }; if (!isNaN(parseInt(rr[0])) && rr.length > 1) returnObj.ttl = parseInt(rr[0]); return returnObj; }; exports.ProcessCAA = async (line) => { const [hostRR, ...rr] = line.trim().split(/\s+/g); const returnOBJ = { host: hostRR, flags: parseInt(rr[rr.length - 3]), tag: rr[rr.length - 2], value: rr[rr.length - 1].replace(/\"/g, '') }; if (!isNaN(parseInt(rr[0])) && rr.length > 1) returnOBJ.ttl = parseInt(rr[0]); return returnOBJ; };