def _reload_user_rules(self): '''Reload firewall rules file''' err_msg = _("problem running") if self.dryrun: msg("> | iptables-restore") if self.use_ipv6(): msg("> | ip6tables-restore") elif self.is_enabled(): # first flush the user logging chains try: for c in self.chains['user']: self._chain_cmd(c, ['-F', c]) self._chain_cmd(c, ['-Z', c]) except Exception: raise UFWError(err_msg) # then restore the system rules (rc, out) = cmd_pipe(['cat', self.files['rules']], \ [self.iptables_restore, '-n']) if rc != 0: raise UFWError(err_msg + " iptables") if self.use_ipv6(): (rc, out) = cmd_pipe(['cat', self.files['rules6']], \ [self.ip6tables_restore, '-n']) if rc != 0: raise UFWError(err_msg + " ip6tables")
def _get_defaults(self): '''Get all settings from defaults file''' self.defaults = {} for f in [self.files['defaults'], self.files['conf']]: try: orig = ufw.util.open_file_read(f) except Exception: # pragma: no coverage err_msg = _("Couldn't open '%s' for reading") % (f) raise UFWError(err_msg) pat = re.compile(r'^\w+="?\w+"?') for line in orig: if pat.search(line): tmp = re.split(r'=', line.strip()) self.defaults[tmp[0].lower()] = tmp[1].lower().strip('"\'') orig.close() # do some default policy sanity checking policies = ['accept', 'drop', 'reject'] for c in ['input', 'output', 'forward']: if 'default_%s_policy' % (c) not in self.defaults: err_msg = _("Missing policy for '%s'" % (c)) raise UFWError(err_msg) p = self.defaults['default_%s_policy' % (c)] if p not in policies: err_msg = _("Invalid policy '%(policy)s' for '%(chain)s'" % \ ({'policy': p, 'chain': c})) 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 not profile.has_key(f): 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, e: debug(e) err_msg = _("Invalid ports in profile '%s'") % (name) raise UFWError(err_msg)
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('|') try: for p in ports: (port, proto) = ufw.util.parse_port_proto(p) # quick checks if error in profile if proto == "any" and (':' in port or ',' in port): raise UFWError(err_msg) 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 set_default(self, fn, opt, value): '''Sets option in defaults file''' if not re.match(r'^[\w_]+$', opt): err_msg = _("Invalid option") raise UFWError(err_msg) # Perform this here so we can present a nice error to the user rather # than a traceback if not os.access(fn, os.W_OK): err_msg = _("'%s' is not writable" % (fn)) raise UFWError(err_msg) fns = ufw.util.open_files(fn) fd = fns['tmp'] found = False pat = re.compile(r'^' + opt + '=') for line in fns['orig']: if pat.search(line): ufw.util.write_to_file(fd, opt + "=" + value + "\n") found = True else: ufw.util.write_to_file(fd, line) # Add the entry if not found if not found: ufw.util.write_to_file(fd, opt + "=" + value + "\n") try: ufw.util.close_files(fns) except Exception: # pragma: no coverage raise # Now that the files are written out, update value in memory self.defaults[opt.lower()] = value.lower().strip('"\'')
def parse(self, argv): assert (argv[0] == "route") # 'ufw delete NUM' is the correct usage, not 'ufw route delete NUM' if 'delete' in argv: idx = argv.index('delete') err_msg = "" if len(argv) > idx: try: # 'route delete NUM' is unsupported int(argv[idx + 1]) err_msg = _( "'route delete NUM' unsupported. Use 'delete NUM' instead." ) raise UFWError(err_msg) except ValueError: # 'route delete RULE' is supported pass # Let's use as much as UFWCommandRule.parse() as possible. The only # difference with our rules is that argv[0] is 'route' and we support # both 'in on <interface>' and 'out on <interface>' in our rules. # Because UFWCommandRule.parse() expects that the interface clause is # specified first, strip out the second clause and add it later rule_argv = None interface = None strip = None # eg: ['route', 'allow', 'in', 'on', 'eth0', 'out', 'on', 'eth1'] s = " ".join(argv) if " in on " in s and " out on " in s: strip = "out" if argv.index("in") > argv.index("out"): strip = "in" # Remove 2nd interface clause from argv and add it to the rule # later. Because we searched for " <strip> on " in our joined # string we are guaranteed to have argv[argv.index(<strip>) + 2] # exist. interface = argv[argv.index(strip) + 2] rule_argv = argv[0:argv.index(strip)] + argv[argv.index(strip) + 3:] elif not re.search(r' (in|out) on ', s) and \ not re.search(r' app (in|out) ', s) and \ (" in " in s or " out " in s): # Specifying a direction without an interface doesn't make any # sense with route rules. application names could be 'in' or 'out' # so don't artificially limit those names. err_msg = _("Invalid interface clause for route rule") raise UFWError(err_msg) else: rule_argv = argv rule_argv[0] = "rule" r = UFWCommandRule.parse(self, rule_argv) if 'rule' in r.data: r.data['rule'].forward = True if strip and interface: r.data['rule'].set_interface(strip, interface) return r
def reset(self): '''Reset the firewall''' res = "" # First make sure we have all the original files allfiles = [] for i in self.files: if not self.files[i].endswith('.rules'): continue allfiles.append(self.files[i]) fn = os.path.join(ufw.common.share_dir, "iptables", \ os.path.basename(self.files[i])) if not os.path.isfile(fn): err_msg = _("Could not find '%s'. Aborting") % (fn) raise UFWError(err_msg) ext = time.strftime("%Y%m%d_%H%M%S") # This implementation will intentionally traceback if someone tries to # do something to take advantage of the race conditions here. # Don't do anything if the files already exist for i in allfiles: fn = "%s.%s" % (i, ext) if os.path.exists(fn): err_msg = _("'%s' already exists. Aborting") % (fn) raise UFWError(err_msg) # Move the old to the new for i in allfiles: fn = "%s.%s" % (i, ext) res += _("Backing up '%(old)s' to '%(new)s'\n") % (\ {'old': os.path.basename(i), 'new': fn}) os.rename(i, fn) # Copy files into place for i in allfiles: old = "%s.%s" % (i, ext) shutil.copy(os.path.join(ufw.common.share_dir, "iptables", \ os.path.basename(i)), \ os.path.dirname(i)) shutil.copymode(old, i) try: statinfo = os.stat(i) mode = statinfo[stat.ST_MODE] except Exception: warn_msg = _("Couldn't stat '%s'") % (i) warn(warn_msg) continue if mode & stat.S_IWOTH: res += _("WARN: '%s' is world writable") % (i) elif mode & stat.S_IROTH: res += _("WARN: '%s' is world readable") % (i) return res
def delete_rule(self, number, force=False): '''Delete rule''' try: n = int(number) except Exception: err_msg = _("Could not find rule '%s'") % number raise UFWError(err_msg) rules = self.backend.get_rules() if n <= 0 or n > len(rules): err_msg = _("Could not find rule '%d'") % n raise UFWError(err_msg) rule = self.backend.get_rule_by_number(n) if not rule: err_msg = _("Could not find rule '%d'") % n raise UFWError(err_msg) rule.remove = True ip_version = "v4" if rule.v6: ip_version = "v6" proceed = True if not force: if rule.forward: rstr = "route %s" % \ ufw.parser.UFWCommandRouteRule.get_command(rule) else: rstr = ufw.parser.UFWCommandRule.get_command(rule) prompt = _("Deleting:\n %(rule)s\nProceed with operation " \ "(%(yes)s|%(no)s)? ") % ({'rule': rstr, \ 'yes': self.yes, \ 'no': self.no}) msg(prompt, output=sys.stdout, newline=False) ans = sys.stdin.readline().lower().strip() if ans != "y" and ans != self.yes.lower() and \ ans != self.yes_full.lower(): proceed = False res = "" if proceed: res = self.set_rule(rule, ip_version) else: res = _("Aborted") return res
def do_application_action(self, action, profile): '''Perform action on profile. action and profile are usually based on return values from parse_command(). ''' res = "" if action == "default-allow": res = self.set_default_application_policy("allow") elif action == "default-deny": res = self.set_default_application_policy("deny") elif action == "default-reject": res = self.set_default_application_policy("reject") elif action == "default-skip": res = self.set_default_application_policy("skip") elif action == "list": res = self.get_application_list() elif action == "info": res = self.get_application_info(profile) elif action == "update" or action == "update-with-new": str1 = self.application_update(profile) str2 = "" if action == "update-with-new": str2 = self.application_add(profile) if str1 != "" and str2 != "": str1 += "\n" res = str1 + str2 else: err_msg = _("Unsupported action '%s'") % (action) raise UFWError(err_msg) return res
def set_default_application_policy(self, policy): '''Sets default application policy of firewall''' if not self.dryrun: if policy == "allow": self.set_default(self.files['defaults'], \ "DEFAULT_APPLICATION_POLICY", \ "\"ACCEPT\"") elif policy == "deny": self.set_default(self.files['defaults'], \ "DEFAULT_APPLICATION_POLICY", \ "\"DROP\"") elif policy == "reject": self.set_default(self.files['defaults'], \ "DEFAULT_APPLICATION_POLICY", \ "\"REJECT\"") elif policy == "skip": self.set_default(self.files['defaults'], \ "DEFAULT_APPLICATION_POLICY", \ "\"SKIP\"") else: err_msg = _("Unsupported policy '%s'") % (policy) raise UFWError(err_msg) rstr = _("Default application policy changed to '%s'") % (policy) return rstr
def get_application_info(self, pname): '''Display information on profile''' names = [] if pname == "all": names = list(self.backend.profiles.keys()) names.sort() else: if not ufw.applications.valid_profile_name(pname): err_msg = _("Invalid profile name") raise UFWError(err_msg) names.append(pname) rstr = "" for name in names: if name not in self.backend.profiles or \ not self.backend.profiles[name]: err_msg = _("Could not find profile '%s'") % (name) raise UFWError(err_msg) if not ufw.applications.verify_profile(name, \ self.backend.profiles[name]): err_msg = _("Invalid profile") raise UFWError(err_msg) rstr += _("Profile: %s\n") % (name) rstr += _("Title: %s\n") % (ufw.applications.get_title(\ self.backend.profiles[name])) rstr += _("Description: %s\n\n") % \ (ufw.applications.get_description(\ self.backend.profiles[name])) ports = ufw.applications.get_ports(self.backend.profiles[name]) if len(ports) > 1 or ',' in ports[0]: rstr += _("Ports:") else: rstr += _("Port:") for p in ports: rstr += "\n %s" % (p) if name != names[len(names) - 1]: rstr += "\n\n--\n\n" return ufw.util.wrap_text(rstr)
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 application_add(self, profile): '''Refresh application profile''' rstr = "" policy = "" if profile == "all": err_msg = _("Cannot specify 'all' with '--add-new'") raise UFWError(err_msg) default = self.backend.defaults['default_application_policy'] if default == "skip": ufw.util.debug("Policy is '%s', not adding profile '%s'" % \ (policy, profile)) return rstr elif default == "accept": policy = "allow" elif default == "drop": policy = "deny" elif default == "reject": policy = "reject" else: err_msg = _("Unknown policy '%s'") % (default) raise UFWError(err_msg) args = ['ufw'] if self.backend.dryrun: args.append("--dry-run") args += [policy, profile] try: pr = parse_command(args) except Exception: # pragma: no cover raise if 'rule' in pr.data: rstr = self.do_action(pr.action, pr.data['rule'], \ pr.data['iptype']) else: rstr = self.do_action(pr.action, "", "") return rstr
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 __init__(self, name, dryrun, extra_files=None, rootdir=None, datadir=None): self.defaults = None self.name = name self.dryrun = dryrun self.rules = [] self.rules6 = [] p = _findpath(ufw.common.config_dir, datadir) self.files = { 'defaults': os.path.join(p, 'default/ufw'), 'conf': os.path.join(p, 'ufw/ufw.conf'), 'apps': os.path.join(p, 'ufw/applications.d') } if extra_files is not None: self.files.update(extra_files) self.loglevels = { 'off': 0, 'low': 100, 'medium': 200, 'high': 300, 'full': 400 } self.do_checks = ufw.common.do_checks self._do_checks() self._get_defaults() self._read_rules() self.profiles = ufw.applications.get_profiles(self.files['apps']) self.iptables = os.path.join(ufw.common.iptables_dir, "iptables") self.iptables_restore = os.path.join(ufw.common.iptables_dir, \ "iptables-restore") self.ip6tables = os.path.join(ufw.common.iptables_dir, "ip6tables") self.ip6tables_restore = os.path.join(ufw.common.iptables_dir, \ "ip6tables-restore") try: self.iptables_version = ufw.util.get_iptables_version( self.iptables) except OSError: # pragma: no coverage err_msg = _("Couldn't determine iptables version") raise UFWError(err_msg) # Initialize via initcaps only when we need it (LP: #1044361) self.caps = None
def __init__(self, dryrun, backend_type="iptables"): if backend_type == "iptables": try: self.backend = UFWBackendIptables(dryrun) except Exception: # pragma: no cover raise else: raise UFWError("Unsupported backend type '%s'" % (backend_type)) # Initialize input strings for translations self.no = _("n") self.yes = _("y") self.yes_full = _("yes")
def register_command(self, c): '''Register a command with the parser''' if c.command is None or c.command == '': # If the command is empty, then use 'type' as command key = "%s" % (c.type) else: key = "%s" % (c.command) if c.type not in self.commands: self.commands[c.type] = {} if key in self.commands[c.type]: err_msg = _("Command '%s' already exists") % (key) raise UFWError(err_msg) self.commands[c.type][key] = c
def set_loglevel(self, level): '''Sets log level of firewall''' if level not in list(self.loglevels.keys()) + ['on']: err_msg = _("Invalid log level '%s'") % (level) raise UFWError(err_msg) new_level = level if level == "on": if 'loglevel' not in self.defaults or \ self.defaults['loglevel'] == "off": new_level = "low" else: new_level = self.defaults['loglevel'] self.set_default(self.files['conf'], "LOGLEVEL", new_level) self.update_logging(new_level) if new_level == "off": return _("Logging disabled") else: return _("Logging enabled")
def find_application_name(self, profile_name): '''Find the application profile name for profile_name''' if self.profiles.has_key(profile_name): return profile_name match = "" matches = 0 for n in 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_default_policy(self, primary="input", check_forward=False): '''Get default policy for specified primary chain''' policy = "default_" + primary + "_policy" rstr = "" if self.defaults[policy] == "accept": rstr = "allow" elif self.defaults[policy] == "reject": rstr = "reject" else: rstr = "deny" if check_forward and primary == "forward": enabled = False err_msg = _("problem running sysctl") (rc, out) = ufw.util.cmd(['sysctl', 'net.ipv4.ip_forward']) if rc != 0: # pragma: no cover raise UFWError(err_msg) if '1' in out: enabled = True # IPv6 may be disabled, so ignore sysctl output if self.use_ipv6(): (rc, out) = ufw.util.cmd( ['sysctl', 'net.ipv6.conf.default.forwarding']) if rc == 0 and '1' in out: enabled = True (rc, out) = ufw.util.cmd( ['sysctl', 'net.ipv6.conf.all.forwarding']) if rc == 0 and '1' in out: enabled = True if not enabled: rstr = "disabled" return rstr
def reset(self): # pragma: no coverage '''Reset the firewall''' raise UFWError("UFWBackend.reset: need to override")
def get_app_rules_from_system(self, template, v6): # pragma: no coverage '''Get a list if rules based on template''' raise UFWError("UFWBackend.get_app_rules_from_system: need to " + \ "override")
def update_logging(self, level): # pragma: no coverage '''Update loglevel of running firewall''' raise UFWError("UFWBackend.update_logging: need to override")
def do_action(self, action, rule, ip_version, force=False): '''Perform action on rule. action, rule and ip_version are usually based on return values from parse_command(). ''' res = "" if action.startswith("logging-on"): tmp = action.split('_') if len(tmp) > 1: res = self.set_loglevel(tmp[1]) else: res = self.set_loglevel("on") elif action == "logging-off": res = self.set_loglevel("off") elif action.startswith("default-"): err_msg = _("Unsupported default policy") tmp = action.split('-') if len(tmp) != 3: raise UFWError(err_msg) res = self.set_default_policy(tmp[1], tmp[2]) elif action == "reset": res = self.reset(force) elif action == "status": res = self.get_status() elif action == "status-verbose": res = self.get_status(True) elif action.startswith("show"): tmp = action.split('-')[1] if tmp == "listening": res = self.get_show_listening() elif tmp == "added": res = self.get_show_added() else: res = self.get_show_raw(tmp) elif action == "status-numbered": res = self.get_status(False, True) elif action == "enable": res = self.set_enabled(True) elif action == "disable": res = self.set_enabled(False) elif action == "reload": if self.backend.is_enabled(): self.set_enabled(False) self.set_enabled(True) res = _("Firewall reloaded") else: res = _("Firewall not enabled (skipping reload)") elif action.startswith("delete-"): res = self.delete_rule(action.split('-')[1], force) elif action == "allow" or action == "deny" or action == "reject" or \ action == "limit": # allow case insensitive matches for application rules if rule.dapp != "": try: tmp = self.backend.find_application_name(rule.dapp) if tmp != rule.dapp: rule.dapp = tmp rule.set_port(tmp, "dst") except UFWError as e: # allow for the profile being deleted (LP: #407810) if not rule.remove: # pragma: no cover error(e.value) if not ufw.applications.valid_profile_name(rule.dapp): err_msg = _("Invalid profile name") raise UFWError(err_msg) if rule.sapp != "": try: tmp = self.backend.find_application_name(rule.sapp) if tmp != rule.sapp: rule.sapp = tmp rule.set_port(tmp, "dst") except UFWError as e: # allow for the profile being deleted (LP: #407810) if not rule.remove: # pragma: no cover error(e.value) if not ufw.applications.valid_profile_name(rule.sapp): err_msg = _("Invalid profile name") raise UFWError(err_msg) res = self.set_rule(rule, ip_version) else: err_msg = _("Unsupported action '%s'") % (action) raise UFWError(err_msg) return res
def set_rule(self, rule, ip_version): '''Updates firewall with rule''' res = "" err_msg = "" tmp = "" rules = [] if rule.dapp == "" and rule.sapp == "": rules.append(rule) else: tmprules = [] try: if rule.remove: if ip_version == "v4": tmprules = self.backend.get_app_rules_from_system( rule, False) elif ip_version == "v6": tmprules = self.backend.get_app_rules_from_system( rule, True) elif ip_version == "both": tmprules = self.backend.get_app_rules_from_system( rule, False) tmprules6 = self.backend.get_app_rules_from_system( rule, True) # Only add rules that are different by more than v6 (we # will handle 'ip_version == both' specially, below). for x in tmprules: for y in tmprules6: prev6 = y.v6 y.v6 = False if not x.match(y): y.v6 = prev6 tmprules.append(y) else: err_msg = _("Invalid IP version '%s'") % (ip_version) raise UFWError(err_msg) # Don't process removal of non-existing application rules if len(tmprules) == 0 and not self.backend.dryrun: tmp = _("Could not delete non-existent rule") if ip_version == "v4": res = tmp elif ip_version == "v6": res = tmp + " (v6)" elif ip_version == "both": res = tmp + "\n" + tmp + " (v6)" return res for tmp in tmprules: r = tmp.dup_rule() r.remove = rule.remove r.set_action(rule.action) r.set_logtype(rule.logtype) rules.append(r) else: rules = self.backend.get_app_rules_from_template(rule) # Reverse the order of rules for inserted rules, so they # are inserted in the right order if rule.position > 0: rules.reverse() except Exception: raise count = 0 set_error = False pos_err_msg = _("Invalid position '") num_v4 = self.backend.get_rules_count(False) num_v6 = self.backend.get_rules_count(True) for i, r in enumerate(rules): count = i if r.position > num_v4 + num_v6: pos_err_msg += str(r.position) + "'" raise UFWError(pos_err_msg) try: if self.backend.use_ipv6(): if ip_version == "v4": if r.position > num_v4: pos_err_msg += str(r.position) + "'" raise UFWError(pos_err_msg) r.set_v6(False) tmp = self.backend.set_rule(r) elif ip_version == "v6": if r.position > num_v4: r.set_position(r.position - num_v4) elif r.position != 0 and r.position <= num_v4: pos_err_msg += str(r.position) + "'" raise UFWError(pos_err_msg) r.set_v6(True) tmp = self.backend.set_rule(r) elif ip_version == "both": user_pos = r.position # user specified position r.set_v6(False) if not r.remove and user_pos > num_v4: # The user specified a v6 rule, so try to find a # match in the v4 rules and use its position. p = self.backend.find_other_position( \ user_pos - num_v4 + count, True) if p > 0: r.set_position(p) else: # If not found, then add the rule r.set_position(0) tmp = self.backend.set_rule(r) # We need to readjust the position since the number # the number of ipv4 rules increased if not r.remove and user_pos > 0: num_v4 = self.backend.get_rules_count(False) r.set_position(user_pos + 1) r.set_v6(True) if not r.remove and r.position > 0 and \ r.position <= num_v4: # The user specified a v4 rule, so try to find a # match in the v6 rules and use its position. p = self.backend.find_other_position(r.position, \ False) if p > 0: # Subtract count since the list is reversed r.set_position(p - count) else: # If not found, then add the rule r.set_position(0) if tmp != "": tmp += "\n" # Readjust position to send to set_rule if not r.remove and r.position > num_v4: r.set_position(r.position - num_v4) tmp += self.backend.set_rule(r) else: err_msg = _("Invalid IP version '%s'") % (ip_version) raise UFWError(err_msg) else: if ip_version == "v4" or ip_version == "both": r.set_v6(False) tmp = self.backend.set_rule(r) elif ip_version == "v6": err_msg = _("IPv6 support not enabled") raise UFWError(err_msg) else: err_msg = _("Invalid IP version '%s'") % (ip_version) raise UFWError(err_msg) except UFWError as e: err_msg = e.value set_error = True break if r.updated: warn_msg = _("Rule changed after normalization") warnings.warn(warn_msg) if not set_error: # Just return the last result if no error res += tmp elif len(rules) == 1: # If no error, and just one rule, error out error(err_msg) # pragma: no cover else: # If error and more than one rule, delete the successfully added # rules in reverse order undo_error = False indexes = list(range(count + 1)) indexes.reverse() for j in indexes: if count > 0 and rules[j]: backout_rule = rules[j].dup_rule() backout_rule.remove = True try: self.set_rule(backout_rule, ip_version) except Exception: # Don't fail, so we can try to backout more undo_error = True warn_msg = _("Could not back out rule '%s'") % \ r.format_rule() warn(warn_msg) err_msg += _("\nError applying application rules.") if undo_error: err_msg += _(" Some rules could not be unapplied.") else: err_msg += _(" Attempted rules successfully unapplied.") raise UFWError(err_msg) return res
def get_show_listening(self): '''Shows listening services and incoming rules that might affect them''' res = "" try: d = ufw.util.parse_netstat_output(self.backend.use_ipv6()) except Exception: # pragma: no cover err_msg = _("Could not get listening status") raise UFWError(err_msg) rules = self.backend.get_rules() protocols = list(d.keys()) protocols.sort() for proto in protocols: if not self.backend.use_ipv6() and proto in ['tcp6', 'udp6']: continue # pragma: no cover res += "%s:\n" % (proto) ports = list(d[proto].keys()) ports.sort() for port in ports: for item in d[proto][port]: addr = item['laddr'] if not addr.startswith("127.") and \ not addr.startswith("::1"): ifname = "" res += " %s " % port if addr == "0.0.0.0" or addr == "::": res += "* " addr = "%s/0" % (item['laddr']) else: res += "%s " % addr ifname = ufw.util.get_if_from_ip(addr) res += "(%s)" % os.path.basename(item['exe']) # Create an incoming rule since matching outgoing and # forward rules doesn't make sense for this report. rule = ufw.common.UFWRule(action="allow", \ protocol=proto[:3], \ dport=port, \ dst=addr, direction="in", \ forward=False ) rule.set_v6(proto.endswith("6")) if ifname != "": rule.set_interface("in", ifname) rule.normalize() # Get the non-tuple rule from get_matching(), and then # add its corresponding CLI command. matching = self.backend.get_matching(rule) if len(matching) > 0: res += "\n" for i in matching: if i > 0 and i - 1 < len(rules): res += " [%2d] %s\n" % (i, \ # Don't need UFWCommandRule here either ufw.parser.UFWCommandRule.get_command(\ rules[i-1]) ) res += "\n" if not self.backend.use_ipv6(): ufw.util.debug("Skipping tcp6 and udp6 (IPv6 is disabled)") return res
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 help(self, args): raise UFWError("UFWCommand.help: need to override")
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