Completed
Push — rhel9-branch ( eb0cee...03dfa6 )
by Matěj
20s queued 14s
created

PackageRules.eval_rules()   C

Complexity

Conditions 9

Size

Total Lines 63
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 42
dl 0
loc 63
rs 6.5386
c 0
b 0
f 0
cc 9
nop 4

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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