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
# Initialize the capabilities database
166
args = ['-n', '-v', '-x', '-L']
170
if rules_type == "raw":
172
items = ['filter', 'nat', 'mangle', 'raw']
173
items6 = ['filter', 'mangle', 'raw']
174
elif rules_type == "builtins":
175
for c in ['INPUT', 'FORWARD', 'OUTPUT']:
176
items.append('filter:%s' % c)
177
items6.append('filter:%s' % c)
178
for c in ['PREROUTING', 'INPUT', 'FORWARD', 'OUTPUT', \
180
items.append('mangle:%s' % c)
181
items6.append('mangle:%s' % c)
182
for c in ['PREROUTING', 'OUTPUT']:
183
items.append('raw:%s' % c)
184
items6.append('raw:%s' % c)
185
for c in ['PREROUTING', 'POSTROUTING', 'OUTPUT']:
186
items.append('nat:%s' % c)
187
elif rules_type == "before":
188
for b in ['input', 'forward', 'output']:
189
items.append('ufw-before-%s' % b)
190
items6.append('ufw6-before-%s' % b)
191
elif rules_type == "user":
192
for b in ['input', 'forward', 'output']:
193
items.append('ufw-user-%s' % b)
194
items6.append('ufw6-user-%s' % b)
195
if self.caps['limit']['4']:
196
items.append('ufw-user-limit-accept')
197
items.append('ufw-user-limit')
198
if self.caps['limit']['6']:
199
items6.append('ufw6-user-limit-accept')
200
items6.append('ufw6-user-limit')
201
elif rules_type == "after":
202
for b in ['input', 'forward', 'output']:
203
items.append('ufw-after-%s' % b)
204
items6.append('ufw6-after-%s' % b)
205
elif rules_type == "logging":
206
for b in ['input', 'forward', 'output']:
207
items.append('ufw-before-logging-%s' % b)
208
items6.append('ufw6-before-logging-%s' % b)
209
items.append('ufw-user-logging-%s' % b)
210
items6.append('ufw6-user-logging-%s' % b)
211
items.append('ufw-after-logging-%s' % b)
212
items6.append('ufw6-after-logging-%s' % b)
213
items.append('ufw-logging-allow')
214
items.append('ufw-logging-deny')
215
items6.append('ufw6-logging-allow')
216
items6.append('ufw6-logging-deny')
218
out = "IPV4 (%s):\n" % (rules_type)
221
(t, c) = i.split(':')
223
(rc, tmp) = cmd([self.iptables] + args + [c, '-t', t])
225
(rc, tmp) = cmd([self.iptables] + args + [i])
227
if rules_type != "raw":
232
if rules_type == "raw" or self.use_ipv6():
236
(t, c) = i.split(':')
238
(rc, tmp) = cmd([self.iptables] + args + [c, '-t', t])
240
(rc, tmp) = cmd([self.ip6tables] + args + [i])
242
if rules_type != "raw":
249
def get_status(self, verbose=False, show_count=False):
250
'''Show ufw managed rules'''
253
out = "> " + _("Checking iptables\n")
255
out += "> " + _("Checking ip6tables\n")
258
err_msg = _("problem running")
259
for direction in ["input", "output"]:
260
# Is the firewall loaded at all?
261
(rc, out) = cmd([self.iptables, '-L', \
262
'ufw-user-%s' % (direction), '-n'])
264
return _("Status: inactive")
266
raise UFWError(err_msg + " iptables: %s\n" % (out))
269
(rc, out6) = cmd([self.ip6tables, '-L', \
270
'ufw6-user-%s' % (direction), '-n'])
272
raise UFWError(err_msg + " ip6tables")
276
rules = self.rules + self.rules6
284
if not verbose and (r.dapp != "" or r.sapp != ""):
286
tupl = r.get_app_tuple()
288
if tupl in app_rules:
289
debug("Skipping found tuple '%s'" % (tupl))
292
app_rules[tupl] = True
294
for loc in [ 'dst', 'src' ]:
301
if not verbose and r.dapp != "":
303
if r.v6 and tmp == "::/0":
309
if not verbose and r.sapp != "":
311
if r.v6 and tmp == "::/0":
316
if tmp != "0.0.0.0/0" and tmp != "::/0":
320
if location[loc] == "":
323
location[loc] += " " + port
325
if show_proto and r.protocol != "any":
326
location[loc] += "/" + r.protocol
329
if loc == "dst" and r.dapp != "":
330
location[loc] += " (%s" % (r.dapp)
331
if r.v6 and tmp == "::/0":
332
location[loc] += " (v6)"
334
if loc == "src" and r.sapp != "":
335
location[loc] += " (%s" % (r.sapp)
336
if r.v6 and tmp == "::/0":
337
location[loc] += " (v6)"
341
if tmp == "0.0.0.0/0" or tmp == "::/0":
342
location[loc] = "Anywhere"
344
# Show the protocol if Anywhere to Anwhere, have
345
# protocol and source and dest ports are any
346
if show_proto and r.protocol != "any" and \
347
r.dst == r.src and r.dport == r.sport:
348
location[loc] += "/" + r.protocol
351
location[loc] += " (v6)"
353
# Show the protocol if have protocol, and source
354
# and dest ports are any
355
if show_proto and r.protocol != "any" and \
357
location[loc] += "/" + r.protocol
358
if loc == 'dst' and r.interface_in != "":
359
location[loc] += " on %s" % (r.interface_in)
360
if loc == 'src' and r.interface_out != "":
361
location[loc] += " on %s" % (r.interface_out)
365
if r.logtype or r.direction.lower() == "out":
367
attribs.append(r.logtype.lower())
368
if show_count and r.direction == "out":
369
attribs.append(r.direction)
371
attrib_str = " (%s)" % (', '.join(attribs))
373
# now construct the rule output string
375
tmp_str += "[%2d] " % (count)
377
dir_str = r.direction.upper()
378
if r.direction == "in" and not verbose and not show_count:
380
tmp_str += "%-26s %-12s%s%s\n" % (location['dst'], \
381
" ".join([r.action.upper(), \
383
location['src'], attrib_str)
385
# Show the list in the order given if a numbered list, otherwise
386
# split incoming and outgoing rules
390
if r.direction == "out":
396
if s != "" or str_out != "":
402
str_action = _("Action")
403
rules_header_fmt = "%-26s %-12s%s\n"
405
rules_header = rules_header_fmt % (str_to, str_action, str_from)
408
rules_header += rules_header_fmt % \
409
("-" * len(str_to), \
410
"-" * len(str_action), \
413
full_str += rules_header
417
if s != "" and str_out != "":
425
(level, logging_str) = self.get_loglevel()
426
policy_str = _("Default: %(in)s (incoming), %(out)s (outgoing)") \
427
% ({'in': self._get_default_policy(), \
428
'out': self._get_default_policy("output")})
429
app_policy_str = self.get_default_application_policy()
430
return _("Status: active\n%(log)s\n%(pol)s\n%(app)s%(status)s") % \
431
({'log': logging_str, 'pol': policy_str, \
432
'app': app_policy_str, 'status': s})
434
return _("Status: active%s") % (s)
436
def stop_firewall(self):
437
'''Stop the firewall'''
439
msg("> " + _("running ufw-init"))
441
(rc, out) = cmd([self.files['init'], 'force-stop'])
443
err_msg = _("problem running ufw-init\n%s" % out)
444
raise UFWError(err_msg)
446
def start_firewall(self):
447
'''Start the firewall'''
449
msg("> " + _("running ufw-init"))
451
(rc, out) = cmd([self.files['init'], 'start'])
453
err_msg = _("problem running ufw-init\n%s" % out)
454
raise UFWError(err_msg)
456
if 'loglevel' not in self.defaults or \
457
self.defaults['loglevel'] not in list(self.loglevels.keys()):
458
# Add the loglevel if not valid
460
self.set_loglevel("low")
462
err_msg = _("Could not set LOGLEVEL")
463
raise UFWError(err_msg)
466
self.update_logging(self.defaults['loglevel'])
468
err_msg = _("Could not load logging rules")
469
raise UFWError(err_msg)
471
def _need_reload(self, v6):
472
'''Check if all chains exist'''
476
# Initialize the capabilities database
485
for chain in [ 'input', 'output', 'forward', 'limit', 'limit-accept' ]:
486
if chain == "limit" or chain == "limit-accept":
487
if v6 and not self.caps['limit']['6']:
489
elif not v6 and not self.caps['limit']['4']:
492
(rc, out) = cmd([exe, '-n', '-L', prefix + "-user-" + chain])
494
debug("_need_reload: forcing reload")
499
def _reload_user_rules(self):
500
'''Reload firewall rules file'''
501
err_msg = _("problem running")
503
msg("> | iptables-restore")
505
msg("> | ip6tables-restore")
506
elif self.is_enabled():
507
# first flush the user logging chains
509
for c in self.chains['user']:
510
self._chain_cmd(c, ['-F', c])
511
self._chain_cmd(c, ['-Z', c])
513
raise UFWError(err_msg)
515
# then restore the system rules
516
(rc, out) = cmd_pipe(['cat', self.files['rules']], \
517
[self.iptables_restore, '-n'])
519
raise UFWError(err_msg + " iptables")
522
(rc, out) = cmd_pipe(['cat', self.files['rules6']], \
523
[self.ip6tables_restore, '-n'])
525
raise UFWError(err_msg + " ip6tables")
527
def _get_rules_from_formatted(self, frule, prefix, suffix):
528
'''Return list of iptables rules appropriate for sending'''
531
# adjust reject and protocol 'all'
532
pat_proto = re.compile(r'-p all ')
533
pat_port = re.compile(r'port ')
534
pat_reject = re.compile(r'-j (REJECT(_log(-all)?)?)')
535
if pat_proto.search(frule):
536
if pat_port.search(frule):
537
if pat_reject.search(frule):
538
snippets.append(pat_proto.sub('-p tcp ', \
539
pat_reject.sub(r'-j \1 --reject-with tcp-reset', \
542
snippets.append(pat_proto.sub('-p tcp ', frule))
543
snippets.append(pat_proto.sub('-p udp ', frule))
545
snippets.append(pat_proto.sub('', frule))
547
snippets.append(frule)
549
# adjust for logging rules
550
pat_log = re.compile(r'(.*)-j ([A-Z]+)_log(-all)?(.*)')
551
pat_logall = re.compile(r'-j [A-Z]+_log-all')
552
pat_chain = re.compile(r'(-A|-D) ([a-zA-Z0-9\-]+)')
553
limit_args = '-m limit --limit 3/min --limit-burst 10'
554
for i, s in enumerate(snippets):
555
if pat_log.search(s):
556
policy = pat_log.sub(r'\2', s).strip()
557
if policy.lower() == "accept":
559
elif policy.lower() == "limit":
564
lstr = '%s -j LOG --log-prefix "[UFW %s] "' % (limit_args, \
566
if not pat_logall.search(s):
567
lstr = '-m state --state NEW ' + lstr
568
snippets[i] = pat_log.sub(r'\1-j \2\4', s)
569
snippets.insert(i, pat_log.sub(r'\1-j ' + prefix + \
570
'-user-logging-' + suffix, s))
571
snippets.insert(i, pat_chain.sub(r'\1 ' + prefix + \
572
'-user-logging-' + suffix,
573
pat_log.sub(r'\1-j RETURN', \
575
snippets.insert(i, pat_chain.sub(r'\1 ' + prefix + \
576
'-user-logging-' + suffix,
577
pat_log.sub(r'\1' + lstr, s)))
580
pat_limit = re.compile(r' -j LIMIT')
581
for i, s in enumerate(snippets):
582
if pat_limit.search(s):
583
tmp1 = pat_limit.sub(' -m state --state NEW -m recent --set', \
585
tmp2 = pat_limit.sub(' -m state --state NEW -m recent' + \
586
' --update --seconds 30 --hitcount 6' + \
587
' -j ' + prefix + '-user-limit', s)
588
tmp3 = pat_limit.sub(' -j ' + prefix + '-user-limit-accept', s)
590
snippets.insert(i, tmp2)
591
snippets.insert(i, tmp1)
595
def _get_lists_from_formatted(self, frule, prefix, suffix):
596
'''Return list of iptables rules appropriate for sending as arguments
600
str_snippets = self._get_rules_from_formatted(frule, prefix, suffix)
602
# split the string such that the log prefix can contain spaces
603
pat = re.compile(r'(.*) --log-prefix (".* ")(.*)')
604
for i, s in enumerate(str_snippets):
605
snippets.append(pat.sub(r'\1', s).split())
607
snippets[i].append("--log-prefix")
608
snippets[i].append(pat.sub(r'\2', s).replace('"', ''))
609
snippets[i] += pat.sub(r'\3', s).split()
613
def _read_rules(self):
614
'''Read in rules that were added by ufw'''
615
rfns = [self.files['rules']]
617
rfns.append(self.files['rules6'])
621
orig = ufw.util.open_file_read(f)
623
err_msg = _("Couldn't open '%s' for reading") % (f)
624
raise UFWError(err_msg)
626
pat_tuple = re.compile(r'^### tuple ###\s*')
628
if pat_tuple.match(line):
629
tupl = pat_tuple.sub('', line)
630
tmp = re.split(r'\s+', tupl.strip())
631
if len(tmp) < 6 or len(tmp) > 9:
632
wmsg = _("Skipping malformed tuple (bad length): %s") \
637
# set direction to "in" to support upgrades
638
# from old format, which only had 6 or 8 fields
641
if len(tmp) == 7 or len(tmp) == 9:
643
(dtype, interface) = tmp[-1].split('_')
648
rule = UFWRule(tmp[0], tmp[1], tmp[2], tmp[3],
649
tmp[4], tmp[5], dtype)
651
rule = UFWRule(tmp[0], tmp[1], tmp[2], tmp[3],
652
tmp[4], tmp[5], dtype)
653
# Removed leading [sd]app_ and unescape spaces
654
pat_space = re.compile('%20')
656
rule.dapp = pat_space.sub(' ', tmp[6])
658
rule.sapp = pat_space.sub(' ', tmp[7])
660
rule.set_interface(dtype, interface)
663
warn_msg = _("Skipping malformed tuple: %s") % \
667
if f == self.files['rules6']:
669
self.rules6.append(rule)
672
self.rules.append(rule)
676
def _write_rules(self, v6=False):
677
'''Write out new rules to file to user chain file'''
678
rules_file = self.files['rules']
680
rules_file = self.files['rules6']
682
# Perform this here so we can present a nice error to the user rather
684
if not os.access(rules_file, os.W_OK):
685
err_msg = _("'%s' is not writable" % (rules_file))
686
raise UFWError(err_msg)
689
fns = ufw.util.open_files(rules_file)
693
# Initialize the capabilities database
699
chain_prefix = "ufw6"
703
fd = sys.stdout.fileno()
708
ufw.util.write_to_file(fd, "*filter\n")
709
ufw.util.write_to_file(fd, ":" + chain_prefix + "-user-input - [0:0]\n")
710
ufw.util.write_to_file(fd, ":" + chain_prefix + \
711
"-user-output - [0:0]\n")
712
ufw.util.write_to_file(fd, ":" + chain_prefix + \
713
"-user-forward - [0:0]\n")
715
ufw.util.write_to_file(fd, ":" + chain_prefix + \
716
"-before-logging-input - [0:0]\n")
717
ufw.util.write_to_file(fd, ":" + chain_prefix + \
718
"-before-logging-output - [0:0]\n")
719
ufw.util.write_to_file(fd, ":" + chain_prefix + \
720
"-before-logging-forward - [0:0]\n")
721
ufw.util.write_to_file(fd, ":" + chain_prefix + \
722
"-user-logging-input - [0:0]\n")
723
ufw.util.write_to_file(fd, ":" + chain_prefix + \
724
"-user-logging-output - [0:0]\n")
725
ufw.util.write_to_file(fd, ":" + chain_prefix + \
726
"-user-logging-forward - [0:0]\n")
727
ufw.util.write_to_file(fd, ":" + chain_prefix + \
728
"-after-logging-input - [0:0]\n")
729
ufw.util.write_to_file(fd, ":" + chain_prefix + \
730
"-after-logging-output - [0:0]\n")
731
ufw.util.write_to_file(fd, ":" + chain_prefix + \
732
"-after-logging-forward - [0:0]\n")
733
ufw.util.write_to_file(fd, ":" + chain_prefix + \
734
"-logging-deny - [0:0]\n")
735
ufw.util.write_to_file(fd, ":" + chain_prefix + \
736
"-logging-allow - [0:0]\n")
738
# Rate limiting is runtime supported
739
if (chain_prefix == "ufw" and self.caps['limit']['4']) or \
740
(chain_prefix == "ufw6" and self.caps['limit']['6']):
741
ufw.util.write_to_file(fd, ":" + chain_prefix + \
742
"-user-limit - [0:0]\n")
743
ufw.util.write_to_file(fd, ":" + chain_prefix + \
744
"-user-limit-accept - [0:0]\n")
746
ufw.util.write_to_file(fd, "### RULES ###\n")
752
action += "_" + r.logtype
754
if r.dapp == "" and r.sapp == "":
755
tstr = "\n### tuple ### %s %s %s %s %s %s %s" % \
756
(action, r.protocol, r.dport, r.dst, r.sport, r.src, \
758
if r.interface_in != "":
759
tstr += "_%s" % (r.interface_in)
760
if r.interface_out != "":
761
tstr += "_%s" % (r.interface_out)
762
ufw.util.write_to_file(fd, tstr + "\n")
764
pat_space = re.compile(' ')
767
dapp = pat_space.sub('%20', r.dapp)
770
sapp = pat_space.sub('%20', r.sapp)
771
tstr = "\n### tuple ### %s %s %s %s %s %s %s %s %s" % \
772
(action, r.protocol, r.dport, r.dst, r.sport, r.src, \
773
dapp, sapp, r.direction)
775
if r.interface_in != "":
776
tstr += "_%s" % (r.interface_in)
777
if r.interface_out != "":
778
tstr += "_%s" % (r.interface_out)
779
ufw.util.write_to_file(fd, tstr + "\n")
781
chain_suffix = "input"
782
if r.direction == "out":
783
chain_suffix = "output"
784
chain = "%s-user-%s" % (chain_prefix, chain_suffix)
785
rule_str = "-A %s %s\n" % (chain, r.format_rule())
787
for s in self._get_rules_from_formatted(rule_str, chain_prefix, \
789
ufw.util.write_to_file(fd, s)
792
ufw.util.write_to_file(fd, "\n### END RULES ###\n")
794
# Add logging rules, skipping any delete ('-D') rules
795
ufw.util.write_to_file(fd, "\n### LOGGING ###\n")
797
lrules_t = self._get_logging_rules(self.defaults['loglevel'])
800
for c, r, q in lrules_t:
801
if len(r) > 0 and r[0] == '-D':
803
if c.startswith(chain_prefix + "-"):
804
ufw.util.write_to_file(fd,
805
" ".join(r).replace('[', '"[').replace('] ', '] "') + \
807
ufw.util.write_to_file(fd, "### END LOGGING ###\n")
809
# Rate limiting is runtime supported
810
if (chain_prefix == "ufw" and self.caps['limit']['4']) or \
811
(chain_prefix == "ufw6" and self.caps['limit']['6']):
812
ufw.util.write_to_file(fd, "\n### RATE LIMITING ###\n")
813
if self.defaults['loglevel'] != "off":
814
ufw.util.write_to_file(fd, "-A " + \
815
chain_prefix + "-user-limit " + \
816
" ".join(self.ufw_user_limit_log) + \
817
" \"" + self.ufw_user_limit_log_text + " \"\n")
818
ufw.util.write_to_file(fd, "-A " + chain_prefix + \
819
"-user-limit -j REJECT\n")
820
ufw.util.write_to_file(fd, "-A " + chain_prefix + \
821
"-user-limit-accept -j ACCEPT\n")
822
ufw.util.write_to_file(fd, "### END RATE LIMITING ###\n")
824
ufw.util.write_to_file(fd, "COMMIT\n")
828
ufw.util.close_files(fns, False)
830
ufw.util.close_files(fns)
834
def set_rule(self, rule, allow_reload=True):
835
'''Updates firewall with rule by:
836
* appending the rule to the chain if new rule and firewall enabled
837
* deleting the rule from the chain if found and firewall enabled
838
* inserting the rule if possible and firewall enabled
839
* updating user rules file
840
* reloading the user rules file if rule is modified
843
# Initialize the capabilities database
849
if not self.use_ipv6():
850
err_msg = _("Adding IPv6 rule failed: IPv6 not enabled")
851
raise UFWError(err_msg)
852
if rule.action == 'limit' and not self.caps['limit']['6']:
853
# Rate limiting is runtime supported
854
return _("Skipping unsupported IPv6 '%s' rule") % (rule.action)
856
if rule.action == 'limit' and not self.caps['limit']['4']:
857
# Rate limiting is runtime supported
858
return _("Skipping unsupported IPv4 '%s' rule") % (rule.action)
860
if rule.multi and rule.protocol != "udp" and rule.protocol != "tcp":
861
err_msg = _("Must specify 'tcp' or 'udp' with multiple ports")
862
raise UFWError(err_msg)
869
position = rule.position
871
if self.iptables_version < "1.4" and (rule.dapp != "" or \
873
return _("Skipping IPv6 application rule. Need at least iptables 1.4")
876
# bail if we have a bad position
877
if position < 0 or position > len(rules):
878
err_msg = _("Invalid position '%d'") % (position)
879
raise UFWError(err_msg)
881
if position > 0 and rule.remove:
882
err_msg = _("Cannot specify insert and delete")
883
raise UFWError(err_msg)
884
if position > len(rules):
885
err_msg = _("Cannot insert rule at position '%d'") % position
886
raise UFWError(err_msg)
888
# First construct the new rules list
897
last = ('', '', '', '')
904
current = (r.dst, r.src, r.dapp, r.sapp)
905
if count == position:
906
# insert the rule if:
907
# 1. the last rule was not an application rule
908
# 2. the current rule is not an application rule
909
# 3. the last application rule is different than the current
910
# while the new rule is different than the current one
911
if (last[2] == '' and last[3] == '' and count > 1) or \
912
(current[2] == '' and current[3] == '') or \
915
newrules.append(rule.dup_rule())
916
last = ('', '', '', '')
922
ret = UFWRule.match(r, rule)
926
if ret == 0 and not found and not inserted:
927
# If find the rule, add it if it's not to be removed, otherwise
931
newrules.append(rule.dup_rule())
932
elif ret < 0 and not rule.remove and not inserted:
933
# If only the action is different, replace the rule if it's not
937
newrules.append(rule.dup_rule())
943
rstr = _("Skipping inserting existing rule")
948
# Add rule to the end if it was not already added.
949
if not found and not rule.remove:
950
newrules.append(rule.dup_rule())
952
# Don't process non-existing or unchanged pre-exisiting rules
953
if not found and rule.remove and not self.dryrun:
954
rstr = _("Could not delete non-existent rule")
958
elif found and not rule.remove and not modified:
959
rstr = _("Skipping adding existing rule")
965
self.rules6 = newrules
967
self.rules = newrules
969
# Update the user rules file
971
self._write_rules(rule.v6)
975
err_msg = _("Couldn't update rules file")
978
# We wrote out the rules, so set reasonable string. We will change
979
# this below when operating on the live firewall.
980
rstr = _("Rules updated")
982
rstr = _("Rules updated (v6)")
984
# Operate on the chains
985
if self.is_enabled() and not self.dryrun:
987
if modified or self._need_reload(rule.v6) or inserted:
990
rstr += _("Rule inserted")
992
rstr += _("Rule updated")
998
self._reload_user_rules()
1002
rstr += _(" (skipped reloading firewall)")
1003
elif found and rule.remove:
1005
rstr = _("Rule deleted")
1006
elif not found and not modified and not rule.remove:
1008
rstr = _("Rule added")
1012
chain_prefix = "ufw"
1014
exe = self.ip6tables
1015
chain_prefix = "ufw6"
1017
chain_suffix = "input"
1018
if rule.direction == "out":
1019
chain_suffix = "output"
1020
chain = "%s-user-%s" % (chain_prefix, chain_suffix)
1022
# Is the firewall running?
1023
err_msg = _("Could not update running firewall")
1024
(rc, out) = cmd([exe, '-L', chain, '-n'])
1026
raise UFWError(err_msg)
1028
rule_str = "%s %s %s" % (flag, chain, rule.format_rule())
1029
pat_log = re.compile(r'(-A +)(ufw6?-user-[a-z\-]+)(.*)')
1030
for s in self._get_lists_from_formatted(rule_str, \
1033
(rc, out) = cmd([exe] + s)
1035
msg(out, sys.stderr)
1038
# delete any lingering RETURN rules (needed for upgrades)
1039
if flag == "-A" and pat_log.search(" ".join(s)):
1040
c = pat_log.sub(r'\2', " ".join(s))
1041
(rc, out) = cmd([exe, '-D', c, '-j', 'RETURN'])
1043
debug("FAILOK: -D %s -j RETURN" % (c))
1047
def get_app_rules_from_system(self, template, v6):
1048
'''Return a list of UFWRules from the system based on template rule'''
1057
norm = template.dup_rule()
1060
tupl = norm.get_app_tuple()
1065
tmp_tuple = tmp.get_app_tuple()
1066
if tmp_tuple == tupl:
1067
app_rules.append(tmp)
1071
def _chain_cmd(self, chain, args, fail_ok=False):
1072
'''Perform command on chain'''
1074
if chain.startswith("ufw6"):
1075
exe = self.ip6tables
1076
(rc, out) = cmd([exe] + args)
1078
err_msg = _("Could not perform '%s'") % (args)
1080
debug("FAILOK: " + err_msg)
1082
raise UFWError(err_msg)
1084
def update_logging(self, level):
1085
'''Update loglevel of running firewall'''
1089
# Initialize the capabilities database
1094
rules_t = self._get_logging_rules(level)
1098
# Update the user rules file
1100
self._write_rules(v6=False)
1101
self._write_rules(v6=True)
1105
err_msg = _("Couldn't update rules file for logging")
1108
# Don't update the running firewall if not enabled
1109
if not self.is_enabled():
1112
# make sure all the chains are here, it's redundant but helps make
1113
# sure the chains are in a consistent state
1114
err_msg = _("Could not update running firewall")
1115
for c in self.chains['before'] + self.chains['user'] + \
1116
self.chains['after'] + self.chains['misc']:
1118
self._chain_cmd(c, ['-L', c, '-n'])
1120
raise UFWError(err_msg)
1122
# Flush all the logging chains except 'user'
1124
for c in self.chains['before'] + self.chains['after'] + \
1125
self.chains['misc']:
1126
self._chain_cmd(c, ['-F', c])
1127
self._chain_cmd(c, ['-Z', c])
1129
raise UFWError(err_msg)
1131
# Add logging rules to running firewall
1132
for c, r, q in rules_t:
1134
if len(r) > 0 and r[0] == '-D':
1137
if q == 'delete_first' and len(r) > 1:
1138
self._chain_cmd(c, ['-D'] + r[1:], fail_ok=True)
1139
self._chain_cmd(c, r, fail_ok)
1141
raise UFWError(err_msg)
1143
# Rate limiting is runtime supported
1144
# Always delete these and re-add them so that we don't have extras
1145
for chain in ['ufw-user-limit', 'ufw6-user-limit']:
1146
if (self.caps['limit']['4'] and chain == 'ufw-user-limit') or \
1147
(self.caps['limit']['6'] and chain == 'ufw6-user-limit'):
1148
self._chain_cmd(chain, ['-D', chain] + \
1149
self.ufw_user_limit_log + \
1150
[self.ufw_user_limit_log_text + " "], \
1152
if self.defaults["loglevel"] != "off":
1153
self._chain_cmd(chain, ['-I', chain] + \
1154
self.ufw_user_limit_log + \
1155
[self.ufw_user_limit_log_text + " "], \
1158
def _get_logging_rules(self, level):
1159
'''Get rules for specified logging level'''
1162
if level not in list(self.loglevels.keys()):
1163
err_msg = _("Invalid log level '%s'") % (level)
1164
raise UFWError(err_msg)
1167
# when off, insert a RETURN rule at the top of user rules, thus
1168
# preserving the rules
1169
for c in self.chains['user']:
1170
rules_t.append([c, ['-I', c, '-j', 'RETURN'], 'delete_first'])
1173
# when on, remove the RETURN rule at the top of user rules, thus
1174
# honoring the log rules
1175
for c in self.chains['user']:
1176
rules_t.append([c, ['-D', c, '-j', 'RETURN'], ''])
1178
limit_args = ['-m', 'limit', '--limit', '3/min', '--limit-burst', '10']
1180
# log levels of low and higher log blocked packets
1181
if self.loglevels[level] >= self.loglevels["low"]:
1182
# Setup the policy violation logging chains
1184
# log levels under high use limit
1185
if self.loglevels[level] < self.loglevels["high"]:
1187
for c in self.chains['after']:
1188
for t in ['input', 'output', 'forward']:
1190
if self._get_default_policy(t) == "reject" or \
1191
self._get_default_policy(t) == "deny":
1192
prefix = "[UFW BLOCK] "
1193
rules_t.append([c, ['-A', c, '-j', 'LOG', \
1194
'--log-prefix', prefix] +
1196
elif self.loglevels[level] >= self.loglevels["medium"]:
1197
prefix = "[UFW ALLOW] "
1198
rules_t.append([c, ['-A', c, '-j', 'LOG', \
1199
'--log-prefix', prefix] + \
1202
# Setup the miscellaneous logging chains
1204
# log levels under high use limit
1205
if self.loglevels[level] < self.loglevels["high"]:
1208
for c in self.chains['misc']:
1209
if c.endswith("allow"):
1210
prefix = "[UFW ALLOW] "
1211
elif c.endswith("deny"):
1212
prefix = "[UFW BLOCK] "
1213
if self.loglevels[level] < self.loglevels["medium"]:
1214
# only log INVALID in medium and higher
1215
rules_t.append([c, ['-I', c, '-m', 'state', \
1216
'--state', 'INVALID', \
1217
'-j', 'RETURN'] + largs, ''])
1219
rules_t.append([c, ['-A', c, '-m', 'state', \
1220
'--state', 'INVALID', \
1223
"[UFW AUDIT INVALID] "] + \
1225
rules_t.append([c, ['-A', c, '-j', 'LOG', \
1226
'--log-prefix', prefix] + largs, ''])
1228
# Setup the audit logging chains
1229
if self.loglevels[level] >= self.loglevels["medium"]:
1230
# loglevel full logs all packets without limit
1233
# loglevel high logs all packets with limit
1234
if self.loglevels[level] < self.loglevels["full"]:
1237
# loglevel medium logs all new packets with limit
1238
if self.loglevels[level] < self.loglevels["high"]:
1239
largs = ['-m', 'state', '--state', 'NEW'] + limit_args
1241
prefix = "[UFW AUDIT] "
1242
for c in self.chains['before']:
1243
rules_t.append([c, ['-I', c, '-j', 'LOG', \
1244
'--log-prefix', prefix] + largs, ''])
1249
'''Reset the firewall'''
1251
# First make sure we have all the original files
1253
for i in self.files:
1254
if not self.files[i].endswith('.rules'):
1256
allfiles.append(self.files[i])
1257
fn = os.path.join(ufw.common.share_dir, "iptables", \
1258
os.path.basename(self.files[i]))
1259
if not os.path.isfile(fn):
1260
err_msg = _("Could not find '%s'. Aborting") % (fn)
1261
raise UFWError(err_msg)
1263
ext = time.strftime("%Y%m%d_%H%M%S")
1265
# This implementation will intentionally traceback if someone tries to
1266
# do something to take advantage of the race conditions here.
1268
# Don't do anything if the files already exist
1270
fn = "%s.%s" % (i, ext)
1271
if os.path.exists(fn):
1272
err_msg = _("'%s' already exists. Aborting") % (fn)
1273
raise UFWError(err_msg)
1275
# Move the old to the new
1277
fn = "%s.%s" % (i, ext)
1278
res += _("Backing up '%(old)s' to '%(new)s'\n") % (\
1279
{'old': os.path.basename(i), 'new': fn})
1282
# Copy files into place
1284
old = "%s.%s" % (i, ext)
1285
shutil.copy(os.path.join(ufw.common.share_dir, "iptables", \
1286
os.path.basename(i)), \
1288
shutil.copymode(old, i)
1291
statinfo = os.stat(i)
1292
mode = statinfo[stat.ST_MODE]
1294
warn_msg = _("Couldn't stat '%s'") % (i)
1298
if mode & stat.S_IWOTH:
1299
res += _("WARN: '%s' is world writable") % (i)
1300
elif mode & stat.S_IROTH:
1301
res += _("WARN: '%s' is world readable") % (i)