'use strict'; 'require uci'; 'require rpc'; 'require tools.prng as random'; function initFirewallState() { return L.resolveDefault(uci.load('firewall')); } function parseEnum(s, values) { if (s == null) return null; s = String(s).toUpperCase(); if (s == '') return null; for (var i = 0; i < values.length; i++) if (values[i].toUpperCase().indexOf(s) == 0) return values[i]; return null; } function parsePolicy(s, defaultValue) { return parseEnum(s, ['DROP', 'REJECT', 'ACCEPT']) || (arguments.length < 2 ? null : defaultValue); } var Firewall, AbstractFirewallItem, Defaults, Zone, Forwarding, Redirect, Rule; function lookupZone(name) { var z = uci.get('firewall', name); if (z != null && z['.type'] == 'zone') return new Zone(z['.name']); var sections = uci.sections('firewall', 'zone'); for (var i = 0; i < sections.length; i++) { if (sections[i].name != name) continue; return new Zone(sections[i]['.name']); } return null; } function getColorForName(forName) { if (forName == null) return '#eeeeee'; else if (forName == 'lan') return '#90f090'; else if (forName == 'wan') return '#f09090'; return random.derive_color(forName); } Firewall = L.Class.extend({ getDefaults: function() { return initFirewallState().then(function() { return new Defaults(); }); }, newZone: function() { return initFirewallState().then(L.bind(function() { var name = 'newzone', count = 1; while (this.getZone(name) != null) name = 'newzone%d'.format(++count); return this.addZone(name); }, this)); }, addZone: function(name) { return initFirewallState().then(L.bind(function() { if (name == null || !/^[a-zA-Z0-9_]+$/.test(name)) return null; if (lookupZone(name) != null) return null; var d = new Defaults(), z = uci.add('firewall', 'zone'); uci.set('firewall', z, 'name', name); uci.set('firewall', z, 'input', d.getInput() || 'DROP'); uci.set('firewall', z, 'output', d.getOutput() || 'DROP'); uci.set('firewall', z, 'forward', d.getForward() || 'DROP'); return new Zone(z); }, this)); }, getZone: function(name) { return initFirewallState().then(function() { return lookupZone(name); }); }, getZones: function() { return initFirewallState().then(function() { var sections = uci.sections('firewall', 'zone'), zones = []; for (var i = 0; i < sections.length; i++) zones.push(new Zone(sections[i]['.name'])); zones.sort(function(a, b) { return a.getName() > b.getName() }); return zones; }); }, getZoneByNetwork: function(network) { return initFirewallState().then(function() { var sections = uci.sections('firewall', 'zone'); for (var i = 0; i < sections.length; i++) if (L.toArray(sections[i].network).indexOf(network) != -1) return new Zone(sections[i]['.name']); return null; }); }, deleteZone: function(name) { return initFirewallState().then(function() { var section = uci.get('firewall', name), found = false; if (section != null && section['.type'] == 'zone') { found = true; name = section.name; uci.remove('firewall', section['.name']); } else if (name != null) { var sections = uci.sections('firewall', 'zone'); for (var i = 0; i < sections.length; i++) { if (sections[i].name != name) continue; found = true; uci.remove('firewall', sections[i]['.name']); } } if (found == true) { sections = uci.sections('firewall'); for (var i = 0; i < sections.length; i++) { if (sections[i]['.type'] != 'rule' && sections[i]['.type'] != 'redirect' && sections[i]['.type'] != 'forwarding') continue; if (sections[i].src == name || sections[i].dest == name) uci.remove('firewall', sections[i]['.name']); } } return found; }); }, renameZone: function(oldName, newName) { return initFirewallState().then(L.bind(function() { if (oldName == null || newName == null || !/^[a-zA-Z0-9_]+$/.test(newName)) return false; if (lookupZone(newName) != null) return false; var sections = uci.sections('firewall', 'zone'), found = false; for (var i = 0; i < sections.length; i++) { if (sections[i].name != oldName) continue; uci.set('firewall', sections[i]['.name'], 'name', newName); found = true; } if (found == true) { sections = uci.sections('firewall'); for (var i = 0; i < sections.length; i++) { if (sections[i]['.type'] != 'rule' && sections[i]['.type'] != 'redirect' && sections[i]['.type'] != 'forwarding') continue; if (sections[i].src == oldName) uci.set('firewall', sections[i]['.name'], 'src', newName); if (sections[i].dest == oldName) uci.set('firewall', sections[i]['.name'], 'dest', newName); } } return found; }, this)); }, deleteNetwork: function(network) { return this.getZones().then(L.bind(function(zones) { var rv = false; for (var i = 0; i < zones.length; i++) if (zones[i].deleteNetwork(network)) rv = true; return rv; }, this)); }, getColorForName: getColorForName, getZoneColorStyle: function(zone) { var hex = (zone instanceof Zone) ? zone.getColor() : getColorForName((zone != null && zone != '*') ? zone : null); return '--zone-color-rgb:%d, %d, %d; background-color:rgb(var(--zone-color-rgb))'.format( parseInt(hex.substring(1, 3), 16), parseInt(hex.substring(3, 5), 16), parseInt(hex.substring(5, 7), 16) ); }, }); AbstractFirewallItem = L.Class.extend({ get: function(option) { return uci.get('firewall', this.sid, option); }, set: function(option, value) { return uci.set('firewall', this.sid, option, value); } }); Defaults = AbstractFirewallItem.extend({ __init__: function() { var sections = uci.sections('firewall', 'defaults'); for (var i = 0; i < sections.length; i++) { this.sid = sections[i]['.name']; break; } if (this.sid == null) this.sid = uci.add('firewall', 'defaults'); }, isSynFlood: function() { return (this.get('syn_flood') == '1'); }, isDropInvalid: function() { return (this.get('drop_invalid') == '1'); }, getInput: function() { return parsePolicy(this.get('input'), 'DROP'); }, getOutput: function() { return parsePolicy(this.get('output'), 'DROP'); }, getForward: function() { return parsePolicy(this.get('forward'), 'DROP'); } }); Zone = AbstractFirewallItem.extend({ __init__: function(name) { var section = uci.get('firewall', name); if (section != null && section['.type'] == 'zone') { this.sid = name; this.data = section; } else if (name != null) { var sections = uci.get('firewall', 'zone'); for (var i = 0; i < sections.length; i++) { if (sections[i].name != name) continue; this.sid = sections[i]['.name']; this.data = sections[i]; break; } } }, isMasquerade: function() { return (this.get('masq') == '1'); }, getName: function() { return this.get('name'); }, getNetwork: function() { return this.get('network'); }, getInput: function() { return parsePolicy(this.get('input'), (new Defaults()).getInput()); }, getOutput: function() { return parsePolicy(this.get('output'), (new Defaults()).getOutput()); }, getForward: function() { return parsePolicy(this.get('forward'), (new Defaults()).getForward()); }, addNetwork: function(network) { var section = uci.get('network', network); if (section == null || section['.type'] != 'interface') return false; var newNetworks = this.getNetworks(); if (newNetworks.filter(function(net) { return net == network }).length) return false; newNetworks.push(network); this.set('network', newNetworks); return true; }, deleteNetwork: function(network) { var oldNetworks = this.getNetworks(), newNetworks = oldNetworks.filter(function(net) { return net != network }); if (newNetworks.length > 0) this.set('network', newNetworks); else this.set('network', null); return (newNetworks.length < oldNetworks.length); }, getNetworks: function() { return L.toArray(this.get('network')); }, clearNetworks: function() { this.set('network', null); }, getDevices: function() { return L.toArray(this.get('device')); }, getSubnets: function() { return L.toArray(this.get('subnet')); }, getForwardingsBy: function(what) { var sections = uci.sections('firewall', 'forwarding'), forwards = []; for (var i = 0; i < sections.length; i++) { if (sections[i].src == null || sections[i].dest == null) continue; if (sections[i][what] != this.getName()) continue; forwards.push(new Forwarding(sections[i]['.name'])); } return forwards; }, addForwardingTo: function(dest) { var forwards = this.getForwardingsBy('src'), zone = lookupZone(dest); if (zone == null || zone.getName() == this.getName()) return null; for (var i = 0; i < forwards.length; i++) if (forwards[i].getDestination() == zone.getName()) return null; var sid = uci.add('firewall', 'forwarding'); uci.set('firewall', sid, 'src', this.getName()); uci.set('firewall', sid, 'dest', zone.getName()); return new Forwarding(sid); }, addForwardingFrom: function(src) { var forwards = this.getForwardingsBy('dest'), zone = lookupZone(src); if (zone == null || zone.getName() == this.getName()) return null; for (var i = 0; i < forwards.length; i++) if (forwards[i].getSource() == zone.getName()) return null; var sid = uci.add('firewall', 'forwarding'); uci.set('firewall', sid, 'src', zone.getName()); uci.set('firewall', sid, 'dest', this.getName()); return new Forwarding(sid); }, deleteForwardingsBy: function(what) { var sections = uci.sections('firewall', 'forwarding'), found = false; for (var i = 0; i < sections.length; i++) { if (sections[i].src == null || sections[i].dest == null) continue; if (sections[i][what] != this.getName()) continue; uci.remove('firewall', sections[i]['.name']); found = true; } return found; }, deleteForwarding: function(forwarding) { if (!(forwarding instanceof Forwarding)) return false; var section = uci.get('firewall', forwarding.sid); if (!section || section['.type'] != 'forwarding') return false; uci.remove('firewall', section['.name']); return true; }, addRedirect: function(options) { var sid = uci.add('firewall', 'redirect'); if (options != null && typeof(options) == 'object') for (var key in options) if (options.hasOwnProperty(key)) uci.set('firewall', sid, key, options[key]); uci.set('firewall', sid, 'src', this.getName()); return new Redirect(sid); }, addRule: function(options) { var sid = uci.add('firewall', 'rule'); if (options != null && typeof(options) == 'object') for (var key in options) if (options.hasOwnProperty(key)) uci.set('firewall', sid, key, options[key]); uci.set('firewall', sid, 'src', this.getName()); return new Rule(sid); }, getColor: function(forName) { var name = (arguments.length > 0 ? forName : this.getName()); return getColorForName(name); } }); Forwarding = AbstractFirewallItem.extend({ __init__: function(sid) { this.sid = sid; }, getSource: function() { return this.get('src'); }, getDestination: function() { return this.get('dest'); }, getSourceZone: function() { return lookupZone(this.getSource()); }, getDestinationZone: function() { return lookupZone(this.getDestination()); } }); Rule = AbstractFirewallItem.extend({ getSource: function() { return this.get('src'); }, getDestination: function() { return this.get('dest'); }, getSourceZone: function() { return lookupZone(this.getSource()); }, getDestinationZone: function() { return lookupZone(this.getDestination()); } }); Redirect = AbstractFirewallItem.extend({ getSource: function() { return this.get('src'); }, getDestination: function() { return this.get('dest'); }, getSourceZone: function() { return lookupZone(this.getSource()); }, getDestinationZone: function() { return lookupZone(this.getDestination()); } }); return Firewall;