firewall.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. 'use strict';
  2. 'require uci';
  3. 'require rpc';
  4. 'require tools.prng as random';
  5. function initFirewallState() {
  6. return L.resolveDefault(uci.load('firewall'));
  7. }
  8. function parseEnum(s, values) {
  9. if (s == null)
  10. return null;
  11. s = String(s).toUpperCase();
  12. if (s == '')
  13. return null;
  14. for (var i = 0; i < values.length; i++)
  15. if (values[i].toUpperCase().indexOf(s) == 0)
  16. return values[i];
  17. return null;
  18. }
  19. function parsePolicy(s, defaultValue) {
  20. return parseEnum(s, ['DROP', 'REJECT', 'ACCEPT']) || (arguments.length < 2 ? null : defaultValue);
  21. }
  22. var Firewall, AbstractFirewallItem, Defaults, Zone, Forwarding, Redirect, Rule;
  23. function lookupZone(name) {
  24. var z = uci.get('firewall', name);
  25. if (z != null && z['.type'] == 'zone')
  26. return new Zone(z['.name']);
  27. var sections = uci.sections('firewall', 'zone');
  28. for (var i = 0; i < sections.length; i++) {
  29. if (sections[i].name != name)
  30. continue;
  31. return new Zone(sections[i]['.name']);
  32. }
  33. return null;
  34. }
  35. function getColorForName(forName) {
  36. if (forName == null)
  37. return '#eeeeee';
  38. else if (forName == 'lan')
  39. return '#90f090';
  40. else if (forName == 'wan')
  41. return '#f09090';
  42. return random.derive_color(forName);
  43. }
  44. Firewall = L.Class.extend({
  45. getDefaults: function() {
  46. return initFirewallState().then(function() {
  47. return new Defaults();
  48. });
  49. },
  50. newZone: function() {
  51. return initFirewallState().then(L.bind(function() {
  52. var name = 'newzone',
  53. count = 1;
  54. while (this.getZone(name) != null)
  55. name = 'newzone%d'.format(++count);
  56. return this.addZone(name);
  57. }, this));
  58. },
  59. addZone: function(name) {
  60. return initFirewallState().then(L.bind(function() {
  61. if (name == null || !/^[a-zA-Z0-9_]+$/.test(name))
  62. return null;
  63. if (lookupZone(name) != null)
  64. return null;
  65. var d = new Defaults(),
  66. z = uci.add('firewall', 'zone');
  67. uci.set('firewall', z, 'name', name);
  68. uci.set('firewall', z, 'input', d.getInput() || 'DROP');
  69. uci.set('firewall', z, 'output', d.getOutput() || 'DROP');
  70. uci.set('firewall', z, 'forward', d.getForward() || 'DROP');
  71. return new Zone(z);
  72. }, this));
  73. },
  74. getZone: function(name) {
  75. return initFirewallState().then(function() {
  76. return lookupZone(name);
  77. });
  78. },
  79. getZones: function() {
  80. return initFirewallState().then(function() {
  81. var sections = uci.sections('firewall', 'zone'),
  82. zones = [];
  83. for (var i = 0; i < sections.length; i++)
  84. zones.push(new Zone(sections[i]['.name']));
  85. zones.sort(function(a, b) { return a.getName() > b.getName() });
  86. return zones;
  87. });
  88. },
  89. getZoneByNetwork: function(network) {
  90. return initFirewallState().then(function() {
  91. var sections = uci.sections('firewall', 'zone');
  92. for (var i = 0; i < sections.length; i++)
  93. if (L.toArray(sections[i].network).indexOf(network) != -1)
  94. return new Zone(sections[i]['.name']);
  95. return null;
  96. });
  97. },
  98. deleteZone: function(name) {
  99. return initFirewallState().then(function() {
  100. var section = uci.get('firewall', name),
  101. found = false;
  102. if (section != null && section['.type'] == 'zone') {
  103. found = true;
  104. name = section.name;
  105. uci.remove('firewall', section['.name']);
  106. }
  107. else if (name != null) {
  108. var sections = uci.sections('firewall', 'zone');
  109. for (var i = 0; i < sections.length; i++) {
  110. if (sections[i].name != name)
  111. continue;
  112. found = true;
  113. uci.remove('firewall', sections[i]['.name']);
  114. }
  115. }
  116. if (found == true) {
  117. sections = uci.sections('firewall');
  118. for (var i = 0; i < sections.length; i++) {
  119. if (sections[i]['.type'] != 'rule' &&
  120. sections[i]['.type'] != 'redirect' &&
  121. sections[i]['.type'] != 'forwarding')
  122. continue;
  123. if (sections[i].src == name || sections[i].dest == name)
  124. uci.remove('firewall', sections[i]['.name']);
  125. }
  126. }
  127. return found;
  128. });
  129. },
  130. renameZone: function(oldName, newName) {
  131. return initFirewallState().then(L.bind(function() {
  132. if (oldName == null || newName == null || !/^[a-zA-Z0-9_]+$/.test(newName))
  133. return false;
  134. if (lookupZone(newName) != null)
  135. return false;
  136. var sections = uci.sections('firewall', 'zone'),
  137. found = false;
  138. for (var i = 0; i < sections.length; i++) {
  139. if (sections[i].name != oldName)
  140. continue;
  141. uci.set('firewall', sections[i]['.name'], 'name', newName);
  142. found = true;
  143. }
  144. if (found == true) {
  145. sections = uci.sections('firewall');
  146. for (var i = 0; i < sections.length; i++) {
  147. if (sections[i]['.type'] != 'rule' &&
  148. sections[i]['.type'] != 'redirect' &&
  149. sections[i]['.type'] != 'forwarding')
  150. continue;
  151. if (sections[i].src == oldName)
  152. uci.set('firewall', sections[i]['.name'], 'src', newName);
  153. if (sections[i].dest == oldName)
  154. uci.set('firewall', sections[i]['.name'], 'dest', newName);
  155. }
  156. }
  157. return found;
  158. }, this));
  159. },
  160. deleteNetwork: function(network) {
  161. return this.getZones().then(L.bind(function(zones) {
  162. var rv = false;
  163. for (var i = 0; i < zones.length; i++)
  164. if (zones[i].deleteNetwork(network))
  165. rv = true;
  166. return rv;
  167. }, this));
  168. },
  169. getColorForName: getColorForName
  170. });
  171. AbstractFirewallItem = L.Class.extend({
  172. get: function(option) {
  173. return uci.get('firewall', this.sid, option);
  174. },
  175. set: function(option, value) {
  176. return uci.set('firewall', this.sid, option, value);
  177. }
  178. });
  179. Defaults = AbstractFirewallItem.extend({
  180. __init__: function() {
  181. var sections = uci.sections('firewall', 'defaults');
  182. for (var i = 0; i < sections.length; i++) {
  183. this.sid = sections[i]['.name'];
  184. break;
  185. }
  186. if (this.sid == null)
  187. this.sid = uci.add('firewall', 'defaults');
  188. },
  189. isSynFlood: function() {
  190. return (this.get('syn_flood') == '1');
  191. },
  192. isDropInvalid: function() {
  193. return (this.get('drop_invalid') == '1');
  194. },
  195. getInput: function() {
  196. return parsePolicy(this.get('input'), 'DROP');
  197. },
  198. getOutput: function() {
  199. return parsePolicy(this.get('output'), 'DROP');
  200. },
  201. getForward: function() {
  202. return parsePolicy(this.get('forward'), 'DROP');
  203. }
  204. });
  205. Zone = AbstractFirewallItem.extend({
  206. __init__: function(name) {
  207. var section = uci.get('firewall', name);
  208. if (section != null && section['.type'] == 'zone') {
  209. this.sid = name;
  210. this.data = section;
  211. }
  212. else if (name != null) {
  213. var sections = uci.get('firewall', 'zone');
  214. for (var i = 0; i < sections.length; i++) {
  215. if (sections[i].name != name)
  216. continue;
  217. this.sid = sections[i]['.name'];
  218. this.data = sections[i];
  219. break;
  220. }
  221. }
  222. },
  223. isMasquerade: function() {
  224. return (this.get('masq') == '1');
  225. },
  226. getName: function() {
  227. return this.get('name');
  228. },
  229. getNetwork: function() {
  230. return this.get('network');
  231. },
  232. getInput: function() {
  233. return parsePolicy(this.get('input'), (new Defaults()).getInput());
  234. },
  235. getOutput: function() {
  236. return parsePolicy(this.get('output'), (new Defaults()).getOutput());
  237. },
  238. getForward: function() {
  239. return parsePolicy(this.get('forward'), (new Defaults()).getForward());
  240. },
  241. addNetwork: function(network) {
  242. var section = uci.get('network', network);
  243. if (section == null || section['.type'] != 'interface')
  244. return false;
  245. var newNetworks = this.getNetworks();
  246. if (newNetworks.filter(function(net) { return net == network }).length)
  247. return false;
  248. newNetworks.push(network);
  249. this.set('network', newNetworks);
  250. return true;
  251. },
  252. deleteNetwork: function(network) {
  253. var oldNetworks = this.getNetworks(),
  254. newNetworks = oldNetworks.filter(function(net) { return net != network });
  255. if (newNetworks.length > 0)
  256. this.set('network', newNetworks);
  257. else
  258. this.set('network', null);
  259. return (newNetworks.length < oldNetworks.length);
  260. },
  261. getNetworks: function() {
  262. return L.toArray(this.get('network'));
  263. },
  264. clearNetworks: function() {
  265. this.set('network', null);
  266. },
  267. getDevices: function() {
  268. return L.toArray(this.get('device'));
  269. },
  270. getSubnets: function() {
  271. return L.toArray(this.get('subnet'));
  272. },
  273. getForwardingsBy: function(what) {
  274. var sections = uci.sections('firewall', 'forwarding'),
  275. forwards = [];
  276. for (var i = 0; i < sections.length; i++) {
  277. if (sections[i].src == null || sections[i].dest == null)
  278. continue;
  279. if (sections[i][what] != this.getName())
  280. continue;
  281. forwards.push(new Forwarding(sections[i]['.name']));
  282. }
  283. return forwards;
  284. },
  285. addForwardingTo: function(dest) {
  286. var forwards = this.getForwardingsBy('src'),
  287. zone = lookupZone(dest);
  288. if (zone == null || zone.getName() == this.getName())
  289. return null;
  290. for (var i = 0; i < forwards.length; i++)
  291. if (forwards[i].getDestination() == zone.getName())
  292. return null;
  293. var sid = uci.add('firewall', 'forwarding');
  294. uci.set('firewall', sid, 'src', this.getName());
  295. uci.set('firewall', sid, 'dest', zone.getName());
  296. return new Forwarding(sid);
  297. },
  298. addForwardingFrom: function(src) {
  299. var forwards = this.getForwardingsBy('dest'),
  300. zone = lookupZone(src);
  301. if (zone == null || zone.getName() == this.getName())
  302. return null;
  303. for (var i = 0; i < forwards.length; i++)
  304. if (forwards[i].getSource() == zone.getName())
  305. return null;
  306. var sid = uci.add('firewall', 'forwarding');
  307. uci.set('firewall', sid, 'src', zone.getName());
  308. uci.set('firewall', sid, 'dest', this.getName());
  309. return new Forwarding(sid);
  310. },
  311. deleteForwardingsBy: function(what) {
  312. var sections = uci.sections('firewall', 'forwarding'),
  313. found = false;
  314. for (var i = 0; i < sections.length; i++) {
  315. if (sections[i].src == null || sections[i].dest == null)
  316. continue;
  317. if (sections[i][what] != this.getName())
  318. continue;
  319. uci.remove('firewall', sections[i]['.name']);
  320. found = true;
  321. }
  322. return found;
  323. },
  324. deleteForwarding: function(forwarding) {
  325. if (!(forwarding instanceof Forwarding))
  326. return false;
  327. var section = uci.get('firewall', forwarding.sid);
  328. if (!section || section['.type'] != 'forwarding')
  329. return false;
  330. uci.remove('firewall', section['.name']);
  331. return true;
  332. },
  333. addRedirect: function(options) {
  334. var sid = uci.add('firewall', 'redirect');
  335. if (options != null && typeof(options) == 'object')
  336. for (var key in options)
  337. if (options.hasOwnProperty(key))
  338. uci.set('firewall', sid, key, options[key]);
  339. uci.set('firewall', sid, 'src', this.getName());
  340. return new Redirect(sid);
  341. },
  342. addRule: function(options) {
  343. var sid = uci.add('firewall', 'rule');
  344. if (options != null && typeof(options) == 'object')
  345. for (var key in options)
  346. if (options.hasOwnProperty(key))
  347. uci.set('firewall', sid, key, options[key]);
  348. uci.set('firewall', sid, 'src', this.getName());
  349. return new Rule(sid);
  350. },
  351. getColor: function(forName) {
  352. var name = (arguments.length > 0 ? forName : this.getName());
  353. return getColorForName(name);
  354. }
  355. });
  356. Forwarding = AbstractFirewallItem.extend({
  357. __init__: function(sid) {
  358. this.sid = sid;
  359. },
  360. getSource: function() {
  361. return this.get('src');
  362. },
  363. getDestination: function() {
  364. return this.get('dest');
  365. },
  366. getSourceZone: function() {
  367. return lookupZone(this.getSource());
  368. },
  369. getDestinationZone: function() {
  370. return lookupZone(this.getDestination());
  371. }
  372. });
  373. Rule = AbstractFirewallItem.extend({
  374. getSource: function() {
  375. return this.get('src');
  376. },
  377. getDestination: function() {
  378. return this.get('dest');
  379. },
  380. getSourceZone: function() {
  381. return lookupZone(this.getSource());
  382. },
  383. getDestinationZone: function() {
  384. return lookupZone(this.getDestination());
  385. }
  386. });
  387. Redirect = AbstractFirewallItem.extend({
  388. getSource: function() {
  389. return this.get('src');
  390. },
  391. getDestination: function() {
  392. return this.get('dest');
  393. },
  394. getSourceZone: function() {
  395. return lookupZone(this.getSource());
  396. },
  397. getDestinationZone: function() {
  398. return lookupZone(this.getDestination());
  399. }
  400. });
  401. return Firewall;