/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

  • Committer: Teddy Hogeborn
  • Date: 2016-03-12 23:42:38 UTC
  • Revision ID: teddy@recompile.se-20160312234238-xdszntu18cfwife9
Server: Use python-gi instead of old python-gobject

The python-gobject module is old, deprecated, and replaced with the
python-gi (GObject Introspection) module; use that instead.

* debian/control (Source: mandos/Build-Depends-Indep): Change
  "python-gi | python-gobject" to "python-gi".
* mandos: Import "GLib" instead of "GObject"; change all users.
* mandos-monitor: - '' -

Show diffs side-by-side

added added

removed removed

Lines of Context:
34
34
from __future__ import (division, absolute_import, print_function,
35
35
                        unicode_literals)
36
36
 
37
 
from future_builtins import *
 
37
try:
 
38
    from future_builtins import *
 
39
except ImportError:
 
40
    pass
38
41
 
39
42
try:
40
43
    import SocketServer as socketserver
76
79
 
77
80
import dbus
78
81
import dbus.service
79
 
try:
80
 
    from gi.repository import GObject
81
 
except ImportError:
82
 
    import gobject as GObject
83
 
import avahi
 
82
from gi.repository import GLib
84
83
from dbus.mainloop.glib import DBusGMainLoop
85
84
import ctypes
86
85
import ctypes.util
98
97
if sys.version_info.major == 2:
99
98
    str = unicode
100
99
 
101
 
version = "1.7.4"
 
100
version = "1.7.5"
102
101
stored_state_file = "clients.pickle"
103
102
 
104
103
logger = logging.getLogger()
119
118
        return interface_index
120
119
 
121
120
 
 
121
def copy_function(func):
 
122
    """Make a copy of a function"""
 
123
    if sys.version_info.major == 2:
 
124
        return types.FunctionType(func.func_code,
 
125
                                  func.func_globals,
 
126
                                  func.func_name,
 
127
                                  func.func_defaults,
 
128
                                  func.func_closure)
 
129
    else:
 
130
        return types.FunctionType(func.__code__,
 
131
                                  func.__globals__,
 
132
                                  func.__name__,
 
133
                                  func.__defaults__,
 
134
                                  func.__closure__)
 
135
 
 
136
 
122
137
def initlogger(debug, level=logging.WARNING):
123
138
    """init logger and add loglevel"""
124
139
    
155
170
        try:
156
171
            output = subprocess.check_output(["gpgconf"])
157
172
            for line in output.splitlines():
158
 
                name, text, path = line.split(":")
 
173
                name, text, path = line.split(b":")
159
174
                if name == "gpg":
160
175
                    self.gpg = path
161
176
                    break
238
253
            raise PGPError(err)
239
254
        return decrypted_plaintext
240
255
 
 
256
# Pretend that we have an Avahi module
 
257
class Avahi(object):
 
258
    """This isn't so much a class as it is a module-like namespace.
 
259
    It is instantiated once, and simulates having an Avahi module."""
 
260
    IF_UNSPEC = -1              # avahi-common/address.h
 
261
    PROTO_UNSPEC = -1           # avahi-common/address.h
 
262
    PROTO_INET = 0              # avahi-common/address.h
 
263
    PROTO_INET6 = 1             # avahi-common/address.h
 
264
    DBUS_NAME = "org.freedesktop.Avahi"
 
265
    DBUS_INTERFACE_ENTRY_GROUP = DBUS_NAME + ".EntryGroup"
 
266
    DBUS_INTERFACE_SERVER = DBUS_NAME + ".Server"
 
267
    DBUS_PATH_SERVER = "/"
 
268
    def string_array_to_txt_array(self, t):
 
269
        return dbus.Array((dbus.ByteArray(s.encode("utf-8"))
 
270
                           for s in t), signature="ay")
 
271
    ENTRY_GROUP_ESTABLISHED = 2 # avahi-common/defs.h
 
272
    ENTRY_GROUP_COLLISION = 3   # avahi-common/defs.h
 
273
    ENTRY_GROUP_FAILURE = 4     # avahi-common/defs.h
 
274
    SERVER_INVALID = 0          # avahi-common/defs.h
 
275
    SERVER_REGISTERING = 1      # avahi-common/defs.h
 
276
    SERVER_RUNNING = 2          # avahi-common/defs.h
 
277
    SERVER_COLLISION = 3        # avahi-common/defs.h
 
278
    SERVER_FAILURE = 4          # avahi-common/defs.h
 
279
avahi = Avahi()
241
280
 
242
281
class AvahiError(Exception):
243
282
    def __init__(self, value, *args, **kwargs):
447
486
    
448
487
    _library = ctypes.cdll.LoadLibrary(
449
488
        ctypes.util.find_library("gnutls"))
450
 
    _need_version = "3.3.0"
 
489
    _need_version = b"3.3.0"
451
490
    def __init__(self):
452
491
        # Need to use class name "GnuTLS" here, since this method is
453
492
        # called before the assignment to the "gnutls" global variable
715
754
    checker:    subprocess.Popen(); a running checker process used
716
755
                                    to see if the client lives.
717
756
                                    'None' if no process is running.
718
 
    checker_callback_tag: a GObject event source tag, or None
 
757
    checker_callback_tag: a GLib event source tag, or None
719
758
    checker_command: string; External command which is run to check
720
759
                     if client lives.  %() expansions are done at
721
760
                     runtime with vars(self) as dict, so that for
722
761
                     instance %(name)s can be used in the command.
723
 
    checker_initiator_tag: a GObject event source tag, or None
 
762
    checker_initiator_tag: a GLib event source tag, or None
724
763
    created:    datetime.datetime(); (UTC) object creation
725
764
    client_structure: Object describing what attributes a client has
726
765
                      and is used for storing the client at exit
727
766
    current_checker_command: string; current running checker_command
728
 
    disable_initiator_tag: a GObject event source tag, or None
 
767
    disable_initiator_tag: a GLib event source tag, or None
729
768
    enabled:    bool()
730
769
    fingerprint: string (40 or 32 hexadecimal digits); used to
731
770
                 uniquely identify the client
794
833
            client["fingerprint"] = (section["fingerprint"].upper()
795
834
                                     .replace(" ", ""))
796
835
            if "secret" in section:
797
 
                client["secret"] = section["secret"].decode("base64")
 
836
                client["secret"] = codecs.decode(section["secret"]
 
837
                                                 .encode("utf-8"),
 
838
                                                 "base64")
798
839
            elif "secfile" in section:
799
840
                with open(os.path.expanduser(os.path.expandvars
800
841
                                             (section["secfile"])),
853
894
        self.changedstate = multiprocessing_manager.Condition(
854
895
            multiprocessing_manager.Lock())
855
896
        self.client_structure = [attr
856
 
                                 for attr in self.__dict__.iterkeys()
 
897
                                 for attr in self.__dict__.keys()
857
898
                                 if not attr.startswith("_")]
858
899
        self.client_structure.append("client_structure")
859
900
        
885
926
        if not quiet:
886
927
            logger.info("Disabling client %s", self.name)
887
928
        if getattr(self, "disable_initiator_tag", None) is not None:
888
 
            GObject.source_remove(self.disable_initiator_tag)
 
929
            GLib.source_remove(self.disable_initiator_tag)
889
930
            self.disable_initiator_tag = None
890
931
        self.expires = None
891
932
        if getattr(self, "checker_initiator_tag", None) is not None:
892
 
            GObject.source_remove(self.checker_initiator_tag)
 
933
            GLib.source_remove(self.checker_initiator_tag)
893
934
            self.checker_initiator_tag = None
894
935
        self.stop_checker()
895
936
        self.enabled = False
896
937
        if not quiet:
897
938
            self.send_changedstate()
898
 
        # Do not run this again if called by a GObject.timeout_add
 
939
        # Do not run this again if called by a GLib.timeout_add
899
940
        return False
900
941
    
901
942
    def __del__(self):
905
946
        # Schedule a new checker to be started an 'interval' from now,
906
947
        # and every interval from then on.
907
948
        if self.checker_initiator_tag is not None:
908
 
            GObject.source_remove(self.checker_initiator_tag)
909
 
        self.checker_initiator_tag = GObject.timeout_add(
 
949
            GLib.source_remove(self.checker_initiator_tag)
 
950
        self.checker_initiator_tag = GLib.timeout_add(
910
951
            int(self.interval.total_seconds() * 1000),
911
952
            self.start_checker)
912
953
        # Schedule a disable() when 'timeout' has passed
913
954
        if self.disable_initiator_tag is not None:
914
 
            GObject.source_remove(self.disable_initiator_tag)
915
 
        self.disable_initiator_tag = GObject.timeout_add(
 
955
            GLib.source_remove(self.disable_initiator_tag)
 
956
        self.disable_initiator_tag = GLib.timeout_add(
916
957
            int(self.timeout.total_seconds() * 1000), self.disable)
917
958
        # Also start a new checker *right now*.
918
959
        self.start_checker()
954
995
        if timeout is None:
955
996
            timeout = self.timeout
956
997
        if self.disable_initiator_tag is not None:
957
 
            GObject.source_remove(self.disable_initiator_tag)
 
998
            GLib.source_remove(self.disable_initiator_tag)
958
999
            self.disable_initiator_tag = None
959
1000
        if getattr(self, "enabled", False):
960
 
            self.disable_initiator_tag = GObject.timeout_add(
 
1001
            self.disable_initiator_tag = GLib.timeout_add(
961
1002
                int(timeout.total_seconds() * 1000), self.disable)
962
1003
            self.expires = datetime.datetime.utcnow() + timeout
963
1004
    
1018
1059
                args = (pipe[1], subprocess.call, command),
1019
1060
                kwargs = popen_args)
1020
1061
            self.checker.start()
1021
 
            self.checker_callback_tag = GObject.io_add_watch(
1022
 
                pipe[0].fileno(), GObject.IO_IN,
 
1062
            self.checker_callback_tag = GLib.io_add_watch(
 
1063
                pipe[0].fileno(), GLib.IO_IN,
1023
1064
                self.checker_callback, pipe[0], command)
1024
 
        # Re-run this periodically if run by GObject.timeout_add
 
1065
        # Re-run this periodically if run by GLib.timeout_add
1025
1066
        return True
1026
1067
    
1027
1068
    def stop_checker(self):
1028
1069
        """Force the checker process, if any, to stop."""
1029
1070
        if self.checker_callback_tag:
1030
 
            GObject.source_remove(self.checker_callback_tag)
 
1071
            GLib.source_remove(self.checker_callback_tag)
1031
1072
            self.checker_callback_tag = None
1032
1073
        if getattr(self, "checker", None) is None:
1033
1074
            return
1507
1548
                interface_names.add(alt_interface)
1508
1549
                # Is this a D-Bus signal?
1509
1550
                if getattr(attribute, "_dbus_is_signal", False):
 
1551
                    # Extract the original non-method undecorated
 
1552
                    # function by black magic
1510
1553
                    if sys.version_info.major == 2:
1511
 
                        # Extract the original non-method undecorated
1512
 
                        # function by black magic
1513
1554
                        nonmethod_func = (dict(
1514
1555
                            zip(attribute.func_code.co_freevars,
1515
1556
                                attribute.__closure__))
1516
1557
                                          ["func"].cell_contents)
1517
1558
                    else:
1518
 
                        nonmethod_func = attribute
 
1559
                        nonmethod_func = (dict(
 
1560
                            zip(attribute.__code__.co_freevars,
 
1561
                                attribute.__closure__))
 
1562
                                          ["func"].cell_contents)
1519
1563
                    # Create a new, but exactly alike, function
1520
1564
                    # object, and decorate it to be a new D-Bus signal
1521
1565
                    # with the alternate D-Bus interface name
1522
 
                    if sys.version_info.major == 2:
1523
 
                        new_function = types.FunctionType(
1524
 
                            nonmethod_func.func_code,
1525
 
                            nonmethod_func.func_globals,
1526
 
                            nonmethod_func.func_name,
1527
 
                            nonmethod_func.func_defaults,
1528
 
                            nonmethod_func.func_closure)
1529
 
                    else:
1530
 
                        new_function = types.FunctionType(
1531
 
                            nonmethod_func.__code__,
1532
 
                            nonmethod_func.__globals__,
1533
 
                            nonmethod_func.__name__,
1534
 
                            nonmethod_func.__defaults__,
1535
 
                            nonmethod_func.__closure__)
 
1566
                    new_function = copy_function(nonmethod_func)
1536
1567
                    new_function = (dbus.service.signal(
1537
1568
                        alt_interface,
1538
1569
                        attribute._dbus_signature)(new_function))
1577
1608
                            alt_interface,
1578
1609
                            attribute._dbus_in_signature,
1579
1610
                            attribute._dbus_out_signature)
1580
 
                        (types.FunctionType(attribute.func_code,
1581
 
                                            attribute.func_globals,
1582
 
                                            attribute.func_name,
1583
 
                                            attribute.func_defaults,
1584
 
                                            attribute.func_closure)))
 
1611
                        (copy_function(attribute)))
1585
1612
                    # Copy annotations, if any
1586
1613
                    try:
1587
1614
                        attr[attrname]._dbus_annotations = dict(
1599
1626
                        attribute._dbus_access,
1600
1627
                        attribute._dbus_get_args_options
1601
1628
                        ["byte_arrays"])
1602
 
                                      (types.FunctionType(
1603
 
                                          attribute.func_code,
1604
 
                                          attribute.func_globals,
1605
 
                                          attribute.func_name,
1606
 
                                          attribute.func_defaults,
1607
 
                                          attribute.func_closure)))
 
1629
                                      (copy_function(attribute)))
1608
1630
                    # Copy annotations, if any
1609
1631
                    try:
1610
1632
                        attr[attrname]._dbus_annotations = dict(
1619
1641
                    # to the class.
1620
1642
                    attr[attrname] = (
1621
1643
                        dbus_interface_annotations(alt_interface)
1622
 
                        (types.FunctionType(attribute.func_code,
1623
 
                                            attribute.func_globals,
1624
 
                                            attribute.func_name,
1625
 
                                            attribute.func_defaults,
1626
 
                                            attribute.func_closure)))
 
1644
                        (copy_function(attribute)))
1627
1645
            if deprecate:
1628
1646
                # Deprecate all alternate interfaces
1629
1647
                iname="_AlternateDBusNames_interface_annotation{}"
1642
1660
            if interface_names:
1643
1661
                # Replace the class with a new subclass of it with
1644
1662
                # methods, signals, etc. as created above.
1645
 
                cls = type(b"{}Alternate".format(cls.__name__),
1646
 
                           (cls, ), attr)
 
1663
                if sys.version_info.major == 2:
 
1664
                    cls = type(b"{}Alternate".format(cls.__name__),
 
1665
                               (cls, ), attr)
 
1666
                else:
 
1667
                    cls = type("{}Alternate".format(cls.__name__),
 
1668
                               (cls, ), attr)
1647
1669
        return cls
1648
1670
    
1649
1671
    return wrapper
1807
1829
    
1808
1830
    def approve(self, value=True):
1809
1831
        self.approved = value
1810
 
        GObject.timeout_add(int(self.approval_duration.total_seconds()
1811
 
                                * 1000), self._reset_approved)
 
1832
        GLib.timeout_add(int(self.approval_duration.total_seconds()
 
1833
                             * 1000), self._reset_approved)
1812
1834
        self.send_changedstate()
1813
1835
    
1814
1836
    ## D-Bus methods, signals & properties
2024
2046
                if (getattr(self, "disable_initiator_tag", None)
2025
2047
                    is None):
2026
2048
                    return
2027
 
                GObject.source_remove(self.disable_initiator_tag)
2028
 
                self.disable_initiator_tag = GObject.timeout_add(
 
2049
                GLib.source_remove(self.disable_initiator_tag)
 
2050
                self.disable_initiator_tag = GLib.timeout_add(
2029
2051
                    int((self.expires - now).total_seconds() * 1000),
2030
2052
                    self.disable)
2031
2053
    
2051
2073
            return
2052
2074
        if self.enabled:
2053
2075
            # Reschedule checker run
2054
 
            GObject.source_remove(self.checker_initiator_tag)
2055
 
            self.checker_initiator_tag = GObject.timeout_add(
 
2076
            GLib.source_remove(self.checker_initiator_tag)
 
2077
            self.checker_initiator_tag = GLib.timeout_add(
2056
2078
                value, self.start_checker)
2057
2079
            self.start_checker() # Start one now, too
2058
2080
    
2462
2484
        gnutls_priority GnuTLS priority string
2463
2485
        use_dbus:       Boolean; to emit D-Bus signals or not
2464
2486
    
2465
 
    Assumes a GObject.MainLoop event loop.
 
2487
    Assumes a GLib.MainLoop event loop.
2466
2488
    """
2467
2489
    
2468
2490
    def __init__(self, server_address, RequestHandlerClass,
2493
2515
    
2494
2516
    def add_pipe(self, parent_pipe, proc):
2495
2517
        # Call "handle_ipc" for both data and EOF events
2496
 
        GObject.io_add_watch(
 
2518
        GLib.io_add_watch(
2497
2519
            parent_pipe.fileno(),
2498
 
            GObject.IO_IN | GObject.IO_HUP,
 
2520
            GLib.IO_IN | GLib.IO_HUP,
2499
2521
            functools.partial(self.handle_ipc,
2500
2522
                              parent_pipe = parent_pipe,
2501
2523
                              proc = proc))
2505
2527
                   proc = None,
2506
2528
                   client_object=None):
2507
2529
        # error, or the other end of multiprocessing.Pipe has closed
2508
 
        if condition & (GObject.IO_ERR | GObject.IO_HUP):
 
2530
        if condition & (GLib.IO_ERR | GLib.IO_HUP):
2509
2531
            # Wait for other process to exit
2510
2532
            proc.join()
2511
2533
            return False
2518
2540
            fpr = request[1]
2519
2541
            address = request[2]
2520
2542
            
2521
 
            for c in self.clients.itervalues():
 
2543
            for c in self.clients.values():
2522
2544
                if c.fingerprint == fpr:
2523
2545
                    client = c
2524
2546
                    break
2532
2554
                parent_pipe.send(False)
2533
2555
                return False
2534
2556
            
2535
 
            GObject.io_add_watch(
 
2557
            GLib.io_add_watch(
2536
2558
                parent_pipe.fileno(),
2537
 
                GObject.IO_IN | GObject.IO_HUP,
 
2559
                GLib.IO_IN | GLib.IO_HUP,
2538
2560
                functools.partial(self.handle_ipc,
2539
2561
                                  parent_pipe = parent_pipe,
2540
2562
                                  proc = proc,
2970
2992
        # Close all input and output, do double fork, etc.
2971
2993
        daemon()
2972
2994
    
2973
 
    # multiprocessing will use threads, so before we use GObject we
2974
 
    # need to inform GObject that threads will be used.
2975
 
    GObject.threads_init()
 
2995
    # multiprocessing will use threads, so before we use GLib we need
 
2996
    # to inform GLib that threads will be used.
 
2997
    GLib.threads_init()
2976
2998
    
2977
2999
    global main_loop
2978
3000
    # From the Avahi example code
2979
3001
    DBusGMainLoop(set_as_default=True)
2980
 
    main_loop = GObject.MainLoop()
 
3002
    main_loop = GLib.MainLoop()
2981
3003
    bus = dbus.SystemBus()
2982
3004
    # End of Avahi example code
2983
3005
    if use_dbus:
3027
3049
    if server_settings["restore"]:
3028
3050
        try:
3029
3051
            with open(stored_state_path, "rb") as stored_state:
3030
 
                clients_data, old_client_settings = pickle.load(
3031
 
                    stored_state)
 
3052
                if sys.version_info.major == 2:                
 
3053
                    clients_data, old_client_settings = pickle.load(
 
3054
                        stored_state)
 
3055
                else:
 
3056
                    bytes_clients_data, bytes_old_client_settings = (
 
3057
                        pickle.load(stored_state, encoding = "bytes"))
 
3058
                    ### Fix bytes to strings
 
3059
                    ## clients_data
 
3060
                    # .keys()
 
3061
                    clients_data = { (key.decode("utf-8")
 
3062
                                      if isinstance(key, bytes)
 
3063
                                      else key): value
 
3064
                                     for key, value in
 
3065
                                     bytes_clients_data.items() }
 
3066
                    del bytes_clients_data
 
3067
                    for key in clients_data:
 
3068
                        value = { (k.decode("utf-8")
 
3069
                                   if isinstance(k, bytes) else k): v
 
3070
                                  for k, v in
 
3071
                                  clients_data[key].items() }
 
3072
                        clients_data[key] = value
 
3073
                        # .client_structure
 
3074
                        value["client_structure"] = [
 
3075
                            (s.decode("utf-8")
 
3076
                             if isinstance(s, bytes)
 
3077
                             else s) for s in
 
3078
                            value["client_structure"] ]
 
3079
                        # .name & .host
 
3080
                        for k in ("name", "host"):
 
3081
                            if isinstance(value[k], bytes):
 
3082
                                value[k] = value[k].decode("utf-8")
 
3083
                    ## old_client_settings
 
3084
                    # .keys()
 
3085
                    old_client_settings = {
 
3086
                        (key.decode("utf-8")
 
3087
                         if isinstance(key, bytes)
 
3088
                         else key): value
 
3089
                        for key, value in
 
3090
                        bytes_old_client_settings.items() }
 
3091
                    del bytes_old_client_settings
 
3092
                    # .host
 
3093
                    for value in old_client_settings.values():
 
3094
                        if isinstance(value["host"], bytes):
 
3095
                            value["host"] = (value["host"]
 
3096
                                             .decode("utf-8"))
3032
3097
            os.remove(stored_state_path)
3033
3098
        except IOError as e:
3034
3099
            if e.errno == errno.ENOENT:
3173
3238
            def GetAllClients(self):
3174
3239
                "D-Bus method"
3175
3240
                return dbus.Array(c.dbus_object_path for c in
3176
 
                                  tcp_server.clients.itervalues())
 
3241
                                  tcp_server.clients.values())
3177
3242
            
3178
3243
            @dbus_annotations({"org.freedesktop.DBus.Deprecated":
3179
3244
                               "true"})
3184
3249
                return dbus.Dictionary(
3185
3250
                    { c.dbus_object_path: c.GetAll(
3186
3251
                        "se.recompile.Mandos.Client")
3187
 
                      for c in tcp_server.clients.itervalues() },
 
3252
                      for c in tcp_server.clients.values() },
3188
3253
                    signature="oa{sv}")
3189
3254
            
3190
3255
            @dbus.service.method(_interface, in_signature="o")
3191
3256
            def RemoveClient(self, object_path):
3192
3257
                "D-Bus method"
3193
 
                for c in tcp_server.clients.itervalues():
 
3258
                for c in tcp_server.clients.values():
3194
3259
                    if c.dbus_object_path == object_path:
3195
3260
                        del tcp_server.clients[c.name]
3196
3261
                        c.remove_from_connection()
3256
3321
        # removed/edited, old secret will thus be unrecovable.
3257
3322
        clients = {}
3258
3323
        with PGPEngine() as pgp:
3259
 
            for client in tcp_server.clients.itervalues():
 
3324
            for client in tcp_server.clients.values():
3260
3325
                key = client_settings[client.name]["secret"]
3261
3326
                client.encrypted_secret = pgp.encrypt(client.secret,
3262
3327
                                                      key)
3286
3351
                    prefix='clients-',
3287
3352
                    dir=os.path.dirname(stored_state_path),
3288
3353
                    delete=False) as stored_state:
3289
 
                pickle.dump((clients, client_settings), stored_state)
 
3354
                pickle.dump((clients, client_settings), stored_state,
 
3355
                            protocol = 2)
3290
3356
                tempname = stored_state.name
3291
3357
            os.rename(tempname, stored_state_path)
3292
3358
        except (IOError, OSError) as e:
3317
3383
    
3318
3384
    atexit.register(cleanup)
3319
3385
    
3320
 
    for client in tcp_server.clients.itervalues():
 
3386
    for client in tcp_server.clients.values():
3321
3387
        if use_dbus:
3322
3388
            # Emit D-Bus signal for adding
3323
3389
            mandos_dbus_service.client_added_signal(client)
3352
3418
                sys.exit(1)
3353
3419
            # End of Avahi example code
3354
3420
        
3355
 
        GObject.io_add_watch(tcp_server.fileno(), GObject.IO_IN,
3356
 
                             lambda *args, **kwargs:
3357
 
                             (tcp_server.handle_request
3358
 
                              (*args[2:], **kwargs) or True))
 
3421
        GLib.io_add_watch(tcp_server.fileno(), GLib.IO_IN,
 
3422
                          lambda *args, **kwargs:
 
3423
                          (tcp_server.handle_request
 
3424
                           (*args[2:], **kwargs) or True))
3359
3425
        
3360
3426
        logger.debug("Starting main loop")
3361
3427
        main_loop.run()