mirror of
https://github.com/fwbuilder/fwbuilder
synced 2026-03-23 19:57:21 +01:00
Implemented TCP flag matching per #2865044: "Add TCP options support for IOS ACL". Uses extended ACL option "match-all" that supports list of TCP flags that should be set and cleared. This requires IOS v12.4 or later even though Cisco documentation seems to indicate this option was introduced in 12.3(4)T. Fixes #455
580 lines
17 KiB
C++
580 lines
17 KiB
C++
/*
|
|
|
|
Firewall Builder
|
|
|
|
Copyright (C) 2007 NetCitadel, LLC
|
|
|
|
Author: Vadim Kurland vadim@vk.crocodile.org
|
|
|
|
$Id$
|
|
|
|
This program is free software which we release under the GNU General Public
|
|
License. You may redistribute and/or modify this program under the terms
|
|
of that license as published by the Free Software Foundation; either
|
|
version 2 of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
To get a copy of the GNU General Public License, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
*/
|
|
|
|
#include "PolicyCompiler_iosacl.h"
|
|
|
|
#include "fwbuilder/Firewall.h"
|
|
#include "fwbuilder/AddressRange.h"
|
|
#include "fwbuilder/RuleElement.h"
|
|
#include "fwbuilder/IPService.h"
|
|
#include "fwbuilder/ICMPService.h"
|
|
#include "fwbuilder/ICMP6Service.h"
|
|
#include "fwbuilder/TCPService.h"
|
|
#include "fwbuilder/UDPService.h"
|
|
#include "fwbuilder/CustomService.h"
|
|
#include "fwbuilder/Policy.h"
|
|
#include "fwbuilder/FWOptions.h"
|
|
#include "fwbuilder/FWObjectDatabase.h"
|
|
#include "fwbuilder/Interface.h"
|
|
#include "fwbuilder/IPv4.h"
|
|
#include "fwbuilder/IPv6.h"
|
|
#include "fwbuilder/Network.h"
|
|
#include "fwbuilder/Management.h"
|
|
#include "fwbuilder/Resources.h"
|
|
#include "fwbuilder/XMLTools.h"
|
|
|
|
#include <iostream>
|
|
#if __GNUC__ > 3 || \
|
|
(__GNUC__ == 3 && (__GNUC_MINOR__ > 2 || (__GNUC_MINOR__ == 2 ) ) ) || \
|
|
_MSC_VER
|
|
# include <streambuf>
|
|
#else
|
|
# include <streambuf.h>
|
|
#endif
|
|
#include <iomanip>
|
|
#include <fstream>
|
|
#include <sstream>
|
|
#include <algorithm>
|
|
#include <functional>
|
|
|
|
#include <assert.h>
|
|
|
|
#include <QStringList>
|
|
|
|
|
|
using namespace libfwbuilder;
|
|
using namespace fwcompiler;
|
|
using namespace std;
|
|
|
|
|
|
bool PolicyCompiler_iosacl::ClearACLs::processNext()
|
|
{
|
|
PolicyCompiler_iosacl *iosacl_comp=dynamic_cast<PolicyCompiler_iosacl*>(compiler);
|
|
string vers = compiler->fw->getStr("version");
|
|
string platform = compiler->fw->getStr("platform");
|
|
string clearACLcmd = Resources::platform_res[platform]->getResourceStr(
|
|
string("/FWBuilderResources/Target/options/")+
|
|
"version_"+vers+"/iosacl_commands/clear_acl");
|
|
|
|
|
|
slurp();
|
|
if (tmp_queue.size()==0) return false;
|
|
|
|
if ( compiler->fw->getOptionsObject()->getBool("iosacl_acl_basic") )
|
|
{
|
|
compiler->output << clearACLcmd << endl;
|
|
}
|
|
|
|
if (compiler->fw->getOptionsObject()->getBool("iosacl_acl_substitution"))
|
|
{
|
|
for (map<string,ciscoACL*>::iterator i=iosacl_comp->acls.begin();
|
|
i!=iosacl_comp->acls.end(); ++i)
|
|
{
|
|
ciscoACL *acl=(*i).second;
|
|
compiler->output << clearACLcmd << " " << acl->workName() << endl;
|
|
}
|
|
compiler->output << endl;
|
|
}
|
|
|
|
if ( !compiler->fw->getOptionsObject()->getBool("iosacl_acl_no_clear") )
|
|
{
|
|
string clearICMPcmd = Resources::platform_res[platform]->getResourceStr(
|
|
string("/FWBuilderResources/Target/options/")+
|
|
"version_"+vers+"/iosacl_commands/clear_icmp");
|
|
string clearTelnetcmd = Resources::platform_res[platform]->getResourceStr(
|
|
string("/FWBuilderResources/Target/options/")+
|
|
"version_"+vers+"/iosacl_commands/clear_telnet");
|
|
string clearSSHcmd = Resources::platform_res[platform]->getResourceStr(
|
|
string("/FWBuilderResources/Target/options/")+
|
|
"version_"+vers+"/iosacl_commands/clear_ssh");
|
|
//compiler->output << clearICMPcmd << endl;
|
|
//compiler->output << clearTelnetcmd << endl;
|
|
//compiler->output << clearSSHcmd << endl;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void PolicyCompiler_iosacl::PrintCompleteACLs::printRulesForACL::operator()(
|
|
Rule* rule)
|
|
{
|
|
// print rule if it belongs to ACL <acl>
|
|
|
|
PolicyRule *prule = PolicyRule::cast(rule);
|
|
|
|
string acl_name = prule->getStr("acl");
|
|
assert (acl_name!="");
|
|
|
|
ciscoACL *rule_acl = iosacl_comp->acls[acl_name];
|
|
assert(rule_acl!=NULL);
|
|
|
|
if (acl == rule_acl)
|
|
{
|
|
*output << print_acl_p->_printRule(prule);
|
|
}
|
|
}
|
|
|
|
bool PolicyCompiler_iosacl::PrintCompleteACLs::processNext()
|
|
{
|
|
PolicyCompiler_iosacl *iosacl_comp=dynamic_cast<PolicyCompiler_iosacl*>(compiler);
|
|
string vers = compiler->fw->getStr("version");
|
|
string platform = compiler->fw->getStr("platform");
|
|
|
|
string xml_element = "clear_ip_acl";
|
|
if (iosacl_comp->ipv6) xml_element = "clear_ipv6_acl";
|
|
|
|
string clearACLCmd = Resources::platform_res[platform]->getResourceStr(
|
|
string("/FWBuilderResources/Target/options/")+
|
|
"version_"+vers+"/iosacl_commands/" + xml_element);
|
|
|
|
assert( !clearACLCmd.empty());
|
|
|
|
slurp();
|
|
if (tmp_queue.size()==0) return false;
|
|
|
|
if ( compiler->fw->getOptionsObject()->getBool("iosacl_acl_basic") ||
|
|
compiler->fw->getOptionsObject()->getBool("iosacl_acl_substitution"))
|
|
{
|
|
for (map<string,ciscoACL*>::iterator i=iosacl_comp->acls.begin();
|
|
i!=iosacl_comp->acls.end(); ++i)
|
|
{
|
|
ciscoACL *acl=(*i).second;
|
|
compiler->output << clearACLCmd << " " << acl->workName() << endl;
|
|
}
|
|
compiler->output << endl;
|
|
}
|
|
|
|
string addr_family_prefix = "ip";
|
|
if (iosacl_comp->ipv6) addr_family_prefix = "ipv6";
|
|
|
|
for (map<string,ciscoACL*>::iterator i=iosacl_comp->acls.begin();
|
|
i!=iosacl_comp->acls.end(); ++i)
|
|
{
|
|
ciscoACL *acl=(*i).second;
|
|
compiler->output << addr_family_prefix
|
|
<< " access-list ";
|
|
|
|
if (!iosacl_comp->ipv6) compiler->output << "extended ";
|
|
|
|
compiler->output<< acl->workName() << endl;
|
|
std::for_each(tmp_queue.begin(), tmp_queue.end(),
|
|
printRulesForACL(iosacl_comp,
|
|
this, acl, &(compiler->output)));
|
|
compiler->output << "exit" << endl;
|
|
compiler->output << endl;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
string PolicyCompiler_iosacl::PrintRule::_printRule(PolicyRule *rule)
|
|
{
|
|
PolicyCompiler_iosacl *iosacl_comp =
|
|
dynamic_cast<PolicyCompiler_iosacl*>(compiler);
|
|
//FWOptions *ruleopt =rule->getOptionsObject();
|
|
bool write_comments =
|
|
compiler->fw->getOptionsObject()->getBool("iosacl_include_comments");
|
|
|
|
ostringstream ruleout;
|
|
ostringstream aclstr;
|
|
|
|
string rl=rule->getLabel();
|
|
|
|
if (write_comments && !compiler->inSingleRuleCompileMode())
|
|
{
|
|
if (rl!=current_rule_label1)
|
|
{
|
|
ruleout << "! " << endl;
|
|
ruleout << "! Rule " << rl << endl;
|
|
|
|
string comm=rule->getComment();
|
|
string::size_type c1,c2;
|
|
c1=0;
|
|
while ( (c2=comm.find('\n',c1))!=string::npos ) {
|
|
ruleout << "! " << comm.substr(c1,c2-c1) << endl;
|
|
c1=c2+1;
|
|
}
|
|
ruleout << "! " << comm.substr(c1) << endl;
|
|
ruleout << "! " << endl;
|
|
|
|
current_rule_label1=rl;
|
|
}
|
|
}
|
|
|
|
string err = rule->getStr(".error_msg");
|
|
if (!err.empty()) ruleout << "! " << err << endl;
|
|
|
|
/*
|
|
* all three rule elements contain exactly one object, which can
|
|
* be either group (in case processor CreateObjectGroups created
|
|
* object group for it) or a regular object
|
|
*/
|
|
RuleElementSrc *src=rule->getSrc();
|
|
RuleElementDst *dst=rule->getDst();
|
|
RuleElementSrv *srv=rule->getSrv();
|
|
|
|
assert(src->size()==1);
|
|
assert(dst->size()==1);
|
|
assert(srv->size()==1);
|
|
|
|
FWObject *srcobj=src->front();
|
|
FWObject *dstobj=dst->front();
|
|
FWObject *srvobj=srv->front();
|
|
|
|
assert(srcobj);
|
|
assert(dstobj);
|
|
assert(srvobj);
|
|
|
|
if (FWReference::cast(srcobj)!=NULL)
|
|
{
|
|
srcobj=FWReference::cast(srcobj)->getPointer();
|
|
assert(srcobj);
|
|
}
|
|
if (FWReference::cast(dstobj)!=NULL)
|
|
{
|
|
dstobj=FWReference::cast(dstobj)->getPointer();
|
|
assert(dstobj);
|
|
}
|
|
if (FWReference::cast(srvobj)!=NULL)
|
|
{
|
|
srvobj=FWReference::cast(srvobj)->getPointer();
|
|
assert(srvobj);
|
|
}
|
|
|
|
string acl_name=rule->getStr("acl");
|
|
assert (acl_name!="");
|
|
|
|
ciscoACL *acl = iosacl_comp->acls[acl_name];
|
|
assert(acl!=NULL);
|
|
|
|
/*
|
|
* Assemble ACL command in aclstr
|
|
*/
|
|
|
|
aclstr << _printAction(rule);
|
|
|
|
aclstr << _printProtocol(Service::cast(srvobj));
|
|
aclstr << _printAddr( compiler->getFirstSrc(rule) );
|
|
aclstr << _printSrcService( compiler->getFirstSrv(rule) );
|
|
aclstr << _printAddr( compiler->getFirstDst(rule) );
|
|
aclstr << _printDstService( compiler->getFirstSrv(rule) );
|
|
aclstr << _printLog( rule );
|
|
// "fragments" should be the last option in the access-list command
|
|
aclstr << _printIPServiceOptions(rule);
|
|
|
|
// aclstr << endl;
|
|
|
|
if (compiler->fw->getOptionsObject()->getBool("iosacl_use_acl_remarks"))
|
|
{
|
|
ruleout << acl->addRemark(rule->getLabel(), rule->getComment());
|
|
}
|
|
|
|
ruleout << acl->addLine(aclstr.str());
|
|
|
|
return ruleout.str();
|
|
}
|
|
|
|
string PolicyCompiler_iosacl::PrintRule::_printAction(PolicyRule *rule)
|
|
{
|
|
ostringstream str;
|
|
|
|
switch (rule->getAction()) {
|
|
case PolicyRule::Accept: str << "permit "; break;
|
|
case PolicyRule::Deny: str << "deny "; break;
|
|
case PolicyRule::Reject: str << "deny "; break;
|
|
default: str << rule->getActionAsString() << " ";
|
|
}
|
|
return str.str();
|
|
}
|
|
|
|
string PolicyCompiler_iosacl::PrintRule::_printACL(PolicyRule *rule)
|
|
{
|
|
// PolicyCompiler_iosacl *iosacl_comp=dynamic_cast<PolicyCompiler_iosacl*>(compiler);
|
|
|
|
string acl_name=rule->getStr("acl");
|
|
assert (acl_name!="");
|
|
|
|
return acl_name+" ";
|
|
}
|
|
|
|
string PolicyCompiler_iosacl::PrintRule::_printLog(PolicyRule *rule)
|
|
{
|
|
if (rule->getLogging())
|
|
{
|
|
FWOptions *ruleopt =rule->getOptionsObject();
|
|
if (ruleopt->getBool("iosacl_log_input")) return "log-input ";
|
|
return "log ";
|
|
}
|
|
return "";
|
|
}
|
|
|
|
string PolicyCompiler_iosacl::PrintRule::_printSrcService(Service *srv)
|
|
{
|
|
ostringstream str;
|
|
|
|
if (TCPService::isA(srv) || UDPService::isA(srv))
|
|
{
|
|
int rs=TCPUDPService::cast(srv)->getSrcRangeStart();
|
|
int re=TCPUDPService::cast(srv)->getSrcRangeEnd();
|
|
|
|
if (rs<0) rs=0;
|
|
if (re<0) re=0;
|
|
|
|
if (rs>0 || re>0) {
|
|
if (rs==re) str << "eq " << rs << " ";
|
|
else
|
|
if (rs==0 && re!=0) str << "lt " << re << " ";
|
|
else
|
|
if (rs!=0 && re==65535) str << "gt " << rs << " ";
|
|
else
|
|
str << "range " << rs << " " << re << " ";
|
|
}
|
|
}
|
|
|
|
return str.str();
|
|
}
|
|
|
|
string PolicyCompiler_iosacl::PrintRule::_printIPServiceOptions(PolicyRule *r)
|
|
{
|
|
Service *srv = compiler->getFirstSrv(r);
|
|
const IPService *ip;
|
|
if ((ip=IPService::constcast(srv))!=NULL)
|
|
{
|
|
string version = compiler->fw->getStr("version");
|
|
|
|
if (srv->getBool("fragm") || srv->getBool("short_fragm"))
|
|
return "fragments ";
|
|
|
|
if (ip->hasIpOptions() && XMLTools::version_compare(version, "12.4")<0)
|
|
compiler->abort(r, "IP options match requires IOS v12.4 or later.");
|
|
|
|
if (ip->getBool("lsrr")) return "option lsr";
|
|
if (ip->getBool("ssrr")) return "option ssr";
|
|
if (ip->getBool("rr")) return "option record-route";
|
|
if (ip->getBool("rtralt")) return "option router-alert";
|
|
if (ip->getBool("any_opt")) return "option any-options ";
|
|
|
|
string tos = ip->getTOSCode();
|
|
string dscp = ip->getDSCPCode();
|
|
if (!dscp.empty()) return string("dscp ") + dscp;
|
|
else
|
|
if (!tos.empty()) return string("tos ") + tos;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
string PolicyCompiler_iosacl::PrintRule::_printDstService(Service *srv)
|
|
{
|
|
ostringstream str;
|
|
|
|
if (TCPService::isA(srv) || UDPService::isA(srv))
|
|
{
|
|
int rs=TCPUDPService::cast(srv)->getDstRangeStart();
|
|
int re=TCPUDPService::cast(srv)->getDstRangeEnd();
|
|
|
|
if (rs<0) rs=0;
|
|
if (re<0) re=0;
|
|
|
|
if (rs>0 || re>0) {
|
|
if (rs==re) str << "eq " << rs << " ";
|
|
else
|
|
if (rs==0 && re!=0) str << "lt " << re << " ";
|
|
else
|
|
if (rs!=0 && re==65535) str << "gt " << rs << " ";
|
|
else
|
|
str << "range " << rs << " " << re << " ";
|
|
}
|
|
}
|
|
|
|
if (TCPService::isA(srv))
|
|
{
|
|
if (srv->getBool("established")) str << "established ";
|
|
else str << _printTCPFlags(TCPService::cast(srv));
|
|
}
|
|
|
|
if ((ICMPService::isA(srv) || ICMP6Service::isA(srv)) && srv->getInt("type")!=-1)
|
|
str << srv->getStr("type") << " ";
|
|
|
|
if (CustomService::isA(srv))
|
|
str << CustomService::cast(srv)->getCodeForPlatform(
|
|
compiler->myPlatformName() ) << " ";
|
|
|
|
return str.str();
|
|
}
|
|
|
|
string PolicyCompiler_iosacl::PrintRule::getTcpFlagName(const TCPService::TCPFlag f)
|
|
{
|
|
switch (f)
|
|
{
|
|
case TCPService::URG: return "urg";
|
|
case TCPService::ACK: return "ack";
|
|
case TCPService::PSH: return "psh";
|
|
case TCPService::RST: return "rst";
|
|
case TCPService::SYN: return "syn";
|
|
case TCPService::FIN: return "fin";
|
|
default: return "";
|
|
}
|
|
return "";
|
|
}
|
|
|
|
string PolicyCompiler_iosacl::PrintRule::_printTCPFlags(TCPService *srv)
|
|
{
|
|
if (srv->inspectFlags())
|
|
{
|
|
// We check the version and call compiler->abort() if its
|
|
// wrong in SpecialServices rule processor. Here we should just execute.
|
|
string version = compiler->fw->getStr("version");
|
|
if (XMLTools::version_compare(version, "12.4")>=0)
|
|
{
|
|
std::set<TCPService::TCPFlag> flags = srv->getAllTCPFlags();
|
|
std::set<TCPService::TCPFlag> masks = srv->getAllTCPFlagMasks();
|
|
std::set<TCPService::TCPFlag>::iterator mit = masks.begin();
|
|
|
|
QStringList match_specs;
|
|
for (; mit!=masks.end(); mit++)
|
|
{
|
|
if (flags.count(*mit) > 0)
|
|
match_specs.push_back(QString("+%1").arg(getTcpFlagName(*mit).c_str()));
|
|
else
|
|
match_specs.push_back(QString("-%1").arg(getTcpFlagName(*mit).c_str()));
|
|
}
|
|
if (!match_specs.empty())
|
|
match_specs.push_front("match-all");
|
|
return match_specs.join(" ").toStdString() + " ";
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
string PolicyCompiler_iosacl::PrintRule::_printProtocol(Service *srv)
|
|
{
|
|
PolicyCompiler_iosacl *iosacl_comp = dynamic_cast<PolicyCompiler_iosacl*>(
|
|
compiler);
|
|
string addr_family_prefix = "ip ";
|
|
if (iosacl_comp->ipv6) addr_family_prefix = "ipv6 ";
|
|
|
|
string proto = srv->getProtocolName();
|
|
if (ICMP6Service::isA(srv)) proto = "icmp";
|
|
|
|
if (CustomService::isA(srv))
|
|
{
|
|
// special case standard CusctomService objects "ESTABLISHED"
|
|
// and "ESTABLISHED ipv6": these require protocol "tcp" but
|
|
// protocol is set in the Custom Service object for all
|
|
// platforms at once, so we can't have protocol defined only
|
|
// for iosacl to be used here.
|
|
string srv_code = CustomService::cast(srv)->getCodeForPlatform(
|
|
compiler->myPlatformName());
|
|
if (srv_code == "established") proto = "tcp";
|
|
}
|
|
|
|
if (proto=="ip") return addr_family_prefix;
|
|
return proto + " ";
|
|
}
|
|
|
|
string PolicyCompiler_iosacl::PrintRule::_printAddr(Address *o)
|
|
{
|
|
PolicyCompiler_iosacl *iosacl_comp = dynamic_cast<PolicyCompiler_iosacl*>(compiler);
|
|
if (Interface::cast(o)!=NULL)
|
|
{
|
|
Interface *interface_ = Interface::cast(o);
|
|
if (interface_->isDyn())
|
|
{
|
|
return string("interface ") + interface_->getLabel() + " ";
|
|
}
|
|
}
|
|
|
|
ostringstream str;
|
|
|
|
const InetAddr *srcaddr = o->getAddressPtr();
|
|
if (srcaddr)
|
|
{
|
|
const InetAddr srcmask = *(o->getNetmaskPtr());
|
|
|
|
if (srcaddr->isAny() && srcmask.isAny())
|
|
{
|
|
str << "any ";
|
|
} else
|
|
{
|
|
if (Interface::cast(o)==NULL &&
|
|
Interface::cast(o->getParent())==NULL &&
|
|
o->dimension() > 1 &&
|
|
!srcmask.isHostMask())
|
|
{
|
|
if (iosacl_comp->ipv6)
|
|
{
|
|
str << srcaddr->toString()
|
|
<< "/"
|
|
<< srcmask.getLength() << " ";
|
|
} else
|
|
{
|
|
str << srcaddr->toString() << " ";
|
|
// cisco uses "wildcards" instead of netmasks
|
|
//long nm = srcmask.to32BitInt();
|
|
//struct in_addr na;
|
|
//na.s_addr = ~nm;
|
|
InetAddr nnm( ~srcmask );
|
|
str << nnm.toString() << " ";
|
|
}
|
|
} else
|
|
{
|
|
str << "host " << srcaddr->toString() << " ";
|
|
}
|
|
}
|
|
return str.str();
|
|
}
|
|
|
|
ostringstream errstr;
|
|
errstr << "Object "
|
|
<< o->getName()
|
|
<< " (id="
|
|
<< o->getId()
|
|
<< ") "
|
|
<< " has no ip address and can not be used "
|
|
<< "in the rule.";
|
|
compiler->abort(errstr.str());
|
|
return ""; // to make compiler happy
|
|
}
|
|
|
|
/*
|
|
* the following additional attributes should have been defined by now:
|
|
*
|
|
* "acl" - string, name of the access list
|
|
* choices are: outside-in, outside-out, inside-in, indside-out,
|
|
* dmz-in, dmz-out etc.
|
|
* General rule for the acl name: "iface_name-{in,out}"
|
|
*/
|
|
bool PolicyCompiler_iosacl::PrintRule::processNext()
|
|
{
|
|
PolicyRule *rule=getNext(); if (rule==NULL) return false;
|
|
|
|
tmp_queue.push_back(rule);
|
|
|
|
compiler->output << _printRule(rule);
|
|
|
|
return true;
|
|
}
|
|
|