1
'''backend_iptables.py: iptables backend for ufw'''
3
# Copyright 2008-2012 Canonical Ltd.
5
# This program is free software: you can redistribute it and/or modify
6
# it under the terms of the GNU General Public License version 3,
7
# as published by the Free Software Foundation.
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU General Public License for more details.
14
# You should have received a copy of the GNU General Public License
15
# along with this program. If not, see <http://www.gnu.org/licenses/>.
25
from ufw.common import UFWError, UFWRule, config_dir, state_dir
26
from ufw.util import warn, debug, msg, cmd, cmd_pipe
30
class UFWBackendIptables(ufw.backend.UFWBackend):
31
'''Instance class for UFWBackend'''
32
def __init__(self, dryrun):
33
'''UFWBackendIptables initialization'''
34
self.comment_str = "# " + ufw.common.programName + "_comment #"
37
files['rules'] = os.path.join(state_dir, 'user.rules')
38
files['before_rules'] = os.path.join(config_dir, 'ufw/before.rules')
39
files['after_rules'] = os.path.join(config_dir, 'ufw/after.rules')
40
files['rules6'] = os.path.join(state_dir, 'user6.rules')
41
files['before6_rules'] = os.path.join(config_dir, 'ufw/before6.rules')
42
files['after6_rules'] = os.path.join(config_dir, 'ufw/after6.rules')
43
files['init'] = os.path.join(state_dir, 'ufw-init')
45
ufw.backend.UFWBackend.__init__(self, "iptables", dryrun, files)
47
self.chains = {'before': [], 'user': [], 'after': [], 'misc': []}
48
for ver in ['4', '6']:
56
for loc in ['before', 'user', 'after']:
57
for target in ['input', 'output', 'forward']:
58
chain = "%s-%s-logging-%s" % (chain_prefix, loc, target)
59
self.chains[loc].append(chain)
60
self.chains['misc'].append(chain_prefix + "-logging-deny")
61
self.chains['misc'].append(chain_prefix + "-logging-allow")
63
# The default log rate limiting rule ('ufw[6]-user-limit chain should
64
# be prepended before use)
65
self.ufw_user_limit_log = ['-m', 'limit', \
66
'--limit', '3/minute', '-j', 'LOG', \
68
self.ufw_user_limit_log_text = "[UFW LIMIT BLOCK]"
70
def get_default_application_policy(self):
71
'''Get current policy'''
72
rstr = _("New profiles:")
73
if self.defaults['default_application_policy'] == "accept":
75
elif self.defaults['default_application_policy'] == "drop":
77
elif self.defaults['default_application_policy'] == "reject":
84
def set_default_policy(self, policy, direction):
85
'''Sets default policy of firewall'''
87
if policy != "allow" and policy != "deny" and policy != "reject":
88
err_msg = _("Unsupported policy '%s'") % (policy)
89
raise UFWError(err_msg)
91
if direction != "incoming" and direction != "outgoing":
92
err_msg = _("Unsupported policy for direction '%s'") % \
94
raise UFWError(err_msg)
97
if direction == "outgoing":
102
if policy == "allow":
104
self.set_default(self.files['defaults'], \
105
"DEFAULT_%s_POLICY" % (chain), \
109
old_log_str = 'UFW BLOCK'
110
new_log_str = 'UFW ALLOW'
111
elif policy == "reject":
113
self.set_default(self.files['defaults'], \
114
"DEFAULT_%s_POLICY" % (chain), \
118
old_log_str = 'UFW ALLOW'
119
new_log_str = 'UFW BLOCK'
122
self.set_default(self.files['defaults'], \
123
"DEFAULT_%s_POLICY" % (chain), \
127
old_log_str = 'UFW ALLOW'
128
new_log_str = 'UFW BLOCK'
130
# Switch logging message in catch-all rules
131
pat = re.compile(r'' + old_log_str)
132
for f in [self.files['after_rules'], self.files['after6_rules']]:
134
fns = ufw.util.open_files(f)
139
for line in fns['orig']:
141
ufw.util.write_to_file(fd, pat.sub(new_log_str, line))
143
ufw.util.write_to_file(fd, line)
146
ufw.util.close_files(fns)
150
rstr = _("Default %(direction)s policy changed to '%(policy)s'\n") % \
151
({'direction': direction, 'policy': policy})
152
rstr += _("(be sure to update your rules accordingly)")
156
def get_running_raw(self, rules_type):
157
'''Show current running status of firewall'''
159
out = "> " + _("Checking raw iptables\n")
160
out += "> " + _("Checking raw ip6tables\n")
163
args = ['-n', '-v', '-x', '-L']
167
if rules_type == "raw":
169
items = ['filter', 'nat', 'mangle', 'raw']
170
items6 = ['filter', 'mangle', 'raw']
171
elif rules_type == "builtins":
172
for c in ['INPUT', 'FORWARD', 'OUTPUT']:
173
items.append('filter:%s' % c)
174
items6.append('filter:%s' % c)
175
for c in ['PREROUTING', 'INPUT', 'FORWARD', 'OUTPUT', \
177
items.append('mangle:%s' % c)
178
items6.append('mangle:%s' % c)
179
for c in ['PREROUTING', 'OUTPUT']:
180
items.append('raw:%s' % c)
181
items6.append('raw:%s' % c)
182
for c in ['PREROUTING', 'POSTROUTING', 'OUTPUT']:
183
items.append('nat:%s' % c)
184
elif rules_type == "before":
185
for b in ['input', 'forward', 'output']:
186
items.append('ufw-before-%s' % b)
187
items6.append('ufw6-before-%s' % b)
188
elif rules_type == "user":
189
for b in ['input', 'forward', 'output']:
190
items.append('ufw-user-%s' % b)
191
items6.append('ufw6-user-%s' % b)
192
if self.caps['limit']['4']:
193
items.append('ufw-user-limit-accept')
194
items.append('ufw-user-limit')
195
if self.caps['limit']['6']:
196
items6.append('ufw6-user-limit-accept')
197
items6.append('ufw6-user-limit')
198
elif rules_type == "after":
199
for b in ['input', 'forward', 'output']:
200
items.append('ufw-after-%s' % b)
201
items6.append('ufw6-after-%s' % b)
202
elif rules_type == "logging":
203
for b in ['input', 'forward', 'output']:
204
items.append('ufw-before-logging-%s' % b)
205
items6.append('ufw6-before-logging-%s' % b)
206
items.append('ufw-user-logging-%s' % b)
207
items6.append('ufw6-user-logging-%s' % b)
208
items.append('ufw-after-logging-%s' % b)
209
items6.append('ufw6-after-logging-%s' % b)
210
items.append('ufw-logging-allow')
211
items.append('ufw-logging-deny')
212
items6.append('ufw6-logging-allow')
213
items6.append('ufw6-logging-deny')
215
out = "IPV4 (%s):\n" % (rules_type)
218
(t, c) = i.split(':')
220
(rc, tmp) = cmd([self.iptables] + args + [c, '-t', t])
222
(rc, tmp) = cmd([self.iptables] + args + [i])
224
if rules_type != "raw":
229
if rules_type == "raw" or self.use_ipv6():
233
(t, c) = i.split(':')
235
(rc, tmp) = cmd([self.iptables] + args + [c, '-t', t])
237
(rc, tmp) = cmd([self.ip6tables] + args + [i])
239
if rules_type != "raw":
246
def get_status(self, verbose=False, show_count=False):
247
'''Show ufw managed rules'''
250
out = "> " + _("Checking iptables\n")
252
out += "> " + _("Checking ip6tables\n")
255
err_msg = _("problem running")
256
for direction in ["input", "output"]:
257
# Is the firewall loaded at all?
258
(rc, out) = cmd([self.iptables, '-L', \
259
'ufw-user-%s' % (direction), '-n'])
261
return _("Status: inactive")
263
raise UFWError(err_msg + " iptables: %s\n" % (out))
266
(rc, out6) = cmd([self.ip6tables, '-L', \
267
'ufw6-user-%s' % (direction), '-n'])
269
raise UFWError(err_msg + " ip6tables")
273
rules = self.rules + self.rules6
281
if not verbose and (r.dapp != "" or r.sapp != ""):
283
tupl = r.get_app_tuple()
285
if tupl in app_rules:
286
debug("Skipping found tuple '%s'" % (tupl))
289
app_rules[tupl] = True
291
for loc in [ 'dst', 'src' ]:
298
if not verbose and r.dapp != "":
300
if r.v6 and tmp == "::/0":
306
if not verbose and r.sapp != "":
308
if r.v6 and tmp == "::/0":
313
if tmp != "0.0.0.0/0" and tmp != "::/0":
317
if location[loc] == "":
320
location[loc] += " " + port
322
if show_proto and r.protocol != "any":
323
location[loc] += "/" + r.protocol
326
if loc == "dst" and r.dapp != "":
327
location[loc] += " (%s" % (r.dapp)
328
if r.v6 and tmp == "::/0":
329
location[loc] += " (v6)"
331
if loc == "src" and r.sapp != "":
332
location[loc] += " (%s" % (r.sapp)
333
if r.v6 and tmp == "::/0":
334
location[loc] += " (v6)"
338
if tmp == "0.0.0.0/0" or tmp == "::/0":
339
location[loc] = "Anywhere"
341
# Show the protocol if Anywhere to Anwhere, have
342
# protocol and source and dest ports are any
343
if show_proto and r.protocol != "any" and \
344
r.dst == r.src and r.dport == r.sport:
345
location[loc] += "/" + r.protocol
348
location[loc] += " (v6)"
350
# Show the protocol if have protocol, and source
351
# and dest ports are any
352
if show_proto and r.protocol != "any" and \
354
location[loc] += "/" + r.protocol
355
if loc == 'dst' and r.interface_in != "":
356
location[loc] += " on %s" % (r.interface_in)
357
if loc == 'src' and r.interface_out != "":
358
location[loc] += " on %s" % (r.interface_out)
362
if r.logtype or r.direction.lower() == "out":
364
attribs.append(r.logtype.lower())
365
if show_count and r.direction == "out":
366
attribs.append(r.direction)
368
attrib_str = " (%s)" % (', '.join(attribs))
370
# now construct the rule output string
372
tmp_str += "[%2d] " % (count)
374
dir_str = r.direction.upper()
375
if r.direction == "in" and not verbose and not show_count:
377
tmp_str += "%-26s %-12s%s%s\n" % (location['dst'], \
378
" ".join([r.action.upper(), \
380
location['src'], attrib_str)
382
# Show the list in the order given if a numbered list, otherwise
383
# split incoming and outgoing rules
387
if r.direction == "out":
393
if s != "" or str_out != "":
399
str_action = _("Action")
400
rules_header_fmt = "%-26s %-12s%s\n"
402
rules_header = rules_header_fmt % (str_to, str_action, str_from)
405
rules_header += rules_header_fmt % \
406
("-" * len(str_to), \
407
"-" * len(str_action), \
410
full_str += rules_header
414
if s != "" and str_out != "":
422
(level, logging_str) = self.get_loglevel()
423
policy_str = _("Default: %(in)s (incoming), %(out)s (outgoing)") \
424
% ({'in': self._get_default_policy(), \
425
'out': self._get_default_policy("output")})
426
app_policy_str = self.get_default_application_policy()
427
return _("Status: active\n%(log)s\n%(pol)s\n%(app)s%(status)s") % \
428
({'log': logging_str, 'pol': policy_str, \
429
'app': app_policy_str, 'status': s})
431
return _("Status: active%s") % (s)
433
def stop_firewall(self):
434
'''Stop the firewall'''
436
msg("> " + _("running ufw-init"))
438
(rc, out) = cmd([self.files['init'], 'force-stop'])
440
err_msg = _("problem running ufw-init\n%s" % out)
441
raise UFWError(err_msg)
443
def start_firewall(self):
444
'''Start the firewall'''
446
msg("> " + _("running ufw-init"))
448
(rc, out) = cmd([self.files['init'], 'start'])
450
err_msg = _("problem running ufw-init\n%s" % out)
451
raise UFWError(err_msg)
453
if 'loglevel' not in self.defaults or \
454
self.defaults['loglevel'] not in list(self.loglevels.keys()):
455
# Add the loglevel if not valid
457
self.set_loglevel("low")
459
err_msg = _("Could not set LOGLEVEL")
460
raise UFWError(err_msg)
463
self.update_logging(self.defaults['loglevel'])
465
err_msg = _("Could not load logging rules")
466
raise UFWError(err_msg)
468
def _need_reload(self, v6):
469
'''Check if all chains exist'''
479
for chain in [ 'input', 'output', 'forward', 'limit', 'limit-accept' ]:
480
if chain == "limit" or chain == "limit-accept":
481
if v6 and not self.caps['limit']['6']:
483
elif not v6 and not self.caps['limit']['4']:
486
(rc, out) = cmd([exe, '-n', '-L', prefix + "-user-" + chain])
488
debug("_need_reload: forcing reload")
493
def _reload_user_rules(self):
494
'''Reload firewall rules file'''
495
err_msg = _("problem running")
497
msg("> | iptables-restore")
499
msg("> | ip6tables-restore")
500
elif self.is_enabled():
501
# first flush the user logging chains
503
for c in self.chains['user']:
504
self._chain_cmd(c, ['-F', c])
505
self._chain_cmd(c, ['-Z', c])
507
raise UFWError(err_msg)
509
# then restore the system rules
510
(rc, out) = cmd_pipe(['cat', self.files['rules']], \
511
[self.iptables_restore, '-n'])
513
raise UFWError(err_msg + " iptables")
516
(rc, out) = cmd_pipe(['cat', self.files['rules6']], \
517
[self.ip6tables_restore, '-n'])
519
raise UFWError(err_msg + " ip6tables")
521
def _get_rules_from_formatted(self, frule, prefix, suffix):
522
'''Return list of iptables rules appropriate for sending'''
525
# adjust reject and protocol 'all'
526
pat_proto = re.compile(r'-p all ')
527
pat_port = re.compile(r'port ')
528
pat_reject = re.compile(r'-j (REJECT(_log(-all)?)?)')
529
if pat_proto.search(frule):
530
if pat_port.search(frule):
531
if pat_reject.search(frule):
532
snippets.append(pat_proto.sub('-p tcp ', \
533
pat_reject.sub(r'-j \1 --reject-with tcp-reset', \
536
snippets.append(pat_proto.sub('-p tcp ', frule))
537
snippets.append(pat_proto.sub('-p udp ', frule))
539
snippets.append(pat_proto.sub('', frule))
541
snippets.append(frule)
543
# adjust for logging rules
544
pat_log = re.compile(r'(.*)-j ([A-Z]+)_log(-all)?(.*)')
545
pat_logall = re.compile(r'-j [A-Z]+_log-all')
546
pat_chain = re.compile(r'(-A|-D) ([a-zA-Z0-9\-]+)')
547
limit_args = '-m limit --limit 3/min --limit-burst 10'
548
for i, s in enumerate(snippets):
549
if pat_log.search(s):
550
policy = pat_log.sub(r'\2', s).strip()
551
if policy.lower() == "accept":
553
elif policy.lower() == "limit":
558
lstr = '%s -j LOG --log-prefix "[UFW %s] "' % (limit_args, \
560
if not pat_logall.search(s):
561
lstr = '-m state --state NEW ' + lstr
562
snippets[i] = pat_log.sub(r'\1-j \2\4', s)
563
snippets.insert(i, pat_log.sub(r'\1-j ' + prefix + \
564
'-user-logging-' + suffix, s))
565
snippets.insert(i, pat_chain.sub(r'\1 ' + prefix + \
566
'-user-logging-' + suffix,
567
pat_log.sub(r'\1-j RETURN', \
569
snippets.insert(i, pat_chain.sub(r'\1 ' + prefix + \
570
'-user-logging-' + suffix,
571
pat_log.sub(r'\1' + lstr, s)))
574
pat_limit = re.compile(r' -j LIMIT')
575
for i, s in enumerate(snippets):
576
if pat_limit.search(s):
577
tmp1 = pat_limit.sub(' -m state --state NEW -m recent --set', \
579
tmp2 = pat_limit.sub(' -m state --state NEW -m recent' + \
580
' --update --seconds 30 --hitcount 6' + \
581
' -j ' + prefix + '-user-limit', s)
582
tmp3 = pat_limit.sub(' -j ' + prefix + '-user-limit-accept', s)
584
snippets.insert(i, tmp2)
585
snippets.insert(i, tmp1)
589
def _get_lists_from_formatted(self, frule, prefix, suffix):
590
'''Return list of iptables rules appropriate for sending as arguments
594
str_snippets = self._get_rules_from_formatted(frule, prefix, suffix)
596
# split the string such that the log prefix can contain spaces
597
pat = re.compile(r'(.*) --log-prefix (".* ")(.*)')
598
for i, s in enumerate(str_snippets):
599
snippets.append(pat.sub(r'\1', s).split())
601
snippets[i].append("--log-prefix")
602
snippets[i].append(pat.sub(r'\2', s).replace('"', ''))
603
snippets[i] += pat.sub(r'\3', s).split()
607
def _read_rules(self):
608
'''Read in rules that were added by ufw'''
609
rfns = [self.files['rules']]
611
rfns.append(self.files['rules6'])
615
orig = ufw.util.open_file_read(f)
617
err_msg = _("Couldn't open '%s' for reading") % (f)
618
raise UFWError(err_msg)
620
pat_tuple = re.compile(r'^### tuple ###\s*')
622
if pat_tuple.match(line):
623
tupl = pat_tuple.sub('', line)
624
tmp = re.split(r'\s+', tupl.strip())
625
if len(tmp) < 6 or len(tmp) > 9:
626
wmsg = _("Skipping malformed tuple (bad length): %s") \
631
# set direction to "in" to support upgrades
632
# from old format, which only had 6 or 8 fields
635
if len(tmp) == 7 or len(tmp) == 9:
637
(dtype, interface) = tmp[-1].split('_')
642
rule = UFWRule(tmp[0], tmp[1], tmp[2], tmp[3],
643
tmp[4], tmp[5], dtype)
645
rule = UFWRule(tmp[0], tmp[1], tmp[2], tmp[3],
646
tmp[4], tmp[5], dtype)
647
# Removed leading [sd]app_ and unescape spaces
648
pat_space = re.compile('%20')
650
rule.dapp = pat_space.sub(' ', tmp[6])
652
rule.sapp = pat_space.sub(' ', tmp[7])
654
rule.set_interface(dtype, interface)
657
warn_msg = _("Skipping malformed tuple: %s") % \
661
if f == self.files['rules6']:
663
self.rules6.append(rule)
666
self.rules.append(rule)
670
def _write_rules(self, v6=False):
671
'''Write out new rules to file to user chain file'''
672
rules_file = self.files['rules']
674
rules_file = self.files['rules6']
676
# Perform this here so we can present a nice error to the user rather
678
if not os.access(rules_file, os.W_OK):
679
err_msg = _("'%s' is not writable" % (rules_file))
680
raise UFWError(err_msg)
683
fns = ufw.util.open_files(rules_file)
690
chain_prefix = "ufw6"
694
fd = sys.stdout.fileno()
699
ufw.util.write_to_file(fd, "*filter\n")
700
ufw.util.write_to_file(fd, ":" + chain_prefix + "-user-input - [0:0]\n")
701
ufw.util.write_to_file(fd, ":" + chain_prefix + \
702
"-user-output - [0:0]\n")
703
ufw.util.write_to_file(fd, ":" + chain_prefix + \
704
"-user-forward - [0:0]\n")
706
ufw.util.write_to_file(fd, ":" + chain_prefix + \
707
"-before-logging-input - [0:0]\n")
708
ufw.util.write_to_file(fd, ":" + chain_prefix + \
709
"-before-logging-output - [0:0]\n")
710
ufw.util.write_to_file(fd, ":" + chain_prefix + \
711
"-before-logging-forward - [0:0]\n")
712
ufw.util.write_to_file(fd, ":" + chain_prefix + \
713
"-user-logging-input - [0:0]\n")
714
ufw.util.write_to_file(fd, ":" + chain_prefix + \
715
"-user-logging-output - [0:0]\n")
716
ufw.util.write_to_file(fd, ":" + chain_prefix + \
717
"-user-logging-forward - [0:0]\n")
718
ufw.util.write_to_file(fd, ":" + chain_prefix + \
719
"-after-logging-input - [0:0]\n")
720
ufw.util.write_to_file(fd, ":" + chain_prefix + \
721
"-after-logging-output - [0:0]\n")
722
ufw.util.write_to_file(fd, ":" + chain_prefix + \
723
"-after-logging-forward - [0:0]\n")
724
ufw.util.write_to_file(fd, ":" + chain_prefix + \
725
"-logging-deny - [0:0]\n")
726
ufw.util.write_to_file(fd, ":" + chain_prefix + \
727
"-logging-allow - [0:0]\n")
729
# Rate limiting is runtime supported
730
if (chain_prefix == "ufw" and self.caps['limit']['4']) or \
731
(chain_prefix == "ufw6" and self.caps['limit']['6']):
732
ufw.util.write_to_file(fd, ":" + chain_prefix + \
733
"-user-limit - [0:0]\n")
734
ufw.util.write_to_file(fd, ":" + chain_prefix + \
735
"-user-limit-accept - [0:0]\n")
737
ufw.util.write_to_file(fd, "### RULES ###\n")
743
action += "_" + r.logtype
745
if r.dapp == "" and r.sapp == "":
746
tstr = "\n### tuple ### %s %s %s %s %s %s %s" % \
747
(action, r.protocol, r.dport, r.dst, r.sport, r.src, \
749
if r.interface_in != "":
750
tstr += "_%s" % (r.interface_in)
751
if r.interface_out != "":
752
tstr += "_%s" % (r.interface_out)
753
ufw.util.write_to_file(fd, tstr + "\n")
755
pat_space = re.compile(' ')
758
dapp = pat_space.sub('%20', r.dapp)
761
sapp = pat_space.sub('%20', r.sapp)
762
tstr = "\n### tuple ### %s %s %s %s %s %s %s %s %s" % \
763
(action, r.protocol, r.dport, r.dst, r.sport, r.src, \
764
dapp, sapp, r.direction)
766
if r.interface_in != "":
767
tstr += "_%s" % (r.interface_in)
768
if r.interface_out != "":
769
tstr += "_%s" % (r.interface_out)
770
ufw.util.write_to_file(fd, tstr + "\n")
772
chain_suffix = "input"
773
if r.direction == "out":
774
chain_suffix = "output"
775
chain = "%s-user-%s" % (chain_prefix, chain_suffix)
776
rule_str = "-A %s %s\n" % (chain, r.format_rule())
778
for s in self._get_rules_from_formatted(rule_str, chain_prefix, \
780
ufw.util.write_to_file(fd, s)
783
ufw.util.write_to_file(fd, "\n### END RULES ###\n")
785
# Add logging rules, skipping any delete ('-D') rules
786
ufw.util.write_to_file(fd, "\n### LOGGING ###\n")
788
lrules_t = self._get_logging_rules(self.defaults['loglevel'])
791
for c, r, q in lrules_t:
792
if len(r) > 0 and r[0] == '-D':
794
if c.startswith(chain_prefix + "-"):
795
ufw.util.write_to_file(fd,
796
" ".join(r).replace('[', '"[').replace('] ', '] "') + \
798
ufw.util.write_to_file(fd, "### END LOGGING ###\n")
800
# Rate limiting is runtime supported
801
if (chain_prefix == "ufw" and self.caps['limit']['4']) or \
802
(chain_prefix == "ufw6" and self.caps['limit']['6']):
803
ufw.util.write_to_file(fd, "\n### RATE LIMITING ###\n")
804
if self.defaults['loglevel'] != "off":
805
ufw.util.write_to_file(fd, "-A " + \
806
chain_prefix + "-user-limit " + \
807
" ".join(self.ufw_user_limit_log) + \
808
" \"" + self.ufw_user_limit_log_text + " \"\n")
809
ufw.util.write_to_file(fd, "-A " + chain_prefix + \
810
"-user-limit -j REJECT\n")
811
ufw.util.write_to_file(fd, "-A " + chain_prefix + \
812
"-user-limit-accept -j ACCEPT\n")
813
ufw.util.write_to_file(fd, "### END RATE LIMITING ###\n")
815
ufw.util.write_to_file(fd, "COMMIT\n")
819
ufw.util.close_files(fns, False)
821
ufw.util.close_files(fns)
825
def set_rule(self, rule, allow_reload=True):
826
'''Updates firewall with rule by:
827
* appending the rule to the chain if new rule and firewall enabled
828
* deleting the rule from the chain if found and firewall enabled
829
* inserting the rule if possible and firewall enabled
830
* updating user rules file
831
* reloading the user rules file if rule is modified
836
if not self.use_ipv6():
837
err_msg = _("Adding IPv6 rule failed: IPv6 not enabled")
838
raise UFWError(err_msg)
839
if rule.action == 'limit' and not self.caps['limit']['6']:
840
# Rate limiting is runtime supported
841
return _("Skipping unsupported IPv6 '%s' rule") % (rule.action)
843
if rule.action == 'limit' and not self.caps['limit']['4']:
844
# Rate limiting is runtime supported
845
return _("Skipping unsupported IPv4 '%s' rule") % (rule.action)
847
if rule.multi and rule.protocol != "udp" and rule.protocol != "tcp":
848
err_msg = _("Must specify 'tcp' or 'udp' with multiple ports")
849
raise UFWError(err_msg)
856
position = rule.position
858
if self.iptables_version < "1.4" and (rule.dapp != "" or \
860
return _("Skipping IPv6 application rule. Need at least iptables 1.4")
863
# bail if we have a bad position
864
if position < 0 or position > len(rules):
865
err_msg = _("Invalid position '%d'") % (position)
866
raise UFWError(err_msg)
868
if position > 0 and rule.remove:
869
err_msg = _("Cannot specify insert and delete")
870
raise UFWError(err_msg)
871
if position > len(rules):
872
err_msg = _("Cannot insert rule at position '%d'") % position
873
raise UFWError(err_msg)
875
# First construct the new rules list
884
last = ('', '', '', '')
891
current = (r.dst, r.src, r.dapp, r.sapp)
892
if count == position:
893
# insert the rule if:
894
# 1. the last rule was not an application rule
895
# 2. the current rule is not an application rule
896
# 3. the last application rule is different than the current
897
# while the new rule is different than the current one
898
if (last[2] == '' and last[3] == '' and count > 1) or \
899
(current[2] == '' and current[3] == '') or \
902
newrules.append(rule.dup_rule())
903
last = ('', '', '', '')
909
ret = UFWRule.match(r, rule)
913
if ret == 0 and not found and not inserted:
914
# If find the rule, add it if it's not to be removed, otherwise
918
newrules.append(rule.dup_rule())
919
elif ret < 0 and not rule.remove and not inserted:
920
# If only the action is different, replace the rule if it's not
924
newrules.append(rule.dup_rule())
930
rstr = _("Skipping inserting existing rule")
935
# Add rule to the end if it was not already added.
936
if not found and not rule.remove:
937
newrules.append(rule.dup_rule())
939
# Don't process non-existing or unchanged pre-exisiting rules
940
if not found and rule.remove and not self.dryrun:
941
rstr = _("Could not delete non-existent rule")
945
elif found and not rule.remove and not modified:
946
rstr = _("Skipping adding existing rule")
952
self.rules6 = newrules
954
self.rules = newrules
956
# Update the user rules file
958
self._write_rules(rule.v6)
962
err_msg = _("Couldn't update rules file")
965
# We wrote out the rules, so set reasonable string. We will change
966
# this below when operating on the live firewall.
967
rstr = _("Rules updated")
969
rstr = _("Rules updated (v6)")
971
# Operate on the chains
972
if self.is_enabled() and not self.dryrun:
974
if modified or self._need_reload(rule.v6) or inserted:
977
rstr += _("Rule inserted")
979
rstr += _("Rule updated")
985
self._reload_user_rules()
989
rstr += _(" (skipped reloading firewall)")
990
elif found and rule.remove:
992
rstr = _("Rule deleted")
993
elif not found and not modified and not rule.remove:
995
rstr = _("Rule added")
1001
exe = self.ip6tables
1002
chain_prefix = "ufw6"
1004
chain_suffix = "input"
1005
if rule.direction == "out":
1006
chain_suffix = "output"
1007
chain = "%s-user-%s" % (chain_prefix, chain_suffix)
1009
# Is the firewall running?
1010
err_msg = _("Could not update running firewall")
1011
(rc, out) = cmd([exe, '-L', chain, '-n'])
1013
raise UFWError(err_msg)
1015
rule_str = "%s %s %s" % (flag, chain, rule.format_rule())
1016
pat_log = re.compile(r'(-A +)(ufw6?-user-[a-z\-]+)(.*)')
1017
for s in self._get_lists_from_formatted(rule_str, \
1020
(rc, out) = cmd([exe] + s)
1022
msg(out, sys.stderr)
1025
# delete any lingering RETURN rules (needed for upgrades)
1026
if flag == "-A" and pat_log.search(" ".join(s)):
1027
c = pat_log.sub(r'\2', " ".join(s))
1028
(rc, out) = cmd([exe, '-D', c, '-j', 'RETURN'])
1030
debug("FAILOK: -D %s -j RETURN" % (c))
1034
def get_app_rules_from_system(self, template, v6):
1035
'''Return a list of UFWRules from the system based on template rule'''
1044
norm = template.dup_rule()
1047
tupl = norm.get_app_tuple()
1052
tmp_tuple = tmp.get_app_tuple()
1053
if tmp_tuple == tupl:
1054
app_rules.append(tmp)
1058
def _chain_cmd(self, chain, args, fail_ok=False):
1059
'''Perform command on chain'''
1061
if chain.startswith("ufw6"):
1062
exe = self.ip6tables
1063
(rc, out) = cmd([exe] + args)
1065
err_msg = _("Could not perform '%s'") % (args)
1067
debug("FAILOK: " + err_msg)
1069
raise UFWError(err_msg)
1071
def update_logging(self, level):
1072
'''Update loglevel of running firewall'''
1078
rules_t = self._get_logging_rules(level)
1082
# Update the user rules file
1084
self._write_rules(v6=False)
1085
self._write_rules(v6=True)
1089
err_msg = _("Couldn't update rules file for logging")
1092
# Don't update the running firewall if not enabled
1093
if not self.is_enabled():
1096
# make sure all the chains are here, it's redundant but helps make
1097
# sure the chains are in a consistent state
1098
err_msg = _("Could not update running firewall")
1099
for c in self.chains['before'] + self.chains['user'] + \
1100
self.chains['after'] + self.chains['misc']:
1102
self._chain_cmd(c, ['-L', c, '-n'])
1104
raise UFWError(err_msg)
1106
# Flush all the logging chains except 'user'
1108
for c in self.chains['before'] + self.chains['after'] + \
1109
self.chains['misc']:
1110
self._chain_cmd(c, ['-F', c])
1111
self._chain_cmd(c, ['-Z', c])
1113
raise UFWError(err_msg)
1115
# Add logging rules to running firewall
1116
for c, r, q in rules_t:
1118
if len(r) > 0 and r[0] == '-D':
1121
if q == 'delete_first' and len(r) > 1:
1122
self._chain_cmd(c, ['-D'] + r[1:], fail_ok=True)
1123
self._chain_cmd(c, r, fail_ok)
1125
raise UFWError(err_msg)
1127
# Rate limiting is runtime supported
1128
# Always delete these and re-add them so that we don't have extras
1129
for chain in ['ufw-user-limit', 'ufw6-user-limit']:
1130
if (self.caps['limit']['4'] and chain == 'ufw-user-limit') or \
1131
(self.caps['limit']['6'] and chain == 'ufw6-user-limit'):
1132
self._chain_cmd(chain, ['-D', chain] + \
1133
self.ufw_user_limit_log + \
1134
[self.ufw_user_limit_log_text + " "], \
1136
if self.defaults["loglevel"] != "off":
1137
self._chain_cmd(chain, ['-I', chain] + \
1138
self.ufw_user_limit_log + \
1139
[self.ufw_user_limit_log_text + " "], \
1142
def _get_logging_rules(self, level):
1143
'''Get rules for specified logging level'''
1146
if level not in list(self.loglevels.keys()):
1147
err_msg = _("Invalid log level '%s'") % (level)
1148
raise UFWError(err_msg)
1151
# when off, insert a RETURN rule at the top of user rules, thus
1152
# preserving the rules
1153
for c in self.chains['user']:
1154
rules_t.append([c, ['-I', c, '-j', 'RETURN'], 'delete_first'])
1157
# when on, remove the RETURN rule at the top of user rules, thus
1158
# honoring the log rules
1159
for c in self.chains['user']:
1160
rules_t.append([c, ['-D', c, '-j', 'RETURN'], ''])
1162
limit_args = ['-m', 'limit', '--limit', '3/min', '--limit-burst', '10']
1164
# log levels of low and higher log blocked packets
1165
if self.loglevels[level] >= self.loglevels["low"]:
1166
# Setup the policy violation logging chains
1168
# log levels under high use limit
1169
if self.loglevels[level] < self.loglevels["high"]:
1171
for c in self.chains['after']:
1172
for t in ['input', 'output', 'forward']:
1174
if self._get_default_policy(t) == "reject" or \
1175
self._get_default_policy(t) == "deny":
1176
prefix = "[UFW BLOCK] "
1177
rules_t.append([c, ['-A', c, '-j', 'LOG', \
1178
'--log-prefix', prefix] +
1180
elif self.loglevels[level] >= self.loglevels["medium"]:
1181
prefix = "[UFW ALLOW] "
1182
rules_t.append([c, ['-A', c, '-j', 'LOG', \
1183
'--log-prefix', prefix] + \
1186
# Setup the miscellaneous logging chains
1188
# log levels under high use limit
1189
if self.loglevels[level] < self.loglevels["high"]:
1192
for c in self.chains['misc']:
1193
if c.endswith("allow"):
1194
prefix = "[UFW ALLOW] "
1195
elif c.endswith("deny"):
1196
prefix = "[UFW BLOCK] "
1197
if self.loglevels[level] < self.loglevels["medium"]:
1198
# only log INVALID in medium and higher
1199
rules_t.append([c, ['-I', c, '-m', 'state', \
1200
'--state', 'INVALID', \
1201
'-j', 'RETURN'] + largs, ''])
1203
rules_t.append([c, ['-A', c, '-m', 'state', \
1204
'--state', 'INVALID', \
1207
"[UFW AUDIT INVALID] "] + \
1209
rules_t.append([c, ['-A', c, '-j', 'LOG', \
1210
'--log-prefix', prefix] + largs, ''])
1212
# Setup the audit logging chains
1213
if self.loglevels[level] >= self.loglevels["medium"]:
1214
# loglevel full logs all packets without limit
1217
# loglevel high logs all packets with limit
1218
if self.loglevels[level] < self.loglevels["full"]:
1221
# loglevel medium logs all new packets with limit
1222
if self.loglevels[level] < self.loglevels["high"]:
1223
largs = ['-m', 'state', '--state', 'NEW'] + limit_args
1225
prefix = "[UFW AUDIT] "
1226
for c in self.chains['before']:
1227
rules_t.append([c, ['-I', c, '-j', 'LOG', \
1228
'--log-prefix', prefix] + largs, ''])
1233
'''Reset the firewall'''
1235
# First make sure we have all the original files
1237
for i in self.files:
1238
if not self.files[i].endswith('.rules'):
1240
allfiles.append(self.files[i])
1241
fn = os.path.join(ufw.common.share_dir, "iptables", \
1242
os.path.basename(self.files[i]))
1243
if not os.path.isfile(fn):
1244
err_msg = _("Could not find '%s'. Aborting") % (fn)
1245
raise UFWError(err_msg)
1247
ext = time.strftime("%Y%m%d_%H%M%S")
1249
# This implementation will intentionally traceback if someone tries to
1250
# do something to take advantage of the race conditions here.
1252
# Don't do anything if the files already exist
1254
fn = "%s.%s" % (i, ext)
1255
if os.path.exists(fn):
1256
err_msg = _("'%s' already exists. Aborting") % (fn)
1257
raise UFWError(err_msg)
1259
# Move the old to the new
1261
fn = "%s.%s" % (i, ext)
1262
res += _("Backing up '%(old)s' to '%(new)s'\n") % (\
1263
{'old': os.path.basename(i), 'new': fn})
1266
# Copy files into place
1268
old = "%s.%s" % (i, ext)
1269
shutil.copy(os.path.join(ufw.common.share_dir, "iptables", \
1270
os.path.basename(i)), \
1272
shutil.copymode(old, i)
1275
statinfo = os.stat(i)
1276
mode = statinfo[stat.ST_MODE]
1278
warn_msg = _("Couldn't stat '%s'") % (i)
1282
if mode & stat.S_IWOTH:
1283
res += _("WARN: '%s' is world writable") % (i)
1284
elif mode & stat.S_IROTH:
1285
res += _("WARN: '%s' is world readable") % (i)