luci-base: firewall.js: gracefully handle missing uci configuration
[oweals/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 Redirect(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;