# Copyright(C) 2005 INL # Written by Jean Gillaux <jean@inl.fr> # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, version 3 of the License. # # 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. # # You should have received a copy of the GNU General Public License # along with this program; if not, see <http://www.gnu.org/licenses/>. from os import linesep from IPy import IP from nupyf.ipt_func import gen_rule, gen_rule_list, make_target, _make_syn from nupyf.ipt_nat import SnatIpt, DnatIpt, PnatIpt import re USE_PROXY = 1 OVERRIDE_PROXY = 2 TRANSPARENT_PROXY_PORT = 3128 def formatProtocol(protocol): args = [] proto = protocol.get('proto') if proto: args.extend(['-p',proto]) icmptype = protocol.get('icmptype') if icmptype: args.extend(['--icmp-type',icmptype]) dport = protocol.get('dport') if dport: args.extend(['--dport',dport]) sport = protocol.get('sport') if sport: args.extend(['--sport',sport]) return args def formatIP(rule): args = [] if rule.src(): ip = IP(rule.elt_src().get('numnet')) if ip.prefixlen(): args.append('-s %s' % ip) if rule.dest(): ip = IP(rule.elt_dest().get('numnet')) if ip.prefixlen(): args.append('-d %s' % ip) return args def formatInterface(rule): args = [] if rule.input_iface: args.append('-i %s' % rule.input_iface.name()) return args def formatRule(rule, table, target, *more_args): args = ['-A %s' % table] args.extend(formatIP(rule)) args.extend(formatProtocol(rule.proto())) args.extend(formatInterface(rule)) args.append('-j %s' % target) args.extend(more_args) args.append(linesep) return gen_rule(*args) def logMessage(command, text): if command == '--ulog-prefix': max_length = 31 else: max_length = 28 if max_length < len(text): full_text = text text = text[:max_length] print "WARNING: Truncate log prefix %r to %s characters: %r" % ( full_text, max_length, text) if command == '--log-prefix': text += ' ' text = text.replace('"', '\\"') return '%s "%s"' % (command, text) class FWipt: """Used to generate iptables rules from a firewall object that contains rules """ def __init__(self,thefw,logtype='log', queue=None): self.__fw = thefw self.__logtype = 'LOG' self.__logprefix = '--log-prefix' self.queue_type = 'QUEUE' self.queue_num = None if thefw.queue: queue = thefw.queue if queue: self.queue_type = 'NFQUEUE' self.queue_num = queue if logtype == 'ulog': self.__logtype = 'ULOG' self.__logprefix = '--ulog-prefix' def dumb_rules(self, rules, chain_obj): chain = chain_obj['name'] res = ['#ERROR_DISPATCH %s' % chain + linesep] sacl = '' for r in rules: stmp = r.aclname if(stmp != sacl): sacl = stmp sc = '#%s (acl %s)' % (sacl, r.aclid) + linesep res.append(sc) rule_print = RulePrintIptSimple(r, fw=self.__fw, chain=chain_obj, add=_make_syn(make_target(r.decision), {'proto':r.proto().get('proto')}), target=make_target(r.decision), ulog_prefix=r.ulog_prefix, logtype=self.__logtype) res.append('#%s' % rule_print + linesep) return res def create_ipt_chains(self): """Generate rules to create firewall chains: DMZ-LAN """ l = self.__fw.chains(all=1,inout=1) s = '' acc=[] for n in l: st = ':%s -' % n['name'] + linesep # Hack for multiple INTERNET (external zones) if not st in acc: s+=st acc.append(st) return s def __gen_global_accept(self, *where): state = 'RELATED' res = '' state = 'ESTABLISHED,%s' % state for ch in where: res = res + gen_rule('-A %s -m state --state %s -j ACCEPT' % (ch,state)) + linesep return res def __gen_default_log_drop(self, *where): res = '' for ch in where: message = logMessage(self.__logprefix, "DFT_%s_DROP" % ch) res = res + gen_rule('-A %s -j %s' % (ch, self.__logtype), message)+ linesep res = res + gen_rule('-A %s -j DROP' % (ch))+linesep return res def __gen_default_invalid_drop(self, *where): res = '' for ch in where: res = res + gen_rule('-A %s -m state --state INVALID -j DROP' % (ch)) + linesep return res def gen_loopback_default_rules(self): s = '' s += gen_rule('-A INPUT -i lo -j ACCEPT%s' % linesep) s += gen_rule('-A OUTPUT -o lo -j ACCEPT%s' % linesep) return s def connect_chains(self): """Generate rules that connect firewall streams into chains => dispatch rules """ l = self.__fw.chains(all=1,inout=1) s = '' sinternet = sinout_internet = '' sinput = '' soutput = '' lwhere = ['FORWARD'] lwhere.append('INPUT') lwhere.append('OUTPUT') estrel = self.__gen_global_accept(*lwhere) h_vpn = {} h_forward = {} h_input = {} l_output = [] h_internet = {} h_ininternet = {} l_outinternet = [] for n in l: has_vpn = False has_internet = False # INPUT/OUTPUT INTEGRATION: change chain name here -> OK where = 'FORWARD' input = output = False if self.__fw.chain_input(n): where = 'INPUT' input = True if self.__fw.chain_output(n): where = 'OUTPUT' output = True t=['-A %s' % (where)] if n['type'] != "output" and not self.__fw.is_default_net(n['net_src']): t.append('-s %s' % n['net_src'].ip()) sort_net_src = n['net_src'].addr() else: sort_net_src = IP('0.0.0.0/0') # TODO: why also output and not just forward ??? if n['type'] != "input" and not self.__fw.is_default_net(n['net_dst']): t.append('-d %s' % n['net_dst'].ip()) sort_net_dst = n['net_dst'].addr() else: sort_net_dst = IP('0.0.0.0/0') # vpn rules if (not input and not output) and (n['net_src'].is_vpn() or n['net_dst'].is_vpn()): has_vpn = True t.extend(['-m policy --pol ipsec --mode tunnel']) #remote vpn network is source of the connexion # net_dst: conn = n['net_dst'].get_conn(self.__fw.id()) iface = self.__fw.get_iface_by_id(int(conn['iface'])) if conn['addr']: ndst_iface_addr = iface.get_addr_by_id(conn['addr']) else: ndst_iface_addr = iface.addr() # net_src: conn = n['net_src'].get_conn(self.__fw.id()) iface = self.__fw.get_iface_by_id(int(conn['iface'])) if conn['addr']: nsrc_iface_addr = iface.get_addr_by_id(conn['addr']) else: nsrc_iface_addr = iface.addr() tunnelsrc = n['net_src'].remote() tunneldst = nsrc_iface_addr #remote vpn network is destination of the connexion dir = 'in' if n['net_dst'].is_vpn(): tunnelsrc = ndst_iface_addr tunneldst = n['net_dst'].remote() dir = 'out' t.extend(['--dir',dir, '--tunnel-src', tunnelsrc, '--tunnel-dst', tunneldst]) if (n.get('net_src') is not None \ and self.__fw.is_default_net(n['net_src'])) or (n.get('net_dst') is not None \ and self.__fw.is_default_net(n['net_dst'])): has_internet = True #for INPUT/OUTPUT, don't put -i -o if not output: t.extend(['-i',n['if_src'].name().split(':')[0]]) if not input: t.extend(['-o',n['if_dst'].name().split(':')[0]]) t.extend(['-j',n['name']]) st = gen_rule_list(t)+linesep if n.get('net_src') is not None \ and n.get('net_dst') is not None \ and n['net_src'].is_vpn() \ and n['net_dst'].is_vpn(): st = '' if has_vpn: # We need vpn related flux to be first self.insert_hash_net_chains(h_vpn, sort_net_src, sort_net_dst, st) else: if has_internet and has_vpn is False: if input: sinout_internet += st h_ininternet[n['net_src'].addr()] = st elif output: sinout_internet += st l_outinternet.append(st) else: sinternet+=st self.insert_hash_net_chains(h_internet, sort_net_src, sort_net_dst, st) else: # Put rules in order: forward, input, output if input: sinput += st h_input[n['net_src'].addr()] = st elif output: soutput += st l_output.append(st) else: s+=st self.insert_hash_net_chains(h_forward, sort_net_src, sort_net_dst, st) # Rules for INVALID state invalid = self.__gen_default_invalid_drop(*lwhere)+linesep # LOG and DROP end rules default_log_drop = self.__gen_default_log_drop(*lwhere) sloopback = self.gen_loopback_default_rules() # Get sorted rules for FORWARD, INPUT and OUTPUT s_sorted_vpn = self.get_ipt_string(h_vpn) s_sorted_forward = self.get_ipt_string(h_forward) input_keys = h_input.keys() input_keys.sort() s_sorted_input = "" for input_key in input_keys: s_sorted_input += h_input[input_key] s_sorted_output = ''.join(l_output) s_sorted_internet = self.get_ipt_string(h_internet) ininternet_keys = h_ininternet.keys() ininternet_keys.sort() s_sorted_ininternet = "" for ininternet_key in ininternet_keys: s_sorted_ininternet += h_ininternet[ininternet_key] s_sorted_outinternet = ''.join(l_outinternet) rules = [ estrel, invalid, s_sorted_vpn, s_sorted_forward, s_sorted_input, s_sorted_output, s_sorted_ininternet, \ s_sorted_outinternet, s_sorted_internet + linesep, sloopback + linesep, default_log_drop + linesep ] return rules def insert_hash_net_chains(self, hash, src, dst, mystring): """hash indexed by src network, dst network""" key = (src, dst) if key in hash: raise KeyError("Duplicate key! %s" % key) hash[key] = mystring def networkKey(self, src_dst): src, dst = src_dst return (-src.prefixlen() - dst.prefixlen(), src, dst) def get_ipt_string(self, hash): """get iptables commandes stored in hash commands are sorted by src_addr, dst_addr """ keys = hash.keys() keys.sort(key=self.networkKey) ret = '' for key in keys: line = hash[key] ret += line return ret def gen_rules_chain(self,chain_obj,rescue=0): """Generate rules in one bichain""" chain = chain_obj['name'] l=[] l2=[] #rules syn, rescue: put after new/rst/fin (l list) l_eld = [] rl = self.__fw.hrules()[chain] lch = rl.ordered_rules() if rl.is_bad(): return self.dumb_rules(lch,chain_obj) sacl = '' stmp = '' comment = 0 has_rules = False queue_num = None # Set queue_num if queue type is NFQUEUE if self.queue_type == 'NFQUEUE' and self.queue_num: queue_num = self.queue_num previous_line = None for (rule_order, r) in lch: if r.acl.transparent_proxy == USE_PROXY: continue stmp = r.aclname; has_rules = True comment_rule = '' if(stmp != sacl): sacl = stmp sc = '%s#%s (acl %s)%s' % (linesep, sacl, r.aclid, linesep) comment = 1 else: comment = 0 iface = '' if r.dest() and r.elt_dest().get('is_local'): iface = self.__fw.get_local_ip_connected_to(r.netsrc.addr())['iface'] if r.use_nufw(): proto = r.proto().get('proto') if proto and proto not in ('tcp', 'udp'): raise ValueError('ACL %s: the protocol "%s" cannot be authenticated' %(r.aclid, proto)) if rescue == 0: if comment == 1: l.append(sc) if r.is_auto == 1: l.append('#rules is auto' + linesep) syn_str = "--syn" if r.proto().get('proto') != 'tcp': syn_str = "" ript = RulePrintIptSimple(r, fw=self.__fw, chain=chain_obj, target=self.queue_type, iface=iface, add='-m state --state NEW %s' % (syn_str), ulog_prefix=r.ulog_prefix, logtype=self.__logtype) if queue_num: ript.set_queue_num(queue_num) ssyn = str(ript)+linesep if comment == 1: l2.append(sc) l7connmark = r.proto().get('l7connmark') if l7connmark and not r.elt_dest().get('is_local'): ript.target = 'CONNMARK' ript.connmark = l7connmark l2.append(str(ript)+linesep) line = comment_rule+ssyn without_comment = line.split('#', 1)[0].rstrip() if without_comment != previous_line: l2.append(line) previous_line = without_comment else: rescue = RulePrintIptSimple(r, fw=self.__fw, chain=chain_obj, iface=iface, add=_make_syn(make_target(r.decision), {'proto': r.proto().get('proto')}), target=make_target(r.decision), ulog_prefix=r.ulog_prefix, logtype=self.__logtype) srescue = str(rescue)+linesep if comment == 1: l2.append(sc) if r.is_auto == 1: l2.append('#rules is auto' + linesep) l7connmark = r.proto().get('l7connmark') if l7connmark and not r.elt_dest().get('is_local'): rescue.target = 'CONNMARK' rescue.connmark = l7connmark l2.append(str(rescue)+linesep) l2.append(srescue) else: # Generate normal iptables cmds: target depends on _decision_ attribut rstd = RulePrintIptSimple(r, fw=self.__fw, chain=chain_obj, iface=iface, add=_make_syn(make_target(r.decision), {'proto': r.proto().get('proto')}), target=make_target(r.decision), ulog_prefix=r.ulog_prefix, logtype=self.__logtype) std = str(rstd) if comment == 1: l2.append(sc) l7connmark = r.proto().get('l7connmark') if l7connmark and not r.elt_dest().get('is_local'): rstd.target = 'CONNMARK' rstd.connmark = l7connmark l2.append(str(rstd)+linesep) l2.append(std+linesep) relaccept = '' prefix = '' if self.__logtype == 'ULOG': if self.__fw.chain_input(chain_obj): prefix = "I" elif self.__fw.chain_output(chain_obj): prefix = 'O' else: prefix = 'F' prefix += '0D:' message = logMessage(self.__logprefix, "%s%s DROP" % (prefix, chain)) log = gen_rule('-A',chain,'-j',self.__logtype, message)+linesep drop = gen_rule('-A',chain,'-j DROP') if has_rules: l.append(2*linesep) l.extend(l2) l_eld.extend([relaccept,log,drop]) return (l,l_eld) def gen_rules(self,rescue=0): """Generate rules for all bichains""" sep = linesep*2 forward=[] forward_end = [] linput = [] linput_eld = [] loutput = [] loutput_eld = [] # End Log Drop # We make INPUT,OUTPUT and FORWARD rules in one step seen_chains = set() for chain in self.__fw.chains(all=1, inout=1): # Don't generate log/drop rules twice # for a chain that exist with iface addr and broadcast if chain['name'] in seen_chains: continue seen_chains.add(chain['name']) do_input = False do_output = False (rule, rule_end) = self.gen_rules_chain(chain,rescue=rescue) if self.__fw.chain_input(chain): if rule: linput.extend(rule) linput.append(sep) linput_eld.extend(rule_end) linput_eld.append(sep) do_input = True if self.__fw.chain_output(chain): if rule: loutput.extend(rule) loutput.append(sep) loutput_eld.extend(rule_end); loutput_eld.append(sep) do_output = True if not do_input and not do_output: if rule: forward.extend(rule) forward.append(sep) forward_end.extend(rule_end) forward_end.append(sep) linput.extend(linput_eld) loutput.extend(loutput_eld) forward.extend(forward_end) return (linput, loutput, forward) def gen_vpn_rules(self): l = [] for iface in self.__fw.ifaces(): for n in iface.allnets(): if n.is_vpn(): l.append('#%s%s' % (n.name(),linesep)) text = gen_rule('-A PREROUTING', '-t mangle', '-i',iface.name(), '-p 50','-s', n.remote(), '-j MARK --set-mark', n.mark()) l.append(text+linesep) text = gen_rule('-A PREROUTING', '-t mangle', '-d %s' % n.addr(), '-j MARK --set-mark', n.mark()) l.append(text+linesep) return l def transparent_proxy_rules(self, only_proxy): rules = [] text_rules = set() allrules = self.__fw.allrules() allrules.sort(key=lambda rule: (rule.acl.id, rule.cpt)) previous_acl = None for rule in allrules: acl = rule.acl proxy = acl.transparent_proxy if proxy != only_proxy: continue if proxy == OVERRIDE_PROXY: target = 'ACCEPT' else: target = self.queue_type if acl != previous_acl: rules.append('# %s (ACL #%s)' % (acl.name(), acl.id) + linesep) previous_acl = acl line = formatRule(rule, 'PREROUTING', target) if line in text_rules: continue text_rules.add(line) rules.append(line) return rules def gen_nat_rules(self): """Generate SNAT, DNAT and PNAT iptables rules """ l = [] snat,dnat,pnat = self.__fw.get_nat_rules() l.append('# SNAT%s' % linesep) def cmp_id(r1, r2): return cmp(r1.id, r2.id) snat.sort(cmp_id) dnat.sort(cmp_id) pnat.sort(cmp_id) for r in snat: l.append('# rule %s %s%s' % (r.id, r._name, linesep)) l.append(str(SnatIpt(r))+linesep) l.append('# DNAT%s' % linesep) for r in dnat: l.append('# rule %s %s%s' % (r.id, r._name, linesep)) l.append(str(DnatIpt(r))+linesep) l.append('# PNAT%s' % linesep) for r in pnat: l.append('# rule %s %s%s' % (r.id, r._name, linesep)) l.append(str(PnatIpt(r))+linesep) # Transparent proxy rule for rule in self.__fw.allrules(): if rule.acl.transparent_proxy != USE_PROXY: continue args = ( '-A PREROUTING', '-p tcp', '-m mark', '!', '--mark 0', '-j REDIRECT', '--to-ports %s' % TRANSPARENT_PROXY_PORT, linesep, ) l.append(gen_rule(*args)) break return l def mangle_rules(self): """Create mangle rules for firewall""" # List of rules lrules = [] """iptables -A PREROUTING -t mangle -j CONNMARK --restore-mark > iptables -A POSTROUTING -t mangle -m mark ! --mark 0 -j CONNMARK > --save-mark > > """ if self.__fw.queue is not None: lrules.append(gen_rule('-A PREROUTING','-j CONNMARK --restore-mark',linesep)) lrules.append(gen_rule('-A POSTROUTING', '-m mark', '! --mark 0', '-j CONNMARK --save-mark --mask 0x0000ffff', linesep)) lrules.extend(self.transparent_proxy_rules(OVERRIDE_PROXY)) lrules.extend(self.transparent_proxy_rules(USE_PROXY)) return lrules class RulePrint: def __init__(self,r,fw=''): self.rule = r self.fw = fw if self.rule.src(): self.ef = self.rule.elt_src() else: self.ef = None if self.rule.dest(): self.et = self.rule.elt_dest() else: self.et = None self.do_vpn = True def __str__(self): pass class RulePrintIptSimple(RulePrint): """produces a iptables command form an acl rule """ #TODO: take chain and target in argument def __init__(self, r, fw='', chain=None, target='DROP', add='', insert='', iface='', connmark='', ulog_prefix='', logtype='LOG'): RulePrint.__init__(self, r, fw) self.target = target self.chain_obj = chain self.chain = chain['name'] self.add = add self.insert = '-A' self.queue_num = None if insert != '': self.insert = insert self.netsrc = self.rule.netsrc self.netdst = self.rule.netdst self.iface = iface self.connmark = connmark self.logtype = logtype # Create log prefix if ulog_prefix: log_prefix = str(ulog_prefix).strip() else: log_prefix = str(self.chain) if self.fw.chain_input(self.chain_obj): direction = 'I' elif self.fw.chain_output(self.chain_obj): direction = 'O' else: direction = 'F' self.ulog_prefix = "%s%s%s:%s" % (direction, self.rule.aclid, self.target[0], log_prefix) def _net_is_inclued_in(self,inner,net): """check if inner is inclued in net """ if net is None: return False if inner.find('/')==-1: return False if net.zone()=='external': return False if net.zone()=='vpn': return False ntest = IP(inner) if ntest == net.addr(): return False conn = net.get_conn(self.fw.id()) if ntest != IP('0.0.0.0/0') and "dftgateway" in conn: return True if ntest in net.addr(): return True return False def set_queue_num(self, queue_num): self.queue_num = queue_num def __str__(self): if self.rule.src(): ef = self.rule.src().elts[0] else: ef = None if self.rule.dest(): et = self.rule.dest().elts[0] else: et = None pr = self.rule.proto() t = [self.insert, self.chain] t.extend(formatProtocol(pr)) mark = '' if et and et.get('mark'): mark = et.get('mark') if ef and ef.get('mark'): mark = ef.get('mark') if self.iface: t.extend(['-i', self.iface.name()]) #do we need to print the src/dst ip or dst/src net? sip = '' dip = '' chain = self.chain.split("-", 1) same_iface = (chain[0] == chain[1]) local_rule = "IF" in (chain[0], chain[1]) print_sip = same_iface or local_rule print_dip = True if ef and ef.get('numnet'): sip = ef.get('numnet') if et and et.get('numnet'): dip = et.get('numnet') if sip and sip.find('/')==-1: print_sip = True if dip and dip.find('/')==-1: print_dip = True if sip.find('/')!=-1: if IP(sip) != self.netsrc.addr(): if self._net_is_inclued_in(sip,self.netsrc): print_sip = True else: print_sip = False if dip.find('/')!=-1: if IP(dip) != self.netdst.addr(): if self._net_is_inclued_in(dip,self.netdst): print_dip = True else: print_dip = False if mark and self.do_vpn: t.extend(['-m mark','--mark',mark]) if print_sip and sip: t.extend(['-s',sip]) if print_dip and dip \ and not(dip.endswith("/0") or dip.endswith("/0.0.0.0")): t.extend(['-d',dip]) t.extend([self.add,'-j',self.target]) if self.target == 'ULOG' and self.ulog_prefix: t.append(logMessage('--ulog-prefix', self.ulog_prefix)) if self.target == 'LOG' and self.ulog_prefix: t.append(logMessage('--log-prefix', self.ulog_prefix)) if self.target == 'CONNMARK': t.extend(['--set-mark', '%s' % (self.connmark)]) if self.target == 'NFQUEUE' and self.queue_num: # Set queue num if NFQUEUE is used t.append('--queue-num %s' % self.queue_num) t.append(' # %s' % self.rule.aclid) t.append(str(self.rule.cpt)) t = gen_rule_list(t) if (not self.rule.use_nufw()) and self.rule.log != "no": t = self.createLogRule(t, self.rule.log) + linesep + t return t def createLogRule(self, text, log_limit): iptables = '-j %s ' % self.logtype if self.logtype == 'ULOG': iptables += logMessage('--ulog-prefix', self.ulog_prefix) else: iptables += logMessage('--log-prefix', self.ulog_prefix) if log_limit != "unlimited": iptables += "-m limit --limit %s " % log_limit text = re.sub(' --queue-num [0-9]+', '', text) text = re.sub(' --set-mark [^ ]+', '', text) text = text.replace('-j %s ' % self.target, iptables) text += ' (log)' return text