home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / pyshared / ufw / backend_iptables.py < prev    next >
Encoding:
Python Source  |  2009-04-03  |  35.6 KB  |  952 lines

  1. #
  2. # backend_iptables.py: iptables backend for ufw
  3. #
  4. # Copyright (C) 2008-2009 Canonical Ltd.
  5. #
  6. #    This program is free software: you can redistribute it and/or modify
  7. #    it under the terms of the GNU General Public License version 3,
  8. #    as published by the Free Software Foundation.
  9. #
  10. #    This program is distributed in the hope that it will be useful,
  11. #    but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13. #    GNU General Public License for more details.
  14. #
  15. #    You should have received a copy of the GNU General Public License
  16. #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
  17. #
  18.  
  19. import os
  20. import re
  21. import sys
  22. import tempfile
  23.  
  24. from ufw.common import UFWError, UFWRule, config_dir, state_dir, prefix_dir
  25. from ufw.util import warn, debug, msg, cmd, cmd_pipe
  26. import ufw.backend
  27.  
  28.  
  29. class UFWBackendIptables(ufw.backend.UFWBackend):
  30.     def __init__(self, d):
  31.         self.comment_str = "# " + ufw.common.programName + "_comment #"
  32.  
  33.         files = {}
  34.         files['rules'] = os.path.join(state_dir, 'user.rules')
  35.         files['before_rules'] = os.path.join(config_dir, 'ufw/before.rules')
  36.         files['after_rules'] = os.path.join(config_dir, 'ufw/after.rules')
  37.         files['rules6'] = os.path.join(state_dir, 'user6.rules')
  38.         files['before6_rules'] = os.path.join(config_dir, 'ufw/before6.rules')
  39.         files['after6_rules'] = os.path.join(config_dir, 'ufw/after6.rules')
  40.         files['init'] = os.path.join(prefix_dir, 'share/ufw/ufw-init')
  41.  
  42.         ufw.backend.UFWBackend.__init__(self, "iptables", d, files)
  43.  
  44.         self.chains = {'before': [], 'user': [], 'after': [], 'misc': []}
  45.         for ver in ['4', '6']:
  46.             chain_prefix = "ufw"
  47.             if ver == "6":
  48.                 if self.use_ipv6():
  49.                     chain_prefix += ver
  50.                 elif ver == "6":
  51.                     continue
  52.  
  53.             for loc in ['before', 'user', 'after']:
  54.                 for target in ['input', 'output', 'forward']:
  55.                    chain = "%s-%s-logging-%s" % (chain_prefix, loc, target)
  56.                    self.chains[loc].append(chain)
  57.             self.chains['misc'].append(chain_prefix + "-logging-deny")
  58.             self.chains['misc'].append(chain_prefix + "-logging-allow")
  59.  
  60.     def get_default_policy(self, primary="input"):
  61.         '''Get current policy'''
  62.         policy = "default_" + primary + "_policy"
  63.  
  64.         rstr = ""
  65.         if self.defaults[policy] == "accept":
  66.             rstr = "allow"
  67.         elif self.defaults[policy] == "reject":
  68.             rstr = "reject"
  69.         else:
  70.             rstr = "deny"
  71.  
  72.         return rstr
  73.  
  74.     def get_default_application_policy(self):
  75.         '''Get current policy'''
  76.         rstr = _("New profiles:")
  77.         if self.defaults['default_application_policy'] == "accept":
  78.             rstr += " allow"
  79.         elif self.defaults['default_application_policy'] == "drop":
  80.             rstr += " deny"
  81.         elif self.defaults['default_application_policy'] == "reject":
  82.             rstr += " reject"
  83.         else:
  84.             rstr += " skip"
  85.  
  86.         return rstr
  87.  
  88.     def set_default_policy(self, policy):
  89.         '''Sets default policy of firewall'''
  90.         if not self.dryrun:
  91.             if policy != "allow" and policy != "deny" and policy != "reject":
  92.                 err_msg = _("Unsupported policy '%s'") % (policy)
  93.                 raise UFWError(err_msg)
  94.  
  95.             old_log_str = ''
  96.             new_log_str = ''
  97.             if policy == "allow":
  98.                 self.set_default(self.files['defaults'], \
  99.                                             "DEFAULT_INPUT_POLICY", \
  100.                                             "\"ACCEPT\"")
  101.                 old_log_str = 'UFW BLOCK'
  102.                 new_log_str = 'UFW ALLOW'
  103.             elif policy == "reject":
  104.                 self.set_default(self.files['defaults'], \
  105.                                             "DEFAULT_INPUT_POLICY", \
  106.                                             "\"REJECT\"")
  107.                 old_log_str = 'UFW ALLOW'
  108.                 new_log_str = 'UFW BLOCK'
  109.             else:
  110.                 self.set_default(self.files['defaults'], \
  111.                                             "DEFAULT_INPUT_POLICY", \
  112.                                             "\"DROP\"")
  113.                 old_log_str = 'UFW ALLOW'
  114.                 new_log_str = 'UFW BLOCK'
  115.  
  116.             # Switch logging message in catch-all rules
  117.             pat = re.compile(r'' + old_log_str)
  118.             for f in [self.files['after_rules'], self.files['after6_rules']]:
  119.                 try:
  120.                     fns = ufw.util.open_files(f)
  121.                 except Exception:
  122.                     raise
  123.                 fd = fns['tmp']
  124.  
  125.                 for line in fns['orig']:
  126.                     if pat.search(line):
  127.                         os.write(fd, pat.sub(new_log_str, line))
  128.                     else:
  129.                         os.write(fd, line)
  130.  
  131.                 ufw.util.close_files(fns)
  132.  
  133.         rstr = _("Default policy changed to '%s'\n") % (policy)
  134.         rstr += _("(be sure to update your rules accordingly)")
  135.  
  136.         return rstr
  137.  
  138.     def get_running_raw(self):
  139.         '''Show current running status of firewall'''
  140.         if self.dryrun:
  141.             out = "> " + _("Checking raw iptables\n")
  142.             out += "> " + _("Checking raw ip6tables\n")
  143.             return out
  144.  
  145.         err_msg = _("problem running")
  146.  
  147.         out = "IPV4:\n"
  148.         for table in ['filter', 'nat', 'mangle', 'raw']:
  149.             (rc, tmp) = cmd(['iptables', '-L', '-n', '-v', '-x', '-t', table])
  150.             out += tmp
  151.             if rc != 0:
  152.                 raise UFWError(out)
  153.  
  154.         out += "\n\nIPV6:\n"
  155.         for table in ['filter', 'mangle', 'raw']:
  156.             (rc, tmp) = cmd(['ip6tables', '-L', '-n', '-v', '-x', '-t', table])
  157.             out += tmp
  158.             if rc != 0:
  159.                 raise UFWError(out)
  160.  
  161.         return out
  162.  
  163.     def get_status(self, verbose=False, show_count=False):
  164.         '''Show ufw managed rules'''
  165.         out = ""
  166.         out6 = ""
  167.         if self.dryrun:
  168.             out = "> " + _("Checking iptables\n")
  169.             if self.use_ipv6():
  170.                 out += "> " + _("Checking ip6tables\n")
  171.             return out
  172.  
  173.         # Is the firewall loaded at all?
  174.         (rc, out) = cmd(['iptables', '-L', 'ufw-user-input', '-n'])
  175.         if rc != 0:
  176.             return _("Status: inactive")
  177.  
  178.         err_msg = _("problem running")
  179.         if self.use_ipv6():
  180.             (rc, out6) = cmd(['ip6tables', '-L', 'ufw6-user-input', '-n'])
  181.             if rc != 0:
  182.                 raise UFWError(err_msg + " ip6tables")
  183.  
  184.         if out == "" and out6 == "":
  185.             return _("Status: active")
  186.  
  187.         str = ""
  188.         rules = self.rules + self.rules6
  189.         count = 1
  190.         app_rules = {}
  191.         for r in rules:
  192.             location = {}
  193.             tuple = ""
  194.             show_proto = True
  195.             if not verbose and (r.dapp != "" or r.sapp != ""):
  196.                 show_proto = False
  197.                 tuple = r.get_app_tuple()
  198.  
  199.                 if app_rules.has_key(tuple):
  200.                     debug("Skipping found tuple '%s'" % (tuple))
  201.                     continue
  202.                 else:
  203.                     app_rules[tuple] = True
  204.  
  205.             for loc in [ 'dst', 'src' ]:
  206.                 location[loc] = ""
  207.  
  208.                 port = ""
  209.                 tmp = ""
  210.                 if loc == "dst":
  211.                     tmp = r.dst
  212.                     if not verbose and r.dapp != "":
  213.                         port = r.dapp
  214.                         if r.v6 and tmp == "::/0":
  215.                             port += " (v6)"
  216.                     else:
  217.                         port = r.dport
  218.                 else:
  219.                     tmp = r.src
  220.                     if not verbose and r.sapp != "":
  221.                         port = r.sapp
  222.                         if r.v6 and tmp == "::/0":
  223.                             port += " (v6)"
  224.                     else:
  225.                         port = r.sport
  226.  
  227.                 if tmp != "0.0.0.0/0" and tmp != "::/0":
  228.                     location[loc] = tmp
  229.  
  230.                 if port != "any":
  231.                     if location[loc] == "":
  232.                         location[loc] = port
  233.                     else:
  234.                         location[loc] += " " + port
  235.  
  236.                     if show_proto and r.protocol != "any":
  237.                         location[loc] += "/" + r.protocol
  238.  
  239.                     if verbose:
  240.                         if loc == "dst" and r.dapp != "":
  241.                             location[loc] += " (%s" % (r.dapp)
  242.                             if r.v6 and tmp == "::/0":
  243.                                 location[loc] += " (v6)"
  244.                             location[loc] += ")"
  245.                         if loc == "src" and r.sapp != "":
  246.                             location[loc] += " (%s" % (r.sapp)
  247.                             if r.v6 and tmp == "::/0":
  248.                                 location[loc] += " (v6)"
  249.                             location[loc] += ")"
  250.  
  251.                 if port == "any":
  252.                     if tmp == "0.0.0.0/0" or tmp == "::/0":
  253.                         location[loc] = "Anywhere"
  254.  
  255.                         # Show the protocol if Anywhere to Anwhere, have
  256.                         # protocol and source and dest ports are any
  257.                         if show_proto and r.protocol != "any" and \
  258.                            r.dst == r.src and r.dport == r.sport:
  259.                             location[loc] += "/" + r.protocol
  260.  
  261.                         if tmp == "::/0":
  262.                             location[loc] += " (v6)"
  263.                     else:
  264.                         # Show the protocol if have protocol, and source
  265.                         # and dest ports are any
  266.                         if show_proto and r.protocol != "any" and \
  267.                            r.dport == r.sport:
  268.                             location[loc] += "/" + r.protocol
  269.  
  270.             if show_count:
  271.                 str += "[%2d] " % (count)
  272.             
  273.             log_str = ""
  274.             if r.logtype:
  275.                 log_str = " (%s)" % (r.logtype.lower())
  276.             str += "%-26s %-8s%s%s\n" % (location['dst'], r.action.upper(), \
  277.                     location['src'], log_str)
  278.             count += 1
  279.  
  280.         if str != "":
  281.             header = "\n\n"
  282.             if show_count:
  283.                 header += "     "
  284.             header += "%-26s %-8s%s\n" % (_("To"), _("Action"), _("From"))
  285.             if show_count:
  286.                 header += "     "
  287.             header += "%-26s %-8s%s\n" % (_("--"), _("------"), _("----"))
  288.             str = header + str
  289.  
  290.         if verbose:
  291.             (level, logging_str) = self.get_loglevel()
  292.             policy_str = _("Default: %s") % (self.get_default_policy())
  293.             app_policy_str = self.get_default_application_policy()
  294.             return _("Status: active") + "\n%s\n%s\n%s%s" % \
  295.                                                     (logging_str, policy_str, \
  296.                                                      app_policy_str, str)
  297.         else:
  298.             return _("Status: active") + "%s" % (str)
  299.  
  300.     def stop_firewall(self):
  301.         '''Stops the firewall'''
  302.         err_msg = _("problem running")
  303.         if self.dryrun:
  304.             msg("> " + _("running ufw-init"))
  305.         else:
  306.             (rc, out) = cmd([self.files['init'], 'force-stop'])
  307.             if rc != 0:
  308.                 raise UFWError(err_msg + " ufw-init")
  309.  
  310.     def start_firewall(self):
  311.         '''Starts the firewall'''
  312.         err_msg = _("problem running")
  313.         if self.dryrun:
  314.             msg("> " + _("running ufw-init"))
  315.         else:
  316.             (rc, out) = cmd([self.files['init'], 'start'])
  317.             if rc != 0:
  318.                 raise UFWError(err_msg + " ufw-init")
  319.  
  320.             if not self.defaults.has_key('loglevel') or \
  321.                self.defaults['loglevel'] not in self.loglevels.keys():
  322.                 # Add the loglevel if not valid
  323.                 try:
  324.                     self.set_loglevel("low")
  325.                 except:
  326.                     err_msg = _("Could not set LOGLEVEL")
  327.                     raise UFWError(err_msg)
  328.             else:
  329.                 try:
  330.                     self.update_logging(self.defaults['loglevel'])
  331.                 except:
  332.                     err_msg = _("Could not load logging rules")
  333.                     raise UFWError(err_msg)
  334.  
  335.     def _need_reload(self, v6):
  336.         '''Check if all chains exist'''
  337.         if self.dryrun:
  338.             return False
  339.  
  340.         prefix = "ufw"
  341.         exe = "iptables"
  342.         if v6:
  343.             prefix = "ufw6"
  344.             exe = "ip6tables"
  345.  
  346.         for chain in [ 'input', 'output', 'forward', 'limit', 'limit-accept' ]:
  347.             if v6 and (chain == "limit" or chain == "limit-accept"):
  348.                 continue
  349.  
  350.             (rc, out) = cmd([exe, '-n', '-L', prefix + "-user-" + chain])
  351.             if rc != 0:
  352.                 debug("_need_reload: forcing reload")
  353.                 return True
  354.  
  355.         return False
  356.  
  357.     def _reload_user_rules(self):
  358.         '''Reload firewall rules file'''
  359.         err_msg = _("problem running")
  360.         if self.dryrun:
  361.             msg("> | iptables-restore")
  362.             if self.use_ipv6():
  363.                 msg("> | ip6tables-restore")
  364.         elif self._is_enabled():
  365.             # first flush the user logging chains
  366.             try:
  367.                 for c in self.chains['user']:
  368.                     self._chain_cmd(c, ['-F', c])
  369.                     self._chain_cmd(c, ['-Z', c])
  370.             except:
  371.                 raise UFWError(err_msg)
  372.  
  373.             # then restore the system rules
  374.             (rc, out) = cmd_pipe(['cat', self.files['rules']], \
  375.                                  ['iptables-restore', '-n'])
  376.             if rc != 0:
  377.                 raise UFWError(err_msg + " iptables")
  378.  
  379.             if self.use_ipv6():
  380.                 (rc, out) = cmd_pipe(['cat', self.files['rules6']], \
  381.                                      ['ip6tables-restore', '-n'])
  382.                 if rc != 0:
  383.                     raise UFWError(err_msg + " ip6tables")
  384.  
  385.     def _get_rules_from_formatted(self, frule, prefix):
  386.         '''Return list of iptables rules appropriate for sending'''
  387.         snippets = []
  388.  
  389.         # adjust reject and protocol 'all'
  390.         pat_proto = re.compile(r'-p all ')
  391.         pat_port = re.compile(r'port ')
  392.         pat_reject = re.compile(r'-j (REJECT(_log(-all)?)?)')
  393.         if pat_proto.search(frule):
  394.             if pat_port.search(frule):
  395.                 if pat_reject.search(frule):
  396.                     snippets.append(pat_proto.sub('-p tcp ', \
  397.                         pat_reject.sub(r'-j \1 --reject-with tcp-reset ', \
  398.                         frule)))
  399.                 else:
  400.                     snippets.append(pat_proto.sub('-p tcp ', frule))
  401.                 snippets.append(pat_proto.sub('-p udp ', frule))
  402.             else:
  403.                 snippets.append(pat_proto.sub('', frule))
  404.         else:
  405.             snippets.append(frule)
  406.  
  407.         # adjust for logging rules
  408.         pat_log = re.compile(r'(.*)-j ([A-Z]+)_log(-all)?(.*)')
  409.         pat_logall = re.compile(r'-j [A-Z]+_log-all')
  410.         pat_chain = re.compile(r'(-A|-D) ([a-zA-Z0-9\-]+)')
  411.         limit_args = '-m limit --limit 3/min --limit-burst 10'
  412.         for i, s in enumerate(snippets):
  413.             if pat_log.search(s):
  414.                 policy = pat_log.sub(r'\2', s).strip()
  415.                 if policy.lower() == "accept":
  416.                     policy = "ALLOW"
  417.                 elif policy.lower() == "limit":
  418.                     policy = "LIMIT"
  419.                 else:
  420.                     policy = "BLOCK"
  421.  
  422.                 lstr = '%s -j LOG --log-prefix "[UFW %s] "' % (limit_args, \
  423.                        policy)
  424.                 if not pat_logall.search(s):
  425.                     lstr = '-m state --state NEW ' + lstr
  426.                 snippets[i] = pat_log.sub(r'\1-j \2\4', s)
  427.                 snippets.insert(i, pat_log.sub(r'\1-j ' + prefix + \
  428.                                                '-user-logging-input', s))
  429.                 snippets.insert(i, pat_chain.sub(r'\1 ' + prefix + \
  430.                                                  '-user-logging-input', 
  431.                                                  pat_log.sub(r'\1-j RETURN', \
  432.                                                  s)))
  433.                 snippets.insert(i, pat_chain.sub(r'\1 ' + prefix + \
  434.                                                  '-user-logging-input', 
  435.                                                  pat_log.sub(r'\1' + lstr, s)))
  436.  
  437.         # adjust for limit
  438.         pat_limit = re.compile(r' -j LIMIT')
  439.         for i, s in enumerate(snippets):
  440.             if pat_limit.search(s):
  441.                 tmp1 = pat_limit.sub(' -m state --state NEW -m recent --set', \
  442.                                      s)
  443.                 tmp2 = pat_limit.sub(' -m state --state NEW -m recent' + \
  444.                                      ' --update --seconds 30 --hitcount 6' + \
  445.                                      ' -j ' + prefix + '-user-limit', s)
  446.                 tmp3 = pat_limit.sub(' -j ' + prefix + '-user-limit-accept', s)
  447.                 snippets[i] = tmp3
  448.                 snippets.insert(i, tmp2)
  449.                 snippets.insert(i, tmp1)
  450.  
  451.         return snippets
  452.  
  453.     def _get_lists_from_formatted(self, frule, prefix):
  454.         '''Return list of iptables rules appropriate for sending as arguments
  455.            to cmd()
  456.         '''
  457.         snippets = []
  458.         str_snippets = self._get_rules_from_formatted(frule, prefix)
  459.  
  460.         # split the string such that the log prefix can contain spaces
  461.         pat = re.compile(r'(.*) --log-prefix (".* ")(.*)')
  462.         for i, s in enumerate(str_snippets):
  463.             snippets.append(pat.sub(r'\1', s).split())
  464.             if pat.match(s):
  465.                 snippets[i].append("--log-prefix")
  466.                 snippets[i].append(pat.sub(r'\2', s).replace('"', ''))
  467.                 snippets[i] += pat.sub(r'\3', s).split()
  468.  
  469.         return snippets
  470.  
  471.     def _read_rules(self):
  472.         '''Read in rules that were added by ufw.'''
  473.         rfns = [self.files['rules']]
  474.         if self.use_ipv6():
  475.             rfns.append(self.files['rules6'])
  476.  
  477.         for f in rfns:
  478.             try:
  479.                 orig = ufw.util.open_file_read(f)
  480.             except Exception:
  481.                 err_msg = _("Couldn't open '%s' for reading") % (f)
  482.                 raise UFWError(err_msg)
  483.  
  484.             pat_tuple = re.compile(r'^### tuple ###\s*')
  485.             for line in orig:
  486.                 if pat_tuple.match(line):
  487.                     tuple = pat_tuple.sub('', line)
  488.                     tmp = re.split(r'\s+', tuple.strip())
  489.                     if len(tmp) != 6 and len(tmp) != 8:
  490.                         warn_msg = _("Skipping malformed tuple (bad length): %s") % (tuple)
  491.                         warn(warn_msg)
  492.                         continue
  493.                     else:
  494.                         try:
  495.                             if len(tmp) == 6:
  496.                                 rule = UFWRule(tmp[0], tmp[1], tmp[2], tmp[3],
  497.                                                tmp[4], tmp[5])
  498.                             else:
  499.                                 rule = UFWRule(tmp[0], tmp[1], tmp[2], tmp[3],
  500.                                                tmp[4], tmp[5])
  501.                                 # Removed leading [sd]app_ and unescape spaces
  502.                                 pat_space = re.compile('%20')
  503.                                 if tmp[6] != "-":
  504.                                     rule.dapp = pat_space.sub(' ', tmp[6])
  505.                                 if tmp[7] != "-":
  506.                                     rule.sapp = pat_space.sub(' ', tmp[7])
  507.                         except UFWError:
  508.                             warn_msg = _("Skipping malformed tuple: %s") % \
  509.                                         (tuple)
  510.                             warn(warn_msg)
  511.                             continue
  512.                         if f == self.files['rules6']:
  513.                             rule.set_v6(True)
  514.                             self.rules6.append(rule)
  515.                         else:
  516.                             rule.set_v6(False)
  517.                             self.rules.append(rule)
  518.  
  519.             orig.close()
  520.  
  521.     def _write_rules(self, v6=False):
  522.         '''Write out new rules to file to user chain file'''
  523.         rules_file = self.files['rules']
  524.         if v6:
  525.             rules_file = self.files['rules6']
  526.  
  527.         try:
  528.             fns = ufw.util.open_files(rules_file)
  529.         except Exception:
  530.             raise
  531.  
  532.         chain_prefix = "ufw"
  533.         rules = self.rules
  534.         if v6:
  535.             chain_prefix = "ufw6"
  536.             rules = self.rules6
  537.  
  538.         if self.dryrun:
  539.             fd = sys.stdout.fileno()
  540.         else:
  541.             fd = fns['tmp']
  542.  
  543.         # Write header
  544.         os.write(fd, "*filter\n")
  545.         os.write(fd, ":" + chain_prefix + "-user-input - [0:0]\n")
  546.         os.write(fd, ":" + chain_prefix + "-user-output - [0:0]\n")
  547.         os.write(fd, ":" + chain_prefix + "-user-forward - [0:0]\n")
  548.  
  549.         if chain_prefix == "ufw":
  550.             # Rate limiting only supported with IPv4
  551.             os.write(fd, ":" + chain_prefix + "-user-limit - [0:0]\n")
  552.             os.write(fd, ":" + chain_prefix + "-user-limit-accept - [0:0]\n")
  553.  
  554.         os.write(fd, "### RULES ###\n")
  555.  
  556.         # Write rules
  557.         for r in rules:
  558.             action = r.action
  559.             if r.logtype != "":
  560.                 action += "_" + r.logtype
  561.  
  562.             if r.dapp == "" and r.sapp == "":
  563.                 os.write(fd, "\n### tuple ### %s %s %s %s %s %s\n" % \
  564.                      (action, r.protocol, r.dport, r.dst, r.sport, r.src))
  565.             else:
  566.                 pat_space = re.compile(' ')
  567.                 dapp = "-"
  568.                 if r.dapp:
  569.                     dapp = pat_space.sub('%20', r.dapp)
  570.                 sapp = "-"
  571.                 if r.sapp:
  572.                     sapp = pat_space.sub('%20', r.sapp)
  573.                 os.write(fd, "\n### tuple ### %s %s %s %s %s %s %s %s\n" \
  574.                      % (action, r.protocol, r.dport, r.dst, r.sport, r.src, \
  575.                         dapp, sapp))
  576.  
  577.             rule_str = "-A " + chain_prefix + "-user-input " + \
  578.                        r.format_rule() + "\n"
  579.             for s in self._get_rules_from_formatted(rule_str, chain_prefix):
  580.                 os.write(fd, s)
  581.  
  582.         # Write footer
  583.         os.write(fd, "\n### END RULES ###\n")
  584.  
  585.         if chain_prefix == "ufw":
  586.             # Rate limiting only supported with IPv4
  587.             os.write(fd, "-A " + chain_prefix + "-user-limit -m limit " + \
  588.                          "--limit 3/minute -j LOG --log-prefix " + \
  589.                          "\"[UFW LIMIT BLOCK] \"\n")
  590.             os.write(fd, "-A " + chain_prefix + "-user-limit -j REJECT\n")
  591.             os.write(fd, "-A " + chain_prefix + "-user-limit-accept -j ACCEPT\n")
  592.  
  593.         os.write(fd, "COMMIT\n")
  594.  
  595.         if self.dryrun:
  596.             ufw.util.close_files(fns, False)
  597.         else:
  598.             ufw.util.close_files(fns)
  599.  
  600.     def set_rule(self, rule, allow_reload=True):
  601.         '''Updates firewall with rule by:
  602.         * appending the rule to the chain if new rule and firewall enabled
  603.         * deleting the rule from the chain if found and firewall enabled
  604.         * inserting the rule if possible and firewall enabled
  605.         * updating user rules file
  606.         * reloading the user rules file if rule is modified
  607.         '''
  608.         rstr = ""
  609.  
  610.         if rule.v6:
  611.             if not self.use_ipv6():
  612.                 err_msg = _("Adding IPv6 rule failed: IPv6 not enabled")
  613.                 raise UFWError(err_msg)
  614.             if rule.action == 'limit':
  615.                 # Netfilter doesn't have ip6t_recent yet, so skip
  616.                 return _("Skipping unsupported IPv6 '") + "%s" % (rule.action) + _("' rule")
  617.  
  618.         if rule.multi and rule.protocol != "udp" and rule.protocol != "tcp":
  619.             err_msg = _("Must specify 'tcp' or 'udp' with multiple ports")
  620.             raise UFWError(err_msg)
  621.  
  622.         newrules = []
  623.         found = False
  624.         modified = False
  625.         delete = False
  626.  
  627.         rules = self.rules
  628.         position = rule.position
  629.         if rule.v6:
  630.             if self.iptables_version < "1.4" and (rule.dapp != "" or rule.sapp != ""):
  631.                 return _("Skipping IPv6 application rule. Need at least iptables 1.4")
  632.             rules = self.rules6
  633.  
  634.         # bail if we have a bad position
  635.         if position < 0 or position > len(rules):
  636.             err_msg = _("Invalid position '%d'") % (position)
  637.             raise UFWError(err_msg)
  638.  
  639.         if position > 0 and rule.remove:
  640.             err_msg = _("Cannot specify insert and delete")
  641.             raise UFWError(err_msg)
  642.         if position > len(rules):
  643.             err_msg = _("Cannot insert rule at position '%d'") % position
  644.             raise UFWError(err_msg)
  645.  
  646.         # First construct the new rules list
  647.         try:
  648.             rule.normalize()
  649.         except Exception:
  650.             raise
  651.  
  652.         count = 1
  653.         inserted = False
  654.         matches = 0
  655.         last = ('', '', '', '')
  656.         for r in rules:
  657.             try:
  658.                 r.normalize()
  659.             except Exception:
  660.                 raise
  661.  
  662.             current = (r.dst, r.src, r.dapp, r.sapp)
  663.             if count == position:
  664.                 # insert the rule if:
  665.                 # 1. the last rule was not an application rule
  666.                 # 2. the current rule is not an application rule
  667.                 # 3. the last application rule is different than the current
  668.                 #    while the new rule is different than the current one
  669.                 if (last[2] == '' and last[3] == '' and count > 1) or \
  670.                    (current[2] == '' and current[3] == '') or \
  671.                    last != current:
  672.                     inserted = True
  673.                     newrules.append(rule)
  674.                     last = ('', '', '', '')
  675.                 else:
  676.                     position += 1
  677.             last = current
  678.             count += 1
  679.  
  680.             ret = UFWRule.match(r, rule)
  681.             if ret < 1:
  682.                 matches += 1
  683.  
  684.             if ret == 0 and not found and not inserted:
  685.                 # If find the rule, add it if it's not to be removed, otherwise
  686.                 # skip it.
  687.                 found = True
  688.                 if not rule.remove:
  689.                     newrules.append(rule)
  690.             elif ret < 0 and not rule.remove and not inserted:
  691.                 # If only the action is different, replace the rule if it's not
  692.                 # to be removed.
  693.                 found = True
  694.                 modified = True
  695.                 newrules.append(rule)
  696.             else:
  697.                 newrules.append(r)
  698.  
  699.         if inserted:
  700.             if matches > 0:
  701.                 rstr = _("Skipping inserting existing rule")
  702.                 if rule.v6:
  703.                     rstr += " (v6)"
  704.                 return rstr
  705.         else:
  706.             # Add rule to the end if it was not already added.
  707.             if not found and not rule.remove:
  708.                 newrules.append(rule)
  709.  
  710.             # Don't process non-existing or unchanged pre-exisiting rules
  711.             if not found and rule.remove and not self.dryrun:
  712.                 rstr = _("Could not delete non-existent rule")
  713.                 if rule.v6:
  714.                     rstr += " (v6)"
  715.                 return rstr
  716.             elif found and not rule.remove and not modified:
  717.                 rstr = _("Skipping adding existing rule")
  718.                 if rule.v6:
  719.                     rstr += " (v6)"
  720.                 return rstr
  721.  
  722.         if rule.v6:
  723.             self.rules6 = newrules
  724.         else:
  725.             self.rules = newrules
  726.  
  727.         # Update the user rules file
  728.         try:
  729.             self._write_rules(rule.v6)
  730.         except Exception:
  731.             err_msg = _("Couldn't update rules file")
  732.             UFWError(err_msg)
  733.  
  734.         # We wrote out the rules, so set reasonable string. We will change
  735.         # this below when operating on the live firewall.
  736.         rstr = _("Rules updated")
  737.         if rule.v6:
  738.             rstr = _("Rules updated (v6)")
  739.  
  740.         # Operate on the chains
  741.         if self._is_enabled() and not self.dryrun:
  742.             flag = ""
  743.             if modified or self._need_reload(rule.v6) or inserted:
  744.                 rstr = _("Rule ")
  745.                 if inserted:
  746.                     rstr += _("inserted")
  747.                 else:
  748.                     rstr += _("updated")
  749.                 if rule.v6:
  750.                     rstr += " (v6)"
  751.                 if allow_reload:
  752.                     # Reload the chain
  753.                     try:
  754.                         self._reload_user_rules()
  755.                     except Exception:
  756.                         raise
  757.                 else:
  758.                     rstr += _(" (skipped reloading firewall)")
  759.             elif found and rule.remove:
  760.                 flag = '-D'
  761.                 rstr = _("Rule deleted")
  762.             elif not found and not modified and not rule.remove:
  763.                 flag = '-A'
  764.                 rstr = _("Rule added")
  765.  
  766.             if flag != "":
  767.                 exe = "iptables"
  768.                 chain_prefix = "ufw"
  769.                 if rule.v6:
  770.                     exe = "ip6tables"
  771.                     chain_prefix = "ufw6"
  772.                     rstr += " (v6)"
  773.                 chain = chain_prefix + "-user-input"
  774.  
  775.                 # Is the firewall running?
  776.                 err_msg = _("Could not update running firewall")
  777.                 (rc, out) = cmd([exe, '-L', chain, '-n'])
  778.                 if rc != 0:
  779.                     raise UFWError(err_msg)
  780.  
  781.                 rule_str = "%s %s %s" % (flag, chain, rule.format_rule())
  782.                 pat_log = re.compile(r'(-A +)(ufw6?-user-[a-z\-]+)(.*)')
  783.                 for s in self._get_lists_from_formatted(rule_str, \
  784.                                                                chain_prefix):
  785.                     (rc, out) = cmd([exe] + s)
  786.                     if rc != 0:
  787.                         msg(out, sys.stderr)
  788.                         UFWError(err_msg)
  789.  
  790.                     # delete any lingering RETURN rules (needed for upgrades)
  791.                     if flag == "-A" and pat_log.search(" ".join(s)):
  792.                         c = pat_log.sub(r'\2', " ".join(s))
  793.                         (rc, out) = cmd([exe, '-D', c, '-j', 'RETURN'])
  794.                         if rc != 0:
  795.                             debug("FAILOK: " + err_msg)
  796.  
  797.         return rstr
  798.  
  799.     def get_app_rules_from_system(self, template, v6):
  800.         '''Return a list of UFWRules from the system based on template rule'''
  801.         rules = []
  802.         app_rules = []
  803.  
  804.         if v6:
  805.             rules = self.rules6
  806.         else:
  807.             rules = self.rules
  808.  
  809.         norm = template.dup_rule()
  810.         norm.set_v6(v6)
  811.         norm.normalize()
  812.         tuple = norm.get_app_tuple()
  813.  
  814.         for r in rules:
  815.             tmp = r.dup_rule()
  816.             tmp.normalize()
  817.             tmp_tuple = tmp.get_app_tuple()
  818.             if tmp_tuple == tuple:
  819.                 app_rules.append(tmp)
  820.  
  821.         return app_rules
  822.  
  823.     def _chain_cmd(self, chain, args, fail_ok=False):
  824.         '''Perform command on chain'''
  825.         exe = "iptables"
  826.         if chain.startswith("ufw6"):
  827.             exe = "ip6tables"
  828.         (rc, out) = cmd([exe] + args)
  829.         if rc != 0:
  830.            err_msg = _("Could not perform '%s'") % (args)
  831.            if fail_ok:
  832.                debug("FAILOK: " + err_msg)
  833.            else: 
  834.                raise UFWError(err_msg)
  835.  
  836.     def update_logging(self, level):
  837.         '''Update loglevel of running firewall'''
  838.         if not self._is_enabled():
  839.             return
  840.  
  841.         if level not in self.loglevels.keys():
  842.             err_msg = _("Invalid log level '%s'") % (level)
  843.             raise UFWError(err_msg)
  844.  
  845.         # make sure all the chains are here, it's redundant but helps make
  846.         # sure the chains are in a consistent state
  847.         err_msg = _("Could not update running firewall")
  848.         for c in self.chains['before'] + self.chains['user'] + \
  849.            self.chains['after'] + self.chains['misc']:
  850.             try:
  851.                 self._chain_cmd(c, ['-L', c, '-n'])
  852.             except:
  853.                 raise UFWError(err_msg)
  854.  
  855.         # Flush all the logging chains except 'user'
  856.         try:
  857.             for c in self.chains['before'] + self.chains['after'] + \
  858.                self.chains['misc']:
  859.                 self._chain_cmd(c, ['-F', c])
  860.                 self._chain_cmd(c, ['-Z', c])
  861.         except:
  862.             raise UFWError(err_msg)
  863.  
  864.         if level == "off":
  865.             # when off, insert a RETURN rule at the top of user rules, thus
  866.             # preserving the rules
  867.             for c in self.chains['user']:
  868.                 self._chain_cmd(c, ['-D', c, '-j', 'RETURN'], fail_ok=True)
  869.                 self._chain_cmd(c, ['-I', c, '-j', 'RETURN'])
  870.             return
  871.         else:
  872.             # when on, remove the RETURN rule at the top of user rules, thus
  873.             # honoring the log rules
  874.             for c in self.chains['user']:
  875.                 self._chain_cmd(c, ['-D', c, '-j', 'RETURN'], fail_ok=True)
  876.  
  877.         limit_args = ['-m', 'limit', '--limit', '3/min', '--limit-burst', '10']
  878.  
  879.         # log levels of low and higher log blocked packets
  880.         if self.loglevels[level] >= self.loglevels["low"]:
  881.             # Setup the policy violation logging chains
  882.             largs = []
  883.             # log levels under high use limit
  884.             if self.loglevels[level] < self.loglevels["high"]:
  885.                 largs = limit_args
  886.             for c in self.chains['after']:
  887.                 for t in ['input', 'output', 'forward']:
  888.                     if c.endswith(t):
  889.                         if self.get_default_policy(t) == "reject" or \
  890.                            self.get_default_policy(t) == "deny":
  891.                             msg = "[UFW BLOCK] "
  892.                             try:
  893.                                 self._chain_cmd(c, ['-A', c, '-j', 'LOG', \
  894.                                                     '--log-prefix', msg] + largs)
  895.                             except:
  896.                                 raise
  897.                         elif self.loglevels[level] >= self.loglevels["medium"]:
  898.                             msg = "[UFW ALLOW] "
  899.                             try:
  900.                                 self._chain_cmd(c, ['-A', c, '-j', 'LOG', \
  901.                                                     '--log-prefix', msg] + largs)
  902.                             except:
  903.                                 raise
  904.  
  905.             # Setup the miscellaneous logging chains
  906.             largs = []
  907.             # log levels under high use limit
  908.             if self.loglevels[level] < self.loglevels["high"]:
  909.                 largs = limit_args
  910.  
  911.             for c in self.chains['misc']:
  912.                 if c.endswith("allow"):
  913.                     msg = "[UFW ALLOW] "
  914.                 elif c.endswith("deny"):
  915.                     msg = "[UFW BLOCK] "
  916.                     if self.loglevels[level] >= self.loglevels["medium"]:
  917.                         # only log INVALID in medium and higher
  918.                         try:
  919.                             self._chain_cmd(c, ['-I', c, '-m', 'state', \
  920.                                                 '--state', 'INVALID', \
  921.                                                 '-j', 'RETURN'] + largs)
  922.                         except:
  923.                             raise
  924.                 try:
  925.                     self._chain_cmd(c, ['-A', c, '-j', 'LOG', \
  926.                                         '--log-prefix', msg] + largs)
  927.                 except:
  928.                     raise
  929.  
  930.         # Setup the audit logging chains
  931.         if self.loglevels[level] >= self.loglevels["medium"]:
  932.             # loglevel full logs all packets without limit
  933.             largs = []
  934.  
  935.             # loglevel high logs all packets with limit
  936.             if self.loglevels[level] < self.loglevels["full"]:
  937.                 largs = limit_args
  938.  
  939.             # loglevel medium logs all new packets with limit
  940.             if self.loglevels[level] < self.loglevels["high"]:
  941.                 largs = ['-m', 'state', '--state', 'NEW'] + limit_args
  942.  
  943.             msg = "[UFW AUDIT] "
  944.             for c in self.chains['before']:
  945.                 try:
  946.                     self._chain_cmd(c, ['-I', c, '-j', 'LOG', \
  947.                                         '--log-prefix', msg] + largs)
  948.                 except:
  949.                     raise UFWError(err_msg)
  950.  
  951.  
  952.