def find_other_position(self, position, v6): '''Return the absolute position in the other list of the rule with the user position of the given list. For example, find_other_position(4, True) will return the absolute position of the rule in the ipv4 list matching the user specified '4' rule in the ipv6 list. ''' # Invalid search (v6 rule with too low position) if v6 and position > len(self.rules6): raise ValueError() # Invalid search (v4 rule with too high position) if not v6 and position > len(self.rules): raise ValueError() if position < 1: raise ValueError() rules = [] if v6: rules = self.rules6 else: rules = self.rules # self.rules[6] is a list of tuples. Some application rules have # multiple tuples but the user specifies by ufw rule, not application # tuple, so we need to find how many tuples there are leading up to # the specified position, which we can then use as an offset for # getting the proper match_rule. app_rules = {} tuple_offset = 0 for i, r in enumerate(rules): if i >= position: break tupl = "" if r.dapp != "" or r.sapp != "": tupl = r.get_app_tuple() if app_rules.has_key(tupl): tuple_offset += 1 else: app_rules[tupl] = True rules = [] if v6: rules = self.rules match_rule = self.rules6[position - 1 + tuple_offset].dup_rule() match_rule.set_v6(False) else: rules = self.rules6 match_rule = self.rules[position - 1 + tuple_offset].dup_rule() match_rule.set_v6(True) count = 1 for r in rules: if UFWRule.match(r, match_rule) == 0: return count count += 1 return 0
def find_other_position(self, position, v6): '''Return the absolute position in the other list of the rule with the user position of the given list. For example, find_other_position(4, True) will return the absolute position of the rule in the ipv4 list matching the user specified '4' rule in the ipv6 list. ''' # Invalid search (v6 rule with too low position) if v6 and position > len(self.rules6): raise ValueError() # Invalid search (v4 rule with too high position) if not v6 and position > len(self.rules): raise ValueError() if position < 1: raise ValueError() rules = [] if v6: rules = self.rules6 else: rules = self.rules # self.rules[6] is a list of tuples. Some application rules have # multiple tuples but the user specifies by ufw rule, not application # tuple, so we need to find how many tuples there are leading up to # the specified position, which we can then use as an offset for # getting the proper match_rule. app_rules = {} tuple_offset = 0 for i, r in enumerate(rules): if i >= position: break tupl = "" if r.dapp != "" or r.sapp != "": tupl = r.get_app_tuple() if tupl in app_rules: tuple_offset += 1 else: app_rules[tupl] = True rules = [] if v6: rules = self.rules match_rule = self.rules6[position - 1 + tuple_offset].dup_rule() match_rule.set_v6(False) else: rules = self.rules6 match_rule = self.rules[position - 1 + tuple_offset].dup_rule() match_rule.set_v6(True) count = 1 for r in rules: if UFWRule.match(r, match_rule) == 0: return count count += 1 return 0
def set_rule(self, rule, allow_reload=True): """Updates firewall with rule by: * appending the rule to the chain if new rule and firewall enabled * deleting the rule from the chain if found and firewall enabled * inserting the rule if possible and firewall enabled * updating user rules file * reloading the user rules file if rule is modified """ rstr = "" if rule.v6: if not self.use_ipv6(): err_msg = _("Adding IPv6 rule failed: IPv6 not enabled") raise UFWError(err_msg) if rule.action == "limit": # Netfilter doesn't have ip6t_recent yet, so skip return _("Skipping unsupported IPv6 '%s' rule") % (rule.action) if rule.multi and rule.protocol != "udp" and rule.protocol != "tcp": err_msg = _("Must specify 'tcp' or 'udp' with multiple ports") raise UFWError(err_msg) newrules = [] found = False modified = False delete = False rules = self.rules position = rule.position if rule.v6: if self.iptables_version < "1.4" and (rule.dapp != "" or rule.sapp != ""): return _("Skipping IPv6 application rule. Need at least iptables 1.4") rules = self.rules6 # bail if we have a bad position if position < 0 or position > len(rules): err_msg = _("Invalid position '%d'") % (position) raise UFWError(err_msg) if position > 0 and rule.remove: err_msg = _("Cannot specify insert and delete") raise UFWError(err_msg) if position > len(rules): err_msg = _("Cannot insert rule at position '%d'") % position raise UFWError(err_msg) # First construct the new rules list try: rule.normalize() except Exception: raise count = 1 inserted = False matches = 0 last = ("", "", "", "") for r in rules: try: r.normalize() except Exception: raise current = (r.dst, r.src, r.dapp, r.sapp) if count == position: # insert the rule if: # 1. the last rule was not an application rule # 2. the current rule is not an application rule # 3. the last application rule is different than the current # while the new rule is different than the current one if ( (last[2] == "" and last[3] == "" and count > 1) or (current[2] == "" and current[3] == "") or last != current ): inserted = True newrules.append(rule.dup_rule()) last = ("", "", "", "") else: position += 1 last = current count += 1 ret = UFWRule.match(r, rule) if ret < 1: matches += 1 if ret == 0 and not found and not inserted: # If find the rule, add it if it's not to be removed, otherwise # skip it. found = True if not rule.remove: newrules.append(rule.dup_rule()) elif ret < 0 and not rule.remove and not inserted: # If only the action is different, replace the rule if it's not # to be removed. found = True modified = True newrules.append(rule.dup_rule()) else: newrules.append(r) if inserted: if matches > 0: rstr = _("Skipping inserting existing rule") if rule.v6: rstr += " (v6)" return rstr else: # Add rule to the end if it was not already added. if not found and not rule.remove: newrules.append(rule.dup_rule()) # Don't process non-existing or unchanged pre-exisiting rules if not found and rule.remove and not self.dryrun: rstr = _("Could not delete non-existent rule") if rule.v6: rstr += " (v6)" return rstr elif found and not rule.remove and not modified: rstr = _("Skipping adding existing rule") if rule.v6: rstr += " (v6)" return rstr if rule.v6: self.rules6 = newrules else: self.rules = newrules # Update the user rules file try: self._write_rules(rule.v6) except UFWError: raise except Exception: err_msg = _("Couldn't update rules file") UFWError(err_msg) # We wrote out the rules, so set reasonable string. We will change # this below when operating on the live firewall. rstr = _("Rules updated") if rule.v6: rstr = _("Rules updated (v6)") # Operate on the chains if self._is_enabled() and not self.dryrun: flag = "" if modified or self._need_reload(rule.v6) or inserted: rstr = "" if inserted: rstr += _("Rule inserted") else: rstr += _("Rule updated") if rule.v6: rstr += " (v6)" if allow_reload: # Reload the chain try: self._reload_user_rules() except Exception: raise else: rstr += _(" (skipped reloading firewall)") elif found and rule.remove: flag = "-D" rstr = _("Rule deleted") elif not found and not modified and not rule.remove: flag = "-A" rstr = _("Rule added") if flag != "": exe = self.iptables chain_prefix = "ufw" if rule.v6: exe = self.ip6tables chain_prefix = "ufw6" rstr += " (v6)" chain_suffix = "input" if rule.direction == "out": chain_suffix = "output" chain = "%s-user-%s" % (chain_prefix, chain_suffix) # Is the firewall running? err_msg = _("Could not update running firewall") (rc, out) = cmd([exe, "-L", chain, "-n"]) if rc != 0: raise UFWError(err_msg) rule_str = "%s %s %s" % (flag, chain, rule.format_rule()) pat_log = re.compile(r"(-A +)(ufw6?-user-[a-z\-]+)(.*)") for s in self._get_lists_from_formatted(rule_str, chain_prefix, chain_suffix): (rc, out) = cmd([exe] + s) if rc != 0: msg(out, sys.stderr) UFWError(err_msg) # delete any lingering RETURN rules (needed for upgrades) if flag == "-A" and pat_log.search(" ".join(s)): c = pat_log.sub(r"\2", " ".join(s)) (rc, out) = cmd([exe, "-D", c, "-j", "RETURN"]) if rc != 0: debug("FAILOK: -D %s -j RETURN" % (c)) return rstr
def set_rule(self, rule, allow_reload=True): '''Updates firewall with rule by: * appending the rule to the chain if new rule and firewall enabled * deleting the rule from the chain if found and firewall enabled * inserting the rule if possible and firewall enabled * updating user rules file * reloading the user rules file if rule is modified ''' rstr = "" if rule.v6: if not self.use_ipv6(): err_msg = _("Adding IPv6 rule failed: IPv6 not enabled") raise UFWError(err_msg) if rule.action == 'limit': # Netfilter doesn't have ip6t_recent yet, so skip return _("Skipping unsupported IPv6 '%s' rule") % (rule.action) if rule.multi and rule.protocol != "udp" and rule.protocol != "tcp": err_msg = _("Must specify 'tcp' or 'udp' with multiple ports") raise UFWError(err_msg) newrules = [] found = False modified = False rules = self.rules position = rule.position if rule.v6: if self.iptables_version < "1.4" and (rule.dapp != "" or \ rule.sapp != ""): return _( "Skipping IPv6 application rule. Need at least iptables 1.4" ) rules = self.rules6 # bail if we have a bad position if position < 0 or position > len(rules): err_msg = _("Invalid position '%d'") % (position) raise UFWError(err_msg) if position > 0 and rule.remove: err_msg = _("Cannot specify insert and delete") raise UFWError(err_msg) if position > len(rules): err_msg = _("Cannot insert rule at position '%d'") % position raise UFWError(err_msg) # First construct the new rules list try: rule.normalize() except Exception: raise count = 1 inserted = False matches = 0 last = ('', '', '', '') for r in rules: try: r.normalize() except Exception: raise current = (r.dst, r.src, r.dapp, r.sapp) if count == position: # insert the rule if: # 1. the last rule was not an application rule # 2. the current rule is not an application rule # 3. the last application rule is different than the current # while the new rule is different than the current one if (last[2] == '' and last[3] == '' and count > 1) or \ (current[2] == '' and current[3] == '') or \ last != current: inserted = True newrules.append(rule.dup_rule()) last = ('', '', '', '') else: position += 1 last = current count += 1 ret = UFWRule.match(r, rule) if ret < 1: matches += 1 if ret == 0 and not found and not inserted: # If find the rule, add it if it's not to be removed, otherwise # skip it. found = True if not rule.remove: newrules.append(rule.dup_rule()) elif ret < 0 and not rule.remove and not inserted: # If only the action is different, replace the rule if it's not # to be removed. found = True modified = True newrules.append(rule.dup_rule()) else: newrules.append(r) if inserted: if matches > 0: rstr = _("Skipping inserting existing rule") if rule.v6: rstr += " (v6)" return rstr else: # Add rule to the end if it was not already added. if not found and not rule.remove: newrules.append(rule.dup_rule()) # Don't process non-existing or unchanged pre-exisiting rules if not found and rule.remove and not self.dryrun: rstr = _("Could not delete non-existent rule") if rule.v6: rstr += " (v6)" return rstr elif found and not rule.remove and not modified: rstr = _("Skipping adding existing rule") if rule.v6: rstr += " (v6)" return rstr if rule.v6: self.rules6 = newrules else: self.rules = newrules # Update the user rules file try: self._write_rules(rule.v6) except UFWError: raise except Exception: err_msg = _("Couldn't update rules file") UFWError(err_msg) # We wrote out the rules, so set reasonable string. We will change # this below when operating on the live firewall. rstr = _("Rules updated") if rule.v6: rstr = _("Rules updated (v6)") # Operate on the chains if self.is_enabled() and not self.dryrun: flag = "" if modified or self._need_reload(rule.v6) or inserted: rstr = "" if inserted: rstr += _("Rule inserted") else: rstr += _("Rule updated") if rule.v6: rstr += " (v6)" if allow_reload: # Reload the chain try: self._reload_user_rules() except Exception: raise else: rstr += _(" (skipped reloading firewall)") elif found and rule.remove: flag = '-D' rstr = _("Rule deleted") elif not found and not modified and not rule.remove: flag = '-A' rstr = _("Rule added") if flag != "": exe = self.iptables chain_prefix = "ufw" if rule.v6: exe = self.ip6tables chain_prefix = "ufw6" rstr += " (v6)" chain_suffix = "input" if rule.direction == "out": chain_suffix = "output" chain = "%s-user-%s" % (chain_prefix, chain_suffix) # Is the firewall running? err_msg = _("Could not update running firewall") (rc, out) = cmd([exe, '-L', chain, '-n']) if rc != 0: raise UFWError(err_msg) rule_str = "%s %s %s" % (flag, chain, rule.format_rule()) pat_log = re.compile(r'(-A +)(ufw6?-user-[a-z\-]+)(.*)') for s in self._get_lists_from_formatted(rule_str, \ chain_prefix, \ chain_suffix): (rc, out) = cmd([exe] + s) if rc != 0: msg(out, sys.stderr) UFWError(err_msg) # delete any lingering RETURN rules (needed for upgrades) if flag == "-A" and pat_log.search(" ".join(s)): c = pat_log.sub(r'\2', " ".join(s)) (rc, out) = cmd([exe, '-D', c, '-j', 'RETURN']) if rc != 0: debug("FAILOK: -D %s -j RETURN" % (c)) return rstr