luci-base: firewall.js: fix Zone.addRule()
[project/luci.git] / modules / luci-base / htdocs / luci-static / resources / firewall.js
1 'use strict';
2 'require uci';
3 'require rpc';
4 'require tools.prng as random';
5
6
7 function initFirewallState() {
8 return L.resolveDefault(uci.load('firewall'));
9 }
10
11 function parseEnum(s, values) {
12 if (s == null)
13 return null;
14
15 s = String(s).toUpperCase();
16
17 if (s == '')
18 return null;
19
20 for (var i = 0; i < values.length; i++)
21 if (values[i].toUpperCase().indexOf(s) == 0)
22 return values[i];
23
24 return null;
25 }
26
27 function parsePolicy(s, defaultValue) {
28 return parseEnum(s, ['DROP', 'REJECT', 'ACCEPT']) || (arguments.length < 2 ? null : defaultValue);
29 }
30
31
32 var Firewall, AbstractFirewallItem, Defaults, Zone, Forwarding, Redirect, Rule;
33
34 function lookupZone(name) {
35 var z = uci.get('firewall', name);
36
37 if (z != null && z['.type'] == 'zone')
38 return new Zone(z['.name']);
39
40 var sections = uci.sections('firewall', 'zone');
41
42 for (var i = 0; i < sections.length; i++) {
43 if (sections[i].name != name)
44 continue;
45
46 return new Zone(sections[i]['.name']);
47 }
48
49 return null;
50 }
51
52 function getColorForName(forName) {
53 if (forName == null)
54 return '#eeeeee';
55 else if (forName == 'lan')
56 return '#90f090';
57 else if (forName == 'wan')
58 return '#f09090';
59
60 random.seed(parseInt(sfh(forName), 16));
61
62 var r = random.get(128),
63 g = random.get(128),
64 min = 0,
65 max = 128;
66
67 if ((r + g) < 128)
68 min = 128 - r - g;
69 else
70 max = 255 - r - g;
71
72 var b = min + Math.floor(random.get() * (max - min));
73
74 return '#%02x%02x%02x'.format(0xff - r, 0xff - g, 0xff - b);
75 }
76
77
78 Firewall = L.Class.extend({
79 getDefaults: function() {
80 return initFirewallState().then(function() {
81 return new Defaults();
82 });
83 },
84
85 newZone: function() {
86 return initFirewallState().then(L.bind(function() {
87 var name = 'newzone',
88 count = 1;
89
90 while (this.getZone(name) != null)
91 name = 'newzone%d'.format(++count);
92
93 return this.addZone(name);
94 }, this));
95 },
96
97 addZone: function(name) {
98 return initFirewallState().then(L.bind(function() {
99 if (name == null || !/^[a-zA-Z0-9_]+$/.test(name))
100 return null;
101
102 if (lookupZone(name) != null)
103 return null;
104
105 var d = new Defaults(),
106 z = uci.add('firewall', 'zone');
107
108 uci.set('firewall', z, 'name', name);
109 uci.set('firewall', z, 'network', ' ');
110 uci.set('firewall', z, 'input', d.getInput() || 'DROP');
111 uci.set('firewall', z, 'output', d.getOutput() || 'DROP');
112 uci.set('firewall', z, 'forward', d.getForward() || 'DROP');
113
114 return new Zone(z);
115 }, this));
116 },
117
118 getZone: function(name) {
119 return initFirewallState().then(function() {
120 return lookupZone(name);
121 });
122 },
123
124 getZones: function() {
125 return initFirewallState().then(function() {
126 var sections = uci.sections('firewall', 'zone'),
127 zones = [];
128
129 for (var i = 0; i < sections.length; i++)
130 zones.push(new Zone(sections[i]['.name']));
131
132 zones.sort(function(a, b) { return a.getName() > b.getName() });
133
134 return zones;
135 });
136 },
137
138 getZoneByNetwork: function(network) {
139 return initFirewallState().then(function() {
140 var sections = uci.sections('firewall', 'zone');
141
142 for (var i = 0; i < sections.length; i++)
143 if (L.toArray(sections[i].network || sections[i].name).indexOf(network) != -1)
144 return new Zone(sections[i]['.name']);
145
146 return null;
147 });
148 },
149
150 deleteZone: function(name) {
151 return initFirewallState().then(function() {
152 var section = uci.get('firewall', name),
153 found = false;
154
155 if (section != null && section['.type'] == 'zone') {
156 found = true;
157 name = section.name;
158 uci.remove('firewall', section['.name']);
159 }
160 else if (name != null) {
161 var sections = uci.sections('firewall', 'zone');
162
163 for (var i = 0; i < sections.length; i++) {
164 if (sections[i].name != name)
165 continue;
166
167 found = true;
168 uci.remove('firewall', sections[i]['.name']);
169 }
170 }
171
172 if (found == true) {
173 sections = uci.sections('firewall');
174
175 for (var i = 0; i < sections.length; i++) {
176 if (sections[i]['.type'] != 'rule' &&
177 sections[i]['.type'] != 'redirect' &&
178 sections[i]['.type'] != 'forwarding')
179 continue;
180
181 if (sections[i].src == name || sections[i].dest == name)
182 uci.remove('firewall', sections[i]['.name']);
183 }
184 }
185
186 return found;
187 });
188 },
189
190 renameZone: function(oldName, newName) {
191 return initFirewallState().then(L.bind(function() {
192 if (oldName == null || newName == null || !/^[a-zA-Z0-9_]+$/.test(newName))
193 return false;
194
195 if (lookupZone(newName) != null)
196 return false;
197
198 var sections = uci.sections('firewall', 'zone'),
199 found = false;
200
201 for (var i = 0; i < sections.length; i++) {
202 if (sections[i].name != oldName)
203 continue;
204
205 if (L.toArray(sections[i].network).length == 0)
206 uci.set('firewall', sections[i]['.name'], 'network', oldName);
207
208 uci.set('firewall', sections[i]['.name'], 'name', newName);
209 found = true;
210 }
211
212 if (found == true) {
213 sections = uci.sections('firewall');
214
215 for (var i = 0; i < sections.length; i++) {
216 if (sections[i]['.type'] != 'rule' &&
217 sections[i]['.type'] != 'redirect' &&
218 sections[i]['.type'] != 'forwarding')
219 continue;
220
221 if (sections[i].src == oldName)
222 uci.set('firewall', sections[i]['.name'], 'src', newName);
223
224 if (sections[i].dest == oldName)
225 uci.set('firewall', sections[i]['.name'], 'dest', newName);
226 }
227 }
228
229 return found;
230 }, this));
231 },
232
233 deleteNetwork: function(network) {
234 return this.getZones().then(L.bind(function(zones) {
235 var rv = false;
236
237 for (var i = 0; i < zones.length; i++)
238 if (zones[i].deleteNetwork(network))
239 rv = true;
240
241 return rv;
242 }, this));
243 },
244
245 getColorForName: getColorForName
246 });
247
248
249 AbstractFirewallItem = L.Class.extend({
250 get: function(option) {
251 return uci.get('firewall', this.sid, option);
252 },
253
254 set: function(option, value) {
255 return uci.set('firewall', this.sid, option, value);
256 }
257 });
258
259
260 Defaults = AbstractFirewallItem.extend({
261 __init__: function() {
262 var sections = uci.sections('firewall', 'defaults');
263
264 for (var i = 0; i < sections.length; i++) {
265 this.sid = sections[i]['.name'];
266 break;
267 }
268
269 if (this.sid == null)
270 this.sid = uci.add('firewall', 'defaults');
271 },
272
273 isSynFlood: function() {
274 return (this.get('syn_flood') == '1');
275 },
276
277 isDropInvalid: function() {
278 return (this.get('drop_invalid') == '1');
279 },
280
281 getInput: function() {
282 return parsePolicy(this.get('input'), 'DROP');
283 },
284
285 getOutput: function() {
286 return parsePolicy(this.get('output'), 'DROP');
287 },
288
289 getForward: function() {
290 return parsePolicy(this.get('forward'), 'DROP');
291 }
292 });
293
294
295 Zone = AbstractFirewallItem.extend({
296 __init__: function(name) {
297 var section = uci.get('firewall', name);
298
299 if (section != null && section['.type'] == 'zone') {
300 this.sid = name;
301 this.data = section;
302 }
303 else if (name != null) {
304 var sections = uci.get('firewall', 'zone');
305
306 for (var i = 0; i < sections.length; i++) {
307 if (sections[i].name != name)
308 continue;
309
310 this.sid = sections[i]['.name'];
311 this.data = sections[i];
312 break;
313 }
314 }
315 },
316
317 isMasquerade: function() {
318 return (this.get('masq') == '1');
319 },
320
321 getName: function() {
322 return this.get('name');
323 },
324
325 getNetwork: function() {
326 return this.get('network');
327 },
328
329 getInput: function() {
330 return parsePolicy(this.get('input'), (new Defaults()).getInput());
331 },
332
333 getOutput: function() {
334 return parsePolicy(this.get('output'), (new Defaults()).getOutput());
335 },
336
337 getForward: function() {
338 return parsePolicy(this.get('forward'), (new Defaults()).getForward());
339 },
340
341 addNetwork: function(network) {
342 var section = uci.get('network', network);
343
344 if (section == null || section['.type'] != 'interface')
345 return false;
346
347 var newNetworks = this.getNetworks();
348
349 if (newNetworks.filter(function(net) { return net == network }).length)
350 return false;
351
352 newNetworks.push(network);
353 this.set('network', newNetworks.join(' '));
354
355 return true;
356 },
357
358 deleteNetwork: function(network) {
359 var oldNetworks = this.getNetworks(),
360 newNetworks = oldNetworks.filter(function(net) { return net != network });
361
362 if (newNetworks.length > 0)
363 this.set('network', newNetworks.join(' '));
364 else
365 this.set('network', null);
366
367 return (newNetworks.length < oldNetworks.length);
368 },
369
370 getNetworks: function() {
371 return L.toArray(this.get('network'));
372 },
373
374 clearNetworks: function() {
375 this.set('network', ' ');
376 },
377
378 getDevices: function() {
379 return L.toArray(this.get('device'));
380 },
381
382 getSubnets: function() {
383 return L.toArray(this.get('subnet'));
384 },
385
386 getForwardingsBy: function(what) {
387 var sections = uci.sections('firewall', 'forwarding'),
388 forwards = [];
389
390 for (var i = 0; i < sections.length; i++) {
391 if (sections[i].src == null || sections[i].dest == null)
392 continue;
393
394 if (sections[i][what] != this.getName())
395 continue;
396
397 forwards.push(new Forwarding(sections[i]['.name']));
398 }
399
400 return forwards;
401 },
402
403 addForwardingTo: function(dest) {
404 var forwards = this.getForwardingsBy('src'),
405 zone = lookupZone(dest);
406
407 if (zone == null || zone.getName() == this.getName())
408 return null;
409
410 for (var i = 0; i < forwards.length; i++)
411 if (forwards[i].getDestination() == zone.getName())
412 return null;
413
414 var sid = uci.add('firewall', 'forwarding');
415
416 uci.set('firewall', sid, 'src', this.getName());
417 uci.set('firewall', sid, 'dest', zone.getName());
418
419 return new Forwarding(sid);
420 },
421
422 addForwardingFrom: function(src) {
423 var forwards = this.getForwardingsBy('dest'),
424 zone = lookupZone(src);
425
426 if (zone == null || zone.getName() == this.getName())
427 return null;
428
429 for (var i = 0; i < forwards.length; i++)
430 if (forwards[i].getSource() == zone.getName())
431 return null;
432
433 var sid = uci.add('firewall', 'forwarding');
434
435 uci.set('firewall', sid, 'src', zone.getName());
436 uci.set('firewall', sid, 'dest', this.getName());
437
438 return new Forwarding(sid);
439 },
440
441 deleteForwardingsBy: function(what) {
442 var sections = uci.sections('firewall', 'forwarding'),
443 found = false;
444
445 for (var i = 0; i < sections.length; i++) {
446 if (sections[i].src == null || sections[i].dest == null)
447 continue;
448
449 if (sections[i][what] != this.getName())
450 continue;
451
452 uci.remove('firewall', sections[i]['.name']);
453 found = true;
454 }
455
456 return found;
457 },
458
459 deleteForwarding: function(forwarding) {
460 if (!(forwarding instanceof Forwarding))
461 return false;
462
463 var section = uci.get('firewall', forwarding.sid);
464
465 if (!section || section['.type'] != 'forwarding')
466 return false;
467
468 uci.remove('firewall', section['.name']);
469
470 return true;
471 },
472
473 addRedirect: function(options) {
474 var sid = uci.add('firewall', 'redirect');
475
476 if (options != null && typeof(options) == 'object')
477 for (var key in options)
478 if (options.hasOwnProperty(key))
479 uci.set('firewall', sid, key, options[key]);
480
481 uci.set('firewall', sid, 'src', this.getName());
482
483 return new Redirect(sid);
484 },
485
486 addRule: function(options) {
487 var sid = uci.add('firewall', 'rule');
488
489 if (options != null && typeof(options) == 'object')
490 for (var key in options)
491 if (options.hasOwnProperty(key))
492 uci.set('firewall', sid, key, options[key]);
493
494 uci.set('firewall', sid, 'src', this.getName());
495
496 return new Rule(sid);
497 },
498
499 getColor: function(forName) {
500 var name = (arguments.length > 0 ? forName : this.getName());
501
502 return getColorForName(name);
503 }
504 });
505
506
507 Forwarding = AbstractFirewallItem.extend({
508 __init__: function(sid) {
509 this.sid = sid;
510 },
511
512 getSource: function() {
513 return this.get('src');
514 },
515
516 getDestination: function() {
517 return this.get('dest');
518 },
519
520 getSourceZone: function() {
521 return lookupZone(this.getSource());
522 },
523
524 getDestinationZone: function() {
525 return lookupZone(this.getDestination());
526 }
527 });
528
529
530 Rule = AbstractFirewallItem.extend({
531 getSource: function() {
532 return this.get('src');
533 },
534
535 getDestination: function() {
536 return this.get('dest');
537 },
538
539 getSourceZone: function() {
540 return lookupZone(this.getSource());
541 },
542
543 getDestinationZone: function() {
544 return lookupZone(this.getDestination());
545 }
546 });
547
548
549 Redirect = AbstractFirewallItem.extend({
550 getSource: function() {
551 return this.get('src');
552 },
553
554 getDestination: function() {
555 return this.get('dest');
556 },
557
558 getSourceZone: function() {
559 return lookupZone(this.getSource());
560 },
561
562 getDestinationZone: function() {
563 return lookupZone(this.getDestination());
564 }
565 });
566
567
568 return Firewall;