def start_firewall(self): '''Start the firewall''' err_msg = _("problem running") if self.dryrun: msg("> " + _("running ufw-init")) else: (rc, out) = cmd([self.files['init'], 'start']) if rc != 0: debug(out) raise UFWError(err_msg + " ufw-init") if not self.defaults.has_key('loglevel') or \ self.defaults['loglevel'] not in self.loglevels.keys(): # Add the loglevel if not valid try: self.set_loglevel("low") except Exception: err_msg = _("Could not set LOGLEVEL") raise UFWError(err_msg) else: try: self.update_logging(self.defaults['loglevel']) except Exception: err_msg = _("Could not load logging rules") raise UFWError(err_msg)
def verify_profile(name, profile): '''Make sure profile has everything needed''' app_fields = ['title', 'description', 'ports'] for f in app_fields: if f not in profile: err_msg = _("Profile '%(fn)s' missing required field '%(f)s'") % \ ({'fn': name, 'f': f}) raise UFWError(err_msg) elif not profile[f]: err_msg = _("Profile '%(fn)s' has empty required field '%(f)s'") \ % ({'fn': name, 'f': f}) raise UFWError(err_msg) ports = profile['ports'].split('|') if len(ports) < 1: err_msg = _("No ports found in profile '%s'") % (name) return False try: for p in ports: (port, proto) = ufw.util.parse_port_proto(p) # quick check if error in profile #if not proto: # proto = "any" rule = ufw.common.UFWRule("ACCEPT", proto, port) debug(rule) except Exception as e: debug(e) err_msg = _("Invalid ports in profile '%s'") % (name) raise UFWError(err_msg) return True
def stop_firewall(self): '''Stop the firewall''' err_msg = _("problem running") if self.dryrun: msg("> " + _("running ufw-init")) else: (rc, out) = cmd([self.files['init'], 'force-stop']) if rc != 0: debug(out) raise UFWError(err_msg + " ufw-init")
def stop_firewall(self): """Stops the firewall""" err_msg = _("problem running") if self.dryrun: msg("> " + _("running ufw-init")) else: (rc, out) = cmd([self.files["init"], "force-stop"]) if rc != 0: debug(out) raise UFWError(err_msg + " ufw-init")
def _chain_cmd(self, chain, args, fail_ok=False): """Perform command on chain""" exe = self.iptables if chain.startswith("ufw6"): exe = self.ip6tables (rc, out) = cmd([exe] + args) if rc != 0: err_msg = _("Could not perform '%s'") % (args) if fail_ok: debug("FAILOK: " + err_msg) else: raise UFWError(err_msg)
def find_application_name(self, str): '''Find the application profile name for str''' if self.profiles.has_key(str): return str match = "" matches = 0 for n in self.profiles.keys(): if n.lower() == str.lower(): match = n matches += 1 debug_msg = "'%d' matches for '%s'" % (matches, str) debug(debug_msg) if matches == 1: return match elif matches > 1: err_msg = _("Found multiple matches for '%s'. Please use exact profile name") % (str) err_msg = _("Could not find a profile matching '%s'") % (str) raise UFWError(err_msg)
def _need_reload(self, v6): """Check if all chains exist""" if self.dryrun: return False prefix = "ufw" exe = self.iptables if v6: prefix = "ufw6" exe = self.ip6tables for chain in ["input", "output", "forward", "limit", "limit-accept"]: if v6 and (chain == "limit" or chain == "limit-accept"): continue (rc, out) = cmd([exe, "-n", "-L", prefix + "-user-" + chain]) if rc != 0: debug("_need_reload: forcing reload") return True return False
def find_application_name(self, profile_name): '''Find the application profile name for profile_name''' if profile_name in self.profiles: return profile_name match = "" matches = 0 for n in list(self.profiles.keys()): if n.lower() == profile_name.lower(): match = n matches += 1 debug_msg = "'%d' matches for '%s'" % (matches, profile_name) debug(debug_msg) if matches == 1: return match elif matches > 1: err_msg = _("Found multiple matches for '%s'. Please use exact profile name") % \ (profile_name) err_msg = _("Could not find a profile matching '%s'") % (profile_name) raise UFWError(err_msg)
def get_rule_by_number(self, n): '''Return rule specified by number seen via "status numbered"''' rules = self.get_rules() count = 1 app_rules = {} for r in rules: tupl = "" if r.dapp != "" or r.sapp != "": tupl = r.get_app_tuple() if app_rules.has_key(tupl): debug("Skipping found tuple '%s'" % (tupl)) continue else: app_rules[tupl] = True if count == int(n): return r count += 1 return None
def _need_reload(self, v6): '''Check if all chains exist''' if self.dryrun: return False prefix = "ufw" exe = self.iptables if v6: prefix = "ufw6" exe = self.ip6tables for chain in [ 'input', 'output', 'forward', 'limit', 'limit-accept' ]: if v6 and (chain == "limit" or chain == "limit-accept"): continue (rc, out) = cmd([exe, '-n', '-L', prefix + "-user-" + chain]) if rc != 0: debug("_need_reload: forcing reload") return True return False
def find_application_name(self, profile_name): '''Find the application profile name for profile_name''' if profile_name in self.profiles: return profile_name match = "" matches = 0 for n in list(self.profiles.keys()): if n.lower() == profile_name.lower(): match = n matches += 1 debug_msg = "'%d' matches for '%s'" % (matches, profile_name) debug(debug_msg) if matches == 1: return match elif matches > 1: err_msg = _("Found multiple matches for '%s'. Please use exact profile name") % \ (profile_name) else: err_msg = _("Could not find a profile matching '%s'") % \ (profile_name) raise UFWError(err_msg)
def get_rules_count(self, v6): '''Return number of ufw rules (not iptables rules)''' rules = [] if v6: rules = self.rules6 else: rules = self.rules count = 0 app_rules = {} for r in rules: tupl = "" if r.dapp != "" or r.sapp != "": tupl = r.get_app_tuple() if app_rules.has_key(tupl): debug("Skipping found tuple '%s'" % (tupl)) continue else: app_rules[tupl] = True count += 1 return count
def match(x, y): '''Check if rules match Return codes: 0 match 1 no match -1 match all but action, log-type and/or comment -2 match all but comment ''' if not x or not y: raise ValueError() dbg_msg = "No match '%s' '%s'" % (x, y) if x.dport != y.dport: debug(dbg_msg) return 1 if x.sport != y.sport: debug(dbg_msg) return 1 if x.protocol != y.protocol: debug(dbg_msg) return 1 if x.src != y.src: debug(dbg_msg) return 1 if x.dst != y.dst: debug(dbg_msg) return 1 if x.v6 != y.v6: debug(dbg_msg) return 1 if x.dapp != y.dapp: debug(dbg_msg) return 1 if x.sapp != y.sapp: debug(dbg_msg) return 1 if x.interface_in != y.interface_in: debug(dbg_msg) return 1 if x.interface_out != y.interface_out: debug(dbg_msg) return 1 if x.direction != y.direction: debug(dbg_msg) return 1 if x.forward != y.forward: debug(dbg_msg) return 1 if x.action == y.action and x.logtype == y.logtype and \ x.comment == y.comment: dbg_msg = _("Found exact match") debug(dbg_msg) return 0 if x.action == y.action and x.logtype == y.logtype and \ x.comment != y.comment: dbg_msg = _("Found exact match, excepting comment") debug(dbg_msg) return -2 dbg_msg = _("Found non-action/non-logtype/comment match " \ "(%(xa)s/%(ya)s/'%(xc)s' %(xl)s/%(yl)s/'%(yc)s')") % \ ({'xa': x.action, 'ya': y.action, 'xl': x.logtype, 'yl': y.logtype, 'xc': x.comment, 'yc': y.comment}) debug(dbg_msg) return -1
def _do_checks(self): '''Perform basic security checks: is setuid or setgid (for non-Linux systems) checks that script is owned by root checks that every component in absolute path are owned by root warn if script is group writable warn if part of script path is group writable Doing this at the beginning causes a race condition with later operations that don't do these checks. However, if the user running this script is root, then need to be root to exploit the race condition (and you are hosed anyway...) ''' if not self.do_checks: err_msg = _("Checks disabled") warn(err_msg) return True # Not needed on Linux, but who knows the places we will go... if os.getuid() != os.geteuid(): err_msg = _("ERROR: this script should not be SUID") raise UFWError(err_msg) if os.getgid() != os.getegid(): err_msg = _("ERROR: this script should not be SGID") raise UFWError(err_msg) uid = os.getuid() if uid != 0: err_msg = _("You need to be root to run this script") raise UFWError(err_msg) # Use these so we only warn once warned_world_write = {} warned_group_write = {} warned_owner = {} profiles = [] if not os.path.isdir(self.files['apps']): warn_msg = _("'%s' does not exist") % (self.files['apps']) warn(warn_msg) else: pat = re.compile(r'^\.') for profile in os.listdir(self.files['apps']): if not pat.search(profile): profiles.append(os.path.join(self.files['apps'], profile)) for path in self.files.values() + [ os.path.abspath(sys.argv[0]) ] + \ profiles: while True: debug("Checking " + path) if path == self.files['apps'] and \ not os.path.isdir(self.files['apps']): break try: statinfo = os.stat(path) mode = statinfo[ST_MODE] except OSError, e: err_msg = _("Couldn't stat '%s'") % (path) raise UFWError(err_msg) except Exception: raise if statinfo.st_uid != 0 and not warned_owner.has_key(path): warn_msg = _("uid is %(uid)s but '%(path)s' is owned by " \ "%(st_uid)s") % ({'uid': str(uid), \ 'path': path, \ 'st_uid': str(statinfo.st_uid)}) warn(warn_msg) warned_owner[path] = True if mode & S_IWOTH and not warned_world_write.has_key(path): warn_msg = _("%s is world writable!") % (path) warn(warn_msg) warned_world_write[path] = True if mode & S_IWGRP and not warned_group_write.has_key(path): warn_msg = _("%s is group writable!") % (path) warn(warn_msg) warned_group_write[path] = True if path == "/": break path = os.path.dirname(path) if not path: raise OSError(errno.ENOENT, "Could not find '%s'" % (path))
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
def fuzzy_dst_match(x, y): '''This will match if x is more specific than y. Eg, for protocol if x is tcp and y is all or for address if y is a network and x is a subset of y (where x is either an address or network). Returns: 0 match 1 no match -1 fuzzy match This is a fuzzy destination match, so source ports or addresses are not considered, and (currently) only incoming. ''' def _match_ports(p, to_match): '''Returns True if p is an exact match or within a multi rule''' for port in to_match.split(','): if p == port: return True if ':' in port: (low, high) = port.split(':') if int(p) >= int(low) and int(p) <= int(high): return True return False if not x or not y: raise ValueError() # Ok if exact match if x.match(y) == 0: return 0 dbg_msg = "No fuzzy match '%s (v6=%s)' '%s (v6=%s)'" % \ (x, x.v6, y, y.v6) # Direction must match if y.direction != "in": debug("(direction) " + dbg_msg + " (not incoming)") return 1 # Protocols must match or y 'any' if x.protocol != y.protocol and y.protocol != "any": debug("(protocol) " + dbg_msg) return 1 # Destination ports must match or y 'any' if y.dport != "any" and not _match_ports(x.dport, y.dport): debug("(dport) " + dbg_msg) return 1 if y.interface_in == "": # If destination interface is not specified, destination addresses # must match or x must be contained in y if x.interface_in == "" and x._is_anywhere(x.dst): # if x and y interfaces are not specified, and x.dst is # anywhere then ok pass elif x.dst != y.dst and '/' not in y.dst: debug("(dst) " + dbg_msg) return 1 elif x.dst != y.dst and '/' in y.dst and x.v6 == y.v6 and \ not ufw.util.in_network(x.dst, y.dst, x.v6): debug("(dst) " + dbg_msg + " ('%s' not in network '%s')" % \ (x.dst, y.dst)) return 1 else: # If destination interface is specified, then: # if specified, both interfaces must match or # the IP of the interface must match the IP of y or # the IP of the interface must be contained in y if x.interface_in != "" and x.interface_in != y.interface_in: debug("(interface) " + dbg_msg + " (%s != %s)" % \ (x.interface_in, y.interface_in)) return 1 if_ip = ufw.util.get_ip_from_if(y.interface_in, x.v6) if y.dst != if_ip and '/' not in y.dst: debug("(interface) " + dbg_msg + " (%s != %s)" % \ (y.dst, if_ip)) return 1 elif y.dst != if_ip and '/' in y.dst and x.v6 == y.v6 and \ not ufw.util.in_network(if_ip, y.dst, x.v6): debug("(interface) " + dbg_msg + \ " ('%s' not in network '%s')" % (if_ip, y.dst)) return 1 if x.v6 != y.v6: debug("(v6) " + dbg_msg + " (%s != %s)" % (x.dst, y.dst)) return 1 # if we made it here, it is a fuzzy match debug("(fuzzy match) '%s (v6=%s)' '%s (v6=%s)'" % (x, x.v6, y, y.v6)) return -1
def get_status(self, verbose=False, show_count=False): """Show ufw managed rules""" out = "" out6 = "" if self.dryrun: out = "> " + _("Checking iptables\n") if self.use_ipv6(): out += "> " + _("Checking ip6tables\n") return out err_msg = _("problem running") for direction in ["input", "output"]: # Is the firewall loaded at all? (rc, out) = cmd([self.iptables, "-L", "ufw-user-%s" % (direction), "-n"]) if rc == 1: return _("Status: inactive") elif rc != 0: raise UFWError(err_msg + " iptables: %s\n" % (out)) if self.use_ipv6(): (rc, out6) = cmd([self.ip6tables, "-L", "ufw6-user-%s" % (direction), "-n"]) if rc != 0: raise UFWError(err_msg + " ip6tables") s = "" str_out = "" rules = self.rules + self.rules6 count = 1 app_rules = {} for r in rules: tmp_str = "" location = {} tupl = "" show_proto = True if not verbose and (r.dapp != "" or r.sapp != ""): show_proto = False tupl = r.get_app_tuple() if app_rules.has_key(tupl): debug("Skipping found tuple '%s'" % (tupl)) continue else: app_rules[tupl] = True for loc in ["dst", "src"]: location[loc] = "" port = "" tmp = "" if loc == "dst": tmp = r.dst if not verbose and r.dapp != "": port = r.dapp if r.v6 and tmp == "::/0": port += " (v6)" else: port = r.dport else: tmp = r.src if not verbose and r.sapp != "": port = r.sapp if r.v6 and tmp == "::/0": port += " (v6)" else: port = r.sport if tmp != "0.0.0.0/0" and tmp != "::/0": location[loc] = tmp if port != "any": if location[loc] == "": location[loc] = port else: location[loc] += " " + port if show_proto and r.protocol != "any": location[loc] += "/" + r.protocol if verbose: if loc == "dst" and r.dapp != "": location[loc] += " (%s" % (r.dapp) if r.v6 and tmp == "::/0": location[loc] += " (v6)" location[loc] += ")" if loc == "src" and r.sapp != "": location[loc] += " (%s" % (r.sapp) if r.v6 and tmp == "::/0": location[loc] += " (v6)" location[loc] += ")" if port == "any": if tmp == "0.0.0.0/0" or tmp == "::/0": location[loc] = "Anywhere" # Show the protocol if Anywhere to Anwhere, have # protocol and source and dest ports are any if show_proto and r.protocol != "any" and r.dst == r.src and r.dport == r.sport: location[loc] += "/" + r.protocol if tmp == "::/0": location[loc] += " (v6)" else: # Show the protocol if have protocol, and source # and dest ports are any if show_proto and r.protocol != "any" and r.dport == r.sport: location[loc] += "/" + r.protocol if loc == "dst" and r.interface_in != "": location[loc] += " on %s" % (r.interface_in) if loc == "src" and r.interface_out != "": location[loc] += " on %s" % (r.interface_out) attribs = [] attrib_str = "" if r.logtype or r.direction.lower() == "out": if r.logtype: attribs.append(r.logtype.lower()) if show_count and r.direction == "out": attribs.append(r.direction) if len(attribs) > 0: attrib_str = " (%s)" % (", ".join(attribs)) # now construct the rule output string if show_count: tmp_str += "[%2d] " % (count) dir_str = r.direction.upper() if r.direction == "in" and not verbose and not show_count: dir_str = "" tmp_str += "%-26s %-12s%s%s\n" % ( location["dst"], " ".join([r.action.upper(), dir_str]), location["src"], attrib_str, ) # Show the list in the order given if a numbered list, otherwise # split incoming and outgoing rules if show_count: s += tmp_str else: if r.direction == "out": str_out += tmp_str else: s += tmp_str count += 1 if s != "" or str_out != "": full_str = "\n\n" if show_count: full_str += " " str_to = _("To") str_from = _("From") str_action = _("Action") rules_header = "%-26s %-12s%s\n" % ( str_to.decode("utf-8", "ignore"), str_action.decode("utf-8", "ignore"), str_from.decode("utf-8", "ignore"), ) if show_count: rules_header += " " rules_header += "%-26s %-12s%s\n" % ( "-" * len(str_to.decode("utf-8", "ignore")), "-" * len(str_action.decode("utf-8", "ignore")), "-" * len(str_from.decode("utf-8", "ignore")), ) full_str += rules_header.encode("utf-8", "ignore") if s != "": full_str += s if s != "" and str_out != "": full_str += _("\n") if str_out != "": full_str += str_out s = full_str if verbose: (level, logging_str) = self.get_loglevel() policy_str = _("Default: %(in)s (incoming), %(out)s (outgoing)") % ( {"in": self.get_default_policy(), "out": self.get_default_policy("output")} ) app_policy_str = self.get_default_application_policy() return _("Status: active\n%(log)s\n%(pol)s\n%(app)s%(status)s") % ( {"log": logging_str, "pol": policy_str, "app": app_policy_str, "status": s} ) else: return _("Status: active%s") % (s)
def parse(self, argv): action = "" rule = "" type = "" from_type = "any" to_type = "any" from_service = "" to_service = "" insert_pos = "" logtype = "" remove = False if len(argv) > 0 and argv[0].lower() == "rule": argv.remove(argv[0]) # TODO: break this out if len(argv) > 0: if argv[0].lower() == "delete" and len(argv) > 1: remove = True argv.remove(argv[0]) rule_num = None try: rule_num = int(argv[0]) except Exception: action = argv[0] # return quickly if deleting by rule number if rule_num != None: r = UFWParserResponse('delete-%d' % rule_num) return r elif argv[0].lower() == "insert": if len(argv) < 4: raise ValueError() insert_pos = argv[1] # Using position '0' adds rule at end, which is potentially # confusing for the end user if insert_pos == "0": err_msg = _("Cannot insert rule at position '%s'") % \ (insert_pos) raise UFWError(err_msg) # strip out 'insert NUM' and parse as normal del argv[1] del argv[0] action = argv[0] if action == "": action = self.command argv.insert(0, action) if action != "allow" and action != "deny" and action != "reject" and \ action != "limit": raise ValueError() nargs = len(argv) if nargs < 2: raise ValueError() # set/strip rule_direction = "in" if nargs > 1 and (argv[1].lower() == "in" or \ argv[1].lower() == "out"): rule_direction = argv[1].lower() # strip out direction if not an interface rule if nargs > 2 and argv[2] != "on" and (argv[1].lower() == "in" or \ argv[1].lower() == "out"): rule_direction = argv[1].lower() del argv[1] nargs = len(argv) # strip out 'on' as in 'allow in on eth0 ...' has_interface = False if nargs > 1 and (argv.count('in') > 0 or argv.count('out') > 0): err_msg = _("Invalid interface clause") if argv[1].lower() != "in" and argv[1].lower() != "out": raise UFWError(err_msg) if nargs < 3 or argv[2].lower() != "on": raise UFWError(err_msg) del argv[2] nargs = len(argv) has_interface = True log_idx = 0 if has_interface and nargs > 3 and (argv[3].lower() == "log" or \ argv[3].lower() == 'log-all'): log_idx = 3 elif nargs > 2 and (argv[1].lower() == "log" or \ argv[1].lower() == 'log-all'): log_idx = 1 if log_idx > 0: logtype = argv[log_idx].lower() # strip out 'log' or 'log-all' and parse as normal del argv[log_idx] nargs = len(argv) if "log" in argv: err_msg = _("Option 'log' not allowed here") raise UFWError(err_msg) if "log-all" in argv: err_msg = _("Option 'log-all' not allowed here") raise UFWError(err_msg) if nargs < 2 or nargs > 13: raise ValueError() rule_action = action if logtype != "": rule_action += "_" + logtype rule = ufw.common.UFWRule(rule_action, "any", "any", \ direction=rule_direction) if remove: rule.remove = remove elif insert_pos != "": try: rule.set_position(insert_pos) except Exception: raise if nargs == 2: # Short form where only app or port/proto is given if ufw.applications.valid_profile_name(argv[1]): # Check if name collision with /etc/services. If so, use # /etc/services instead of application profile try: ufw.util.get_services_proto(argv[1]) except Exception: type = "both" rule.dapp = argv[1] rule.set_port(argv[1], "dst") if rule.dapp == "": try: (port, proto) = ufw.util.parse_port_proto(argv[1]) except UFWError: err_msg = _("Bad port") raise UFWError(err_msg) if not re.match('^\d([0-9,:]*\d+)*$', port): if ',' in port or ':' in port: err_msg = _("Port ranges must be numeric") raise UFWError(err_msg) to_service = port try: rule.set_protocol(proto) rule.set_port(port, "dst") type = "both" except UFWError: err_msg = _("Bad port") raise UFWError(err_msg) # Don't specify a port with ipv6, esp or ah protocols if rule.protocol in [ 'ipv6', 'esp', 'ah' ]: err_msg = _("Invalid port with protocol '%s'") % \ (rule.protocol) raise UFWError(err_msg) elif (nargs + 1) % 2 != 0: err_msg = _("Wrong number of arguments") raise UFWError(err_msg) elif not 'from' in argv and not 'to' in argv and not 'in' in argv and \ not 'out' in argv: err_msg = _("Need 'to' or 'from' clause") raise UFWError(err_msg) else: # Full form with PF-style syntax keys = [ 'proto', 'from', 'to', 'port', 'app', 'in', 'out' ] # quick check if argv.count("to") > 1 or \ argv.count("from") > 1 or \ argv.count("proto") > 1 or \ argv.count("port") > 2 or \ argv.count("in") > 1 or \ argv.count("out") > 1 or \ argv.count("app") > 2 or \ argv.count("app") > 0 and argv.count("proto") > 0: err_msg = _("Improper rule syntax") raise UFWError(err_msg) i = 0 loc = "" for arg in argv: if i % 2 != 0 and argv[i] not in keys: err_msg = _("Invalid token '%s'") % (argv[i]) raise UFWError(err_msg) if arg == "proto": if i+1 < nargs: try: rule.set_protocol(argv[i+1]) except Exception: raise else: err_msg = _("Invalid 'proto' clause") raise UFWError(err_msg) elif arg == "in" or arg == "out": if i+1 < nargs: try: if arg == "in": rule.set_interface("in", argv[i+1]) elif arg == "out": rule.set_interface("out", argv[i+1]) except Exception: raise else: err_msg = _("Invalid '%s' clause") % (arg) raise UFWError(err_msg) elif arg == "from": if i+1 < nargs: try: faddr = argv[i+1].lower() if faddr == "any": faddr = "0.0.0.0/0" from_type = "any" else: if ufw.util.valid_address(faddr, "6"): from_type = "v6" else: from_type = "v4" rule.set_src(faddr) except Exception: raise loc = "src" else: err_msg = _("Invalid 'from' clause") raise UFWError(err_msg) elif arg == "to": if i+1 < nargs: try: saddr = argv[i+1].lower() if saddr == "any": saddr = "0.0.0.0/0" to_type = "any" else: if ufw.util.valid_address(saddr, "6"): to_type = "v6" else: to_type = "v4" rule.set_dst(saddr) except Exception: raise loc = "dst" else: err_msg = _("Invalid 'to' clause") raise UFWError(err_msg) elif arg == "port" or arg == "app": if i+1 < nargs: if loc == "": err_msg = _("Need 'from' or 'to' with '%s'") % \ (arg) raise UFWError(err_msg) tmp = argv[i+1] if arg == "app": if loc == "src": rule.sapp = tmp else: rule.dapp = tmp elif not re.match('^\d([0-9,:]*\d+)*$', tmp): if ',' in tmp or ':' in tmp: err_msg = _("Port ranges must be numeric") raise UFWError(err_msg) if loc == "src": from_service = tmp else: to_service = tmp try: rule.set_port(tmp, loc) except Exception: raise else: err_msg = _("Invalid 'port' clause") raise UFWError(err_msg) i += 1 # Figure out the type of rule (IPv4, IPv6, or both) this is if from_type == "any" and to_type == "any": type = "both" elif from_type != "any" and to_type != "any" and \ from_type != to_type: err_msg = _("Mixed IP versions for 'from' and 'to'") raise UFWError(err_msg) elif from_type != "any": type = from_type elif to_type != "any": type = to_type # Adjust protocol if to_service != "" or from_service != "": proto = "" if to_service != "": try: proto = ufw.util.get_services_proto(to_service) except Exception: err_msg = _("Could not find protocol") raise UFWError(err_msg) if from_service != "": if proto == "any" or proto == "": try: proto = ufw.util.get_services_proto(from_service) except Exception: err_msg = _("Could not find protocol") raise UFWError(err_msg) else: try: tmp = ufw.util.get_services_proto(from_service) except Exception: err_msg = _("Could not find protocol") raise UFWError(err_msg) if proto == "any" or proto == tmp: proto = tmp elif tmp == "any": pass else: err_msg = _("Protocol mismatch (from/to)") raise UFWError(err_msg) # Verify found proto with specified proto if rule.protocol == "any": rule.set_protocol(proto) elif proto != "any" and rule.protocol != proto: err_msg = _("Protocol mismatch with specified protocol %s") % \ (rule.protocol) raise UFWError(err_msg) # Verify protocol not specified with application rule if rule and rule.protocol != "any" and \ (rule.sapp != "" or rule.dapp != ""): err_msg = _("Improper rule syntax ('%s' specified with app rule)") \ % (rule.protocol) raise UFWError(err_msg) if rule.protocol == 'ipv6': if type == "v6": # Can't use protocol ipv6 with v6 addresses err_msg = _("Invalid IPv6 address with protocol '%s'") % \ (rule.protocol) raise UFWError(err_msg) elif type == "both": debug("Adjusting iptype to 'v4' for protocol '%s'" % \ (rule.protocol)) type = "v4" if rule.dport != "any" or rule.sport != "any": # Don't specify a port with ipv6, esp, or ah protocol err_msg = _("Invalid port with protocol '%s'") % \ (rule.protocol) raise UFWError(err_msg) r = UFWParserResponse(action) r.data['type'] = self.type r.data['rule'] = rule r.data['iptype'] = type return r
def match(x, y): '''Check if rules match Return codes: 0 match 1 no match -1 match all but action ''' if not x or not y: raise ValueError() dbg_msg = "No match '%s' '%s'" % (x, y) if x.dport != y.dport: debug(dbg_msg) return 1 if x.sport != y.sport: debug(dbg_msg) return 1 if x.protocol != y.protocol: debug(dbg_msg) return 1 if x.src != y.src: debug(dbg_msg) return 1 if x.dst != y.dst: debug(dbg_msg) return 1 if x.v6 != y.v6: debug(dbg_msg) return 1 if x.dapp != y.dapp: debug(dbg_msg) return 1 if x.sapp != y.sapp: debug(dbg_msg) return 1 if x.interface_in != y.interface_in: debug(dbg_msg) return 1 if x.interface_out != y.interface_out: debug(dbg_msg) return 1 if x.direction != y.direction: debug(dbg_msg) return 1 if x.action == y.action and x.logtype == y.logtype: dbg_msg = _("Found exact match") debug(dbg_msg) return 0 dbg_msg = _("Found non-action/non-logtype match " \ "(%(xa)s/%(ya)s %(xl)s/%(yl)s)") % \ ({'xa': x.action, 'ya': y.action, \ 'xl': x.logtype, 'yl': y.logtype}) debug(dbg_msg) return -1
def get_profiles(profiles_dir): '''Get profiles found in profiles database. Returns dictionary with profile name as key and tuples for fields ''' if not os.path.isdir(profiles_dir): err_msg = _("Profiles directory does not exist") raise UFWError(err_msg) max_size = 10 * 1024 * 1024 # 10MB profiles = {} files = os.listdir(profiles_dir) files.sort() total_size = 0 pat = re.compile(r'^\.') for f in files: abs_path = profiles_dir + "/" + f if not os.path.isfile(abs_path): continue if pat.search(f): debug("Skipping '%s': hidden file" % (f)) continue if f.endswith('.dpkg-new') or f.endswith('.dpkg-old') or \ f.endswith('.dpkg-dist') or f.endswith('.rpmnew') or \ f.endswith('.rpmsave') or f.endswith('~'): debug("Skipping '%s'" % (f)) continue # Try to gracefully handle huge files for the user (no security # benefit, just usability) size = 0 try: size = os.stat(abs_path)[stat.ST_SIZE] except Exception: warn_msg = _("Skipping '%s': couldn't stat") % (f) warn(warn_msg) continue if size > max_size: warn_msg = _("Skipping '%s': too big") % (f) warn(warn_msg) continue if total_size + size > max_size: warn_msg = _("Skipping '%s': too many files read already") % (f) warn(warn_msg) continue total_size += size if sys.version_info[0] < 3: # pragma: no cover cdict = ConfigParser.RawConfigParser() else: # pragma: no cover cdict = configparser.RawConfigParser() try: cdict.read(abs_path) except Exception: warn_msg = _("Skipping '%s': couldn't process") % (f) warn(warn_msg) continue # If multiple occurences of profile name, use the last one for p in cdict.sections(): if len(p) > 64: warn_msg = _("Skipping '%s': name too long") % (p) warn(warn_msg) continue if not valid_profile_name(p): warn_msg = _("Skipping '%s': invalid name") % (p) warn(warn_msg) continue try: ufw.util.get_services_proto(p) warn_msg = _("Skipping '%s': also in /etc/services") % (p) warn(warn_msg) continue except Exception: pass skip = False for key, value in cdict.items(p): if len(key) > 64: warn_msg = _("Skipping '%s': field too long") % (p) warn(warn_msg) skip = True break if len(value) > 1024: warn_msg = _("Skipping '%(value)s': value too long for " \ "'%(field)s'") % \ ({'value': p, 'field': key}) warn(warn_msg) skip = True break if skip: continue if p in profiles: warn_msg = _("Duplicate profile '%s', using last found") % (p) warn(warn_msg) pdict = {} for key, value in cdict.items(p): #debug("add '%s' = '%s' to '%s'" % (key, value, p)) pdict[key] = value try: verify_profile(p, pdict) profiles[p] = pdict except UFWError as e: warn(e) return profiles
def parse(self, argv): action = "" rule = "" type = "" from_type = "any" to_type = "any" from_service = "" to_service = "" insert_pos = "" logtype = "" remove = False if len(argv) > 0 and argv[0].lower() == "rule": argv.remove(argv[0]) # TODO: break this out if len(argv) > 0: if argv[0].lower() == "delete" and len(argv) > 1: remove = True argv.remove(argv[0]) rule_num = None try: rule_num = int(argv[0]) except Exception: action = argv[0] # return quickly if deleting by rule number if rule_num != None: r = UFWParserResponse('delete-%d' % rule_num) return r elif argv[0].lower() == "insert": if len(argv) < 4: raise ValueError() insert_pos = argv[1] # Using position '0' adds rule at end, which is potentially # confusing for the end user if insert_pos == "0": err_msg = _("Cannot insert rule at position '%s'") % \ (insert_pos) raise UFWError(err_msg) # strip out 'insert NUM' and parse as normal del argv[1] del argv[0] action = argv[0] if action == "": action = self.command argv.insert(0, action) if action != "allow" and action != "deny" and action != "reject" and \ action != "limit": raise ValueError() nargs = len(argv) if nargs < 2: raise ValueError() # set/strip rule_direction = "in" if nargs > 1 and (argv[1].lower() == "in" or \ argv[1].lower() == "out"): rule_direction = argv[1].lower() # strip out direction if not an interface rule if nargs > 2 and argv[2] != "on" and (argv[1].lower() == "in" or \ argv[1].lower() == "out"): rule_direction = argv[1].lower() del argv[1] nargs = len(argv) # strip out 'on' as in 'allow in on eth0 ...' has_interface = False if nargs > 1 and (argv.count('in') > 0 or argv.count('out') > 0): err_msg = _("Invalid interface clause") if argv[1].lower() != "in" and argv[1].lower() != "out": raise UFWError(err_msg) if nargs < 3 or argv[2].lower() != "on": raise UFWError(err_msg) del argv[2] nargs = len(argv) has_interface = True log_idx = 0 if has_interface and nargs > 3 and (argv[3].lower() == "log" or \ argv[3].lower() == 'log-all'): log_idx = 3 elif nargs > 2 and (argv[1].lower() == "log" or \ argv[1].lower() == 'log-all'): log_idx = 1 if log_idx > 0: logtype = argv[log_idx].lower() # strip out 'log' or 'log-all' and parse as normal del argv[log_idx] nargs = len(argv) if "log" in argv: err_msg = _("Option 'log' not allowed here") raise UFWError(err_msg) if "log-all" in argv: err_msg = _("Option 'log-all' not allowed here") raise UFWError(err_msg) if nargs < 2 or nargs > 13: raise ValueError() rule_action = action if logtype != "": rule_action += "_" + logtype rule = ufw.common.UFWRule(rule_action, "any", "any", \ direction=rule_direction) if remove: rule.remove = remove elif insert_pos != "": try: rule.set_position(insert_pos) except Exception: raise if nargs == 2: # Short form where only app or port/proto is given if ufw.applications.valid_profile_name(argv[1]): # Check if name collision with /etc/services. If so, use # /etc/services instead of application profile try: ufw.util.get_services_proto(argv[1]) except Exception: type = "both" rule.dapp = argv[1] rule.set_port(argv[1], "dst") if rule.dapp == "": try: (port, proto) = ufw.util.parse_port_proto(argv[1]) except UFWError: err_msg = _("Bad port") raise UFWError(err_msg) if not re.match('^\d([0-9,:]*\d+)*$', port): if ',' in port or ':' in port: err_msg = _("Port ranges must be numeric") raise UFWError(err_msg) to_service = port try: rule.set_protocol(proto) rule.set_port(port, "dst") type = "both" except UFWError: err_msg = _("Bad port") raise UFWError(err_msg) # Don't specify a port with ipv6, esp or ah protocols if rule.protocol in ['ipv6', 'esp', 'ah']: err_msg = _("Invalid port with protocol '%s'") % \ (rule.protocol) raise UFWError(err_msg) elif (nargs + 1) % 2 != 0: err_msg = _("Wrong number of arguments") raise UFWError(err_msg) elif not 'from' in argv and not 'to' in argv and not 'in' in argv and \ not 'out' in argv: err_msg = _("Need 'to' or 'from' clause") raise UFWError(err_msg) else: # Full form with PF-style syntax keys = ['proto', 'from', 'to', 'port', 'app', 'in', 'out'] # quick check if argv.count("to") > 1 or \ argv.count("from") > 1 or \ argv.count("proto") > 1 or \ argv.count("port") > 2 or \ argv.count("in") > 1 or \ argv.count("out") > 1 or \ argv.count("app") > 2 or \ argv.count("app") > 0 and argv.count("proto") > 0: err_msg = _("Improper rule syntax") raise UFWError(err_msg) i = 0 loc = "" for arg in argv: if i % 2 != 0 and argv[i] not in keys: err_msg = _("Invalid token '%s'") % (argv[i]) raise UFWError(err_msg) if arg == "proto": if i + 1 < nargs: try: rule.set_protocol(argv[i + 1]) except Exception: raise else: err_msg = _("Invalid 'proto' clause") raise UFWError(err_msg) elif arg == "in" or arg == "out": if i + 1 < nargs: try: if arg == "in": rule.set_interface("in", argv[i + 1]) elif arg == "out": rule.set_interface("out", argv[i + 1]) except Exception: raise else: err_msg = _("Invalid '%s' clause") % (arg) raise UFWError(err_msg) elif arg == "from": if i + 1 < nargs: try: faddr = argv[i + 1].lower() if faddr == "any": faddr = "0.0.0.0/0" from_type = "any" else: if ufw.util.valid_address(faddr, "6"): from_type = "v6" else: from_type = "v4" rule.set_src(faddr) except Exception: raise loc = "src" else: err_msg = _("Invalid 'from' clause") raise UFWError(err_msg) elif arg == "to": if i + 1 < nargs: try: saddr = argv[i + 1].lower() if saddr == "any": saddr = "0.0.0.0/0" to_type = "any" else: if ufw.util.valid_address(saddr, "6"): to_type = "v6" else: to_type = "v4" rule.set_dst(saddr) except Exception: raise loc = "dst" else: err_msg = _("Invalid 'to' clause") raise UFWError(err_msg) elif arg == "port" or arg == "app": if i + 1 < nargs: if loc == "": err_msg = _("Need 'from' or 'to' with '%s'") % \ (arg) raise UFWError(err_msg) tmp = argv[i + 1] if arg == "app": if loc == "src": rule.sapp = tmp else: rule.dapp = tmp elif not re.match('^\d([0-9,:]*\d+)*$', tmp): if ',' in tmp or ':' in tmp: err_msg = _("Port ranges must be numeric") raise UFWError(err_msg) if loc == "src": from_service = tmp else: to_service = tmp try: rule.set_port(tmp, loc) except Exception: raise else: err_msg = _("Invalid 'port' clause") raise UFWError(err_msg) i += 1 # Figure out the type of rule (IPv4, IPv6, or both) this is if from_type == "any" and to_type == "any": type = "both" elif from_type != "any" and to_type != "any" and \ from_type != to_type: err_msg = _("Mixed IP versions for 'from' and 'to'") raise UFWError(err_msg) elif from_type != "any": type = from_type elif to_type != "any": type = to_type # Adjust protocol if to_service != "" or from_service != "": proto = "" if to_service != "": try: proto = ufw.util.get_services_proto(to_service) except Exception: err_msg = _("Could not find protocol") raise UFWError(err_msg) if from_service != "": if proto == "any" or proto == "": try: proto = ufw.util.get_services_proto(from_service) except Exception: err_msg = _("Could not find protocol") raise UFWError(err_msg) else: try: tmp = ufw.util.get_services_proto(from_service) except Exception: err_msg = _("Could not find protocol") raise UFWError(err_msg) if proto == "any" or proto == tmp: proto = tmp elif tmp == "any": pass else: err_msg = _("Protocol mismatch (from/to)") raise UFWError(err_msg) # Verify found proto with specified proto if rule.protocol == "any": rule.set_protocol(proto) elif proto != "any" and rule.protocol != proto: err_msg = _("Protocol mismatch with specified protocol %s") % \ (rule.protocol) raise UFWError(err_msg) # Verify protocol not specified with application rule if rule and rule.protocol != "any" and \ (rule.sapp != "" or rule.dapp != ""): err_msg = _("Improper rule syntax ('%s' specified with app rule)") \ % (rule.protocol) raise UFWError(err_msg) if rule.protocol == 'ipv6': if type == "v6": # Can't use protocol ipv6 with v6 addresses err_msg = _("Invalid IPv6 address with protocol '%s'") % \ (rule.protocol) raise UFWError(err_msg) elif type == "both": debug("Adjusting iptype to 'v4' for protocol '%s'" % \ (rule.protocol)) type = "v4" if rule.dport != "any" or rule.sport != "any": # Don't specify a port with ipv6, esp, or ah protocol err_msg = _("Invalid port with protocol '%s'") % \ (rule.protocol) raise UFWError(err_msg) r = UFWParserResponse(action) r.data['type'] = self.type r.data['rule'] = rule r.data['iptype'] = type return r
def fuzzy_dst_match(x, y): '''This will match if x is more specific than y. Eg, for protocol if x is tcp and y is all or for address if y is a network and x is a subset of y (where x is either an address or network). Returns: 0 match 1 no match -1 fuzzy match This is a fuzzy destination match, so source ports or addresses are not considered, and (currently) only incoming. ''' def _match_ports(test_p, to_match): '''Returns True if p is an exact match or within a multi rule''' if ',' in test_p or ':' in test_p: if test_p == to_match: return True return False for port in to_match.split(','): if test_p == port: return True if ':' in port: (low, high) = port.split(':') if int(test_p) >= int(low) and int(test_p) <= int(high): return True return False if not x or not y: raise ValueError() # Ok if exact match if x.match(y) == 0: return 0 dbg_msg = "No fuzzy match '%s (v6=%s)' '%s (v6=%s)'" % \ (x, x.v6, y, y.v6) # Direction must match if y.direction != "in": debug("(direction) " + dbg_msg + " (not incoming)") return 1 # forward must match if y.forward != x.forward: debug(dbg_msg + " (forward does not match)") return 1 # Protocols must match or y 'any' if x.protocol != y.protocol and y.protocol != "any": debug("(protocol) " + dbg_msg) return 1 # Destination ports must match or y 'any' if y.dport != "any" and not _match_ports(x.dport, y.dport): debug("(dport) " + dbg_msg) return 1 if y.interface_in == "": # If destination interface is not specified, destination addresses # must match or x must be contained in y if x.interface_in == "" and x._is_anywhere(x.dst): # if x and y interfaces are not specified, and x.dst is # anywhere then ok pass elif x.dst != y.dst and '/' not in y.dst: debug("(dst) " + dbg_msg) return 1 elif x.dst != y.dst and '/' in y.dst and x.v6 == y.v6 and \ not ufw.util.in_network(x.dst, y.dst, x.v6): debug("(dst) " + dbg_msg + " ('%s' not in network '%s')" % \ (x.dst, y.dst)) return 1 else: # If destination interface is specified, then: # if specified, both interfaces must match or # the IP of the interface must match the IP of y or # the IP of the interface must be contained in y if x.interface_in != "" and x.interface_in != y.interface_in: debug("(interface) " + dbg_msg + " (%s != %s)" % \ (x.interface_in, y.interface_in)) return 1 try: if_ip = ufw.util.get_ip_from_if(y.interface_in, x.v6) except IOError: debug("(interface) " + dbg_msg + " %s does not exist" % \ (y.interface_in)) return 1 if y.dst != if_ip and '/' not in y.dst: debug("(interface) " + dbg_msg + " (%s != %s)" % \ (y.dst, if_ip)) return 1 elif y.dst != if_ip and '/' in y.dst and x.v6 == y.v6 and \ not ufw.util.in_network(if_ip, y.dst, x.v6): debug("(interface) " + dbg_msg + \ " ('%s' not in network '%s')" % (if_ip, y.dst)) return 1 if x.v6 != y.v6: debug("(v6) " + dbg_msg + " (%s != %s)" % (x.dst, y.dst)) return 1 # if we made it here, it is a fuzzy match debug("(fuzzy match) '%s (v6=%s)' '%s (v6=%s)'" % (x, x.v6, y, y.v6)) return -1
def get_status(self, verbose=False, show_count=False): '''Show ufw managed rules''' out = "" if self.dryrun: out = "> " + _("Checking iptables\n") if self.use_ipv6(): out += "> " + _("Checking ip6tables\n") return out err_msg = _("problem running") for direction in ["input", "output"]: # Is the firewall loaded at all? (rc, out) = cmd([self.iptables, '-L', \ 'ufw-user-%s' % (direction), '-n']) if rc == 1: return _("Status: inactive") elif rc != 0: raise UFWError(err_msg + " iptables: %s\n" % (out)) if self.use_ipv6(): (rc, out6) = cmd([self.ip6tables, '-L', \ 'ufw6-user-%s' % (direction), '-n']) if rc != 0: raise UFWError(err_msg + " ip6tables") s = "" str_out = "" rules = self.rules + self.rules6 count = 1 app_rules = {} for r in rules: tmp_str = "" location = {} tupl = "" show_proto = True if not verbose and (r.dapp != "" or r.sapp != ""): show_proto = False tupl = r.get_app_tuple() if app_rules.has_key(tupl): debug("Skipping found tuple '%s'" % (tupl)) continue else: app_rules[tupl] = True for loc in ['dst', 'src']: location[loc] = "" port = "" tmp = "" if loc == "dst": tmp = r.dst if not verbose and r.dapp != "": port = r.dapp if r.v6 and tmp == "::/0": port += " (v6)" else: port = r.dport else: tmp = r.src if not verbose and r.sapp != "": port = r.sapp if r.v6 and tmp == "::/0": port += " (v6)" else: port = r.sport if tmp != "0.0.0.0/0" and tmp != "::/0": location[loc] = tmp if port != "any": if location[loc] == "": location[loc] = port else: location[loc] += " " + port if show_proto and r.protocol != "any": location[loc] += "/" + r.protocol if verbose: if loc == "dst" and r.dapp != "": location[loc] += " (%s" % (r.dapp) if r.v6 and tmp == "::/0": location[loc] += " (v6)" location[loc] += ")" if loc == "src" and r.sapp != "": location[loc] += " (%s" % (r.sapp) if r.v6 and tmp == "::/0": location[loc] += " (v6)" location[loc] += ")" if port == "any": if tmp == "0.0.0.0/0" or tmp == "::/0": location[loc] = "Anywhere" # Show the protocol if Anywhere to Anwhere, have # protocol and source and dest ports are any if show_proto and r.protocol != "any" and \ r.dst == r.src and r.dport == r.sport: location[loc] += "/" + r.protocol if tmp == "::/0": location[loc] += " (v6)" else: # Show the protocol if have protocol, and source # and dest ports are any if show_proto and r.protocol != "any" and \ r.dport == r.sport: location[loc] += "/" + r.protocol if loc == 'dst' and r.interface_in != "": location[loc] += " on %s" % (r.interface_in) if loc == 'src' and r.interface_out != "": location[loc] += " on %s" % (r.interface_out) attribs = [] attrib_str = "" if r.logtype or r.direction.lower() == "out": if r.logtype: attribs.append(r.logtype.lower()) if show_count and r.direction == "out": attribs.append(r.direction) if len(attribs) > 0: attrib_str = " (%s)" % (', '.join(attribs)) # now construct the rule output string if show_count: tmp_str += "[%2d] " % (count) dir_str = r.direction.upper() if r.direction == "in" and not verbose and not show_count: dir_str = "" tmp_str += "%-26s %-12s%s%s\n" % (location['dst'], \ " ".join([r.action.upper(), \ dir_str]), \ location['src'], attrib_str) # Show the list in the order given if a numbered list, otherwise # split incoming and outgoing rules if show_count: s += tmp_str else: if r.direction == "out": str_out += tmp_str else: s += tmp_str count += 1 if s != "" or str_out != "": full_str = "\n\n" if show_count: full_str += " " str_to = _("To") str_from = _("From") str_action = _("Action") rules_header = "%-26s %-12s%s\n" % \ (str_to.decode("utf-8", 'ignore'), \ str_action.decode("utf-8", 'ignore'), \ str_from.decode("utf-8", 'ignore')) if show_count: rules_header += " " rules_header += "%-26s %-12s%s\n" % \ ("-" * len(str_to.decode("utf-8", 'ignore')), \ "-" * len(str_action.decode("utf-8", 'ignore')), \ "-" * len(str_from.decode("utf-8", 'ignore'))) full_str += rules_header.encode('utf-8', 'ignore') if s != "": full_str += s if s != "" and str_out != "": full_str += _("\n") if str_out != "": full_str += str_out s = full_str if verbose: (level, logging_str) = self.get_loglevel() policy_str = _("Default: %(in)s (incoming), %(out)s (outgoing)") \ % ({'in': self._get_default_policy(), \ 'out': self._get_default_policy("output")}) app_policy_str = self.get_default_application_policy() return _("Status: active\n%(log)s\n%(pol)s\n%(app)s%(status)s") % \ ({'log': logging_str, 'pol': policy_str, \ 'app': app_policy_str, 'status': s}) else: return _("Status: active%s") % (s)
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 get_profiles(profiles_dir): '''Get profiles found in profiles database. Returns dictionary with profile name as key and tuples for fields ''' if not os.path.isdir(profiles_dir): err_msg = _("Profiles directory does not exist") raise UFWError(err_msg) max_size = 10 * 1024 * 1024 # 10MB profiles = {} files = os.listdir(profiles_dir) files.sort() total_size = 0 pat = re.compile(r'^\.') for f in files: abs_path = profiles_dir + "/" + f if not os.path.isfile(abs_path): continue if pat.search(f): debug("Skipping '%s': hidden file" % (f)) continue if f.endswith('.dpkg-new') or f.endswith('.dpkg-old') or \ f.endswith('.dpkg-dist') or f.endswith('.rpmnew') or \ f.endswith('.rpmsave') or f.endswith('~'): debug("Skipping '%s'" % (f)) continue # Try to gracefully handle huge files for the user (no security # benefit, just usability) size = 0 try: size = os.stat(abs_path)[stat.ST_SIZE] except Exception: warn_msg = _("Skipping '%s': couldn't stat") % (f) warn(warn_msg) continue if size > max_size: warn_msg = _("Skipping '%s': too big") % (f) warn(warn_msg) continue if total_size + size > max_size: warn_msg = _("Skipping '%s': too many files read already") % (f) warn(warn_msg) continue total_size += size if sys.version_info[0] < 3: cdict = ConfigParser.RawConfigParser() else: cdict = configparser.RawConfigParser() try: cdict.read(abs_path) except Exception: warn_msg = _("Skipping '%s': couldn't process") % (f) warn(warn_msg) continue # If multiple occurences of profile name, use the last one for p in cdict.sections(): if len(p) > 64: warn_msg = _("Skipping '%s': name too long") % (p) warn(warn_msg) continue if not valid_profile_name(p): warn_msg = _("Skipping '%s': invalid name") % (p) warn(warn_msg) continue try: ufw.util.get_services_proto(p) warn_msg = _("Skipping '%s': also in /etc/services") % (p) warn(warn_msg) continue except Exception: pass skip = False for key, value in cdict.items(p): if len(key) > 64: warn_msg = _("Skipping '%s': field too long") % (p) warn(warn_msg) skip = True break if len(value) > 1024: warn_msg = _("Skipping '%(value)s': value too long for " \ "'%(field)s'") % \ ({'value': p, 'field': key}) warn(warn_msg) skip = True break if skip: continue if p in profiles: warn_msg = _("Duplicate profile '%s', using last found") % (p) warn(warn_msg) pdict = {} for key, value in cdict.items(p): #debug("add '%s' = '%s' to '%s'" % (key, value, p)) pdict[key] = value profiles[p] = pdict return profiles
def match(x, y): '''Check if rules match Return codes: 0 match 1 no match -1 match all but action ''' if not x or not y: raise ValueError() dbg_msg = "No match '%s' '%s'" % (x, y) if x.dport != y.dport: debug(dbg_msg) return 1 if x.sport != y.sport: debug(dbg_msg) return 1 if x.protocol != y.protocol: debug(dbg_msg) return 1 if x.src != y.src: debug(dbg_msg) return 1 if x.dst != y.dst: debug(dbg_msg) return 1 if x.v6 != y.v6: debug(dbg_msg) return 1 if x.dapp != y.dapp: debug(dbg_msg) return 1 if x.sapp != y.sapp: debug(dbg_msg) return 1 if x.interface_in != y.interface_in: debug(dbg_msg) return 1 if x.interface_out != y.interface_out: debug(dbg_msg) return 1 if x.direction != y.direction: debug(dbg_msg) return 1 if x.forward != y.forward: debug(dbg_msg) return 1 if x.action == y.action and x.logtype == y.logtype: dbg_msg = _("Found exact match") debug(dbg_msg) return 0 dbg_msg = _("Found non-action/non-logtype match " \ "(%(xa)s/%(ya)s %(xl)s/%(yl)s)") % \ ({'xa': x.action, 'ya': y.action, \ 'xl': x.logtype, 'yl': y.logtype}) debug(dbg_msg) return -1
def parse(self, argv): action = "" rule = "" type = "" from_type = "any" to_type = "any" from_service = "" to_service = "" insert_pos = "" logtype = "" remove = False if len(argv) > 0 and argv[0].lower() == "rule": argv.remove(argv[0]) # TODO: break this out if len(argv) > 0: if argv[0].lower() == "delete" and len(argv) > 1: remove = True argv.remove(argv[0]) rule_num = None try: rule_num = int(argv[0]) except Exception: action = argv[0] # return quickly if deleting by rule number if rule_num is not None: r = UFWParserResponse('delete-%d' % rule_num) return r elif argv[0].lower() == "insert": if len(argv) < 4: raise ValueError() insert_pos = argv[1] # Using position '0' appends the rule while '-1' prepends, # which is potentially confusing for the end user if insert_pos == "0" or insert_pos == "-1": err_msg = _("Cannot insert rule at position '%s'") % \ (insert_pos) raise UFWError(err_msg) # strip out 'insert NUM' and parse as normal del argv[1] del argv[0] elif argv[0].lower() == "prepend": insert_pos = -1 del argv[0] action = argv[0] if action != "allow" and action != "deny" and action != "reject" and \ action != "limit": raise ValueError() nargs = len(argv) if nargs < 2: raise ValueError() # set/strip rule_direction = "in" if nargs > 1 and (argv[1].lower() == "in" or \ argv[1].lower() == "out"): rule_direction = argv[1].lower() # strip out direction if not an interface rule if nargs > 2 and argv[2] != "on" and (argv[1].lower() == "in" or \ argv[1].lower() == "out"): rule_direction = argv[1].lower() del argv[1] nargs = len(argv) # strip out 'on' as in 'allow in on eth0 ...' has_interface = False if nargs > 1 and (argv.count('in') > 0 or argv.count('out') > 0): err_msg = _("Invalid interface clause") if argv[1].lower() != "in" and argv[1].lower() != "out": raise UFWError(err_msg) if nargs < 3 or argv[2].lower() != "on": raise UFWError(err_msg) del argv[2] nargs = len(argv) has_interface = True log_idx = 0 if has_interface and nargs > 3 and (argv[3].lower() == "log" or \ argv[3].lower() == 'log-all'): log_idx = 3 elif nargs > 2 and (argv[1].lower() == "log" or \ argv[1].lower() == 'log-all'): log_idx = 1 if log_idx > 0: logtype = argv[log_idx].lower() # strip out 'log' or 'log-all' and parse as normal del argv[log_idx] nargs = len(argv) if "log" in argv: err_msg = _("Option 'log' not allowed here") raise UFWError(err_msg) if "log-all" in argv: err_msg = _("Option 'log-all' not allowed here") raise UFWError(err_msg) comment = "" if 'comment' in argv: comment_idx = argv.index("comment") if comment_idx == len(argv) - 1: err_msg = _("Option 'comment' missing required argument") raise UFWError(err_msg) comment = argv[comment_idx + 1] # TODO: properly support "'" in the comment string. See r949 for # details if "'" in comment: err_msg = _("Comment may not contain \"'\"") raise ValueError(err_msg) del argv[comment_idx + 1] del argv[comment_idx] nargs = len(argv) if nargs < 2 or nargs > 13: raise ValueError() rule_action = action if logtype != "": rule_action += "_" + logtype rule = ufw.common.UFWRule(rule_action, "any", "any", \ direction=rule_direction, comment=ufw.util.hex_encode(comment)) if remove: rule.remove = remove elif insert_pos != "": try: rule.set_position(insert_pos) except Exception: raise if nargs == 2: # Short form where only app or port/proto is given if ufw.applications.valid_profile_name(argv[1]): # Check if name collision with /etc/services. If so, use # /etc/services instead of application profile try: ufw.util.get_services_proto(argv[1]) except Exception: type = "both" rule.dapp = argv[1] rule.set_port(argv[1], "dst") if rule.dapp == "": try: (port, proto) = ufw.util.parse_port_proto(argv[1]) except ValueError as e: raise UFWError(e) if not re.match('^\d([0-9,:]*\d+)*$', port): if ',' in port or ':' in port: err_msg = _("Port ranges must be numeric") raise UFWError(err_msg) to_service = port try: rule.set_protocol(proto) rule.set_port(port, "dst") type = "both" except UFWError: err_msg = _("Bad port") raise UFWError(err_msg) elif (nargs + 1) % 2 != 0: err_msg = _("Wrong number of arguments") raise UFWError(err_msg) elif 'from' not in argv and 'to' not in argv and 'in' not in argv and \ 'out' not in argv: err_msg = _("Need 'to' or 'from' clause") raise UFWError(err_msg) else: # Full form with PF-style syntax keys = ['proto', 'from', 'to', 'port', 'app', 'in', 'out'] # quick check if argv.count("to") > 1 or \ argv.count("from") > 1 or \ argv.count("proto") > 1 or \ argv.count("port") > 2 or \ argv.count("in") > 1 or \ argv.count("out") > 1 or \ argv.count("app") > 2 or \ argv.count("app") > 0 and argv.count("proto") > 0: err_msg = _("Improper rule syntax") raise UFWError(err_msg) i = 0 loc = "" for arg in argv: if i % 2 != 0 and argv[i] not in keys: err_msg = _("Invalid token '%s'") % (argv[i]) raise UFWError(err_msg) if arg == "proto": if i + 1 < nargs: try: rule.set_protocol(argv[i + 1]) except Exception: raise else: # pragma: no cover # This can't normally be reached because of nargs # checks above, but leave it here in case our parsing # changes err_msg = _("Invalid 'proto' clause") raise UFWError(err_msg) elif arg == "in" or arg == "out": if i + 1 < nargs: try: if arg == "in": rule.set_interface("in", argv[i + 1]) elif arg == "out": rule.set_interface("out", argv[i + 1]) except Exception: raise else: # pragma: no cover # This can't normally be reached because of nargs # checks above, but leave it here in case our parsing # changes err_msg = _("Invalid '%s' clause") % (arg) raise UFWError(err_msg) elif arg == "from": if i + 1 < nargs: try: faddr = argv[i + 1].lower() if faddr == "any": faddr = "0.0.0.0/0" from_type = "any" else: if ufw.util.valid_address(faddr, "6"): from_type = "v6" else: from_type = "v4" rule.set_src(faddr) except Exception: raise loc = "src" else: # pragma: no cover # This can't normally be reached because of nargs # checks above, but leave it here in case our parsing # changes err_msg = _("Invalid 'from' clause") raise UFWError(err_msg) elif arg == "to": if i + 1 < nargs: try: saddr = argv[i + 1].lower() if saddr == "any": saddr = "0.0.0.0/0" to_type = "any" else: if ufw.util.valid_address(saddr, "6"): to_type = "v6" else: to_type = "v4" rule.set_dst(saddr) except Exception: raise loc = "dst" else: # pragma: no cover # This can't normally be reached because of nargs # checks above, but leave it here in case our parsing # changes err_msg = _("Invalid 'to' clause") raise UFWError(err_msg) elif arg == "port" or arg == "app": if i + 1 < nargs: if loc == "": err_msg = _("Need 'from' or 'to' with '%s'") % \ (arg) raise UFWError(err_msg) tmp = argv[i + 1] if arg == "app": if loc == "src": rule.sapp = tmp else: rule.dapp = tmp elif not re.match('^\d([0-9,:]*\d+)*$', tmp): if ',' in tmp or ':' in tmp: err_msg = _("Port ranges must be numeric") raise UFWError(err_msg) if loc == "src": from_service = tmp else: to_service = tmp try: rule.set_port(tmp, loc) except Exception: raise else: # pragma: no cover # This can't normally be reached because of nargs # checks above, but leave it here in case our parsing # changes err_msg = _("Invalid 'port' clause") raise UFWError(err_msg) i += 1 # Figure out the type of rule (IPv4, IPv6, or both) this is if from_type == "any" and to_type == "any": type = "both" elif from_type != "any" and to_type != "any" and \ from_type != to_type: err_msg = _("Mixed IP versions for 'from' and 'to'") raise UFWError(err_msg) elif from_type != "any": type = from_type elif to_type != "any": type = to_type # Adjust protocol if to_service != "" or from_service != "": proto = "" if to_service != "": try: proto = ufw.util.get_services_proto(to_service) except Exception: # pragma: no cover # This can't normally be reached because of set_port() # checks above, but leave it here in case our parsing # changes err_msg = _("Could not find protocol") raise UFWError(err_msg) if from_service != "": if proto == "any" or proto == "": try: proto = ufw.util.get_services_proto(from_service) except Exception: # pragma: no cover # This can't normally be reached because of set_port() # checks above, but leave it here in case our parsing # changes err_msg = _("Could not find protocol") raise UFWError(err_msg) else: try: tmp = ufw.util.get_services_proto(from_service) except Exception: # pragma: no cover # This can't normally be reached because of set_port() # checks above, but leave it here in case our parsing # changes err_msg = _("Could not find protocol") raise UFWError(err_msg) if proto == "any" or proto == tmp: proto = tmp elif tmp == "any": pass else: err_msg = _("Protocol mismatch (from/to)") raise UFWError(err_msg) # Verify found proto with specified proto if rule.protocol == "any": rule.set_protocol(proto) elif proto != "any" and rule.protocol != proto: err_msg = _("Protocol mismatch with specified protocol %s") % \ (rule.protocol) raise UFWError(err_msg) # adjust type as needed if rule: if rule.protocol in ufw.util.ipv4_only_protocols and \ type == "both": debug("Adjusting iptype to 'v4' for protocol '%s'" % \ (rule.protocol)) type = "v4" # Now verify the rule rule.verify(type) r = UFWParserResponse(action) r.data['type'] = self.type r.data['rule'] = rule r.data['iptype'] = type return r
def _do_checks(self): # pragma: no coverage '''Perform basic security checks: is setuid or setgid (for non-Linux systems) checks that script is owned by root checks that every component in absolute path are owned by root warn if script is group writable warn if part of script path is group writable Doing this at the beginning causes a race condition with later operations that don't do these checks. However, if the user running this script is root, then need to be root to exploit the race condition (and you are hosed anyway...) ''' if not self.do_checks: err_msg = _("Checks disabled") warn(err_msg) return True # Not needed on Linux, but who knows the places we will go... if os.getuid() != os.geteuid(): err_msg = _("ERROR: this script should not be SUID") raise UFWError(err_msg) if os.getgid() != os.getegid(): err_msg = _("ERROR: this script should not be SGID") raise UFWError(err_msg) uid = os.getuid() if uid != 0: err_msg = _("You need to be root to run this script") raise UFWError(err_msg) # Use these so we only warn once warned_world_write = {} warned_group_write = {} warned_owner = {} profiles = [] if not os.path.isdir(self.files['apps']): warn_msg = _("'%s' does not exist") % (self.files['apps']) warn(warn_msg) else: pat = re.compile(r'^\.') for profile in os.listdir(self.files['apps']): if not pat.search(profile): profiles.append(os.path.join(self.files['apps'], profile)) for path in list(self.files.values()) + \ [ os.path.abspath(sys.argv[0]) ] + \ profiles: if not path.startswith('/'): path = "%s/%s" % (os.getcwd(), path) while True: debug("Checking " + path) if path == self.files['apps'] and \ not os.path.isdir(self.files['apps']): break try: statinfo = os.stat(path) mode = statinfo[stat.ST_MODE] except OSError: err_msg = _("Couldn't stat '%s'") % (path) raise UFWError(err_msg) except Exception: raise # snaps and clicks unpack to this, so handle it click_user = '******' snap_user = '******' is_unpack_user = False try: if pwd.getpwuid(statinfo.st_uid)[0] == click_user or \ pwd.getpwuid(statinfo.st_uid)[0] == snap_user: is_unpack_user = True except KeyError: pass if statinfo.st_uid != 0 and not is_unpack_user and \ path not in warned_owner: warn_msg = _("uid is %(uid)s but '%(path)s' is owned by " \ "%(st_uid)s") % ({'uid': str(uid), \ 'path': path, \ 'st_uid': str(statinfo.st_uid)}) warn(warn_msg) warned_owner[path] = True if mode & stat.S_IWOTH and path not in warned_world_write: warn_msg = _("%s is world writable!") % (path) warn(warn_msg) warned_world_write[path] = True if mode & stat.S_IWGRP and path not in warned_group_write and \ statinfo.st_gid != 0: warn_msg = _("%s is group writable!") % (path) warn(warn_msg) warned_group_write[path] = True if path == "/": break last_path = path path = os.path.dirname(path) if not path: raise OSError(errno.ENOENT, \ "Could not find parent for '%s'" % \ (last_path)) for f in self.files: if f != 'apps' and not os.path.isfile(self.files[f]): err_msg = _("'%(f)s' file '%(name)s' does not exist") % \ ({'f': f, 'name': self.files[f]}) raise UFWError(err_msg)