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