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