1 /*
2
3 Firewall Builder
4
5 Copyright (C) 2009 NetCitadel, LLC
6
7 Author: Vadim Kurland vadim@vk.crocodile.org
8
9 $Id$
10
11 This program is free software which we release under the GNU General Public
12 License. You may redistribute and/or modify this program under the terms
13 of that license as published by the Free Software Foundation; either
14 version 2 of the License, or (at your option) any later version.
15
16 This program is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License for more details.
20
21 To get a copy of the GNU General Public License, write to the Free Software
22 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23
24 */
25
26 #include "../../config.h"
27
28 #include <fstream>
29 #include <iostream>
30 #include <iomanip>
31 #include <set>
32 #include <algorithm>
33 #include <functional>
34 #include <memory>
35
36 // for chdir
37 #ifndef _WIN32
38 # include <unistd.h>
39 #else
40 # include <direct.h>
41 # include <stdlib.h>
42 # include <io.h>
43 #endif
44
45 #include "CompilerDriver.h"
46 #include "interfaceProperties.h"
47 #include "interfacePropertiesObjectFactory.h"
48
49 #include "fwbuilder/Cluster.h"
50 #include "fwbuilder/ClusterGroup.h"
51 #include "fwbuilder/FWException.h"
52 #include "fwbuilder/FWObject.h"
53 #include "fwbuilder/FWObjectDatabase.h"
54 #include "fwbuilder/FailoverClusterGroup.h"
55 #include "fwbuilder/Firewall.h"
56 #include "fwbuilder/IPv4.h"
57 #include "fwbuilder/IPv6.h"
58 #include "fwbuilder/Interface.h"
59 #include "fwbuilder/Library.h"
60 #include "fwbuilder/NAT.h"
61 #include "fwbuilder/Policy.h"
62 #include "fwbuilder/Resources.h"
63 #include "fwbuilder/Routing.h"
64 #include "fwbuilder/Rule.h"
65 #include "fwbuilder/StateSyncClusterGroup.h"
66
67 #include "fwcompiler/Compiler.h"
68
69 #include <QStringList>
70 #include <QtDebug>
71
72
73 using namespace std;
74 using namespace libfwbuilder;
75 using namespace fwcompiler;
76
77
CompilerDriver(FWObjectDatabase * db)78 CompilerDriver::CompilerDriver(FWObjectDatabase *db) : BaseCompiler()
79 {
80 fwbdebug = 0;
81 filename = "";
82 wdir = "";
83 fwobjectname = "";
84 single_rule_compile_on = false;
85 prepend_cluster_name_to_output_file = false;
86 dl = 0;
87 rule_debug_on = false;
88 drp = -1;
89 drn = -1;
90 drr = -1;
91 verbose = 0;
92 have_dynamic_interfaces = false;
93 ipv4_run = true;
94 ipv6_run = true;
95 fw_by_id = false;
96
97 objdb = new FWObjectDatabase(*db);
98 objdb->setIgnoreReadOnlyFlag(true);
99
100 //objdb = db;
101
102 persistent_objects = new Library();
103 persistent_objects->setName("Persistent Objects");
104 objdb->add(persistent_objects);
105
106 workspace = new Library();
107 workspace->setName("Workspace");
108 objdb->add(workspace);
109
110 prolog_done = false;
111 epilog_done = false;
112 have_filter = false;
113 have_nat = false;
114 start_current_dir = QDir::current();
115 }
116
~CompilerDriver()117 CompilerDriver::~CompilerDriver()
118 {
119 if (persistent_objects->getParent() == NULL)
120 delete persistent_objects;
121 else
122 {
123 if (persistent_objects->getParent() == objdb)
124 {
125 objdb->remove(persistent_objects, false);
126 delete persistent_objects;
127 }
128 }
129
130 if (workspace->getParent() == NULL)
131 delete workspace;
132 else
133 {
134 if (workspace->getParent() == objdb)
135 {
136 objdb->remove(workspace, false);
137 delete workspace;
138 }
139 }
140
141 delete objdb;
142 }
143
144 // create a copy of itself, including objdb
clone()145 CompilerDriver* CompilerDriver::clone()
146 {
147 CompilerDriver* new_cd = new CompilerDriver(objdb);
148 if (inEmbeddedMode()) new_cd->setEmbeddedMode();
149 return new_cd;
150 }
151
configure(const QStringList & args)152 bool CompilerDriver::configure(const QStringList &args)
153 {
154 QString last_arg;
155 for (int idx=0; idx < args.size(); idx++)
156 {
157 QString arg = args.at(idx);
158
159 last_arg = arg;
160 if (arg == "-i")
161 {
162 fw_by_id = true;
163 continue;
164 }
165 if (arg == "-v")
166 {
167 verbose++;
168 continue;
169 }
170
171 if (arg == "-4")
172 {
173 ipv4_run = true;
174 ipv6_run = false;
175 continue;
176 }
177 if (arg == "-6")
178 {
179 ipv4_run = false;
180 ipv6_run = true;
181 continue;
182 }
183 if (arg == "-d")
184 {
185 // TODO: deal with UTF-8 in directory name
186 idx++;
187 wdir = string(args.at(idx).toLatin1().constData());
188 continue;
189 }
190 if (arg == "-D")
191 {
192 idx++;
193 FWObject::setDataDir(args.at(idx).toUtf8().constData());
194 continue;
195 }
196 if (arg == "-f")
197 {
198 idx++;
199 filename = string(args.at(idx).toLatin1().constData());
200 continue;
201 }
202 if (arg == "-o")
203 {
204 idx++;
205 file_name_setting_from_command_line = args.at(idx);
206 continue;
207 }
208 if (arg == "-O")
209 {
210 // parameter is ',' separated list of <member fw object ID>,
211 // <corresponding output file name>
212 // All separated by commands, the id and file name just
213 // follow one after another.
214 idx++;
215 QString member_files = args.at(idx);
216 QStringList mf_list = member_files.split(",");
217 QStringListIterator it(mf_list);
218 while (it.hasNext())
219 {
220 QString fw_id = it.next();
221 if (it.hasNext())
222 {
223 QString file_name = it.next();
224 member_file_names[fw_id] = file_name;
225 } else
226 {
227 QString err("Misconfigured -O option, missing file "
228 "name component for ID %1");
229 abort(err.arg(fw_id).toStdString());
230 }
231 }
232 continue;
233 }
234
235 if (arg == "-xc")
236 {
237 prepend_cluster_name_to_output_file = true;
238 continue;
239 }
240
241 if (arg == "-xt")
242 {
243 setTestMode();
244 info("*** Running in test mode, fatal errors are treated as warnings");
245 continue;
246 }
247
248 if (arg == "-xp")
249 {
250 idx++;
251 bool ok = false;
252 drp = args.at(idx).toInt(&ok);
253 if (!ok) return false;
254 rule_debug_on = true;
255 continue;
256 }
257
258 if (arg == "-xn")
259 {
260 idx++;
261 bool ok = false;
262 drn = args.at(idx).toInt(&ok);
263 if (!ok) return false;
264 rule_debug_on = true;
265 continue;
266 }
267
268 if (arg == "-xr")
269 {
270 idx++;
271 bool ok = false;
272 drr = args.at(idx).toInt(&ok);
273 if (!ok) return false;
274 rule_debug_on = true;
275 continue;
276 }
277
278 if (arg == "-s")
279 {
280 idx++;
281 single_rule_id = args.at(idx).toStdString();
282 single_rule_compile_on = true;
283 continue;
284 }
285 }
286
287 fwobjectname = last_arg;
288
289 if (wdir.empty()) wdir="./";
290
291 return true;
292 }
293
chDir()294 void CompilerDriver::chDir()
295 {
296 if (
297 #ifdef _WIN32
298 _chdir(wdir.c_str())
299 #else
300 chdir(wdir.c_str())
301 #endif
302 ) {
303 cerr << "Can't change to: " << wdir << endl;
304 exit(1);
305 }
306 }
307
308 /*
309 * See #1994. We need to reset read-only flag on the firewall and up
310 * the tree all the way to the root in order to let compilers make any
311 * modifications they need. Note that this resets read-only flags in
312 * the copy of the database this class works with.
313 */
clearReadOnly(Firewall * fw)314 void CompilerDriver::clearReadOnly(Firewall *fw)
315 {
316 if (fw->isReadOnly())
317 {
318 FWObject *p = fw;
319 while (p)
320 {
321 p->setReadOnly(false);
322 p = p->getParent();
323 }
324 }
325 }
326
327
getAbsOutputFileName(const QString & output_file_name)328 QString CompilerDriver::getAbsOutputFileName(const QString &output_file_name)
329 {
330 QFileInfo finfo(output_file_name);
331 if (finfo.isRelative())
332 {
333 // if fw_file_name is relative, it is relative to the
334 // directory the program started in, or if wdir was defined
335 // via "-d" command line switch, then it is relative to that.
336 if (wdir.empty())
337 {
338 QFileInfo new_finfo(start_current_dir, output_file_name);
339 return new_finfo.absoluteFilePath();
340 } else
341 {
342 QFileInfo new_finfo(QDir(wdir.c_str()), output_file_name);
343 return new_finfo.absoluteFilePath();
344 }
345 }
346 return output_file_name;
347 }
348
commonChecks(Firewall * fw)349 void CompilerDriver::commonChecks(Firewall *fw)
350 {
351 if (Cluster::isA(fw))
352 {
353 Cluster *cluster = Cluster::cast(fw);
354
355 // Check #1 : make sure output file names are different in member
356 // firewalls
357 set<string> output_file_names;
358 list<Firewall*> members;
359 cluster->getMembersList(members);
360 for (list<Firewall*>::iterator it=members.begin(); it!=members.end(); ++it)
361 {
362 FWOptions *fwopt = (*it)->getOptionsObject();
363 string ofname = fwopt->getStr("output_file");
364 if (ofname.empty()) continue;
365 if (output_file_names.count(ofname) > 0)
366 {
367 QString err("Member firewalls use the same output file name %1");
368 error(cluster, NULL, NULL, err.arg(ofname.c_str()).toStdString());
369 }
370 output_file_names.insert(ofname);
371 }
372 }
373 }
374
375 /*
376 * This method performs series of checks for the configuration
377 * consitency of clusters and cluster members as well as common
378 * problems with interfaces, addresses and their combinations. There
379 * are several possible levels of errors:
380 *
381 * - errors that can be worked around. Compiler makes minor changes
382 * to objects and continues. These are not warnings though, the user
383 * should fix these problems. Using Compiler::error() to report.
384 *
385 * - serious errors that should stop processing because generated file
386 * will be incorrect or inconsistent. However it is possible to
387 * continue in single rule compile mode because the error may not
388 * affect the rule being compiled. Using Compiler::abort() to
389 * report. Normally this method throws FWException() but in single
390 * rule compile mode or in testing mode it records the error and
391 * continues.
392 *
393 * - fatal errors that make it impossible to continue even in test or
394 * single rule compile modes. To report call Compiler::abort() and
395 * then throw FatalErrorInSingleRuleCompileMode exception. This
396 * exception should be caught in CompilerDriver::run() (virtual
397 * method) where recorded error can be shown to the user in the GUI.
398 *
399 */
commonChecks2(Cluster * cluster,Firewall * fw)400 void CompilerDriver::commonChecks2(Cluster *cluster, Firewall *fw)
401 {
402 QString current_firewall_name = fw->getName().c_str();
403 string host_os = fw->getStr("host_OS");
404
405 if (cluster)
406 {
407 // firewall is a member of a cluster.
408 // Rely on the caller to make sure this firewall is really a member
409 // of this cluster. Do not perform redundant check here.
410
411 processStateSyncGroups(cluster, fw);
412
413 // some initial sanity checks
414 validateClusterGroups(cluster);
415 }
416
417
418 list<FWObject*> all_policies = fw->getByType(Policy::TYPENAME);
419 list<FWObject*> all_nat = fw->getByType(NAT::TYPENAME);
420
421 bool have_top = false;
422 for (list<FWObject*>::iterator p=all_nat.begin(); p!=all_nat.end(); ++p)
423 {
424 if (RuleSet::cast(*p)->isTop())
425 {
426 have_top = true;
427 break;
428 }
429 }
430 if ( ! have_top )
431 warning(fw, NULL, NULL,"Missing top level NAT ruleset");
432
433 have_top = false;
434 for (list<FWObject*>::iterator p=all_policies.begin(); p!=all_policies.end(); ++p)
435 {
436 if (RuleSet::cast(*p)->isTop())
437 {
438 have_top = true;
439 break;
440 }
441 }
442 if ( ! have_top )
443 warning(fw, NULL, NULL,"Missing top level Policy ruleset");
444
445
446 list<FWObject*> interfaces = fw->getByTypeDeep(Interface::TYPENAME);
447 for (list<FWObject*>::iterator i=interfaces.begin(); i!=interfaces.end(); ++i)
448 {
449 Interface *iface = Interface::cast(*i);
450 assert(iface);
451
452 string::size_type n;
453 if ( (n=iface->getName().find("*"))!=string::npos)
454 {
455 /* this is a special 'wildcard' interface. Its name must end with '*',
456 * it must be dynamic and should not have a child IPv4 or
457 * physAddress object
458 */
459 if (n!=iface->getName().length()-1)
460 {
461 QString err("'*' must be the last character in "
462 "the wildcard's interface name: '%1'.");
463 abort(fw, NULL, NULL,
464 err.arg(iface->getName().c_str()).toStdString());
465 throw FatalErrorInSingleRuleCompileMode();
466 }
467 /*
468 removed test to implement RFE #837238: "unnummbered wildcard interfaces"
469
470 if (!iface->isDyn())
471 {
472 QString errstr;
473 errstr.sprintf(_("Wildcard interface '%s' must be dynamic."),
474 iface->getName().c_str() );
475 throw FWException(errstr);
476 }
477 */
478 list<FWObject*> l3=iface->getByType(physAddress::TYPENAME);
479 if (l3.size()>0)
480 {
481 QString err("Wildcard interface '%1' should not have "
482 "physcal address object attached to it. "
483 "The physical address object will be ignored.");
484 error(fw, NULL, NULL,
485 err.arg(iface->getName().c_str()).toStdString());
486 for (list<FWObject*>::iterator j=l3.begin(); j!=l3.end(); ++j)
487 iface->remove(*j);
488 }
489 }
490
491 if (iface->isDyn())
492 {
493 have_dynamic_interfaces=true;
494
495 iface->setBool("use_var_address",true);
496
497 list<FWObject*> l3=iface->getByType(IPv4::TYPENAME);
498 if (l3.size()>0)
499 {
500 for (list<FWObject*>::iterator j=l3.begin(); j!=l3.end(); ++j)
501 if ( objdb->findAllReferences(*j).size()!=0 )
502 {
503 QString err("Dynamic interface %1 has IP address "
504 "that is used in the firewall policy rule.");
505 abort(fw, NULL, NULL,
506 err.arg(iface->getName().c_str()).toStdString());
507 throw FatalErrorInSingleRuleCompileMode();
508 }
509
510 QString err("Dynamic interface %1 should not have an "
511 "IP address object attached to it. "
512 "This IP address object will be ignored.");
513 error(fw, NULL, NULL,
514 err.arg(iface->getName().c_str()).toStdString());
515 for (list<FWObject*>::iterator j=l3.begin(); j!=l3.end(); ++j)
516 iface->remove(*j);
517 }
518 }
519
520 if (iface->isRegular())
521 {
522 // Regular interface (should have an ip address)
523 bool no_addr_ok = false;
524 if (iface->getOptionsObject()->getBool("cluster_interface"))
525 {
526 // cluster interface with failover type heartbeat or
527 // openais may have no ip address. Other failover
528 // types require an address.
529 FWObject *failover_group =
530 iface->getFirstByType(FailoverClusterGroup::TYPENAME);
531 if (failover_group)
532 {
533 string failover_type = failover_group->getStr("type");
534 no_addr_ok = Resources::os_res[host_os]->getResourceBool(
535 "/FWBuilderResources/Target/protocols/" + failover_type +
536 "/no_ip_ok");
537 }
538 }
539
540 list<FWObject*> all_addr = iface->getByType(IPv4::TYPENAME);
541 list<FWObject*> all_ipv6 = iface->getByType(IPv6::TYPENAME);
542 all_addr.insert(all_addr.begin(), all_ipv6.begin(), all_ipv6.end());
543
544 if (iface->isRegular() &&
545 !no_addr_ok &&
546 all_addr.empty() &&
547 all_ipv6.empty())
548 {
549 QString err("Missing IP address for interface %1");
550 abort(fw, NULL, NULL,
551 err.arg(iface->getName().c_str()).toStdString());
552 throw FatalErrorInSingleRuleCompileMode();
553 }
554
555 for (list<FWObject*>::iterator j = all_addr.begin();
556 j != all_addr.end(); ++j)
557 {
558 const InetAddr *ip_addr = Address::cast(*j)->getAddressPtr();
559 const InetAddr *netmask = Address::cast(*j)->getNetmaskPtr();
560
561 if (ip_addr && ip_addr->isAny())
562 {
563 QString err("Interface %1 (id=%2) has IP address %3.");
564 abort(fw, NULL, NULL,
565 err.arg(iface->getName().c_str())
566 .arg(FWObjectDatabase::getStringId(
567 iface->getId()).c_str())
568 .arg(ip_addr->toString().c_str()).toStdString());
569 throw FatalErrorInSingleRuleCompileMode();
570 }
571
572 if (ip_addr && netmask && netmask->isAny())
573 {
574 QString err("Interface %1 (id=%2) has invalid netmask %3.");
575 abort(fw, NULL, NULL,
576 err.arg(iface->getName().c_str())
577 .arg(FWObjectDatabase::getStringId(
578 iface->getId()).c_str())
579 .arg(netmask->toString().c_str()).toStdString());
580 throw FatalErrorInSingleRuleCompileMode();
581 }
582 }
583 }
584
585 FWObject *parent = iface->getParent();
586
587 if (Interface::isA(parent))
588 {
589 Resources* os_res = Resources::os_res[fw->getStr("host_OS")];
590 string os_family = fw->getStr("host_OS");
591 if (os_res!=NULL)
592 os_family = os_res->getResourceStr("/FWBuilderResources/Target/family");
593
594 std::auto_ptr<interfaceProperties> int_prop(
595 interfacePropertiesObjectFactory::getInterfacePropertiesObject(
596 os_family));
597
598 #if 0
599 // See #2103. All interface name validation checks should
600 // be done in the GUI.
601 QString err;
602 if (!int_prop->validateInterface(parent, iface, true, err))
603 {
604 abort(fw, NULL, NULL, err.toStdString());
605 throw FatalErrorInSingleRuleCompileMode();
606 }
607 #endif
608
609
610 string interface_type = iface->getOptionsObject()->getStr("type");
611 if (interface_type.empty()) interface_type = "ethernet";
612
613 string parent_interface_type =
614 Interface::cast(parent)->getOptionsObject()->getStr("type");
615
616 if (parent_interface_type == "bridge" &&
617 interface_type == "ethernet" &&
618 int_prop->looksLikeVlanInterface(iface->getName().c_str()))
619 {
620 // if vlan interface is used as a bridge port, it
621 // should be a copy of the top-level interface object
622 // with the same name
623 bool have_top_level_copy = false;
624 for (list<FWObject*>::iterator i2=interfaces.begin();
625 i2!=interfaces.end(); ++i2)
626 {
627 Interface *in = Interface::cast(*i2);
628 assert(in);
629 if (in == iface) continue;
630 if (in->getName() == iface->getName())
631 {
632 have_top_level_copy = true;
633 break;
634 }
635 }
636 if (!have_top_level_copy)
637 {
638 QString err("Interface %1 looks like Vlan interface and is "
639 "used as a bridge port. This configuration "
640 "is only allowed if this object is a copy of another "
641 "top-level interface with the same name"
642 );
643 abort(fw, NULL, NULL,
644 err.arg(iface->getName().c_str()).toStdString());
645 throw FatalErrorInSingleRuleCompileMode();
646 }
647 }
648 }
649 }
650 }
651
setTargetId(const string & id)652 void CompilerDriver::setTargetId(const string &id)
653 {
654 fw_by_id = true;
655 fwobjectname = id.c_str();
656 }
657
locateObject()658 Firewall* CompilerDriver::locateObject()
659 {
660 Firewall* obj;
661 if (fw_by_id)
662 {
663 // fwobjectname is actually object id
664 obj = Firewall::cast(
665 objdb->findInIndex(
666 #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
667 objdb->getIntId(fwobjectname.toAscii().constData())));
668 #else
669 objdb->getIntId(fwobjectname.toLatin1().constData())));
670 #endif
671 //fwobjectname = obj->getName().c_str();
672 }
673 else
674 obj = objdb->findFirewallByName(fwobjectname.toUtf8().constData());
675
676 return obj;
677 }
678
679 /* Find rulesets that belong to other firewall objects but are
680 * referenced by rules of this firewall using action Branch.
681 *
682 * Important: rulesets that belong to other firewalls may be marked as
683 * "top rulesets", which means they should be translated into the
684 * built-in chains INPUT/OUTPUT/FORWARD rather then into named chain
685 * with the name the same as the name of the ruleset. However this
686 * does not make sense if we want to jump to that ruleset from a rule
687 * from a ruleset that belongs to the firewall we are compiling. If we
688 * compile such "foreighn" ruleset as "top ruleset", then we do not
689 * create chain we would jump to. To avoid this will reset "top
690 * ruleset" flag of rulesets of other firewalls referenced by
691 * branching rules of the firewall being compiled.
692 */
findImportedRuleSets(Firewall * fw,list<FWObject * > & all_policies)693 void CompilerDriver::findImportedRuleSets(Firewall *fw,
694 list<FWObject*> &all_policies)
695 {
696 bool cluster_member = fw->getOptionsObject()->getBool("cluster_member");
697 int cluster_id = fw->getInt("parent_cluster_id");
698
699 list<FWObject*> imported_policies;
700 for (list<FWObject*>::iterator i=all_policies.begin(); i!=all_policies.end(); ++i)
701 {
702 RuleSet *ruleset = RuleSet::cast(*i);
703 if (ruleset == NULL) continue; // should not happen
704
705 for (list<FWObject*>::iterator r=ruleset->begin(); r!=ruleset->end(); ++r)
706 {
707 Rule *rule = Rule::cast(*r);
708 if (rule == NULL) continue; // skip RuleSetOptions object
709
710 RuleSet *branch_ruleset = rule->getBranch();
711 if (branch_ruleset!=NULL)
712 {
713 // qDebug() << "ruleset=" << ruleset->getName().c_str()
714 // << "branch=" << branch_ruleset->getName().c_str();
715
716 map<FWObject*, int> referenced_branch_rulesets;
717
718 _findImportedRuleSetsRecursively(
719 fw, branch_ruleset, referenced_branch_rulesets);
720
721 map<FWObject*, int>::iterator it;
722 for (it=referenced_branch_rulesets.begin();
723 it!=referenced_branch_rulesets.end(); ++it)
724 {
725 RuleSet *branch_ruleset = RuleSet::cast(it->first);
726 int counter = it->second;
727
728 // qDebug() << " "
729 // << "branch=" << branch_ruleset->getName().c_str()
730 // << "counter=" << counter;
731
732 if (counter > 1)
733 {
734 QString err(
735 "Rule branches to rule set %1 which branches "
736 "back to it, creating a loop");
737 warning(ruleset->getParent(), ruleset, rule,
738 err.arg(branch_ruleset->getName().c_str())
739 .toStdString());
740 }
741
742 if (branch_ruleset->isChildOf(fw)) continue;
743
744 list<FWObject*>::iterator it =
745 std::find(
746 imported_policies.begin(),
747 imported_policies.end(),
748 branch_ruleset);
749
750 if (it != imported_policies.end()) continue;
751
752 // Additional check: the rule set may be child of a
753 // cluster this firewall is member of. If it is, it
754 // has been taken care of in CompilerDriver::mergeRuleSets()
755 FWObject *ruleset_parent = branch_ruleset->getParent();
756 if (cluster_member && Cluster::isA(ruleset_parent) &&
757 ruleset_parent->getId() == cluster_id) continue;
758
759 branch_ruleset->setTop(false);
760 imported_policies.push_back(branch_ruleset);
761 }
762 }
763 }
764 }
765
766 if (imported_policies.size() > 0)
767 all_policies.insert(all_policies.end(),
768 imported_policies.begin(), imported_policies.end());
769 }
770
_findImportedRuleSetsRecursively(Firewall * fw,RuleSet * branch_ruleset,map<FWObject *,int> & branch_rulesets)771 void CompilerDriver::_findImportedRuleSetsRecursively(
772 Firewall *fw, RuleSet *branch_ruleset, map<FWObject*, int> &branch_rulesets)
773 {
774 // multiple rules in the rule set may branch to the same branch rule set
775 map<FWObject*, int> local_branch_ruleset_counters;
776
777 int c = branch_rulesets[branch_ruleset];
778 branch_rulesets[branch_ruleset] = ++c;
779 if (c > 1) return; // we have seen this one already
780
781 for (list<FWObject*>::iterator r=branch_ruleset->begin(); r!=branch_ruleset->end(); ++r)
782 {
783 Rule *rule = Rule::cast(*r);
784 if (rule == NULL) continue; // skip RuleSetOptions object
785
786 RuleSet *next_branch_ruleset = rule->getBranch();
787 if (next_branch_ruleset!=NULL &&
788 local_branch_ruleset_counters.count(next_branch_ruleset)==0)
789 {
790 local_branch_ruleset_counters[next_branch_ruleset] = 1;
791 _findImportedRuleSetsRecursively(
792 fw, next_branch_ruleset, branch_rulesets);
793 }
794 }
795 }
796
assignUniqueRuleIds(list<FWObject * > & all_rulesets)797 void CompilerDriver::assignUniqueRuleIds(list<FWObject*> &all_rulesets)
798 {
799 for_each(all_rulesets.begin(), all_rulesets.end(),
800 RuleSet::UniqueRuleIdsSetter());
801 }
802
run(const std::string &,const std::string &,const std::string &)803 QString CompilerDriver::run(const std::string&, const std::string&, const std::string&)
804 {
805 return "";
806 }
807
validateClusterGroups(Cluster * cluster)808 void CompilerDriver::validateClusterGroups(Cluster *cluster)
809 {
810 string host_os = cluster->getStr("host_OS");
811 Resources* os_res = Resources::os_res[host_os];
812 if (os_res==NULL) return;
813
814 // check if state sync groups are of supported type
815 list<string> state_sync_protocols;
816 os_res->getResourceStrList("/FWBuilderResources/Target/protocols/state_sync",
817 state_sync_protocols);
818
819 for (FWObjectTypedChildIterator it = cluster->findByType(StateSyncClusterGroup::TYPENAME);
820 it != it.end(); ++it)
821 {
822 string state_sync_type = (*it)->getStr("type");
823 if (!isSupported(&state_sync_protocols, state_sync_type))
824 {
825 QString err("State sync group type '%1' is not supported");
826 abort(cluster, NULL, NULL, err.arg(state_sync_type.c_str()).toStdString());
827 throw FatalErrorInSingleRuleCompileMode();
828 }
829 }
830
831 // same for failover groups
832 list<string> failover_protocols;
833 os_res->getResourceStrList("/FWBuilderResources/Target/protocols/failover",
834 failover_protocols);
835
836 list<FWObject*> failover_groups = cluster->getByTypeDeep(FailoverClusterGroup::TYPENAME);
837 for (list<FWObject*>::iterator it = failover_groups.begin(); it != failover_groups.end(); ++it)
838 {
839 FWObject *failover_group = *it;
840 FWObject *parent = failover_group->getParent();
841 string failover_type = failover_group->getStr("type");
842 if (!isSupported(&failover_protocols, failover_type))
843 {
844 QString err("Failover group type '%1' is not supported");
845 abort(cluster, NULL, NULL, err.arg(failover_type.c_str()).toStdString());
846 throw FatalErrorInSingleRuleCompileMode();
847 }
848
849 list<FWObject*> l2 = failover_group->getByTypeDeep(FWObjectReference::TYPENAME);
850 if (l2.size() == 0)
851 {
852 QString err("Failover group of cluster interface '%1' is empty");
853 abort(cluster, NULL, NULL,
854 err.arg(parent->getName().c_str()).toStdString());
855 throw FatalErrorInSingleRuleCompileMode();
856 }
857 }
858 }
859
isSupported(list<string> * protocols,const string & cluster_group_type)860 bool CompilerDriver::isSupported(list<string> *protocols, const string &cluster_group_type)
861 {
862 bool supported = false;
863 for (list<string>::iterator supported_types=protocols->begin();
864 supported_types!=protocols->end(); ++supported_types)
865 {
866 QString str = QString(supported_types->c_str());
867 QStringList pl = str.split(",");
868 if (cluster_group_type.c_str() == pl[0])
869 {
870 supported = true;
871 break;
872 }
873 }
874 return supported;
875 }
876
877
operator <<(QTextStream & text_stream,const string & str)878 QTextStream& operator<< (QTextStream &text_stream, const string &str)
879 {
880 text_stream << str.c_str();
881 return text_stream;
882 }
883
884 /*
885 * Add indentation to each line in txt
886 */
indent(int n_spaces,const string & txt)887 string CompilerDriver::indent(int n_spaces, const string &txt)
888 {
889 QString res = indent(n_spaces, QString(txt.c_str()));
890 return res.toStdString();
891 }
892
indent(int n_spaces,const QString & txt)893 QString CompilerDriver::indent(int n_spaces, const QString &txt)
894 {
895 QString fill = QString("%1").arg("", n_spaces, ' ');
896 return prepend(fill, txt);
897 }
898
899 /*
900 * prepend each line in @txt with @prep, however there is no need to
901 * prepend empty lines
902 */
prepend(const QString & prep,const QString & txt)903 QString CompilerDriver::prepend(const QString &prep, const QString &txt)
904 {
905 QStringList str;
906 foreach (QString line, txt.split("\n"))
907 {
908 if (line.isEmpty()) str.append(line);
909 else str.append(line.prepend(prep));
910 }
911 return str.join("\n");
912 }
913
mergeRuleSets(Cluster * cluster,Firewall * fw,const string & type)914 void CompilerDriver::mergeRuleSets(Cluster *cluster, Firewall *fw,
915 const string &type)
916 {
917 list<FWObject*> all_rulesets = cluster->getByType(type);
918 for (list<FWObject*>::iterator p = all_rulesets.begin();
919 p != all_rulesets.end(); ++p)
920 {
921 FWObject *ruleset = *p;
922
923 FWObject::iterator i = std::find_if(
924 fw->begin(), fw->end(),
925 FWObjectNameEQPredicate(ruleset->getName()));
926
927 if (i!=fw->end() && (*i)->getTypeName() == type)
928 {
929 FWObject *fw_ruleset = *i;
930
931 /*
932 * fw has rule set with the same name. See ticket #372
933 * for details.
934 *
935 * if member firewall has rule set of the same type and
936 * with the same name as cluster and firewall's rule set
937 * is not empty, cluster's rule set is ignored and warning
938 * is ussued
939 *
940 * if matching firewall's rule set is empty, cluster's
941 * rule set is used
942 *
943 * all rule sets from the cluster that do not have
944 * matching ones in the member firewall are merged into
945 * the firewall before compilation and used.
946 *
947 * Note that rule set object has two different kinds of
948 * children: rules and RuleSetOption objects. Check if it
949 * has any rules.
950 */
951
952 int rule_cntr = 0;
953 list<FWObject*>::iterator it = fw_ruleset->begin();
954 for ( ; it!=fw_ruleset->end(); ++it)
955 {
956 if (Rule::cast(*it)!=NULL) rule_cntr++;
957 }
958
959 if (rule_cntr > 0)
960 {
961 QString err("ignoring cluster rule set \"%1\" "
962 "because member firewall \"%2\" "
963 "has rule set with the same name.");
964 warning(fw, fw_ruleset, NULL,
965 err.arg(fw_ruleset->getName().c_str())
966 .arg(fw->getName().c_str()).toStdString());
967 } else
968 {
969 fw_ruleset->clear();
970 fw_ruleset->duplicate(ruleset, false);
971 fw_ruleset->setStr(".ruleset_owner", cluster->getName());
972 fw_ruleset->setInt(".ruleset_owner_id", cluster->getId());
973 }
974 } else
975 {
976 // fw does not have rule set with this name
977 FWObject *copy_ruleset = fw->addCopyOf(ruleset, false);
978 copy_ruleset->setStr(".ruleset_owner", cluster->getName());
979 copy_ruleset->setInt(".ruleset_owner_id", cluster->getId());
980 }
981 }
982 }
983
984 /*
985 * 1. Iterate over all fw interfaces and check if they are referenced in a
986 * ClusterGroup.
987 * -> if yes then make copy of vrrp interface and set BASEDEV accordingly
988 * 2. clear Policy, NAT & Routing rules of the firewall, then copy cluster
989 * policy, NAT and routing rules.
990 */
populateClusterElements(Cluster * cluster,Firewall * fw)991 void CompilerDriver::populateClusterElements(Cluster *cluster, Firewall *fw)
992 {
993 if (cluster==NULL) return;
994
995 #ifdef DEBUG_CLUSTER_INTERFACES
996 cerr << "CompilerDriver::populateClusterElements " << endl;
997
998 cerr << cluster->getPath(false, true) << endl;
999 list<FWObject*> cl_interfaces = cluster->getByTypeDeep(Interface::TYPENAME);
1000 cerr << cl_interfaces.size() << " interface" << endl;
1001 cluster->dump(false, true);
1002
1003 cerr << fw->getPath(false, true) << endl;
1004 list<FWObject*> fw_interfaces = fw->getByTypeDeep(Interface::TYPENAME);
1005 cerr << fw_interfaces.size() << " interface" << endl;
1006 fw->dump(false, true);
1007 #endif
1008
1009 // int addedPolicies = 0;
1010 set<string> state_sync_types;
1011
1012 checkCluster(cluster);
1013
1014 for (FWObjectTypedChildIterator it = cluster->findByType(StateSyncClusterGroup::TYPENAME);
1015 it != it.end(); ++it)
1016 {
1017 StateSyncClusterGroup *state_sync_group = StateSyncClusterGroup::cast(*it);
1018 /* For the state syncing cluster group, hierarchy looks like this:
1019 * Cluster->StateSyncClusterGroup->ObjectRef
1020 */
1021 string grp_type = state_sync_group->getStr("type");
1022 if (state_sync_types.count(grp_type) > 0)
1023 throw FWException("Several state synchronization groups of the same type in one cluster object.");
1024
1025 state_sync_types.insert(grp_type);
1026
1027 for (FWObjectTypedChildIterator it =
1028 state_sync_group->findByType(FWObjectReference::TYPENAME);
1029 it != it.end(); ++it)
1030 {
1031 Interface *iface = Interface::cast(FWObjectReference::getObject(*it));
1032 assert(iface);
1033 //processStateSyncGroup(cluster, fw, state_sync_group, iface);
1034
1035 iface->getOptionsObject()->setBool("state_sync_group_member", true);
1036 iface->getOptionsObject()->setStr(
1037 "state_sync_group_id",
1038 FWObjectDatabase::getStringId(state_sync_group->getId()));
1039 string master_id = state_sync_group->getStr("master_iface");
1040 string iface_str_id = FWObjectDatabase::getStringId(iface->getId());
1041 iface->getOptionsObject()->setBool("state_sync_master",
1042 master_id == iface_str_id);
1043 fw->getOptionsObject()->setBool("cluster_member", true);
1044 }
1045 }
1046
1047 // For VRRP references the hierarchy is as follows:
1048 // Cluster->Interface->FailoverClusterGroup->ObjectRef
1049
1050 // get a list of pointers to all cluster interfaces. Can't use findByType()
1051 // and iterator because we'll be adding interfaces in the middle of the loop
1052 list<FWObject*> cluster_interfaces = cluster->getByTypeDeep(Interface::TYPENAME);
1053
1054 list<FWObject*>::iterator cl_iface = cluster_interfaces.begin();
1055 for (; cl_iface != cluster_interfaces.end(); ++cl_iface)
1056 {
1057 Interface *cluster_interface = Interface::cast(*cl_iface);
1058 FailoverClusterGroup *failover_group =
1059 FailoverClusterGroup::cast(
1060 cluster_interface->getFirstByType(FailoverClusterGroup::TYPENAME));
1061 if (failover_group)
1062 {
1063 Interface *member_iface =
1064 failover_group->getInterfaceForMemberFirewall(fw);
1065 if (member_iface == NULL) continue;
1066
1067 assert(fw->getOptionsObject() != NULL);
1068
1069 member_iface->getOptionsObject()->setStr(
1070 "failover_group_id",
1071 FWObjectDatabase::getStringId(failover_group->getId()));
1072
1073 // per #971: cluster interface should inherit attributes
1074 // of the member interfaces: regular / dynamic / unnimbered
1075 cluster_interface->setDyn(member_iface->isDyn());
1076 cluster_interface->setUnnumbered(member_iface->isUnnumbered());
1077 cluster_interface->setUnprotected(member_iface->isUnprotected());
1078 cluster_interface->setSecurityLevel(member_iface->getSecurityLevel());
1079
1080 copyFailoverInterface(cluster, fw, failover_group, member_iface);
1081
1082 } else
1083 {
1084 // cluster interface without failover group
1085 // is this a loopback interface ?
1086 if (cluster_interface->isLoopback())
1087 {
1088 /* Add copy of the interface from the cluster to the
1089 * firewall object so that when it is encountered in
1090 * the "intrface" rule element of its rules, it
1091 * belongs to the firewall and is therefore valid.
1092 */
1093 Interface* new_cl_if = Interface::cast(fw->addCopyOf(cluster_interface, true));
1094 assert(new_cl_if != NULL);
1095 new_cl_if->getOptionsObject()->setBool("cluster_interface", true);
1096 }
1097 }
1098 }
1099
1100 mergeRuleSets(cluster, fw, Policy::TYPENAME);
1101 mergeRuleSets(cluster, fw, NAT::TYPENAME);
1102 mergeRuleSets(cluster, fw, Routing::TYPENAME);
1103
1104 // finally need to remember cluster object ID so that compiler can later
1105 // associate it in rules with the firewall.
1106 //
1107 // The alternative is to find all references to the cluster object
1108 // in rules and replace them with refs to the firewall. That could
1109 // be done either in prolog or in a special rule processor. It is
1110 // _much_ cheaper to just remember cluster ID though.
1111 fw->setInt("parent_cluster_id", cluster->getId());
1112
1113 // return addedPolicies;
1114 }
1115
1116 /*
1117 * Perform checks for failover interfaces and their addresses, add a
1118 * copy of failover interface form the cluster to the firewall object.
1119 *
1120 * This method assumes the following:
1121 *
1122 * - Failover interface owns its ip address which is different from
1123 * addresses of either firewall
1124 *
1125 * - address of the failover interface must be on the same subnet as
1126 * addresses of the firewalls (perhaps this restriction can be
1127 * lifted? Was originally implemented by Secunet folks like this)
1128 */
copyFailoverInterface(Cluster *,Firewall * fw,FailoverClusterGroup * cluster_group,Interface * iface)1129 void CompilerDriver::copyFailoverInterface(Cluster * /*UNUSED cluster */,
1130 Firewall *fw,
1131 FailoverClusterGroup *cluster_group,
1132 Interface *iface)
1133 {
1134 Interface* cluster_if = Interface::cast(cluster_group->getParent());
1135 assert(cluster_if != NULL);
1136
1137 /* Add copy of the cluster interface to the firewall object
1138 *
1139 * While adding a copy of cluster interface to the firewall, make
1140 * sure it has new unique ID instead of a copy of the ID of the
1141 * cluster's interface object. If the ID is the same,
1142 * RuleElementItf::validateChild() finds clusters' interface which
1143 * is not a child of the firewall object and therefore is
1144 * rejected.
1145 */
1146 Interface* new_cl_if = Interface::cast(fw->addCopyOf(cluster_if, true));
1147 assert(new_cl_if != NULL);
1148 new_cl_if->getOptionsObject()->setBool("cluster_interface", true);
1149 new_cl_if->getOptionsObject()->setStr("base_device", iface->getName());
1150 new_cl_if->getOptionsObject()->setStr(
1151 "base_interface_id", FWObjectDatabase::getStringId(iface->getId()));
1152
1153 /* Set master property if interface is referenced
1154 * as master_iface
1155 */
1156 string master_id = cluster_group->getStr("master_iface");
1157 string iface_str_id = FWObjectDatabase::getStringId(iface->getId());
1158
1159 new_cl_if->getOptionsObject()->setBool("failover_master",
1160 master_id == iface_str_id);
1161
1162 /*
1163 * cluster interface should "inherit" some of the attributes of
1164 * the member interfaces it represents. For example, if member
1165 * interfaces are marked "unprotected" or "dedicated failover",
1166 * should be the cluster interface. What else?
1167 */
1168 new_cl_if->setDedicatedFailover(iface->isDedicatedFailover());
1169 new_cl_if->setUnprotected(iface->isUnprotected());
1170
1171 fw->getOptionsObject()->setBool("cluster_member", true);
1172 }
1173
1174 /**
1175 * Do something with state sync cluster groups. Find interfaces that
1176 * were placed in the group and store the name in the variable
1177 * "state_sync_interface" which is used later to associate policy rule
1178 * that should be added to permit state sync protocol with right
1179 * interface. For iptables we add rule to permit conntrackd, for PIX
1180 * we generate "failover" commands, etc.
1181 */
processStateSyncGroups(Cluster * cluster,Firewall * member_fw)1182 void CompilerDriver::processStateSyncGroups(Cluster *cluster, Firewall *member_fw)
1183 {
1184 for (FWObjectTypedChildIterator it = cluster->findByType(StateSyncClusterGroup::TYPENAME);
1185 it != it.end(); ++it)
1186 {
1187 FWObject *state_sync_group = *it;
1188 for (FWObjectTypedChildIterator grp_it =
1189 state_sync_group->findByType(FWObjectReference::TYPENAME);
1190 grp_it != grp_it.end(); ++grp_it)
1191 {
1192 FWObject *iface = FWObjectReference::getObject(*grp_it);
1193 if (iface->isChildOf(member_fw))
1194 {
1195 member_fw->getOptionsObject()->setStr(
1196 "state_sync_group_id",
1197 FWObjectDatabase::getStringId(state_sync_group->getId()));
1198
1199 member_fw->getOptionsObject()->setStr(
1200 "state_sync_interface",
1201 iface->getName());
1202 break;
1203 }
1204 }
1205 }
1206 }
1207
1208 /*
1209 * Verify that there is at least one Cluster interface and that all
1210 * have unique names and IP addresses.
1211 */
checkCluster(Cluster * cluster)1212 int CompilerDriver::checkCluster(Cluster* cluster)
1213 {
1214 assert(cluster != NULL);
1215 FWObjectTypedChildIterator cluster_ifaces = cluster->findByType(Interface::TYPENAME);
1216 if (cluster_ifaces == cluster_ifaces.end())
1217 {
1218 /* No configured cluster interface found */
1219 abort(cluster, NULL, NULL, "The cluster has no interfaces.");
1220 throw FatalErrorInSingleRuleCompileMode();
1221 }
1222
1223 for (; cluster_ifaces != cluster_ifaces.end(); ++cluster_ifaces)
1224 {
1225 string iface_name = Interface::cast(*cluster_ifaces)->getName();
1226 const InetAddr* iface_address = Interface::cast(*cluster_ifaces)->getAddressPtr();
1227 if (iface_address==NULL) continue; // cluster interface with no address
1228 FWObjectTypedChildIterator other_ifaces = cluster_ifaces;
1229 for (++other_ifaces; other_ifaces != cluster_ifaces.end(); ++other_ifaces)
1230 {
1231 if (iface_name == Interface::cast(*other_ifaces)->getName())
1232 {
1233 QString err("Found duplicate cluster interface %1");
1234 abort(cluster, NULL, NULL, err.arg(iface_name.c_str()).toStdString());
1235 throw FatalErrorInSingleRuleCompileMode();
1236 }
1237 const InetAddr *other_iface_address = Interface::cast(*other_ifaces)->getAddressPtr();
1238 if (other_iface_address==NULL) continue; // cluster interface with no address
1239 if (*iface_address == *other_iface_address)
1240 {
1241 QString err("Found duplicate cluster interface address %1");
1242 abort(cluster, NULL, NULL, err.arg(iface_address->toString().c_str()).toStdString());
1243 throw FatalErrorInSingleRuleCompileMode();
1244 }
1245 }
1246 }
1247
1248 return 0;
1249 }
1250
formSingleRuleCompileOutput(const QString & generated_code)1251 QString CompilerDriver::formSingleRuleCompileOutput(const QString &generated_code)
1252 {
1253 // in single rule compile mode just return the
1254 // result. Note that we do not return all_errors because
1255 // all compilers include errors and warnings with
1256 // generated code for each rule. Two exceptions: 1)
1257 // CompilerDriver errors need to be added on top, 2) if no
1258 // output has been produced by the compiler, we have to
1259 // show all_errors to the user because there could be an
1260 // error message explaining this. Combined output of all
1261 // compilers we assemble here may consist of a bunch of
1262 // empty lines separated by LF. Need to account for that.
1263 QString res = generated_code;
1264 QString res2 = res.split("\n", QString::SkipEmptyParts).join("").replace(" ", "");
1265 if (res2.isEmpty()) res = all_errors.join("\n");
1266 return res;
1267 }
1268
getFirewallAndClusterObjects(const string & cluster_id,const string & firewall_id,Cluster ** cl,Firewall ** fw)1269 void CompilerDriver::getFirewallAndClusterObjects(const string &cluster_id,
1270 const string &firewall_id,
1271 Cluster **cl,
1272 Firewall **fw)
1273 {
1274 if (!cluster_id.empty())
1275 {
1276 Cluster *orig_cluster = Cluster::cast(
1277 objdb->findInIndex(objdb->getIntId(cluster_id)));
1278
1279 #ifdef WORK_ON_COPIES
1280 *cl = objdb->createCluster();
1281 workspace->add(*cl);
1282 (*cl)->duplicate(orig_cluster);
1283 #else
1284
1285 *cl = orig_cluster;
1286
1287 #endif
1288
1289 }
1290
1291 Firewall *orig_fw = Firewall::cast(
1292 objdb->findInIndex(objdb->getIntId(firewall_id)));
1293 assert(orig_fw);
1294
1295 #ifdef WORK_ON_COPIES
1296
1297 *fw = objdb->createFirewall();
1298 workspace->add(*fw);
1299 (*fw)->duplicate(orig_fw);
1300
1301 if (*cl != NULL)
1302 {
1303 const map<int, int> &id_map = (*fw)->getIDMappingTable();
1304 map<int, int>::const_iterator it;
1305 for (it=id_map.begin(); it!=id_map.end(); ++it)
1306 (*cl)->replaceRef(it->first, it->second);
1307 }
1308 #else
1309
1310 *fw = orig_fw;
1311
1312 #endif
1313
1314 }
1315
1316
1317