/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: Björn Påhlsson
  • Date: 2011-10-02 13:45:45 UTC
  • mto: (237.7.53 trunk)
  • mto: This revision was merged to the branch mainline in revision 286.
  • Revision ID: belorn@fukt.bsnet.se-20111002134545-oytmfbl15r8lsm6p
working transition code for going between se.bsnet.fukt to se.recompile

Show diffs side-by-side

added added

removed removed

Lines of Context:
28
28
# along with this program.  If not, see
29
29
# <http://www.gnu.org/licenses/>.
30
30
31
 
# Contact the authors at <mandos@recompile.se>.
 
31
# Contact the authors at <mandos@fukt.bsnet.se>.
32
32
33
33
 
34
34
from __future__ import (division, absolute_import, print_function,
160
160
                            " after %i retries, exiting.",
161
161
                            self.rename_count)
162
162
            raise AvahiServiceError("Too many renames")
163
 
        self.name = unicode(self.server
164
 
                            .GetAlternativeServiceName(self.name))
 
163
        self.name = unicode(self.server.GetAlternativeServiceName(self.name))
165
164
        logger.info("Changing Zeroconf service name to %r ...",
166
165
                    self.name)
167
166
        syslogger.setFormatter(logging.Formatter
362
361
        self.last_enabled = None
363
362
        self.last_checked_ok = None
364
363
        self.timeout = string_to_delta(config["timeout"])
365
 
        self.extended_timeout = string_to_delta(config
366
 
                                                ["extended_timeout"])
 
364
        self.extended_timeout = string_to_delta(config["extended_timeout"])
367
365
        self.interval = string_to_delta(config["interval"])
368
366
        self.disable_hook = disable_hook
369
367
        self.checker = None
382
380
            config["approval_delay"])
383
381
        self.approval_duration = string_to_delta(
384
382
            config["approval_duration"])
385
 
        self.changedstate = (multiprocessing_manager
386
 
                             .Condition(multiprocessing_manager
387
 
                                        .Lock()))
 
383
        self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
388
384
    
389
385
    def send_changedstate(self):
390
386
        self.changedstate.acquire()
391
387
        self.changedstate.notify_all()
392
388
        self.changedstate.release()
393
 
    
 
389
        
394
390
    def enable(self):
395
391
        """Start this client's checker and timeout hooks"""
396
392
        if getattr(self, "enabled", False):
467
463
        gobject.source_remove(self.disable_initiator_tag)
468
464
        self.expires = datetime.datetime.utcnow() + timeout
469
465
        self.disable_initiator_tag = (gobject.timeout_add
470
 
                                      (_timedelta_to_milliseconds
471
 
                                       (timeout), self.disable))
 
466
                                      (_timedelta_to_milliseconds(timeout),
 
467
                                       self.disable))
472
468
    
473
469
    def need_approval(self):
474
470
        self.last_approval_request = datetime.datetime.utcnow()
631
627
    def _get_all_dbus_properties(self):
632
628
        """Returns a generator of (name, attribute) pairs
633
629
        """
634
 
        return ((prop.__get__(self)._dbus_name, prop.__get__(self))
635
 
                for cls in self.__class__.__mro__
 
630
        return ((prop._dbus_name, prop)
636
631
                for name, prop in
637
 
                inspect.getmembers(cls, self._is_dbus_property))
 
632
                inspect.getmembers(self, self._is_dbus_property))
638
633
    
 
634
#    def _get_dbus_property(self, interface_name, property_name):
 
635
#        """Returns a bound method if one exists which is a D-Bus
 
636
#        property with the specified name and interface.
 
637
#        """
 
638
#        print("get_property({0!r}, {1!r}".format(interface_name, property_name),file=sys.stderr)
 
639
#        print(dir(self), sys.stderr)
 
640
#        for name in (property_name,
 
641
#                     property_name + "_dbus_property"):
 
642
#            prop = getattr(self, name, None)
 
643
#            if (prop is None
 
644
#                or not self._is_dbus_property(prop)
 
645
#                or prop._dbus_name != property_name
 
646
#                or (interface_name and prop._dbus_interface
 
647
#                    and interface_name != prop._dbus_interface)):
 
648
#                continue
 
649
#            return prop
 
650
#        # No such property
 
651
#        raise DBusPropertyNotFound(self.dbus_object_path + ":"
 
652
#                                   + interface_name + "."
 
653
#                                   + property_name)
 
654
 
639
655
    def _get_dbus_property(self, interface_name, property_name):
640
656
        """Returns a bound method if one exists which is a D-Bus
641
657
        property with the specified name and interface.
642
658
        """
643
 
        for cls in  self.__class__.__mro__:
644
 
            for name, value in (inspect.getmembers
645
 
                                (cls, self._is_dbus_property)):
646
 
                if (value._dbus_name == property_name
647
 
                    and value._dbus_interface == interface_name):
648
 
                    return value.__get__(self)
 
659
        for name, value in inspect.getmembers(self, self._is_dbus_property):
 
660
            if value._dbus_name == property_name and value._dbus_interface == interface_name:
 
661
                return value
649
662
        
650
663
        # No such property
651
664
        raise DBusPropertyNotFound(self.dbus_object_path + ":"
652
665
                                   + interface_name + "."
653
666
                                   + property_name)
 
667
 
654
668
    
655
669
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
656
670
                         out_signature="v")
761
775
    return dbus.String(dt.isoformat(),
762
776
                       variant_level=variant_level)
763
777
 
764
 
class AlternateDBusNamesMetaclass(DBusObjectWithProperties
765
 
                                  .__metaclass__):
766
 
    """Applied to an empty subclass of a D-Bus object, this metaclass
767
 
    will add additional D-Bus attributes matching a certain pattern.
768
 
    """
 
778
class transitional_clientdbus(DBusObjectWithProperties.__metaclass__):
769
779
    def __new__(mcs, name, bases, attr):
770
 
        # Go through all the base classes which could have D-Bus
771
 
        # methods, signals, or properties in them
772
 
        for base in (b for b in bases
773
 
                     if issubclass(b, dbus.service.Object)):
774
 
            # Go though all attributes of the base class
775
 
            for attrname, attribute in inspect.getmembers(base):
776
 
                # Ignore non-D-Bus attributes, and D-Bus attributes
777
 
                # with the wrong interface name
778
 
                if (not hasattr(attribute, "_dbus_interface")
779
 
                    or not attribute._dbus_interface
780
 
                    .startswith("se.recompile.Mandos")):
781
 
                    continue
782
 
                # Create an alternate D-Bus interface name based on
783
 
                # the current name
784
 
                alt_interface = (attribute._dbus_interface
785
 
                                 .replace("se.recompile.Mandos",
786
 
                                          "se.bsnet.fukt.Mandos"))
787
 
                # Is this a D-Bus signal?
788
 
                if getattr(attribute, "_dbus_is_signal", False):
789
 
                    # Extract the original non-method function by
790
 
                    # black magic
791
 
                    nonmethod_func = (dict(
792
 
                            zip(attribute.func_code.co_freevars,
793
 
                                attribute.__closure__))["func"]
794
 
                                      .cell_contents)
795
 
                    # Create a new, but exactly alike, function
796
 
                    # object, and decorate it to be a new D-Bus signal
797
 
                    # with the alternate D-Bus interface name
798
 
                    new_function = (dbus.service.signal
799
 
                                    (alt_interface,
800
 
                                     attribute._dbus_signature)
801
 
                                    (types.FunctionType(
802
 
                                nonmethod_func.func_code,
803
 
                                nonmethod_func.func_globals,
804
 
                                nonmethod_func.func_name,
805
 
                                nonmethod_func.func_defaults,
806
 
                                nonmethod_func.func_closure)))
807
 
                    # Define a creator of a function to call both the
808
 
                    # old and new functions, so both the old and new
809
 
                    # signals gets sent when the function is called
810
 
                    def fixscope(func1, func2):
811
 
                        """This function is a scope container to pass
812
 
                        func1 and func2 to the "call_both" function
813
 
                        outside of its arguments"""
814
 
                        def call_both(*args, **kwargs):
815
 
                            """This function will emit two D-Bus
816
 
                            signals by calling func1 and func2"""
817
 
                            func1(*args, **kwargs)
818
 
                            func2(*args, **kwargs)
819
 
                        return call_both
820
 
                    # Create the "call_both" function and add it to
821
 
                    # the class
822
 
                    attr[attrname] = fixscope(attribute,
823
 
                                              new_function)
824
 
                # Is this a D-Bus method?
825
 
                elif getattr(attribute, "_dbus_is_method", False):
826
 
                    # Create a new, but exactly alike, function
827
 
                    # object.  Decorate it to be a new D-Bus method
828
 
                    # with the alternate D-Bus interface name.  Add it
829
 
                    # to the class.
830
 
                    attr[attrname] = (dbus.service.method
831
 
                                      (alt_interface,
832
 
                                       attribute._dbus_in_signature,
833
 
                                       attribute._dbus_out_signature)
834
 
                                      (types.FunctionType
835
 
                                       (attribute.func_code,
836
 
                                        attribute.func_globals,
837
 
                                        attribute.func_name,
838
 
                                        attribute.func_defaults,
839
 
                                        attribute.func_closure)))
840
 
                # Is this a D-Bus property?
841
 
                elif getattr(attribute, "_dbus_is_property", False):
842
 
                    # Create a new, but exactly alike, function
843
 
                    # object, and decorate it to be a new D-Bus
844
 
                    # property with the alternate D-Bus interface
845
 
                    # name.  Add it to the class.
846
 
                    attr[attrname] = (dbus_service_property
847
 
                                      (alt_interface,
848
 
                                       attribute._dbus_signature,
849
 
                                       attribute._dbus_access,
850
 
                                       attribute
851
 
                                       ._dbus_get_args_options
852
 
                                       ["byte_arrays"])
853
 
                                      (types.FunctionType
854
 
                                       (attribute.func_code,
855
 
                                        attribute.func_globals,
856
 
                                        attribute.func_name,
857
 
                                        attribute.func_defaults,
858
 
                                        attribute.func_closure)))
 
780
        for key, old_dbusobj in attr.items():
 
781
            new_interface = getattr(old_dbusobj, "_dbus_interface", "").replace("se.bsnet.fukt.", "se.recompile.")
 
782
            if getattr(old_dbusobj, "_dbus_is_signal", False):
 
783
                unwrappedfunc = dict(zip(old_dbusobj.func_code.co_freevars,
 
784
                                    old_dbusobj.__closure__))["func"].cell_contents
 
785
                newfunc = types.FunctionType(unwrappedfunc.func_code,
 
786
                                             unwrappedfunc.func_globals,
 
787
                                             unwrappedfunc.func_name,
 
788
                                             unwrappedfunc.func_defaults,
 
789
                                             unwrappedfunc.func_closure)
 
790
                new_dbusfunc = dbus.service.signal(
 
791
                    new_interface, old_dbusobj._dbus_signature)(newfunc)            
 
792
                attr["_transitional_{0}_1".format(key)] = new_dbusfunc
 
793
                attr["_transitional_{0}_0".format(key)] = old_dbusobj                
 
794
                def fixscope(func1, func2):
 
795
                    def newcall(*args, **kwargs):
 
796
                        func1(*args, **kwargs)
 
797
                        func2(*args, **kwargs)
 
798
                    return newcall
 
799
 
 
800
                attr[key] = fixscope(
 
801
                    old_dbusobj, attr["_transitional_{0}_1".format(key)])
 
802
            
 
803
            if getattr(old_dbusobj, "_dbus_is_method", False):
 
804
                new_dbusfunc = (dbus.service.method
 
805
                                (new_interface,
 
806
                                 old_dbusobj._dbus_in_signature,
 
807
                                 old_dbusobj._dbus_out_signature)
 
808
                                (types.FunctionType
 
809
                                 (old_dbusobj.func_code,
 
810
                                  old_dbusobj.func_globals,
 
811
                                  old_dbusobj.func_name,
 
812
                                  old_dbusobj.func_defaults,
 
813
                                  old_dbusobj.func_closure)))
 
814
 
 
815
                attr["_transitional_{0}".format(key)] = new_dbusfunc
 
816
            if getattr(old_dbusobj, "_dbus_is_property", False):
 
817
                new_dbusfunc = (dbus_service_property
 
818
                                (new_interface,
 
819
                                 old_dbusobj._dbus_signature,
 
820
                                 old_dbusobj._dbus_access,
 
821
                                 old_dbusobj._dbus_get_args_options["byte_arrays"])
 
822
                                (types.FunctionType
 
823
                                 (old_dbusobj.func_code,
 
824
                                  old_dbusobj.func_globals,
 
825
                                  old_dbusobj.func_name,
 
826
                                  old_dbusobj.func_defaults,
 
827
                                  old_dbusobj.func_closure)))
 
828
 
 
829
                attr["_transitional_{0}".format(key)] = new_dbusfunc
859
830
        return type.__new__(mcs, name, bases, attr)
860
831
 
861
832
class ClientDBus(Client, DBusObjectWithProperties):
868
839
    
869
840
    runtime_expansions = (Client.runtime_expansions
870
841
                          + ("dbus_object_path",))
 
842
 
 
843
    __metaclass__ = transitional_clientdbus
871
844
    
872
845
    # dbus.service.Object doesn't use super(), so we can't either.
873
846
    
888
861
    def notifychangeproperty(transform_func,
889
862
                             dbus_name, type_func=lambda x: x,
890
863
                             variant_level=1):
891
 
        """ Modify a variable so that it's a property which announces
892
 
        its changes to DBus.
893
 
 
894
 
        transform_fun: Function that takes a value and transforms it
895
 
                       to a D-Bus type.
896
 
        dbus_name: D-Bus name of the variable
 
864
        """ Modify a variable so that its a property that announce its
 
865
        changes to DBus.
 
866
        transform_fun: Function that takes a value and transform it to
 
867
                       DBus type.
 
868
        dbus_name: DBus name of the variable
897
869
        type_func: Function that transform the value before sending it
898
 
                   to the D-Bus.  Default: no transform
899
 
        variant_level: D-Bus variant level.  Default: 1
 
870
                   to DBus
 
871
        variant_level: DBus variant level. default: 1
900
872
        """
901
873
        real_value = [None,]
902
874
        def setter(self, value):
904
876
            real_value[0] = value
905
877
            if hasattr(self, "dbus_object_path"):
906
878
                if type_func(old_value) != type_func(real_value[0]):
907
 
                    dbus_value = transform_func(type_func
908
 
                                                (real_value[0]),
 
879
                    dbus_value = transform_func(type_func(real_value[0]),
909
880
                                                variant_level)
910
881
                    self.PropertyChanged(dbus.String(dbus_name),
911
882
                                         dbus_value)
921
892
    last_enabled = notifychangeproperty(datetime_to_dbus,
922
893
                                        "LastEnabled")
923
894
    checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
924
 
                                   type_func = lambda checker:
925
 
                                       checker is not None)
 
895
                                   type_func = lambda checker: checker is not None)
926
896
    last_checked_ok = notifychangeproperty(datetime_to_dbus,
927
897
                                           "LastCheckedOK")
928
 
    last_approval_request = notifychangeproperty(
929
 
        datetime_to_dbus, "LastApprovalRequest")
 
898
    last_approval_request = notifychangeproperty(datetime_to_dbus,
 
899
                                                 "LastApprovalRequest")
930
900
    approved_by_default = notifychangeproperty(dbus.Boolean,
931
901
                                               "ApprovedByDefault")
932
 
    approval_delay = notifychangeproperty(dbus.UInt16,
933
 
                                          "ApprovalDelay",
934
 
                                          type_func =
935
 
                                          _timedelta_to_milliseconds)
936
 
    approval_duration = notifychangeproperty(
937
 
        dbus.UInt16, "ApprovalDuration",
938
 
        type_func = _timedelta_to_milliseconds)
 
902
    approval_delay = notifychangeproperty(dbus.UInt16, "ApprovalDelay",
 
903
                                          type_func = _timedelta_to_milliseconds)
 
904
    approval_duration = notifychangeproperty(dbus.UInt16, "ApprovalDuration",
 
905
                                             type_func = _timedelta_to_milliseconds)
939
906
    host = notifychangeproperty(dbus.String, "Host")
940
907
    timeout = notifychangeproperty(dbus.UInt16, "Timeout",
941
 
                                   type_func =
942
 
                                   _timedelta_to_milliseconds)
943
 
    extended_timeout = notifychangeproperty(
944
 
        dbus.UInt16, "ExtendedTimeout",
945
 
        type_func = _timedelta_to_milliseconds)
946
 
    interval = notifychangeproperty(dbus.UInt16,
947
 
                                    "Interval",
948
 
                                    type_func =
949
 
                                    _timedelta_to_milliseconds)
 
908
                                   type_func = _timedelta_to_milliseconds)
 
909
    extended_timeout = notifychangeproperty(dbus.UInt16, "ExtendedTimeout",
 
910
                                            type_func = _timedelta_to_milliseconds)
 
911
    interval = notifychangeproperty(dbus.UInt16, "Interval",
 
912
                                    type_func = _timedelta_to_milliseconds)
950
913
    checker_command = notifychangeproperty(dbus.String, "Checker")
951
914
    
952
915
    del notifychangeproperty
1006
969
    
1007
970
    
1008
971
    ## D-Bus methods, signals & properties
1009
 
    _interface = "se.recompile.Mandos.Client"
1010
 
    
 
972
    _interface = "se.bsnet.fukt.Mandos.Client"
 
973
 
1011
974
    ## Signals
1012
975
    
1013
976
    # CheckerCompleted - signal
1198
1161
            self.disable()
1199
1162
        else:
1200
1163
            self.expires = (datetime.datetime.utcnow()
1201
 
                            + datetime.timedelta(milliseconds =
1202
 
                                                 time_to_die))
 
1164
                            + datetime.timedelta(milliseconds = time_to_die))
1203
1165
            self.disable_initiator_tag = (gobject.timeout_add
1204
1166
                                          (time_to_die, self.disable))
1205
1167
    
1284
1246
            return super(ProxyClient, self).__setattr__(name, value)
1285
1247
        self._pipe.send(('setattr', name, value))
1286
1248
 
1287
 
class ClientDBusTransitional(ClientDBus):
1288
 
    __metaclass__ = AlternateDBusNamesMetaclass
1289
1249
 
1290
1250
class ClientHandler(socketserver.BaseRequestHandler, object):
1291
1251
    """A class to handle client connections.
1371
1331
                                       client.name)
1372
1332
                        if self.server.use_dbus:
1373
1333
                            # Emit D-Bus signal
1374
 
                            client.Rejected("Disabled")
 
1334
                            client.Rejected("Disabled")                    
1375
1335
                        return
1376
1336
                    
1377
1337
                    if client._approved or not client.approval_delay:
1394
1354
                        return
1395
1355
                    
1396
1356
                    #wait until timeout or approved
1397
 
                    #x = float(client
1398
 
                    #          ._timedelta_to_milliseconds(delay))
 
1357
                    #x = float(client._timedelta_to_milliseconds(delay))
1399
1358
                    time = datetime.datetime.now()
1400
1359
                    client.changedstate.acquire()
1401
 
                    (client.changedstate.wait
1402
 
                     (float(client._timedelta_to_milliseconds(delay)
1403
 
                            / 1000)))
 
1360
                    client.changedstate.wait(float(client._timedelta_to_milliseconds(delay) / 1000))
1404
1361
                    client.changedstate.release()
1405
1362
                    time2 = datetime.datetime.now()
1406
1363
                    if (time2 - time) >= delay:
1638
1595
        gobject.io_add_watch(parent_pipe.fileno(),
1639
1596
                             gobject.IO_IN | gobject.IO_HUP,
1640
1597
                             functools.partial(self.handle_ipc,
1641
 
                                               parent_pipe =
1642
 
                                               parent_pipe))
 
1598
                                               parent_pipe = parent_pipe))
1643
1599
        
1644
1600
    def handle_ipc(self, source, condition, parent_pipe=None,
1645
1601
                   client_object=None):
1678
1634
                            "dress: %s", fpr, address)
1679
1635
                if self.use_dbus:
1680
1636
                    # Emit D-Bus signal
1681
 
                    mandos_dbus_service.ClientNotFound(fpr,
1682
 
                                                       address[0])
 
1637
                    mandos_dbus_service.ClientNotFound(fpr, address[0])
1683
1638
                parent_pipe.send(False)
1684
1639
                return False
1685
1640
            
1686
1641
            gobject.io_add_watch(parent_pipe.fileno(),
1687
1642
                                 gobject.IO_IN | gobject.IO_HUP,
1688
1643
                                 functools.partial(self.handle_ipc,
1689
 
                                                   parent_pipe =
1690
 
                                                   parent_pipe,
1691
 
                                                   client_object =
1692
 
                                                   client))
 
1644
                                                   parent_pipe = parent_pipe,
 
1645
                                                   client_object = client))
1693
1646
            parent_pipe.send(True)
1694
 
            # remove the old hook in favor of the new above hook on
1695
 
            # same fileno
 
1647
            # remove the old hook in favor of the new above hook on same fileno
1696
1648
            return False
1697
1649
        if command == 'funcall':
1698
1650
            funcname = request[1]
1699
1651
            args = request[2]
1700
1652
            kwargs = request[3]
1701
1653
            
1702
 
            parent_pipe.send(('data', getattr(client_object,
1703
 
                                              funcname)(*args,
1704
 
                                                         **kwargs)))
 
1654
            parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
1705
1655
        
1706
1656
        if command == 'getattr':
1707
1657
            attrname = request[1]
1708
1658
            if callable(client_object.__getattribute__(attrname)):
1709
1659
                parent_pipe.send(('function',))
1710
1660
            else:
1711
 
                parent_pipe.send(('data', client_object
1712
 
                                  .__getattribute__(attrname)))
 
1661
                parent_pipe.send(('data', client_object.__getattribute__(attrname)))
1713
1662
        
1714
1663
        if command == 'setattr':
1715
1664
            attrname = request[1]
2004
1953
    # End of Avahi example code
2005
1954
    if use_dbus:
2006
1955
        try:
2007
 
            bus_name = dbus.service.BusName("se.recompile.Mandos",
2008
 
                                            bus, do_not_queue=True)
2009
 
            old_bus_name = (dbus.service.BusName
2010
 
                            ("se.bsnet.fukt.Mandos", bus,
2011
 
                             do_not_queue=True))
 
1956
            bus_name = dbus.service.BusName("se.bsnet.fukt.Mandos",
 
1957
                                            bus, do_not_queue=True)
 
1958
            bus_name2 = dbus.service.BusName("se.recompile.Mandos",
 
1959
                                            bus, do_not_queue=True)
2012
1960
        except dbus.exceptions.NameExistsException as e:
2013
1961
            logger.error(unicode(e) + ", disabling D-Bus")
2014
1962
            use_dbus = False
2027
1975
    
2028
1976
    client_class = Client
2029
1977
    if use_dbus:
2030
 
        client_class = functools.partial(ClientDBusTransitional,
2031
 
                                         bus = bus)        
 
1978
        client_class = functools.partial(ClientDBus, bus = bus)
2032
1979
    def client_config_items(config, section):
2033
1980
        special_settings = {
2034
1981
            "approved_by_default":
2073
2020
            """A D-Bus proxy object"""
2074
2021
            def __init__(self):
2075
2022
                dbus.service.Object.__init__(self, bus, "/")
2076
 
            _interface = "se.recompile.Mandos"
 
2023
            _interface = "se.bsnet.fukt.Mandos"
2077
2024
            
2078
2025
            @dbus.service.signal(_interface, signature="o")
2079
2026
            def ClientAdded(self, objpath):
2121
2068
            
2122
2069
            del _interface
2123
2070
        
2124
 
        class MandosDBusServiceTransitional(MandosDBusService):
2125
 
            __metaclass__ = AlternateDBusNamesMetaclass
2126
 
        mandos_dbus_service = MandosDBusServiceTransitional()
 
2071
        mandos_dbus_service = MandosDBusService()
2127
2072
    
2128
2073
    def cleanup():
2129
2074
        "Cleanup function; run on exit"
2138
2083
            client.disable(quiet=True)
2139
2084
            if use_dbus:
2140
2085
                # Emit D-Bus signal
2141
 
                mandos_dbus_service.ClientRemoved(client
2142
 
                                                  .dbus_object_path,
 
2086
                mandos_dbus_service.ClientRemoved(client.dbus_object_path,
2143
2087
                                                  client.name)
2144
2088
    
2145
2089
    atexit.register(cleanup)