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