/mandos/trunk

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/trunk

« back to all changes in this revision

Viewing changes to mandos-ctl

  • Committer: Teddy Hogeborn
  • Date: 2019-03-03 01:08:58 UTC
  • Revision ID: teddy@recompile.se-20190303010858-c2l0sr6ekvzo7rlb
mandos-ctl: Separate determining what to do and actually doing it

* mandos-ctl (defaultkeywords): Removed; value moved into
                                PrintTableCmd.
  (Command): New abstract base class for commands to be run.
  (PrintCmd, PropertyCmd): New abstract classes for commands.
  (ValueArgumentMixIn, MillisecondsValueArgumentMixIn): New mixins for
                                                        commands.
  (PrintTableCmd, DumpJSONCmd, IsEnabledCmd, RemoveCmd, ApproveCmd,
  DenyCmd, EnableCmd, DisableCmd, BumpTimeoutCmd, StartCheckerCmd,
  StopCheckerCmd, ApproveByDefaultCmd, DenyByDefaultCmd,
  SetCheckerCmd, SetTimeoutCmd, SetExtendedTimeoutCmd,
  SetApprovalDelayCmd, SetApprovalDurationCmd): New commands.
  (main): Don't look directly at options and do things; instead go
          through all options and add commands to a list, then run all
          commands on clients.

Show diffs side-by-side

added added

removed removed

Lines of Context:
61
61
 
62
62
locale.setlocale(locale.LC_ALL, "")
63
63
 
64
 
defaultkeywords = ("Name", "Enabled", "Timeout", "LastCheckedOK")
65
64
domain = "se.recompile"
66
65
busname = domain + ".Mandos"
67
66
server_path = "/"
344
343
               for key in self.keywords})
345
344
 
346
345
 
 
346
## Classes for commands.
 
347
 
 
348
# Abstract classes first
 
349
class Command(object):
 
350
    """Abstract class for commands"""
 
351
    def run(self, clients):
 
352
        """Normal commands should implement run_on_one_client(), but
 
353
        commands which want to operate on all clients at the same time
 
354
        can override this run() method instead."""
 
355
        for client in clients:
 
356
            self.run_on_one_client(client)
 
357
 
 
358
class PrintCmd(Command):
 
359
    """Abstract class for commands printing client details"""
 
360
    all_keywords = ("Name", "Enabled", "Timeout", "LastCheckedOK",
 
361
                    "Created", "Interval", "Host", "KeyID",
 
362
                    "Fingerprint", "CheckerRunning", "LastEnabled",
 
363
                    "ApprovalPending", "ApprovedByDefault",
 
364
                    "LastApprovalRequest", "ApprovalDelay",
 
365
                    "ApprovalDuration", "Checker", "ExtendedTimeout",
 
366
                    "Expires", "LastCheckerStatus")
 
367
    def run(self, clients):
 
368
        print(self.output(clients))
 
369
 
 
370
class PropertyCmd(Command):
 
371
    """Abstract class for Actions for setting one client property"""
 
372
    def run_on_one_client(self, client):
 
373
        """Set the Client's D-Bus property"""
 
374
        client.Set(client_interface, self.property, self.value_to_set,
 
375
                   dbus_interface=dbus.PROPERTIES_IFACE)
 
376
 
 
377
class ValueArgumentMixIn(object):
 
378
    """Mixin class for commands taking a value as argument"""
 
379
    def __init__(self, value):
 
380
        self.value_to_set = value
 
381
 
 
382
class MillisecondsValueArgumentMixIn(ValueArgumentMixIn):
 
383
    """Mixin class for commands taking a value argument as
 
384
    milliseconds."""
 
385
    @property
 
386
    def value_to_set(self):
 
387
        return self._vts
 
388
    @value_to_set.setter
 
389
    def value_to_set(self, value):
 
390
        """When setting, convert value to a datetime.timedelta"""
 
391
        self._vts = string_to_delta(value).total_seconds() * 1000
 
392
 
 
393
# Actual (non-abstract) command classes
 
394
 
 
395
class PrintTableCmd(PrintCmd):
 
396
    def __init__(self, verbose=False):
 
397
        self.verbose = verbose
 
398
    def output(self, clients):
 
399
        if self.verbose:
 
400
            keywords = self.all_keywords
 
401
        else:
 
402
            keywords = ("Name", "Enabled", "Timeout", "LastCheckedOK")
 
403
        return str(TableOfClients(clients.values(), keywords))
 
404
 
 
405
class DumpJSONCmd(PrintCmd):
 
406
    def output(self, clients):
 
407
        data = {client["Name"]:
 
408
                {key: self.dbus_boolean_to_bool(client[key])
 
409
                 for key in self.all_keywords}
 
410
                for client in clients.values()}
 
411
        return json.dumps(data, indent=4, separators=(',', ': '))
 
412
    @staticmethod
 
413
    def dbus_boolean_to_bool(value):
 
414
        if isinstance(value, dbus.Boolean):
 
415
            value = bool(value)
 
416
        return value
 
417
 
 
418
class IsEnabledCmd(Command):
 
419
    def run_on_one_client(self, client):
 
420
        if self.is_enabled(client):
 
421
            sys.exit(0)
 
422
        sys.exit(1)
 
423
    def is_enabled(self, client):
 
424
        return client.Get(client_interface, "Enabled",
 
425
                          dbus_interface=dbus.PROPERTIES_IFACE)
 
426
 
 
427
class RemoveCmd(Command):
 
428
    def __init__(self, mandos):
 
429
        self.mandos = mandos
 
430
    def run_on_one_client(self, client):
 
431
        self.mandos.RemoveClient(client.__dbus_object_path__)
 
432
 
 
433
class ApproveCmd(Command):
 
434
    def run_on_one_client(self, client):
 
435
        client.Approve(dbus.Boolean(True),
 
436
                       dbus_interface=client_interface)
 
437
 
 
438
class DenyCmd(Command):
 
439
    def run_on_one_client(self, client):
 
440
        client.Approve(dbus.Boolean(False),
 
441
                       dbus_interface=client_interface)
 
442
 
 
443
class EnableCmd(PropertyCmd):
 
444
    property = "Enabled"
 
445
    value_to_set = dbus.Boolean(True)
 
446
 
 
447
class DisableCmd(PropertyCmd):
 
448
    property = "Enabled"
 
449
    value_to_set = dbus.Boolean(False)
 
450
 
 
451
class BumpTimeoutCmd(PropertyCmd):
 
452
    property = "LastCheckedOK"
 
453
    value_to_set = ""
 
454
 
 
455
class StartCheckerCmd(PropertyCmd):
 
456
    property = "CheckerRunning"
 
457
    value_to_set = dbus.Boolean(True)
 
458
 
 
459
class StopCheckerCmd(PropertyCmd):
 
460
    property = "CheckerRunning"
 
461
    value_to_set = dbus.Boolean(False)
 
462
 
 
463
class ApproveByDefaultCmd(PropertyCmd):
 
464
    property = "ApprovedByDefault"
 
465
    value_to_set = dbus.Boolean(True)
 
466
 
 
467
class DenyByDefaultCmd(PropertyCmd):
 
468
    property = "ApprovedByDefault"
 
469
    value_to_set = dbus.Boolean(False)
 
470
 
 
471
class SetCheckerCmd(PropertyCmd, ValueArgumentMixIn):
 
472
    property = "Checker"
 
473
 
 
474
class SetHostCmd(PropertyCmd, ValueArgumentMixIn):
 
475
    property = "Host"
 
476
 
 
477
class SetSecretCmd(PropertyCmd, ValueArgumentMixIn):
 
478
    property = "Secret"
 
479
 
 
480
class SetTimeoutCmd(PropertyCmd, MillisecondsValueArgumentMixIn):
 
481
    property = "Timeout"
 
482
 
 
483
class SetExtendedTimeoutCmd(PropertyCmd,
 
484
                            MillisecondsValueArgumentMixIn):
 
485
    property = "ExtendedTimeout"
 
486
 
 
487
class SetIntervalCmd(PropertyCmd, MillisecondsValueArgumentMixIn):
 
488
    property = "Interval"
 
489
 
 
490
class SetApprovalDelayCmd(PropertyCmd,
 
491
                          MillisecondsValueArgumentMixIn):
 
492
    property = "ApprovalDelay"
 
493
 
 
494
class SetApprovalDurationCmd(PropertyCmd,
 
495
                             MillisecondsValueArgumentMixIn):
 
496
    property = "ApprovalDuration"
 
497
 
347
498
def has_actions(options):
348
499
    return any((options.enable,
349
500
                options.disable,
455
606
    mandos_serv_object_manager = dbus.Interface(
456
607
        mandos_dbus_objc, dbus_interface=dbus.OBJECT_MANAGER_IFACE)
457
608
 
 
609
    commands = []
 
610
 
 
611
    if options.dump_json:
 
612
        commands.append(DumpJSONCmd())
 
613
 
 
614
    if options.enable:
 
615
        commands.append(EnableCmd())
 
616
 
 
617
    if options.disable:
 
618
        commands.append(DisableCmd())
 
619
 
 
620
    if options.bump_timeout:
 
621
        commands.append(BumpTimeoutCmd(options.bump_timeout))
 
622
 
 
623
    if options.start_checker:
 
624
        commands.append(StartCheckerCmd())
 
625
 
 
626
    if options.stop_checker:
 
627
        commands.append(StopCheckerCmd())
 
628
 
 
629
    if options.is_enabled:
 
630
        commands.append(IsEnabledCmd())
 
631
 
 
632
    if options.remove:
 
633
        commands.append(RemoveCmd(mandos_serv))
 
634
 
 
635
    if options.checker is not None:
 
636
        commands.append(SetCheckerCmd())
 
637
 
 
638
    if options.timeout is not None:
 
639
        commands.append(SetTimeoutCmd(options.timeout))
 
640
 
 
641
    if options.extended_timeout:
 
642
        commands.append(
 
643
            SetExtendedTimeoutCmd(options.extended_timeout))
 
644
 
 
645
    if options.interval is not None:
 
646
        command.append(SetIntervalCmd(options.interval))
 
647
 
 
648
    if options.approved_by_default is not None:
 
649
        if options.approved_by_default:
 
650
            command.append(ApproveByDefaultCmd())
 
651
        else:
 
652
            command.append(DenyByDefaultCmd())
 
653
 
 
654
    if options.approval_delay is not None:
 
655
        command.append(SetApprovalDelayCmd(options.approval_delay))
 
656
 
 
657
    if options.approval_duration is not None:
 
658
        command.append(
 
659
            SetApprovalDurationCmd(options.approval_duration))
 
660
 
 
661
    if options.host is not None:
 
662
        command.append(SetHostCmd(options.host))
 
663
 
 
664
    if options.secret is not None:
 
665
        command.append(SetSecretCmd(options.secret))
 
666
 
 
667
    if options.approve:
 
668
        commands.append(ApproveCmd())
 
669
 
 
670
    if options.deny:
 
671
        commands.append(DenyCmd())
 
672
 
 
673
    # If no command option has been given, show table of clients,
 
674
    # optionally verbosely
 
675
    if not commands:
 
676
        commands.append(PrintTableCmd(verbose=options.verbose))
 
677
 
458
678
    # block stderr since dbus library prints to stderr
459
679
    null = os.open(os.path.devnull, os.O_RDWR)
460
680
    stderrcopy = os.dup(sys.stderr.fileno())
493
713
                log.critical("Client not found on server: %r", name)
494
714
                sys.exit(1)
495
715
 
496
 
    if not has_actions(options) and clients:
497
 
        if options.verbose or options.dump_json:
498
 
            keywords = ("Name", "Enabled", "Timeout", "LastCheckedOK",
499
 
                        "Created", "Interval", "Host", "KeyID",
500
 
                        "Fingerprint", "CheckerRunning",
501
 
                        "LastEnabled", "ApprovalPending",
502
 
                        "ApprovedByDefault", "LastApprovalRequest",
503
 
                        "ApprovalDelay", "ApprovalDuration",
504
 
                        "Checker", "ExtendedTimeout", "Expires",
505
 
                        "LastCheckerStatus")
506
 
        else:
507
 
            keywords = defaultkeywords
508
 
 
509
 
        if options.dump_json:
510
 
            json.dump({client["Name"]: {key:
511
 
                                        bool(client[key])
512
 
                                        if isinstance(client[key],
513
 
                                                      dbus.Boolean)
514
 
                                        else client[key]
515
 
                                        for key in keywords}
516
 
                       for client in clients.values()},
517
 
                      fp=sys.stdout, indent=4,
518
 
                      separators=(',', ': '))
519
 
            print()
520
 
        else:
521
 
            print(TableOfClients(clients.values(), keywords))
522
 
    else:
523
 
        # Process each client in the list by all selected options
524
 
        for client in clients:
525
 
 
526
 
            def set_client_prop(prop, value):
527
 
                """Set a Client D-Bus property"""
528
 
                client.Set(client_interface, prop, value,
529
 
                           dbus_interface=dbus.PROPERTIES_IFACE)
530
 
 
531
 
            def set_client_prop_ms(prop, value):
532
 
                """Set a Client D-Bus property, converted
533
 
                from a string to milliseconds."""
534
 
                set_client_prop(prop,
535
 
                                string_to_delta(value).total_seconds()
536
 
                                * 1000)
537
 
 
538
 
            if options.remove:
539
 
                mandos_serv.RemoveClient(client.__dbus_object_path__)
540
 
            if options.enable:
541
 
                set_client_prop("Enabled", dbus.Boolean(True))
542
 
            if options.disable:
543
 
                set_client_prop("Enabled", dbus.Boolean(False))
544
 
            if options.bump_timeout:
545
 
                set_client_prop("LastCheckedOK", "")
546
 
            if options.start_checker:
547
 
                set_client_prop("CheckerRunning", dbus.Boolean(True))
548
 
            if options.stop_checker:
549
 
                set_client_prop("CheckerRunning", dbus.Boolean(False))
550
 
            if options.is_enabled:
551
 
                if client.Get(client_interface, "Enabled",
552
 
                              dbus_interface=dbus.PROPERTIES_IFACE):
553
 
                    sys.exit(0)
554
 
                else:
555
 
                    sys.exit(1)
556
 
            if options.checker is not None:
557
 
                set_client_prop("Checker", options.checker)
558
 
            if options.host is not None:
559
 
                set_client_prop("Host", options.host)
560
 
            if options.interval is not None:
561
 
                set_client_prop_ms("Interval", options.interval)
562
 
            if options.approval_delay is not None:
563
 
                set_client_prop_ms("ApprovalDelay",
564
 
                                   options.approval_delay)
565
 
            if options.approval_duration is not None:
566
 
                set_client_prop_ms("ApprovalDuration",
567
 
                                   options.approval_duration)
568
 
            if options.timeout is not None:
569
 
                set_client_prop_ms("Timeout", options.timeout)
570
 
            if options.extended_timeout is not None:
571
 
                set_client_prop_ms("ExtendedTimeout",
572
 
                                   options.extended_timeout)
573
 
            if options.secret is not None:
574
 
                set_client_prop("Secret",
575
 
                                dbus.ByteArray(options.secret.read()))
576
 
            if options.approved_by_default is not None:
577
 
                set_client_prop("ApprovedByDefault",
578
 
                                dbus.Boolean(options
579
 
                                             .approved_by_default))
580
 
            if options.approve:
581
 
                client.Approve(dbus.Boolean(True),
582
 
                               dbus_interface=client_interface)
583
 
            elif options.deny:
584
 
                client.Approve(dbus.Boolean(False),
585
 
                               dbus_interface=client_interface)
 
716
    # Run all commands on clients
 
717
    for command in commands:
 
718
        command.run(clients)
586
719
 
587
720
 
588
721
class Test_milliseconds_to_string(unittest.TestCase):