--- /dev/null
+'use strict';
+'require uci';
+'require rpc';
+'require tools.prng as random';
+
+
+function initFirewallState() {
+ return uci.load('firewall');
+}
+
+function toArray(val) {
+ if (val == null)
+ return [];
+
+ if (Array.isArray(val))
+ return val;
+
+ var s = String(val).trim();
+
+ if (s == '')
+ return [];
+
+ return s.split(/\s+/);
+}
+
+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';
+
+ random.seed(parseInt(sfh(forName), 16));
+
+ var r = random.get(128),
+ g = random.get(128),
+ min = 0,
+ max = 128;
+
+ if ((r + g) < 128)
+ min = 128 - r - g;
+ else
+ max = 255 - r - g;
+
+ var b = min + Math.floor(random.get() * (max - min));
+
+ return '#%02x%02x%02x'.format(0xff - r, 0xff - g, 0xff - b);
+}
+
+
+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 (this.getZone(name) != null)
+ return null;
+
+ var d = new Defaults(),
+ z = uci.add('firewall', 'zone');
+
+ uci.set('firewall', z, 'name', name);
+ uci.set('firewall', z, 'network', ' ');
+ 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 (toArray(sections[i].network || sections[i].name).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 = zone.name;
+ uci.remove('firewall', zone['.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;
+
+ if (toArray(sections[i].network).length == 0)
+ uci.set('firewall', sections[i]['.name'], 'network', oldName);
+
+ 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
+});
+
+
+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.join(' '));
+
+ 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.join(' '));
+ else
+ this.set('network', ' ');
+
+ return (newNetworks.length < oldNetworks.length);
+ },
+
+ getNetworks: function() {
+ return toArray(this.get('network') || this.get('name'));
+ },
+
+ clearNetworks: function() {
+ this.set('network', ' ');
+ },
+
+ 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 Redirect(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;