Completed
Push — master ( 090952...55b5a5 )
by Matěj
20s queued 12s
created

ModifiedOptionParser.exit()   A

Complexity

Conditions 1

Size

Total Lines 2
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nop 3
dl 0
loc 2
rs 10
c 0
b 0
f 0
1
#
2
# Copyright (C) 2013  Red Hat, Inc.
3
#
4
# This copyrighted material is made available to anyone wishing to use,
5
# modify, copy, or redistribute it subject to the terms and conditions of
6
# the GNU General Public License v.2, or (at your option) any later version.
7
# This program is distributed in the hope that it will be useful, but WITHOUT
8
# ANY WARRANTY expressed or implied, including the implied warranties of
9
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
10
# Public License for more details.  You should have received a copy of the
11
# GNU General Public License along with this program; if not, write to the
12
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
13
# 02110-1301, USA.  Any Red Hat trademarks that are incorporated in the
14
# source code or documentation are not subject to the GNU General Public
15
# License and may only be used or replicated with the express permission of
16
# Red Hat, Inc.
17
#
18
# Red Hat Author(s): Vratislav Podzimek <[email protected]>
19
#
20
21
"""
22
Module with various classes for handling pre-installation rules.
23
24
"""
25
26
import optparse
27
import shlex
28
import logging
29
30
from pyanaconda.modules.common.util import is_module_available
31
from pyanaconda.pwpolicy import F22_PwPolicyData
32
from pyanaconda.core.constants import (
33
    FIREWALL_ENABLED, FIREWALL_DISABLED, FIREWALL_USE_SYSTEM_DEFAULTS)
34
from pyanaconda.modules.common.constants.objects import FIREWALL, BOOTLOADER, DEVICE_TREE
35
from pyanaconda.modules.common.constants.services import NETWORK, STORAGE, USERS
36
37
from org_fedora_oscap import common
38
from org_fedora_oscap.common import OSCAPaddonError, RuleMessage, KDUMP, get_packages_data, \
39
    set_packages_data
40
41
# everything else should be private
42
__all__ = ["RuleData"]
43
44
45
ESSENTIAL_PACKAGES = {
46
    "xorg-x11-server-common": {
47
        "env": ["graphical-server-environment", "workstation-product-environment"],
48
    },
49
    "nfs-utils": {
50
        "env": ["graphical-server-environment", "workstation-product-environment"],
51
    },
52
}
53
54
log = logging.getLogger("anaconda")
55
56
_ = common._
57
58
59
# TODO: use set instead of list for mount options?
60
def parse_csv(option, opt_str, value, parser):
61
    for item in value.split(","):
62
        if item:
63
            parser.values.ensure_value(option.dest, []).append(item)
64
65
66
class ModifiedOptionParserException(Exception):
67
    """Exception to be raised by ModifiedOptionParser."""
68
    pass
69
70
71
class ModifiedOptionParser(optparse.OptionParser):
72
    """Overrides error behavior of OptionParser."""
73
    def error(self, msg):
74
        raise ModifiedOptionParserException(msg)
75
76
    def exit(self, status=0, msg=None):
77
        raise ModifiedOptionParserException(msg)
78
79
80
PART_RULE_PARSER = ModifiedOptionParser()
81
PART_RULE_PARSER.add_option("--mountoptions", dest="mount_options",
82
                            action="callback", callback=parse_csv, nargs=1,
83
                            type="string")
84
85
PASSWD_RULE_PARSER = ModifiedOptionParser()
86
PASSWD_RULE_PARSER.add_option("--minlen", dest="minlen", action="store",
87
                              default=0, type="int")
88
89
PACKAGE_RULE_PARSER = ModifiedOptionParser()
90
PACKAGE_RULE_PARSER.add_option("--add", dest="add_pkgs", action="append",
91
                               type="string")
92
PACKAGE_RULE_PARSER.add_option("--remove", dest="remove_pkgs", action="append",
93
                               type="string")
94
95
BOOTLOADER_RULE_PARSER = ModifiedOptionParser()
96
BOOTLOADER_RULE_PARSER.add_option("--passwd", dest="passwd", action="store_true",
97
                                  default=False)
98
99
KDUMP_RULE_PARSER = ModifiedOptionParser()
100
KDUMP_RULE_PARSER.add_option("--enable", action="store_true",
101
                             dest="kdenabled", default=None)
102
KDUMP_RULE_PARSER.add_option("--disable", action="store_false",
103
                             dest="kdenabled", default=None)
104
105
FIREWALL_RULE_PARSER = ModifiedOptionParser()
106
FIREWALL_RULE_PARSER.add_option("--enable", action="store_true",
107
                                dest="fwenabled", default=None)
108
FIREWALL_RULE_PARSER.add_option("--disable", action="store_false",
109
                                dest="fwenabled", default=None)
110
FIREWALL_RULE_PARSER.add_option("--service", dest="add_svcs", action="append",
111
                                type="string")
112
FIREWALL_RULE_PARSER.add_option("--port", dest="add_port", action="append",
113
                                type="string")
114
FIREWALL_RULE_PARSER.add_option("--trust", dest="add_trust", action="append",
115
                                type="string")
116
FIREWALL_RULE_PARSER.add_option("--remove-service", dest="remove_svcs",
117
                                action="append", type="string")
118
119
120
class RuleHandler(object):
121
    """Base class for the rule handlers."""
122
123
    def eval_rules(self, ksdata, storage, report_only=False):
124
        """
125
        Method that should check the current state (as defined by the ksdata
126
        and storage parameters) against the rules the instance of RuleHandler
127
        holds. Depending on the value of report_only it should fix the state
128
        with changes that can be done automatically or not and return the list
129
        of warnings and errors for fixes that need to be done manually together
130
        with info messages about the automatic changes. One should make sure
131
        this method is called with report_only set to False at least once so
132
        that the automatic fixes are done.
133
134
        :param ksdata: data representing the values set by user
135
        :type ksdata: pykickstart.base.BaseHandler
136
        :param storage: object storing storage-related information
137
                        (disks, partitioning, bootloader, etc.)
138
        :type storage: blivet.Blivet
139
        :param report_only: whether to do fixing or just report information
140
        :type report_only: bool
141
        :return: errors and warnings for fixes that need to be done manually
142
                 and info messages about the automatic changes
143
        :rtype: list of common.RuleMessage objects
144
145
        """
146
147
        return []
148
149
    def revert_changes(self, ksdata, storage):
150
        """
151
        Method that should revert all changes done by the previous calls of the
152
        eval_rules method with the report_only set to False.
153
154
        :see: eval_rules
155
156
        """
157
158
        # inheriting classes are supposed to override this
159
        pass
160
161
162
class UknownRuleError(OSCAPaddonError):
163
    """Exception class for cases when an uknown rule is to be processed."""
164
165
    pass
166
167
168
class RuleData(RuleHandler):
169
    """Class holding data parsed from the applied rules."""
170
171
    def __init__(self):
172
        """Constructor initializing attributes."""
173
174
        self._part_rules = PartRules()
175
        self._passwd_rules = PasswdRules()
176
        self._package_rules = PackageRules()
177
        self._bootloader_rules = BootloaderRules()
178
        self._kdump_rules = KdumpRules()
179
        self._firewall_rules = FirewallRules()
180
181
        self._rule_handlers = (self._part_rules, self._passwd_rules,
182
                               self._package_rules, self._bootloader_rules,
183
                               self._kdump_rules, self._firewall_rules,
184
                               )
185
186
    def __str__(self):
187
        """Standard method useful for debugging and testing."""
188
189
        ret = ""
190
191
        part_strs = str(self._part_rules)
192
        if part_strs:
193
            ret += part_strs
194
195
        passwd_str = str(self._passwd_rules)
196
        if passwd_str:
197
            ret += "\n" + passwd_str
198
199
        packages_str = str(self._package_rules)
200
        if packages_str:
201
            ret += "\n" + packages_str
202
203
        firewall_str = str(self._firewall_rules)
204
        if firewall_str:
205
            ret += "\n" + firewall_str
206
207
        return ret
208
209
    def new_rule(self, rule):
210
        """
211
        Method that handles a single rule line (e.g. "part /tmp").
212
213
        :param rule: a single rule line
214
        :type rule: str
215
216
        """
217
218
        actions = {"part": self._new_part_rule,
219
                   "passwd": self._new_passwd_rule,
220
                   "package": self._new_package_rule,
221
                   "bootloader": self._new_bootloader_rule,
222
                   "kdump": self._new_kdump_rule,
223
                   "firewall": self._new_firewall_rule,
224
                   }
225
226
        rule = rule.strip()
227
        if not rule:
228
            return
229
230
        first_word = rule.split(None, 1)[0]
231
        try:
232
            actions[first_word](rule)
233
        except (ModifiedOptionParserException, KeyError) as e:
234
            log.warning("Unknown OSCAP Addon rule '{}': {}".format(rule, e))
235
236
    def eval_rules(self, ksdata, storage, report_only=False):
237
        """:see: RuleHandler.eval_rules"""
238
239
        messages = []
240
241
        # evaluate all subgroups of rules
242
        for rule_handler in self._rule_handlers:
243
            messages += rule_handler.eval_rules(ksdata, storage, report_only)
244
245
        return messages
246
247
    def revert_changes(self, ksdata, storage):
248
        """:see: RuleHandler.revert_changes"""
249
250
        # revert changes in all subgroups of rules
251
        for rule_handler in self._rule_handlers:
252
            rule_handler.revert_changes(ksdata, storage)
253
254
    def _new_part_rule(self, rule):
255
        args = shlex.split(rule)
256
        (opts, args) = PART_RULE_PARSER.parse_args(args)
257
258
        # args contain both "part" and mount point (e.g. "/tmp")
259
        mount_point = args[1]
260
261
        self._part_rules.ensure_mount_point(mount_point)
262
263
        if opts.mount_options:
264
            part_data = self._part_rules[mount_point]
265
            part_data.add_mount_options(opts.mount_options)
266
267
    def _new_passwd_rule(self, rule):
268
        args = shlex.split(rule)
269
        (opts, args) = PASSWD_RULE_PARSER.parse_args(args)
270
271
        self._passwd_rules.update_minlen(opts.minlen)
272
273
    def _new_package_rule(self, rule):
274
        args = shlex.split(rule)
275
        (opts, args) = PACKAGE_RULE_PARSER.parse_args(args)
276
277
        self._package_rules.add_packages(opts.add_pkgs)
278
        self._package_rules.remove_packages(opts.remove_pkgs)
279
280
    def _new_bootloader_rule(self, rule):
281
        args = shlex.split(rule)
282
        (opts, args) = BOOTLOADER_RULE_PARSER.parse_args(args)
283
284
        if opts.passwd:
285
            self._bootloader_rules.require_password()
286
287
    def _new_kdump_rule(self, rule):
288
        args = shlex.split(rule)
289
        (opts, args) = KDUMP_RULE_PARSER.parse_args(args)
290
291
        self._kdump_rules.kdump_enabled(opts.kdenabled)
292
293
    def _new_firewall_rule(self, rule):
294
        args = shlex.split(rule)
295
        (opts, args) = FIREWALL_RULE_PARSER.parse_args(args)
296
297
        self._firewall_rules.add_services(opts.add_svcs)
298
        self._firewall_rules.remove_services(opts.remove_svcs)
299
        self._firewall_rules.add_trusts(opts.add_trust)
300
        self._firewall_rules.add_ports(opts.add_port)
301
        self._firewall_rules.firewall_enabled(opts.fwenabled)
302
303
    @property
304
    def passwd_rules(self):
305
        # needed for fixups in GUI
306
        return self._passwd_rules
307
308
309
class PartRules(RuleHandler):
310
    """Simple class holding data from the rules affecting partitioning."""
311
312
    def __init__(self):
313
        """Constructor initializing attributes."""
314
315
        self._rules = dict()
316
317
    def __str__(self):
318
        """Standard method useful for debugging and testing."""
319
320
        return "\n".join(str(rule) for rule in self._rules.values())
321
322
    def __getitem__(self, key):
323
        """Method to support dictionary-like syntax."""
324
325
        return self._rules[key]
326
327
    def __setitem__(self, key, value):
328
        """Method to support dictionary-like syntax."""
329
330
        self._rules[key] = value
331
332
    def __delitem__(self, key):
333
        """One of the methods needed to implement a container."""
334
335
        self._rules.__delitem__(key)
336
337
    def __len__(self):
338
        """One of the methods needed to implement a container."""
339
340
        return self._rules.__len__()
341
342
    def __contains__(self, key):
343
        """Method needed for the 'in' operator to work."""
344
345
        return key in self._rules
346
347
    def ensure_mount_point(self, mount_point):
348
        if mount_point not in self._rules:
349
            self._rules[mount_point] = PartRule(mount_point)
350
351
    def eval_rules(self, ksdata, storage, report_only=False):
352
        """:see: RuleHandler.eval_rules"""
353
354
        messages = []
355
        for part_rule in self._rules.values():
356
            messages += part_rule.eval_rules(ksdata, storage, report_only)
357
358
        return messages
359
360
    def revert_changes(self, ksdata, storage):
361
        """:see: RuleHandler.revert_changes"""
362
363
        for part_rule in self._rules.values():
364
            part_rule.revert_changes(ksdata, storage)
365
366
367
class PartRule(RuleHandler):
368
    """Simple class holding rule data for a single partition/mount point."""
369
370
    def __init__(self, mount_point):
371
        """
372
        Constructor initializing attributes.
373
374
        :param mount_point: the mount point the object holds data for
375
        :type mount_point: str
376
377
        """
378
379
        self._mount_point = mount_point
380
        self._mount_options = []
381
        self._added_mount_options = []
382
383
    def __str__(self):
384
        """Standard method useful for debugging and testing."""
385
386
        ret = "part %s" % self._mount_point
387
        if self._mount_options:
388
            ret += " --mountoptions=%s" % ",".join(self._mount_options)
389
390
        return ret
391
392
    def add_mount_options(self, mount_options):
393
        """
394
        Add  new mount options (do not add duplicates).
395
396
        :param mount_options: list of mount options to be added
397
        :type mount_options: list of strings
398
399
        """
400
401
        self._mount_options.extend(opt for opt in mount_options
402
                                   if opt not in self._mount_options)
403
404
    def eval_rules(self, ksdata, storage, report_only=False):
405
        """:see: RuleHandler.eval_rules"""
406
        device_tree = STORAGE.get_proxy(DEVICE_TREE)
407
        mount_points = device_tree.GetMountPoints()
408
        messages = []
409
410
        if self._mount_point not in mount_points:
411
            msg = _("{0} must be on a separate partition or logical "
412
                    "volume and has to be created in the "
413
                    "partitioning layout before installation can occur "
414
                    "with a security profile").format(self._mount_point)
415
            messages.append(RuleMessage(self.__class__,
416
                                        common.MESSAGE_TYPE_FATAL, msg))
417
418
            # mount point doesn't exist, nothing more can be found here
419
            return messages
420
421
        # template for the message
422
        msg_tmpl = _("mount option '%(mount_option)s' added for "
423
                     "the mount point %(mount_point)s")
424
425
        # add message for every option already added
426
        for opt in self._added_mount_options:
427
            msg = msg_tmpl % {"mount_option": opt,
428
                              "mount_point": self._mount_point}
429
            messages.append(RuleMessage(self.__class__,
430
                                        common.MESSAGE_TYPE_INFO, msg))
431
432
        # mount point to be created during installation
433
        target_name = mount_points[self._mount_point]
434
        mount_options = device_tree.GetDeviceMountOptions(target_name)
435
436
        # generator for the new options that should be added
437
        new_opts = (opt for opt in self._mount_options
0 ignored issues
show
introduced by
The variable opt does not seem to be defined in case the for loop on line 426 is not entered. Are you sure this can never be the case?
Loading history...
438
                    if opt not in mount_options.split(","))
439
440
        # add message for every mount option added
441
        for opt in new_opts:
442
            msg = msg_tmpl % {"mount_option": opt,
443
                              "mount_point": self._mount_point}
444
445
            # add message for the mount option in any case
446
            messages.append(RuleMessage(self.__class__,
447
                                        common.MESSAGE_TYPE_INFO, msg))
448
449
            # add new options to the target mount point if not reporting only
450
            if not report_only:
451
                mount_options += ",%s" % opt
452
                self._added_mount_options.append(opt)
453
454
        if new_opts and not report_only:
455
            device_tree.SetDeviceMountOptions(target_name, mount_options)
456
457
        return messages
458
459
    def revert_changes(self, ksdata, storage):
460
        """
461
        Removes the mount options added to the mount point by this PartRule
462
        instance.
463
464
        :see: RuleHandler.revert_changes
465
466
        """
467
        device_tree = STORAGE.get_proxy(DEVICE_TREE)
468
        mount_points = device_tree.GetMountPoints()
469
470
        if self._mount_point not in mount_points:
471
            # mount point doesn't exist, nothing can be reverted
472
            return
473
474
        # mount point to be created during installation
475
        target_name = mount_points[self._mount_point]
476
477
        # mount options to be defined for the created mount point
478
        mount_options = device_tree.GetDeviceMountOptions(target_name)
479
480
        # generator of the options that should remain
481
        result_opts = (opt for opt in mount_options.split(",")
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable opt does not seem to be defined.
Loading history...
482
                       if opt not in self._added_mount_options)
483
484
        # set the new list of options
485
        mount_options = ",".join(result_opts)
486
        device_tree.SetDeviceMountOptions(target_name, mount_options)
487
488
        # reset the remembered added mount options
489
        self._added_mount_options = []
490
491
492
class PasswdRules(RuleHandler):
493
    """Simple class holding data from the rules affecting passwords."""
494
495
    def __init__(self):
496
        """Constructor initializing attributes."""
497
498
        self._minlen = 0
499
        self._created_policy = False
500
        self._orig_minlen = None
501
        self._orig_strict = None
502
503
    def __str__(self):
504
        """Standard method useful for debugging and testing."""
505
506
        if self._minlen > 0:
507
            return "passwd --minlen=%d" % self._minlen
508
        else:
509
            return ""
510
511
    def update_minlen(self, minlen):
512
        """Update password minimal length requirements."""
513
514
        if minlen > self._minlen:
515
            self._minlen = minlen
516
517
    def eval_rules(self, ksdata, storage, report_only=False):
518
        """:see: RuleHandler.eval_rules"""
519
520
        if self._minlen == 0:
521
            # no password restrictions, nothing to be done here
522
            return []
523
524
        ret = []
525
526
        users_proxy = USERS.get_proxy()
527
528
        if not users_proxy.IsRootPasswordSet:
529
            # root password was not set
530
531
            msg = _("make sure to create password with minimal length of %d "
532
                    "characters") % self._minlen
533
            ret = [RuleMessage(self.__class__,
534
                               common.MESSAGE_TYPE_WARNING, msg)]
535
        else:
536
            # root password set
537
            if users_proxy.IsRootPasswordCrypted:
538
                msg = _("cannot check root password length (password is crypted)")
539
                log.warning("cannot check root password length (password is crypted)")
540
                return [RuleMessage(self.__class__,
541
                                    common.MESSAGE_TYPE_WARNING, msg)]
542
            elif len(users_proxy.RootPassword) < self._minlen:
543
                # too short
544
                msg = _("root password is too short, a longer one with at "
545
                        "least %d characters is required") % self._minlen
546
                ret = [RuleMessage(self.__class__,
547
                                   common.MESSAGE_TYPE_FATAL, msg)]
548
            else:
549
                ret = []
550
551
        if report_only:
552
            return ret
553
554
        # set the policy in any case (so that a weaker password is not entered)
555
        pw_policy = ksdata.anaconda.pwpolicy.get_policy("root")
556
        if pw_policy is None:
557
            pw_policy = F22_PwPolicyData()
558
            log.info("OSCAP addon: setting password policy %s" % pw_policy)
559
            ksdata.anaconda.pwpolicy.policyList.append(pw_policy)
560
            log.info("OSCAP addon: password policy list: %s" % ksdata.anaconda.pwpolicy.policyList)
561
            self._created_policy = True
562
563
        self._orig_minlen = pw_policy.minlen
564
        self._orig_strict = pw_policy.strict
565
        pw_policy.minlen = self._minlen
566
        pw_policy.strict = True
567
568
        return ret
569
570
    def revert_changes(self, ksdata, storage):
571
        """:see: RuleHander.revert_changes"""
572
573
        pw_policy = ksdata.anaconda.pwpolicy.get_policy("root")
574
        if self._created_policy:
575
            log.info("OSCAP addon: removing password policy: %s" % pw_policy)
576
            ksdata.anaconda.pwpolicy.policyList.remove(pw_policy)
577
            log.info("OSCAP addon: password policy list: %s" % ksdata.anaconda.pwpolicy.policyList)
578
            self._created_policy = False
579
        else:
580
            if self._orig_minlen is not None:
581
                pw_policy.minlen = self._orig_minlen
582
                self._orig_minlen = None
583
            if self._orig_strict is not None:
584
                pw_policy.strict = self._orig_strict
585
                self._orig_strict = None
586
587
588
class PackageRules(RuleHandler):
589
    """Simple class holding data from the rules affecting installed packages.
590
591
    """
592
593
    def __init__(self):
594
        """Constructor setting the initial value of attributes."""
595
596
        self._add_pkgs = set()
597
        self._remove_pkgs = set()
598
599
        self._added_pkgs = set()
600
        self._removed_pkgs = set()
601
602
    def add_packages(self, packages):
603
        """
604
        New packages that should be added.
605
606
        :param packages: packages to be added
607
        :type packages: iterable
608
609
        """
610
611
        if packages:
612
            self._add_pkgs.update(packages)
613
614
    def remove_packages(self, packages):
615
        """
616
        New packages that should be removed.
617
618
        :param packages: packages to be removed
619
        :type packages: iterable
620
621
        """
622
623
        if packages:
624
            self._remove_pkgs.update(packages)
625
626
    def __str__(self):
627
        """Standard method useful for debugging and testing."""
628
629
        ret = "packages"
630
        adds = " ".join("--add=%s" % package for package in self._add_pkgs)
631
        if adds:
632
            ret += " " + adds
633
634
        rems = " ".join("--remove=%s" % package
635
                        for package in self._remove_pkgs)
636
        if rems:
637
            ret += " " + rems
638
639
        return ret
640
641
    def _package_is_essential(self, package_name, packages_data):
642
        if package_name not in ESSENTIAL_PACKAGES:
643
            return False
644
645
        if package_name in packages_data.packages:
646
            return True
647
648
        selected_install_env = packages_data.environment
649
        if selected_install_env in ESSENTIAL_PACKAGES[package_name].get("env"):
650
            return True
651
652
        for g in ESSENTIAL_PACKAGES[package_name].get("groups", []):
653
            if g in packages_data.groups:
654
                return True
655
656
        return False
657
658
    def eval_rules(self, ksdata, storage, report_only=False):
659
        """:see: RuleHandler.eval_rules"""
660
        messages = []
661
        packages_data = get_packages_data()
662
663
        # add messages for the already added packages
664
        for pkg in self._added_pkgs:
665
            msg = _("package '%s' has been added to the list of to be installed "
666
                    "packages" % pkg)
667
            messages.append(RuleMessage(self.__class__,
668
                                        common.MESSAGE_TYPE_INFO, msg))
669
670
        # packages, that should be added
671
        packages_to_add = (pkg for pkg in self._add_pkgs
0 ignored issues
show
introduced by
The variable pkg does not seem to be defined in case the for loop on line 664 is not entered. Are you sure this can never be the case?
Loading history...
672
                           if pkg not in packages_data.packages)
673
674
        for pkg in packages_to_add:
675
            # add the package unless already added
676
            if not report_only:
677
                self._added_pkgs.add(pkg)
678
                packages_data.packages.append(pkg)
679
680
            msg = _("package '%s' has been added to the list of to be installed "
681
                    "packages" % pkg)
682
            messages.append(RuleMessage(self.__class__,
683
                                        common.MESSAGE_TYPE_INFO, msg))
684
685
        # now do the same for the packages that should be excluded
686
        # add messages for the already excluded packages
687
        for pkg in self._removed_pkgs:
688
            if self._package_is_essential(pkg, packages_data):
689
                msg = _(
690
                    "package '{package}' has been added to the list "
691
                    "of excluded packages, but it can't be removed "
692
                    "from the current software selection without breaking the installation."
693
                    .format(package=pkg))
694
                messages.append(RuleMessage(self.__class__,
695
                                            common.MESSAGE_TYPE_FATAL, msg))
696
            else:
697
                msg = _("package '%s' has been added to the list of excluded "
698
                        "packages" % pkg)
699
                messages.append(RuleMessage(self.__class__,
700
                                            common.MESSAGE_TYPE_INFO, msg))
701
702
        # packages, that should be added
703
        packages_to_remove = (pkg for pkg in self._remove_pkgs
704
                              if pkg not in packages_data.excluded_packages)
705
706
        for pkg in packages_to_remove:
707
            # exclude the package unless already excluded
708
            if not report_only:
709
                self._removed_pkgs.add(pkg)
710
                packages_data.excluded_packages.append(pkg)
711
712
            msg = _("package '%s' has been added to the list of excluded "
713
                    "packages" % pkg)
714
            messages.append(RuleMessage(self.__class__,
715
                                        common.MESSAGE_TYPE_INFO, msg))
716
717
        if not report_only:
718
            set_packages_data(packages_data)
719
720
        return messages
721
722
    def revert_changes(self, ksdata, storage):
723
        """:see: RuleHander.revert_changes"""
724
        packages_data = get_packages_data()
725
726
        # remove all packages this handler added
727
        for pkg in self._added_pkgs:
728
            if pkg in packages_data.packages:
729
                packages_data.packages.remove(pkg)
730
731
        # remove all packages this handler excluded
732
        for pkg in self._removed_pkgs:
733
            if pkg in packages_data.excluded_packages:
734
                packages_data.excluded_packages.remove(pkg)
735
736
        self._added_pkgs = set()
737
        self._removed_pkgs = set()
738
739
        set_packages_data(packages_data)
740
741
742
class BootloaderRules(RuleHandler):
743
    """Simple class holding data from the rules affecting bootloader."""
744
745
    def __init__(self):
746
        """Constructor setting the initial value of attributes."""
747
748
        self._require_password = False
749
750
    def require_password(self):
751
        """Requests the bootloader password should be required."""
752
753
        self._require_password = True
754
755
    def __str__(self):
756
        """Standard method useful for debugging and testing."""
757
758
        ret = "bootloader"
759
760
        if self._require_password:
761
            ret += " --passwd"
762
763
        return ret
764
765
    def eval_rules(self, ksdata, storage, report_only=False):
766
        """:see: RuleHandler.eval_rules"""
767
768
        bootloader_proxy = STORAGE.get_proxy(BOOTLOADER)
769
770
        if self._require_password and not bootloader_proxy.IsPasswordSet:
771
            # TODO: Anaconda provides a way to set bootloader password:
772
            # bootloader_proxy.SetEncryptedPassword(...)
773
            # We don't support setting the bootloader password yet,
774
            # but we shouldn't stop the installation, just because of that.
775
            return [RuleMessage(self.__class__, common.MESSAGE_TYPE_WARNING,
776
                                "boot loader password not set up")]
777
        else:
778
            return []
779
780
    # nothing to be reverted for now
781
782
783
class KdumpRules(RuleHandler):
784
    """Simple class holding data from the rules affecting the kdump addon."""
785
786
    def __init__(self):
787
        """Constructor setting the initial value of attributes."""
788
789
        self._kdump_enabled = None
790
        self._kdump_default_enabled = None
791
792
    def kdump_enabled(self, kdenabled):
793
        """Enable or Disable Kdump"""
794
795
        if kdenabled is not None:
796
            self._kdump_enabled = kdenabled
797
798
    def __str__(self):
799
        """Standard method useful for debugging and testing."""
800
801
        ret = "kdump"
802
803
        if self._kdump_enabled is True:
804
            ret += " --enable"
805
806
        if self._kdump_enabled is False:
807
            ret += " --disable"
808
809
        return ret
810
811
    def eval_rules(self, ksdata, storage, report_only=False):
812
        """:see: RuleHandler.eval_rules"""
813
814
        messages = []
815
816
        if self._kdump_enabled is None:
817
            return []
818
        elif self._kdump_enabled is False:
819
            msg = _("Kdump will be disabled on startup")
820
        elif self._kdump_enabled is True:
821
            msg = _("Kdump will be enabled on startup")
822
823
        messages.append(RuleMessage(self.__class__,
824
                                    common.MESSAGE_TYPE_INFO, msg))
0 ignored issues
show
introduced by
The variable msg does not seem to be defined for all execution paths.
Loading history...
825
826
        if not report_only:
827
            if is_module_available(KDUMP):
828
                kdump_proxy = KDUMP.get_proxy()
829
830
                if self._kdump_default_enabled is None:
831
                    # Kdump addon default startup setting
832
                    self._kdump_default_enabled = kdump_proxy.KdumpEnabled
833
834
                kdump_proxy.KdumpEnabled = self._kdump_enabled
835
            else:
836
                log.warning("com_redhat_kdump is not installed. "
837
                            "Skipping kdump configuration")
838
839
        return messages
840
841
    def revert_changes(self, ksdata, storage):
842
        """:see: RuleHander.revert_changes"""
843
844
        if is_module_available(KDUMP):
845
            kdump_proxy = KDUMP.get_proxy()
846
847
            if self._kdump_enabled is not None:
848
                kdump_proxy.KdumpEnabled = self._kdump_default_enabled
849
        else:
850
            log.warning("com_redhat_kdump is not installed. "
851
                        "Skipping reverting kdump configuration")
852
853
        self._kdump_enabled = None
854
        self._kdump_default_enabled = None
855
856
857
class FirewallRules(RuleHandler):
858
    """Simple class holding data from the rules affecting firewall configurations."""
859
860
    def __init__(self):
861
        """Constructor setting the initial value of attributes."""
862
863
        self._add_svcs = set()
864
        self._remove_svcs = set()
865
        self._add_trusts = set()
866
        self._add_ports = set()
867
868
        self._added_svcs = set()
869
        self._added_ports = set()
870
        self._added_trusts = set()
871
        self._removed_svcs = set()
872
873
        self._new_services_to_add = set()
874
        self._new_ports_to_add = set()
875
        self._new_trusts_to_add = set()
876
        self._new_services_to_remove = set()
877
878
        self._firewall_enabled = None
879
        self._firewall_default_state = None
880
881
    def add_services(self, services):
882
        """
883
        Services that should be allowed through firewall.
884
885
        :param services: services to be added
886
        :type services: iterable
887
888
        """
889
890
        if services:
891
            self._add_svcs.update(services)
892
893
    def add_ports(self, ports):
894
        """
895
        Ports that should be allowed through firewall.
896
897
        :param ports: ports to be added
898
        :type ports: iterable
899
900
        """
901
902
        if ports:
903
            self._add_ports.update(ports)
904
905
    def add_trusts(self, trusts):
906
        """
907
        trusts that should be allowed through firewall.
908
909
        :param trusts: trusts to be added
910
        :type trusts: iterable
911
912
        """
913
914
        if trusts:
915
            self._add_trusts.update(trusts)
916
917
    def remove_services(self, services):
918
        """
919
        New services that should not be allowed through firewall.
920
921
        :param services: services to be removed
922
        :type services: iterable
923
924
        """
925
926
        if services:
927
            self._remove_svcs.update(services)
928
929
    def firewall_enabled(self, fwenabled):
930
        """Enable or disable firewall"""
931
932
        if fwenabled is not None:
933
            self._firewall_enabled = fwenabled
934
935
    def __str__(self):
936
        """Standard method useful for debugging and testing."""
937
938
        ret = "firewall"
939
940
        if self._firewall_enabled is True:
941
            ret += " --enable"
942
943
        if self._firewall_enabled is False:
944
            ret += " --disable"
945
946
        adds = " ".join("--service=%s" % service
947
                        for service in self._add_svcs)
948
        if adds:
949
            ret += " " + adds
950
951
        rems = " ".join("--remove-service=%s" % service
952
                        for service in self._remove_svcs)
953
        if rems:
954
            ret += " " + rems
955
956
        ports = " ".join("--port=%s" % port
957
                         for port in self._add_ports)
958
        if ports:
959
            ret += " " + ports
960
961
        trusts = " ".join("--trust=%s" % trust
962
                          for trust in self._add_trusts)
963
        if trusts:
964
            ret += " " + trusts
965
966
        return ret
967
968
    def eval_rules(self, ksdata, storage, report_only=False):
969
        """:see: RuleHandler.eval_rules"""
970
971
        firewall_proxy = NETWORK.get_proxy(FIREWALL)
972
        messages = []
973
974
        if self._firewall_default_state is None:
975
            # firewall default startup setting
976
            self._firewall_default_state = firewall_proxy.FirewallMode
977
978
        if self._firewall_enabled is False:
979
            msg = _("Firewall will be disabled on startup")
980
            messages.append(RuleMessage(self.__class__,
981
                                        common.MESSAGE_TYPE_INFO, msg))
982
            if not report_only:
983
                firewall_proxy.SetFirewallMode(FIREWALL_DISABLED)
984
985
        elif self._firewall_enabled is True:
986
            msg = _("Firewall will be enabled on startup")
987
            messages.append(RuleMessage(self.__class__,
988
                                        common.MESSAGE_TYPE_INFO, msg))
989
            if not report_only:
990
                firewall_proxy.SetFirewallMode(FIREWALL_ENABLED)
991
992
        # add messages for the already added services
993
        for svc in self._added_svcs:
994
            msg = _("service '%s' has been added to the list of services to be "
995
                    "added to the firewall" % svc)
996
            messages.append(RuleMessage(self.__class__,
997
                                        common.MESSAGE_TYPE_INFO, msg))
998
999
        # add messages for the already added ports
1000
        for port in self._added_ports:
1001
            msg = _("port '%s' has been added to the list of ports to be "
1002
                    "added to the firewall" % port)
1003
            messages.append(RuleMessage(self.__class__,
1004
                                        common.MESSAGE_TYPE_INFO, msg))
1005
1006
        # add messages for the already added trusts
1007
        for trust in self._added_trusts:
1008
            msg = _("trust '%s' has been added to the list of trusts to be "
1009
                    "added to the firewall" % trust)
1010
            messages.append(RuleMessage(self.__class__,
1011
                                        common.MESSAGE_TYPE_INFO, msg))
1012
1013
        # services, that should be added
1014
        self._new_services_to_add = {
1015
            svc for svc in self._add_svcs
1016
            if svc not in firewall_proxy.EnabledServices}
1017
1018
        # ports, that should be added
1019
        self._new_ports_to_add = {
1020
            ports for ports in self._add_ports
1021
            if ports not in firewall_proxy.EnabledPorts}
1022
1023
        # trusts, that should be added
1024
        self._new_trusts_to_add = {
1025
            trust for trust in self._add_trusts
1026
            if trust not in firewall_proxy.Trusts}
1027
1028
        for svc in self._new_services_to_add:
1029
            # add the service unless already added
1030
            if not report_only:
1031
                self._added_svcs.add(svc)
1032
1033
            msg = _("service '%s' has been added to the list of services to be "
1034
                    "added to the firewall" % svc)
1035
            messages.append(RuleMessage(self.__class__,
1036
                                        common.MESSAGE_TYPE_INFO, msg))
1037
        if not report_only:
1038
            all_services = list(self._add_svcs.union(set(firewall_proxy.EnabledServices)))
1039
            firewall_proxy.SetEnabledServices(all_services)
1040
1041
        for port in self._new_ports_to_add:
1042
            # add the port unless already added
1043
            if not report_only:
1044
                self._added_ports.add(port)
1045
1046
            msg = _("port '%s' has been added to the list of ports to be "
1047
                    "added to the firewall" % port)
1048
            messages.append(RuleMessage(self.__class__,
1049
                                        common.MESSAGE_TYPE_INFO, msg))
1050
        if not report_only:
1051
            all_ports = list(self._add_ports.union(set(firewall_proxy.EnabledPorts)))
1052
            firewall_proxy.SetEnabledPorts(all_ports)
1053
1054
        for trust in self._new_trusts_to_add:
1055
            # add the trust unless already added
1056
            if not report_only:
1057
                self._added_trusts.add(trust)
1058
1059
            msg = _("trust '%s' has been added to the list of trusts to be "
1060
                    "added to the firewall" % trust)
1061
            messages.append(RuleMessage(self.__class__,
1062
                                        common.MESSAGE_TYPE_INFO, msg))
1063
        if not report_only:
1064
            all_trusts = list(self._add_trusts.union(set(firewall_proxy.Trusts)))
1065
            firewall_proxy.SetTrusts(all_trusts)
1066
1067
        # now do the same for the services that should be excluded
1068
1069
        # add messages for the already excluded services
1070
        for svc in self._removed_svcs:
1071
            msg = _("service '%s' has been added to the list of services to be "
1072
                    "removed from the firewall" % svc)
1073
            messages.append(RuleMessage(self.__class__,
1074
                                        common.MESSAGE_TYPE_INFO, msg))
1075
1076
        # services, that should be excluded
1077
        self._new_services_to_remove = {
1078
            svc for svc in self._remove_svcs
1079
            if svc not in firewall_proxy.DisabledServices}
1080
1081
        for svc in self._new_services_to_remove:
1082
            # exclude the service unless already excluded
1083
            if not report_only:
1084
                self._removed_svcs.add(svc)
1085
1086
            msg = _("service '%s' has been added to the list of services to be "
1087
                    "removed from the firewall" % svc)
1088
            messages.append(RuleMessage(self.__class__,
1089
                                        common.MESSAGE_TYPE_INFO, msg))
1090
        if not report_only:
1091
            all_services = list(self._remove_svcs.union(set(firewall_proxy.DisabledServices)))
1092
            firewall_proxy.SetDisabledServices(all_services)
1093
1094
        return messages
1095
1096
    def revert_changes(self, ksdata, storage):
1097
        """:see: RuleHander.revert_changes"""
1098
        firewall_proxy = NETWORK.get_proxy(FIREWALL)
1099
1100
        if self._firewall_enabled is not None:
1101
            firewall_proxy.SetFirewallMode(self._firewall_default_state)
1102
1103
        # remove all services this handler added
1104
        all_services = firewall_proxy.EnabledServices
1105
        orig_services = set(all_services).difference(self._new_services_to_add)
1106
        firewall_proxy.SetEnabledServices(list(orig_services))
1107
1108
        # remove all ports this handler added
1109
        all_ports = firewall_proxy.EnabledPorts
1110
        orig_ports = set(all_ports).difference(self._new_ports_to_add)
1111
        firewall_proxy.SetEnabledPorts(list(orig_ports))
1112
1113
        # remove all trusts this handler added
1114
        all_trusts = firewall_proxy.Trusts
1115
        orig_trusts = set(all_trusts).difference(self._new_trusts_to_add)
1116
        firewall_proxy.SetTrusts(list(orig_trusts))
1117
1118
        # remove all services this handler excluded
1119
        all_services = firewall_proxy.DisabledServices
1120
        orig_services = set(all_services).difference(self._new_services_to_remove)
1121
        firewall_proxy.SetDisabledServices(list(orig_services))
1122
1123
        self._added_svcs = set()
1124
        self._added_ports = set()
1125
        self._added_trusts = set()
1126
        self._removed_svcs = set()
1127
        self._firewall_enabled = None
1128
        self._firewall_default_state = None
1129