/mandos/release

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

« back to all changes in this revision

Viewing changes to mandos

  • Committer: Teddy Hogeborn
  • Date: 2011-11-29 20:58:01 UTC
  • mto: (237.12.13 mandos-persistent)
  • mto: This revision was merged to the branch mainline in revision 290.
  • Revision ID: teddy@recompile.se-20111129205801-oufxx7hwq0nq2zau
* mandos (main): Bug fix: Syntax fix when restoring settings.

Show diffs side-by-side

added added

removed removed

Lines of Context:
11
11
# "AvahiService" class, and some lines in "main".
12
12
13
13
# Everything else is
14
 
# Copyright © 2008-2012 Teddy Hogeborn
15
 
# Copyright © 2008-2012 Björn Påhlsson
 
14
# Copyright © 2008-2011 Teddy Hogeborn
 
15
# Copyright © 2008-2011 Björn Påhlsson
16
16
17
17
# This program is free software: you can redistribute it and/or modify
18
18
# it under the terms of the GNU General Public License as published by
65
65
import types
66
66
import binascii
67
67
import tempfile
68
 
import itertools
69
68
 
70
69
import dbus
71
70
import dbus.service
86
85
    except ImportError:
87
86
        SO_BINDTODEVICE = None
88
87
 
89
 
version = "1.5.3"
 
88
 
 
89
version = "1.4.1"
90
90
stored_state_file = "clients.pickle"
91
91
 
92
92
logger = logging.getLogger()
111
111
        return interface_index
112
112
 
113
113
 
114
 
def initlogger(debug, level=logging.WARNING):
 
114
def initlogger(level=logging.WARNING):
115
115
    """init logger and add loglevel"""
116
116
    
117
117
    syslogger.setFormatter(logging.Formatter
119
119
                            ' %(message)s'))
120
120
    logger.addHandler(syslogger)
121
121
    
122
 
    if debug:
123
 
        console = logging.StreamHandler()
124
 
        console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
125
 
                                               ' [%(process)d]:'
126
 
                                               ' %(levelname)s:'
127
 
                                               ' %(message)s'))
128
 
        logger.addHandler(console)
 
122
    console = logging.StreamHandler()
 
123
    console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
 
124
                                           ' [%(process)d]:'
 
125
                                           ' %(levelname)s:'
 
126
                                           ' %(message)s'))
 
127
    logger.addHandler(console)
129
128
    logger.setLevel(level)
130
129
 
131
130
 
143
142
        self.gnupg.options.meta_interactive = False
144
143
        self.gnupg.options.homedir = self.tempdir
145
144
        self.gnupg.options.extra_args.extend(['--force-mdc',
146
 
                                              '--quiet',
147
 
                                              '--no-use-agent'])
 
145
                                              '--quiet'])
148
146
    
149
147
    def __enter__(self):
150
148
        return self
176
174
    
177
175
    def encrypt(self, data, password):
178
176
        self.gnupg.passphrase = self.password_encode(password)
179
 
        with open(os.devnull, "w") as devnull:
 
177
        with open(os.devnull) as devnull:
180
178
            try:
181
179
                proc = self.gnupg.run(['--symmetric'],
182
180
                                      create_fhs=['stdin', 'stdout'],
193
191
    
194
192
    def decrypt(self, data, password):
195
193
        self.gnupg.passphrase = self.password_encode(password)
196
 
        with open(os.devnull, "w") as devnull:
 
194
        with open(os.devnull) as devnull:
197
195
            try:
198
196
                proc = self.gnupg.run(['--decrypt'],
199
197
                                      create_fhs=['stdin', 'stdout'],
200
198
                                      attach_fhs={'stderr': devnull})
201
 
                with contextlib.closing(proc.handles['stdin']) as f:
 
199
                with contextlib.closing(proc.handles['stdin'] ) as f:
202
200
                    f.write(data)
203
201
                with contextlib.closing(proc.handles['stdout']) as f:
204
202
                    decrypted_plaintext = f.read()
277
275
        try:
278
276
            self.add()
279
277
        except dbus.exceptions.DBusException as error:
280
 
            logger.critical("D-Bus Exception", exc_info=error)
 
278
            logger.critical("DBusException: %s", error)
281
279
            self.cleanup()
282
280
            os._exit(1)
283
281
        self.rename_count += 1
414
412
    interval:   datetime.timedelta(); How often to start a new checker
415
413
    last_approval_request: datetime.datetime(); (UTC) or None
416
414
    last_checked_ok: datetime.datetime(); (UTC) or None
 
415
 
417
416
    last_checker_status: integer between 0 and 255 reflecting exit
418
417
                         status of last checker. -1 reflects crashed
419
 
                         checker, -2 means no checker completed yet.
 
418
                         checker, or None.
420
419
    last_enabled: datetime.datetime(); (UTC) or None
421
420
    name:       string; from the config file, used in log messages and
422
421
                        D-Bus identifiers
423
422
    secret:     bytestring; sent verbatim (over TLS) to client
424
423
    timeout:    datetime.timedelta(); How long from last_checked_ok
425
424
                                      until this client is disabled
426
 
    extended_timeout:   extra long timeout when secret has been sent
 
425
    extended_timeout:   extra long timeout when password has been sent
427
426
    runtime_expansions: Allowed attributes for runtime expansion.
428
427
    expires:    datetime.datetime(); time (UTC) when a client will be
429
428
                disabled, or None
433
432
                          "created", "enabled", "fingerprint",
434
433
                          "host", "interval", "last_checked_ok",
435
434
                          "last_enabled", "name", "timeout")
436
 
    client_defaults = { "timeout": "5m",
437
 
                        "extended_timeout": "15m",
438
 
                        "interval": "2m",
439
 
                        "checker": "fping -q -- %%(host)s",
440
 
                        "host": "",
441
 
                        "approval_delay": "0s",
442
 
                        "approval_duration": "1s",
443
 
                        "approved_by_default": "True",
444
 
                        "enabled": "True",
445
 
                        }
446
435
    
447
436
    def timeout_milliseconds(self):
448
437
        "Return the 'timeout' attribute in milliseconds"
458
447
    
459
448
    def approval_delay_milliseconds(self):
460
449
        return timedelta_to_milliseconds(self.approval_delay)
461
 
 
462
 
    @staticmethod
463
 
    def config_parser(config):
464
 
        """Construct a new dict of client settings of this form:
465
 
        { client_name: {setting_name: value, ...}, ...}
466
 
        with exceptions for any special settings as defined above.
467
 
        NOTE: Must be a pure function. Must return the same result
468
 
        value given the same arguments.
469
 
        """
470
 
        settings = {}
471
 
        for client_name in config.sections():
472
 
            section = dict(config.items(client_name))
473
 
            client = settings[client_name] = {}
474
 
            
475
 
            client["host"] = section["host"]
476
 
            # Reformat values from string types to Python types
477
 
            client["approved_by_default"] = config.getboolean(
478
 
                client_name, "approved_by_default")
479
 
            client["enabled"] = config.getboolean(client_name,
480
 
                                                  "enabled")
481
 
            
482
 
            client["fingerprint"] = (section["fingerprint"].upper()
483
 
                                     .replace(" ", ""))
484
 
            if "secret" in section:
485
 
                client["secret"] = section["secret"].decode("base64")
486
 
            elif "secfile" in section:
487
 
                with open(os.path.expanduser(os.path.expandvars
488
 
                                             (section["secfile"])),
489
 
                          "rb") as secfile:
490
 
                    client["secret"] = secfile.read()
491
 
            else:
492
 
                raise TypeError("No secret or secfile for section %s"
493
 
                                % section)
494
 
            client["timeout"] = string_to_delta(section["timeout"])
495
 
            client["extended_timeout"] = string_to_delta(
496
 
                section["extended_timeout"])
497
 
            client["interval"] = string_to_delta(section["interval"])
498
 
            client["approval_delay"] = string_to_delta(
499
 
                section["approval_delay"])
500
 
            client["approval_duration"] = string_to_delta(
501
 
                section["approval_duration"])
502
 
            client["checker_command"] = section["checker"]
503
 
            client["last_approval_request"] = None
504
 
            client["last_checked_ok"] = None
505
 
            client["last_checker_status"] = -2
506
 
        
507
 
        return settings
508
 
        
509
 
        
510
 
    def __init__(self, settings, name = None):
 
450
    
 
451
    def __init__(self, name = None, config=None):
511
452
        """Note: the 'checker' key in 'config' sets the
512
453
        'checker_command' attribute and *not* the 'checker'
513
454
        attribute."""
514
455
        self.name = name
515
 
        # adding all client settings
516
 
        for setting, value in settings.iteritems():
517
 
            setattr(self, setting, value)
518
 
        
519
 
        if self.enabled:
520
 
            if not hasattr(self, "last_enabled"):
521
 
                self.last_enabled = datetime.datetime.utcnow()
522
 
            if not hasattr(self, "expires"):
523
 
                self.expires = (datetime.datetime.utcnow()
524
 
                                + self.timeout)
525
 
        else:
526
 
            self.last_enabled = None
527
 
            self.expires = None
528
 
       
 
456
        if config is None:
 
457
            config = {}
529
458
        logger.debug("Creating client %r", self.name)
530
459
        # Uppercase and remove spaces from fingerprint for later
531
460
        # comparison purposes with return value from the fingerprint()
532
461
        # function
 
462
        self.fingerprint = (config["fingerprint"].upper()
 
463
                            .replace(" ", ""))
533
464
        logger.debug("  Fingerprint: %s", self.fingerprint)
534
 
        self.created = settings.get("created",
535
 
                                    datetime.datetime.utcnow())
536
 
 
537
 
        # attributes specific for this server instance
 
465
        if "secret" in config:
 
466
            self.secret = config["secret"].decode("base64")
 
467
        elif "secfile" in config:
 
468
            with open(os.path.expanduser(os.path.expandvars
 
469
                                         (config["secfile"])),
 
470
                      "rb") as secfile:
 
471
                self.secret = secfile.read()
 
472
        else:
 
473
            raise TypeError("No secret or secfile for client %s"
 
474
                            % self.name)
 
475
        self.host = config.get("host", "")
 
476
        self.created = datetime.datetime.utcnow()
 
477
        self.enabled = config.get("enabled", True)
 
478
        self.last_approval_request = None
 
479
        if self.enabled:
 
480
            self.last_enabled = datetime.datetime.utcnow()
 
481
        else:
 
482
            self.last_enabled = None
 
483
        self.last_checked_ok = None
 
484
        self.last_checker_status = None
 
485
        self.timeout = string_to_delta(config["timeout"])
 
486
        self.extended_timeout = string_to_delta(config
 
487
                                                ["extended_timeout"])
 
488
        self.interval = string_to_delta(config["interval"])
538
489
        self.checker = None
539
490
        self.checker_initiator_tag = None
540
491
        self.disable_initiator_tag = None
 
492
        if self.enabled:
 
493
            self.expires = datetime.datetime.utcnow() + self.timeout
 
494
        else:
 
495
            self.expires = None
541
496
        self.checker_callback_tag = None
 
497
        self.checker_command = config["checker"]
542
498
        self.current_checker_command = None
543
499
        self.approved = None
 
500
        self.approved_by_default = config.get("approved_by_default",
 
501
                                              True)
544
502
        self.approvals_pending = 0
 
503
        self.approval_delay = string_to_delta(
 
504
            config["approval_delay"])
 
505
        self.approval_duration = string_to_delta(
 
506
            config["approval_duration"])
545
507
        self.changedstate = (multiprocessing_manager
546
508
                             .Condition(multiprocessing_manager
547
509
                                        .Lock()))
627
589
            logger.warning("Checker for %(name)s crashed?",
628
590
                           vars(self))
629
591
    
630
 
    def checked_ok(self):
631
 
        """Assert that the client has been seen, alive and well."""
632
 
        self.last_checked_ok = datetime.datetime.utcnow()
633
 
        self.last_checker_status = 0
634
 
        self.bump_timeout()
635
 
    
636
 
    def bump_timeout(self, timeout=None):
637
 
        """Bump up the timeout for this client."""
 
592
    def checked_ok(self, timeout=None):
 
593
        """Bump up the timeout for this client.
 
594
        
 
595
        This should only be called when the client has been seen,
 
596
        alive and well.
 
597
        """
638
598
        if timeout is None:
639
599
            timeout = self.timeout
 
600
        self.last_checked_ok = datetime.datetime.utcnow()
640
601
        if self.disable_initiator_tag is not None:
641
602
            gobject.source_remove(self.disable_initiator_tag)
642
603
        if getattr(self, "enabled", False):
693
654
                try:
694
655
                    command = self.checker_command % escaped_attrs
695
656
                except TypeError as error:
696
 
                    logger.error('Could not format string "%s"',
697
 
                                 self.checker_command, exc_info=error)
 
657
                    logger.error('Could not format string "%s":'
 
658
                                 ' %s', self.checker_command, error)
698
659
                    return True # Try again later
699
660
            self.current_checker_command = command
700
661
            try:
718
679
                    gobject.source_remove(self.checker_callback_tag)
719
680
                    self.checker_callback(pid, status, command)
720
681
            except OSError as error:
721
 
                logger.error("Failed to start subprocess",
722
 
                             exc_info=error)
 
682
                logger.error("Failed to start subprocess: %s",
 
683
                             error)
723
684
        # Re-run this periodically if run by gobject.timeout_add
724
685
        return True
725
686
    
732
693
            return
733
694
        logger.debug("Stopping checker for %(name)s", vars(self))
734
695
        try:
735
 
            self.checker.terminate()
 
696
            os.kill(self.checker.pid, signal.SIGTERM)
736
697
            #time.sleep(0.5)
737
698
            #if self.checker.poll() is None:
738
 
            #    self.checker.kill()
 
699
            #    os.kill(self.checker.pid, signal.SIGKILL)
739
700
        except OSError as error:
740
701
            if error.errno != errno.ESRCH: # No such process
741
702
                raise
772
733
    return decorator
773
734
 
774
735
 
775
 
def dbus_interface_annotations(dbus_interface):
776
 
    """Decorator for marking functions returning interface annotations.
777
 
    
778
 
    Usage:
779
 
    
780
 
    @dbus_interface_annotations("org.example.Interface")
781
 
    def _foo(self):  # Function name does not matter
782
 
        return {"org.freedesktop.DBus.Deprecated": "true",
783
 
                "org.freedesktop.DBus.Property.EmitsChangedSignal":
784
 
                    "false"}
785
 
    """
786
 
    def decorator(func):
787
 
        func._dbus_is_interface = True
788
 
        func._dbus_interface = dbus_interface
789
 
        func._dbus_name = dbus_interface
790
 
        return func
791
 
    return decorator
792
 
 
793
 
 
794
 
def dbus_annotations(annotations):
795
 
    """Decorator to annotate D-Bus methods, signals or properties
796
 
    Usage:
797
 
    
798
 
    @dbus_service_property("org.example.Interface", signature="b",
799
 
                           access="r")
800
 
    @dbus_annotations({{"org.freedesktop.DBus.Deprecated": "true",
801
 
                        "org.freedesktop.DBus.Property."
802
 
                        "EmitsChangedSignal": "false"})
803
 
    def Property_dbus_property(self):
804
 
        return dbus.Boolean(False)
805
 
    """
806
 
    def decorator(func):
807
 
        func._dbus_annotations = annotations
808
 
        return func
809
 
    return decorator
810
 
 
811
 
 
812
736
class DBusPropertyException(dbus.exceptions.DBusException):
813
737
    """A base class for D-Bus property-related exceptions
814
738
    """
837
761
    """
838
762
    
839
763
    @staticmethod
840
 
    def _is_dbus_thing(thing):
841
 
        """Returns a function testing if an attribute is a D-Bus thing
842
 
        
843
 
        If called like _is_dbus_thing("method") it returns a function
844
 
        suitable for use as predicate to inspect.getmembers().
845
 
        """
846
 
        return lambda obj: getattr(obj, "_dbus_is_{0}".format(thing),
847
 
                                   False)
 
764
    def _is_dbus_property(obj):
 
765
        return getattr(obj, "_dbus_is_property", False)
848
766
    
849
 
    def _get_all_dbus_things(self, thing):
 
767
    def _get_all_dbus_properties(self):
850
768
        """Returns a generator of (name, attribute) pairs
851
769
        """
852
 
        return ((getattr(athing.__get__(self), "_dbus_name",
853
 
                         name),
854
 
                 athing.__get__(self))
 
770
        return ((prop.__get__(self)._dbus_name, prop.__get__(self))
855
771
                for cls in self.__class__.__mro__
856
 
                for name, athing in
857
 
                inspect.getmembers(cls,
858
 
                                   self._is_dbus_thing(thing)))
 
772
                for name, prop in
 
773
                inspect.getmembers(cls, self._is_dbus_property))
859
774
    
860
775
    def _get_dbus_property(self, interface_name, property_name):
861
776
        """Returns a bound method if one exists which is a D-Bus
863
778
        """
864
779
        for cls in  self.__class__.__mro__:
865
780
            for name, value in (inspect.getmembers
866
 
                                (cls,
867
 
                                 self._is_dbus_thing("property"))):
 
781
                                (cls, self._is_dbus_property)):
868
782
                if (value._dbus_name == property_name
869
783
                    and value._dbus_interface == interface_name):
870
784
                    return value.__get__(self)
899
813
            # signatures other than "ay".
900
814
            if prop._dbus_signature != "ay":
901
815
                raise ValueError
902
 
            value = dbus.ByteArray(b''.join(chr(byte)
903
 
                                            for byte in value))
 
816
            value = dbus.ByteArray(''.join(unichr(byte)
 
817
                                           for byte in value))
904
818
        prop(value)
905
819
    
906
820
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
912
826
        Note: Will not include properties with access="write".
913
827
        """
914
828
        properties = {}
915
 
        for name, prop in self._get_all_dbus_things("property"):
 
829
        for name, prop in self._get_all_dbus_properties():
916
830
            if (interface_name
917
831
                and interface_name != prop._dbus_interface):
918
832
                # Interface non-empty but did not match
933
847
                         path_keyword='object_path',
934
848
                         connection_keyword='connection')
935
849
    def Introspect(self, object_path, connection):
936
 
        """Overloading of standard D-Bus method.
937
 
        
938
 
        Inserts property tags and interface annotation tags.
 
850
        """Standard D-Bus method, overloaded to insert property tags.
939
851
        """
940
852
        xmlstring = dbus.service.Object.Introspect(self, object_path,
941
853
                                                   connection)
948
860
                e.setAttribute("access", prop._dbus_access)
949
861
                return e
950
862
            for if_tag in document.getElementsByTagName("interface"):
951
 
                # Add property tags
952
863
                for tag in (make_tag(document, name, prop)
953
864
                            for name, prop
954
 
                            in self._get_all_dbus_things("property")
 
865
                            in self._get_all_dbus_properties()
955
866
                            if prop._dbus_interface
956
867
                            == if_tag.getAttribute("name")):
957
868
                    if_tag.appendChild(tag)
958
 
                # Add annotation tags
959
 
                for typ in ("method", "signal", "property"):
960
 
                    for tag in if_tag.getElementsByTagName(typ):
961
 
                        annots = dict()
962
 
                        for name, prop in (self.
963
 
                                           _get_all_dbus_things(typ)):
964
 
                            if (name == tag.getAttribute("name")
965
 
                                and prop._dbus_interface
966
 
                                == if_tag.getAttribute("name")):
967
 
                                annots.update(getattr
968
 
                                              (prop,
969
 
                                               "_dbus_annotations",
970
 
                                               {}))
971
 
                        for name, value in annots.iteritems():
972
 
                            ann_tag = document.createElement(
973
 
                                "annotation")
974
 
                            ann_tag.setAttribute("name", name)
975
 
                            ann_tag.setAttribute("value", value)
976
 
                            tag.appendChild(ann_tag)
977
 
                # Add interface annotation tags
978
 
                for annotation, value in dict(
979
 
                    itertools.chain(
980
 
                        *(annotations().iteritems()
981
 
                          for name, annotations in
982
 
                          self._get_all_dbus_things("interface")
983
 
                          if name == if_tag.getAttribute("name")
984
 
                          ))).iteritems():
985
 
                    ann_tag = document.createElement("annotation")
986
 
                    ann_tag.setAttribute("name", annotation)
987
 
                    ann_tag.setAttribute("value", value)
988
 
                    if_tag.appendChild(ann_tag)
989
869
                # Add the names to the return values for the
990
870
                # "org.freedesktop.DBus.Properties" methods
991
871
                if (if_tag.getAttribute("name")
1006
886
        except (AttributeError, xml.dom.DOMException,
1007
887
                xml.parsers.expat.ExpatError) as error:
1008
888
            logger.error("Failed to override Introspection method",
1009
 
                         exc_info=error)
 
889
                         error)
1010
890
        return xmlstring
1011
891
 
1012
892
 
1026
906
    def __new__(mcs, name, bases, attr):
1027
907
        # Go through all the base classes which could have D-Bus
1028
908
        # methods, signals, or properties in them
1029
 
        old_interface_names = []
1030
909
        for base in (b for b in bases
1031
910
                     if issubclass(b, dbus.service.Object)):
1032
911
            # Go though all attributes of the base class
1042
921
                alt_interface = (attribute._dbus_interface
1043
922
                                 .replace("se.recompile.Mandos",
1044
923
                                          "se.bsnet.fukt.Mandos"))
1045
 
                if alt_interface != attribute._dbus_interface:
1046
 
                    old_interface_names.append(alt_interface)
1047
924
                # Is this a D-Bus signal?
1048
925
                if getattr(attribute, "_dbus_is_signal", False):
1049
926
                    # Extract the original non-method function by
1064
941
                                nonmethod_func.func_name,
1065
942
                                nonmethod_func.func_defaults,
1066
943
                                nonmethod_func.func_closure)))
1067
 
                    # Copy annotations, if any
1068
 
                    try:
1069
 
                        new_function._dbus_annotations = (
1070
 
                            dict(attribute._dbus_annotations))
1071
 
                    except AttributeError:
1072
 
                        pass
1073
944
                    # Define a creator of a function to call both the
1074
945
                    # old and new functions, so both the old and new
1075
946
                    # signals gets sent when the function is called
1103
974
                                        attribute.func_name,
1104
975
                                        attribute.func_defaults,
1105
976
                                        attribute.func_closure)))
1106
 
                    # Copy annotations, if any
1107
 
                    try:
1108
 
                        attr[attrname]._dbus_annotations = (
1109
 
                            dict(attribute._dbus_annotations))
1110
 
                    except AttributeError:
1111
 
                        pass
1112
977
                # Is this a D-Bus property?
1113
978
                elif getattr(attribute, "_dbus_is_property", False):
1114
979
                    # Create a new, but exactly alike, function
1128
993
                                        attribute.func_name,
1129
994
                                        attribute.func_defaults,
1130
995
                                        attribute.func_closure)))
1131
 
                    # Copy annotations, if any
1132
 
                    try:
1133
 
                        attr[attrname]._dbus_annotations = (
1134
 
                            dict(attribute._dbus_annotations))
1135
 
                    except AttributeError:
1136
 
                        pass
1137
 
                # Is this a D-Bus interface?
1138
 
                elif getattr(attribute, "_dbus_is_interface", False):
1139
 
                    # Create a new, but exactly alike, function
1140
 
                    # object.  Decorate it to be a new D-Bus interface
1141
 
                    # with the alternate D-Bus interface name.  Add it
1142
 
                    # to the class.
1143
 
                    attr[attrname] = (dbus_interface_annotations
1144
 
                                      (alt_interface)
1145
 
                                      (types.FunctionType
1146
 
                                       (attribute.func_code,
1147
 
                                        attribute.func_globals,
1148
 
                                        attribute.func_name,
1149
 
                                        attribute.func_defaults,
1150
 
                                        attribute.func_closure)))
1151
 
        # Deprecate all old interfaces
1152
 
        basename="_AlternateDBusNamesMetaclass_interface_annotation{0}"
1153
 
        for old_interface_name in old_interface_names:
1154
 
            @dbus_interface_annotations(old_interface_name)
1155
 
            def func(self):
1156
 
                return { "org.freedesktop.DBus.Deprecated": "true" }
1157
 
            # Find an unused name
1158
 
            for aname in (basename.format(i) for i in
1159
 
                          itertools.count()):
1160
 
                if aname not in attr:
1161
 
                    attr[aname] = func
1162
 
                    break
1163
996
        return type.__new__(mcs, name, bases, attr)
1164
997
 
1165
998
 
1179
1012
    def __init__(self, bus = None, *args, **kwargs):
1180
1013
        self.bus = bus
1181
1014
        Client.__init__(self, *args, **kwargs)
 
1015
        
 
1016
        self._approvals_pending = 0
1182
1017
        # Only now, when this client is initialized, can it show up on
1183
1018
        # the D-Bus
1184
1019
        client_object_name = unicode(self.name).translate(
1230
1065
                                       checker is not None)
1231
1066
    last_checked_ok = notifychangeproperty(datetime_to_dbus,
1232
1067
                                           "LastCheckedOK")
1233
 
    last_checker_status = notifychangeproperty(dbus.Int16,
1234
 
                                               "LastCheckerStatus")
1235
1068
    last_approval_request = notifychangeproperty(
1236
1069
        datetime_to_dbus, "LastApprovalRequest")
1237
1070
    approved_by_default = notifychangeproperty(dbus.Boolean,
1315
1148
    ## D-Bus methods, signals & properties
1316
1149
    _interface = "se.recompile.Mandos.Client"
1317
1150
    
1318
 
    ## Interfaces
1319
 
    
1320
 
    @dbus_interface_annotations(_interface)
1321
 
    def _foo(self):
1322
 
        return { "org.freedesktop.DBus.Property.EmitsChangedSignal":
1323
 
                     "false"}
1324
 
    
1325
1151
    ## Signals
1326
1152
    
1327
1153
    # CheckerCompleted - signal
1363
1189
        "D-Bus signal"
1364
1190
        return self.need_approval()
1365
1191
    
 
1192
    # NeRwequest - signal
 
1193
    @dbus.service.signal(_interface, signature="s")
 
1194
    def NewRequest(self, ip):
 
1195
        """D-Bus signal
 
1196
        Is sent after a client request a password.
 
1197
        """
 
1198
        pass
 
1199
    
1366
1200
    ## Methods
1367
1201
    
1368
1202
    # Approve - method
1478
1312
            return
1479
1313
        return datetime_to_dbus(self.last_checked_ok)
1480
1314
    
1481
 
    # LastCheckerStatus - property
1482
 
    @dbus_service_property(_interface, signature="n",
1483
 
                           access="read")
1484
 
    def LastCheckerStatus_dbus_property(self):
1485
 
        return dbus.Int16(self.last_checker_status)
1486
 
    
1487
1315
    # Expires - property
1488
1316
    @dbus_service_property(_interface, signature="s", access="read")
1489
1317
    def Expires_dbus_property(self):
1501
1329
        if value is None:       # get
1502
1330
            return dbus.UInt64(self.timeout_milliseconds())
1503
1331
        self.timeout = datetime.timedelta(0, 0, 0, value)
 
1332
        if getattr(self, "disable_initiator_tag", None) is None:
 
1333
            return
1504
1334
        # Reschedule timeout
1505
 
        if self.enabled:
1506
 
            now = datetime.datetime.utcnow()
1507
 
            time_to_die = timedelta_to_milliseconds(
1508
 
                (self.last_checked_ok + self.timeout) - now)
1509
 
            if time_to_die <= 0:
1510
 
                # The timeout has passed
1511
 
                self.disable()
1512
 
            else:
1513
 
                self.expires = (now +
1514
 
                                datetime.timedelta(milliseconds =
1515
 
                                                   time_to_die))
1516
 
                if (getattr(self, "disable_initiator_tag", None)
1517
 
                    is None):
1518
 
                    return
1519
 
                gobject.source_remove(self.disable_initiator_tag)
1520
 
                self.disable_initiator_tag = (gobject.timeout_add
1521
 
                                              (time_to_die,
1522
 
                                               self.disable))
 
1335
        gobject.source_remove(self.disable_initiator_tag)
 
1336
        self.disable_initiator_tag = None
 
1337
        self.expires = None
 
1338
        time_to_die = timedelta_to_milliseconds((self
 
1339
                                                 .last_checked_ok
 
1340
                                                 + self.timeout)
 
1341
                                                - datetime.datetime
 
1342
                                                .utcnow())
 
1343
        if time_to_die <= 0:
 
1344
            # The timeout has passed
 
1345
            self.disable()
 
1346
        else:
 
1347
            self.expires = (datetime.datetime.utcnow()
 
1348
                            + datetime.timedelta(milliseconds =
 
1349
                                                 time_to_die))
 
1350
            self.disable_initiator_tag = (gobject.timeout_add
 
1351
                                          (time_to_die, self.disable))
1523
1352
    
1524
1353
    # ExtendedTimeout - property
1525
1354
    @dbus_service_property(_interface, signature="t",
1681
1510
                except KeyError:
1682
1511
                    return
1683
1512
                
 
1513
                if self.server.use_dbus:
 
1514
                    # Emit D-Bus signal
 
1515
                    client.NewRequest(str(self.client_address))
 
1516
                
1684
1517
                if client.approval_delay:
1685
1518
                    delay = client.approval_delay
1686
1519
                    client.approvals_pending += 1
1750
1583
                
1751
1584
                logger.info("Sending secret to %s", client.name)
1752
1585
                # bump the timeout using extended_timeout
1753
 
                client.bump_timeout(client.extended_timeout)
 
1586
                client.checked_ok(client.extended_timeout)
1754
1587
                if self.server.use_dbus:
1755
1588
                    # Emit D-Bus signal
1756
1589
                    client.GotSecret()
1832
1665
    def sub_process_main(self, request, address):
1833
1666
        try:
1834
1667
            self.finish_request(request, address)
1835
 
        except Exception:
 
1668
        except:
1836
1669
            self.handle_error(request, address)
1837
1670
        self.close_request(request)
1838
1671
    
2099
1932
        sys.exit()
2100
1933
    if not noclose:
2101
1934
        # Close all standard open file descriptors
2102
 
        null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
 
1935
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
2103
1936
        if not stat.S_ISCHR(os.fstat(null).st_mode):
2104
1937
            raise OSError(errno.ENODEV,
2105
1938
                          "%s not a character device"
2106
 
                          % os.devnull)
 
1939
                          % os.path.devnull)
2107
1940
        os.dup2(null, sys.stdin.fileno())
2108
1941
        os.dup2(null, sys.stdout.fileno())
2109
1942
        os.dup2(null, sys.stderr.fileno())
2217
2050
                                     stored_state_file)
2218
2051
    
2219
2052
    if debug:
2220
 
        initlogger(debug, logging.DEBUG)
 
2053
        initlogger(logging.DEBUG)
2221
2054
    else:
2222
2055
        if not debuglevel:
2223
 
            initlogger(debug)
 
2056
            initlogger()
2224
2057
        else:
2225
2058
            level = getattr(logging, debuglevel.upper())
2226
 
            initlogger(debug, level)
 
2059
            initlogger(level)
2227
2060
    
2228
2061
    if server_settings["servicename"] != "Mandos":
2229
2062
        syslogger.setFormatter(logging.Formatter
2232
2065
                                % server_settings["servicename"]))
2233
2066
    
2234
2067
    # Parse config file with clients
2235
 
    client_config = configparser.SafeConfigParser(Client
2236
 
                                                  .client_defaults)
 
2068
    client_defaults = { "timeout": "5m",
 
2069
                        "extended_timeout": "15m",
 
2070
                        "interval": "2m",
 
2071
                        "checker": "fping -q -- %%(host)s",
 
2072
                        "host": "",
 
2073
                        "approval_delay": "0s",
 
2074
                        "approval_duration": "1s",
 
2075
                        }
 
2076
    client_config = configparser.SafeConfigParser(client_defaults)
2237
2077
    client_config.read(os.path.join(server_settings["configdir"],
2238
2078
                                    "clients.conf"))
2239
2079
    
2256
2096
        except IOError:
2257
2097
            logger.error("Could not open file %r", pidfilename)
2258
2098
    
2259
 
    for name in ("_mandos", "mandos", "nobody"):
 
2099
    try:
 
2100
        uid = pwd.getpwnam("_mandos").pw_uid
 
2101
        gid = pwd.getpwnam("_mandos").pw_gid
 
2102
    except KeyError:
2260
2103
        try:
2261
 
            uid = pwd.getpwnam(name).pw_uid
2262
 
            gid = pwd.getpwnam(name).pw_gid
2263
 
            break
 
2104
            uid = pwd.getpwnam("mandos").pw_uid
 
2105
            gid = pwd.getpwnam("mandos").pw_gid
2264
2106
        except KeyError:
2265
 
            continue
2266
 
    else:
2267
 
        uid = 65534
2268
 
        gid = 65534
 
2107
            try:
 
2108
                uid = pwd.getpwnam("nobody").pw_uid
 
2109
                gid = pwd.getpwnam("nobody").pw_gid
 
2110
            except KeyError:
 
2111
                uid = 65534
 
2112
                gid = 65534
2269
2113
    try:
2270
2114
        os.setgid(gid)
2271
2115
        os.setuid(uid)
2288
2132
         .gnutls_global_set_log_function(debug_gnutls))
2289
2133
        
2290
2134
        # Redirect stdin so all checkers get /dev/null
2291
 
        null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
 
2135
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
2292
2136
        os.dup2(null, sys.stdin.fileno())
2293
2137
        if null > 2:
2294
2138
            os.close(null)
 
2139
    else:
 
2140
        # No console logging
 
2141
        logger.removeHandler(console)
2295
2142
    
2296
2143
    # Need to fork before connecting to D-Bus
2297
2144
    if not debug:
2298
2145
        # Close all input and output, do double fork, etc.
2299
2146
        daemon()
2300
2147
    
2301
 
    gobject.threads_init()
2302
 
    
2303
2148
    global main_loop
2304
2149
    # From the Avahi example code
2305
 
    DBusGMainLoop(set_as_default=True)
 
2150
    DBusGMainLoop(set_as_default=True )
2306
2151
    main_loop = gobject.MainLoop()
2307
2152
    bus = dbus.SystemBus()
2308
2153
    # End of Avahi example code
2335
2180
        client_class = functools.partial(ClientDBusTransitional,
2336
2181
                                         bus = bus)
2337
2182
    
2338
 
    client_settings = Client.config_parser(client_config)
 
2183
    special_settings = {
 
2184
        # Some settings need to be accessd by special methods;
 
2185
        # booleans need .getboolean(), etc.  Here is a list of them:
 
2186
        "approved_by_default":
 
2187
            lambda section:
 
2188
            client_config.getboolean(section, "approved_by_default"),
 
2189
        "enabled":
 
2190
            lambda section:
 
2191
            client_config.getboolean(section, "enabled"),
 
2192
        }
 
2193
    # Construct a new dict of client settings of this form:
 
2194
    # { client_name: {setting_name: value, ...}, ...}
 
2195
    # with exceptions for any special settings as defined above
 
2196
    client_settings = dict((clientname,
 
2197
                           dict((setting,
 
2198
                                 (value
 
2199
                                  if setting not in special_settings
 
2200
                                  else special_settings[setting]
 
2201
                                  (clientname)))
 
2202
                                for setting, value in
 
2203
                                client_config.items(clientname)))
 
2204
                          for clientname in client_config.sections())
 
2205
    
2339
2206
    old_client_settings = {}
2340
 
    clients_data = {}
 
2207
    clients_data = []
2341
2208
    
2342
2209
    # Get client data and settings from last running state.
2343
2210
    if server_settings["restore"]:
2351
2218
                           .format(e))
2352
2219
            if e.errno != errno.ENOENT:
2353
2220
                raise
2354
 
        except EOFError as e:
2355
 
            logger.warning("Could not load persistent state: "
2356
 
                           "EOFError: {0}".format(e))
2357
2221
    
2358
2222
    with PGPEngine() as pgp:
2359
 
        for client_name, client in clients_data.iteritems():
 
2223
        for client in clients_data:
 
2224
            client_name = client["name"]
 
2225
            
2360
2226
            # Decide which value to use after restoring saved state.
2361
2227
            # We have three different values: Old config file,
2362
2228
            # new config file, and saved state.
2375
2241
                    pass
2376
2242
            
2377
2243
            # Clients who has passed its expire date can still be
2378
 
            # enabled if its last checker was successful.  Clients
2379
 
            # whose checker succeeded before we stored its state is
2380
 
            # assumed to have successfully run all checkers during
2381
 
            # downtime.
 
2244
            # enabled if its last checker was sucessful.  Clients
 
2245
            # whose checker failed before we stored its state is
 
2246
            # assumed to have failed all checkers during downtime.
2382
2247
            if client["enabled"]:
2383
 
                if datetime.datetime.utcnow() >= client["expires"]:
2384
 
                    if not client["last_checked_ok"]:
2385
 
                        logger.warning(
2386
 
                            "disabling client {0} - Client never "
2387
 
                            "performed a successful checker"
2388
 
                            .format(client_name))
2389
 
                        client["enabled"] = False
2390
 
                    elif client["last_checker_status"] != 0:
2391
 
                        logger.warning(
2392
 
                            "disabling client {0} - Client "
2393
 
                            "last checker failed with error code {1}"
2394
 
                            .format(client_name,
2395
 
                                    client["last_checker_status"]))
 
2248
                if client["expires"] <= (datetime.datetime
 
2249
                                         .utcnow()):
 
2250
                    # Client has expired
 
2251
                    if client["last_checker_status"] != 0:
2396
2252
                        client["enabled"] = False
2397
2253
                    else:
2398
2254
                        client["expires"] = (datetime.datetime
2399
2255
                                             .utcnow()
2400
2256
                                             + client["timeout"])
2401
 
                        logger.debug("Last checker succeeded,"
2402
 
                                     " keeping {0} enabled"
2403
 
                                     .format(client_name))
 
2257
            
 
2258
            client["changedstate"] = (multiprocessing_manager
 
2259
                                      .Condition
 
2260
                                      (multiprocessing_manager
 
2261
                                       .Lock()))
 
2262
            if use_dbus:
 
2263
                new_client = (ClientDBusTransitional.__new__
 
2264
                              (ClientDBusTransitional))
 
2265
                tcp_server.clients[client_name] = new_client
 
2266
                new_client.bus = bus
 
2267
                for name, value in client.iteritems():
 
2268
                    setattr(new_client, name, value)
 
2269
                client_object_name = unicode(client_name).translate(
 
2270
                    {ord("."): ord("_"),
 
2271
                     ord("-"): ord("_")})
 
2272
                new_client.dbus_object_path = (dbus.ObjectPath
 
2273
                                               ("/clients/"
 
2274
                                                + client_object_name))
 
2275
                DBusObjectWithProperties.__init__(new_client,
 
2276
                                                  new_client.bus,
 
2277
                                                  new_client
 
2278
                                                  .dbus_object_path)
 
2279
            else:
 
2280
                tcp_server.clients[client_name] = (Client.__new__
 
2281
                                                   (Client))
 
2282
                for name, value in client.iteritems():
 
2283
                    setattr(tcp_server.clients[client_name],
 
2284
                            name, value)
 
2285
            
2404
2286
            try:
2405
 
                client["secret"] = (
2406
 
                    pgp.decrypt(client["encrypted_secret"],
 
2287
                tcp_server.clients[client_name].secret = (
 
2288
                    pgp.decrypt(tcp_server.clients[client_name]
 
2289
                                .encrypted_secret,
2407
2290
                                client_settings[client_name]
2408
2291
                                ["secret"]))
2409
2292
            except PGPError:
2410
2293
                # If decryption fails, we use secret from new settings
2411
2294
                logger.debug("Failed to decrypt {0} old secret"
2412
2295
                             .format(client_name))
2413
 
                client["secret"] = (
 
2296
                tcp_server.clients[client_name].secret = (
2414
2297
                    client_settings[client_name]["secret"])
2415
 
 
2416
2298
    
2417
 
    # Add/remove clients based on new changes made to config
2418
 
    for client_name in (set(old_client_settings)
2419
 
                        - set(client_settings)):
2420
 
        del clients_data[client_name]
2421
 
    for client_name in (set(client_settings)
2422
 
                        - set(old_client_settings)):
2423
 
        clients_data[client_name] = client_settings[client_name]
2424
 
 
2425
 
    # Create all client objects
2426
 
    for client_name, client in clients_data.iteritems():
2427
 
        tcp_server.clients[client_name] = client_class(
2428
 
            name = client_name, settings = client)
 
2299
    # Create/remove clients based on new changes made to config
 
2300
    for clientname in set(old_client_settings) - set(client_settings):
 
2301
        del tcp_server.clients[clientname]
 
2302
    for clientname in set(client_settings) - set(old_client_settings):
 
2303
        tcp_server.clients[clientname] = (client_class(name
 
2304
                                                       = clientname,
 
2305
                                                       config =
 
2306
                                                       client_settings
 
2307
                                                       [clientname]))
2429
2308
    
2430
2309
    if not tcp_server.clients:
2431
2310
        logger.warning("No clients defined")
2443
2322
            # "pidfile" was never created
2444
2323
            pass
2445
2324
        del pidfilename
 
2325
        
2446
2326
        signal.signal(signal.SIGINT, signal.SIG_IGN)
2447
2327
    
2448
2328
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2449
2329
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2450
2330
    
2451
2331
    if use_dbus:
2452
 
        class MandosDBusService(DBusObjectWithProperties):
 
2332
        class MandosDBusService(dbus.service.Object):
2453
2333
            """A D-Bus proxy object"""
2454
2334
            def __init__(self):
2455
2335
                dbus.service.Object.__init__(self, bus, "/")
2456
2336
            _interface = "se.recompile.Mandos"
2457
2337
            
2458
 
            @dbus_interface_annotations(_interface)
2459
 
            def _foo(self):
2460
 
                return { "org.freedesktop.DBus.Property"
2461
 
                         ".EmitsChangedSignal":
2462
 
                             "false"}
2463
 
            
2464
2338
            @dbus.service.signal(_interface, signature="o")
2465
2339
            def ClientAdded(self, objpath):
2466
2340
                "D-Bus signal"
2523
2397
        # Store client before exiting. Secrets are encrypted with key
2524
2398
        # based on what config file has. If config file is
2525
2399
        # removed/edited, old secret will thus be unrecovable.
2526
 
        clients = {}
 
2400
        clients = []
2527
2401
        with PGPEngine() as pgp:
2528
2402
            for client in tcp_server.clients.itervalues():
2529
2403
                key = client_settings[client.name]["secret"]
2531
2405
                                                      key)
2532
2406
                client_dict = {}
2533
2407
                
2534
 
                # A list of attributes that can not be pickled
2535
 
                # + secret.
2536
 
                exclude = set(("bus", "changedstate", "secret",
2537
 
                               "checker"))
 
2408
                # A list of attributes that will not be stored when
 
2409
                # shutting down.
 
2410
                exclude = set(("bus", "changedstate", "secret"))
2538
2411
                for name, typ in (inspect.getmembers
2539
2412
                                  (dbus.service.Object)):
2540
2413
                    exclude.add(name)
2545
2418
                    if attr not in exclude:
2546
2419
                        client_dict[attr] = getattr(client, attr)
2547
2420
                
2548
 
                clients[client.name] = client_dict
 
2421
                clients.append(client_dict)
2549
2422
                del client_settings[client.name]["secret"]
2550
2423
        
2551
2424
        try:
2552
 
            tempfd, tempname = tempfile.mkstemp(suffix=".pickle",
2553
 
                                                prefix="clients-",
2554
 
                                                dir=os.path.dirname
2555
 
                                                (stored_state_path))
2556
 
            with os.fdopen(tempfd, "wb") as stored_state:
 
2425
            with os.fdopen(os.open(stored_state_path,
 
2426
                                   os.O_CREAT|os.O_WRONLY|os.O_TRUNC,
 
2427
                                   0600), "wb") as stored_state:
2557
2428
                pickle.dump((clients, client_settings), stored_state)
2558
 
            os.rename(tempname, stored_state_path)
2559
2429
        except (IOError, OSError) as e:
2560
2430
            logger.warning("Could not save persistent state: {0}"
2561
2431
                           .format(e))
2562
 
            if not debug:
2563
 
                try:
2564
 
                    os.remove(tempname)
2565
 
                except NameError:
2566
 
                    pass
2567
 
            if e.errno not in set((errno.ENOENT, errno.EACCES,
2568
 
                                   errno.EEXIST)):
2569
 
                raise e
 
2432
            if e.errno not in (errno.ENOENT, errno.EACCES):
 
2433
                raise
2570
2434
        
2571
2435
        # Delete all clients, and settings from config
2572
2436
        while tcp_server.clients:
2612
2476
        try:
2613
2477
            service.activate()
2614
2478
        except dbus.exceptions.DBusException as error:
2615
 
            logger.critical("D-Bus Exception", exc_info=error)
 
2479
            logger.critical("DBusException: %s", error)
2616
2480
            cleanup()
2617
2481
            sys.exit(1)
2618
2482
        # End of Avahi example code
2625
2489
        logger.debug("Starting main loop")
2626
2490
        main_loop.run()
2627
2491
    except AvahiError as error:
2628
 
        logger.critical("Avahi Error", exc_info=error)
 
2492
        logger.critical("AvahiError: %s", error)
2629
2493
        cleanup()
2630
2494
        sys.exit(1)
2631
2495
    except KeyboardInterrupt:
2636
2500
    # Must run before the D-Bus bus name gets deregistered
2637
2501
    cleanup()
2638
2502
 
 
2503
 
2639
2504
if __name__ == '__main__':
2640
2505
    main()