/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

merge

Show diffs side-by-side

added added

removed removed

Lines of Context:
170
170
# End of Avahi example code
171
171
 
172
172
 
173
 
def _datetime_to_dbus_struct(dt, variant_level=0):
174
 
    """Convert a UTC datetime.datetime() to a D-Bus struct.
175
 
    The format is special to this application, since we could not find
176
 
    any other standard way."""
177
 
    return dbus.Struct((dbus.Int16(dt.year),
178
 
                        dbus.Byte(dt.month),
179
 
                        dbus.Byte(dt.day),
180
 
                        dbus.Byte(dt.hour),
181
 
                        dbus.Byte(dt.minute),
182
 
                        dbus.Byte(dt.second),
183
 
                        dbus.UInt32(dt.microsecond)),
184
 
                       signature="nyyyyyu",
185
 
                       variant_level=variant_level)
186
 
 
187
 
 
188
173
class Client(dbus.service.Object):
189
174
    """A representation of a client host served by this server.
190
175
    Attributes:
191
 
    name:       string; from the config file, used in log messages
 
176
    name:      string; from the config file, used in log messages
192
177
    fingerprint: string (40 or 32 hexadecimal digits); used to
193
178
                 uniquely identify the client
194
 
    secret:     bytestring; sent verbatim (over TLS) to client
195
 
    host:       string; available for use by the checker command
196
 
    created:    datetime.datetime(); (UTC) object creation
197
 
    last_started: datetime.datetime(); (UTC)
198
 
    started:    bool()
 
179
    secret:    bytestring; sent verbatim (over TLS) to client
 
180
    host:      string; available for use by the checker command
 
181
    created:   datetime.datetime(); (UTC) object creation
 
182
    started:   datetime.datetime(); (UTC) last started
199
183
    last_checked_ok: datetime.datetime(); (UTC) or None
200
 
    timeout:    datetime.timedelta(); How long from last_checked_ok
201
 
                                      until this client is invalid
202
 
    interval:   datetime.timedelta(); How often to start a new checker
203
 
    stop_hook:  If set, called by stop() as stop_hook(self)
204
 
    checker:    subprocess.Popen(); a running checker process used
205
 
                                    to see if the client lives.
206
 
                                    'None' if no process is running.
 
184
    timeout:   datetime.timedelta(); How long from last_checked_ok
 
185
                                     until this client is invalid
 
186
    interval:  datetime.timedelta(); How often to start a new checker
 
187
    stop_hook: If set, called by stop() as stop_hook(self)
 
188
    checker:   subprocess.Popen(); a running checker process used
 
189
                                   to see if the client lives.
 
190
                                   'None' if no process is running.
207
191
    checker_initiator_tag: a gobject event source tag, or None
208
192
    stop_initiator_tag:    - '' -
209
193
    checker_callback_tag:  - '' -
211
195
                     client lives.  %() expansions are done at
212
196
                     runtime with vars(self) as dict, so that for
213
197
                     instance %(name)s can be used in the command.
214
 
    dbus_object_path: dbus.ObjectPath
215
198
    Private attibutes:
216
199
    _timeout: Real variable for 'timeout'
217
200
    _interval: Real variable for 'interval'
227
210
                                      + (self.timeout.microseconds
228
211
                                         // 1000))
229
212
        # Emit D-Bus signal
230
 
        self.PropertyChanged(dbus.String(u"timeout"),
231
 
                             (dbus.UInt64(self._timeout_milliseconds,
232
 
                                          variant_level=1)))
 
213
        self.TimeoutChanged(self._timeout_milliseconds)
233
214
    timeout = property(lambda self: self._timeout, _set_timeout)
234
215
    del _set_timeout
235
216
    
243
224
                                       + (self.interval.microseconds
244
225
                                          // 1000))
245
226
        # Emit D-Bus signal
246
 
        self.PropertyChanged(dbus.String(u"interval"),
247
 
                             (dbus.UInt64(self._interval_milliseconds,
248
 
                                          variant_level=1)))
 
227
        self.IntervalChanged(self._interval_milliseconds)
249
228
    interval = property(lambda self: self._interval, _set_interval)
250
229
    del _set_interval
251
230
    
253
232
        """Note: the 'checker' key in 'config' sets the
254
233
        'checker_command' attribute and *not* the 'checker'
255
234
        attribute."""
256
 
        self.dbus_object_path = (dbus.ObjectPath
257
 
                                 ("/Mandos/clients/"
258
 
                                  + name.replace(".", "_")))
259
235
        dbus.service.Object.__init__(self, bus,
260
 
                                     self.dbus_object_path)
 
236
                                     "/Mandos/clients/%s"
 
237
                                     % name.replace(".", "_"))
261
238
        if config is None:
262
239
            config = {}
263
240
        self.name = name
280
257
                            % self.name)
281
258
        self.host = config.get("host", "")
282
259
        self.created = datetime.datetime.utcnow()
283
 
        self.started = False
284
 
        self.last_started = None
 
260
        self.started = None
285
261
        self.last_checked_ok = None
286
262
        self.timeout = string_to_delta(config["timeout"])
287
263
        self.interval = string_to_delta(config["interval"])
290
266
        self.checker_initiator_tag = None
291
267
        self.stop_initiator_tag = None
292
268
        self.checker_callback_tag = None
293
 
        self.checker_command = config["checker"]
 
269
        self.check_command = config["checker"]
294
270
    
295
271
    def start(self):
296
272
        """Start this client's checker and timeout hooks"""
297
 
        self.last_started = datetime.datetime.utcnow()
 
273
        self.started = datetime.datetime.utcnow()
298
274
        # Schedule a new checker to be started an 'interval' from now,
299
275
        # and every interval from then on.
300
276
        self.checker_initiator_tag = (gobject.timeout_add
306
282
        self.stop_initiator_tag = (gobject.timeout_add
307
283
                                   (self._timeout_milliseconds,
308
284
                                    self.stop))
309
 
        self.started = True
310
285
        # Emit D-Bus signal
311
 
        self.PropertyChanged(dbus.String(u"started"),
312
 
                             dbus.Boolean(True, variant_level=1))
313
 
        self.PropertyChanged(dbus.String(u"last_started"),
314
 
                             (_datetime_to_dbus_struct
315
 
                              (self.last_started, variant_level=1)))
 
286
        self.StateChanged(True)
316
287
    
317
288
    def stop(self):
318
289
        """Stop this client."""
319
 
        if not getattr(self, "started", False):
 
290
        if getattr(self, "started", None) is not None:
 
291
            logger.info(u"Stopping client %s", self.name)
 
292
        else:
320
293
            return False
321
 
        logger.info(u"Stopping client %s", self.name)
322
294
        if getattr(self, "stop_initiator_tag", False):
323
295
            gobject.source_remove(self.stop_initiator_tag)
324
296
            self.stop_initiator_tag = None
328
300
        self.stop_checker()
329
301
        if self.stop_hook:
330
302
            self.stop_hook(self)
331
 
        self.started = False
 
303
        self.started = None
332
304
        # Emit D-Bus signal
333
 
        self.PropertyChanged(dbus.String(u"started"),
334
 
                             dbus.Boolean(False, variant_level=1))
 
305
        self.StateChanged(False)
335
306
        # Do not run this again if called by a gobject.timeout_add
336
307
        return False
337
308
    
339
310
        self.stop_hook = None
340
311
        self.stop()
341
312
    
342
 
    def checker_callback(self, pid, condition, command):
 
313
    def checker_callback(self, pid, condition):
343
314
        """The checker has completed, so take appropriate actions."""
344
315
        self.checker_callback_tag = None
345
316
        self.checker = None
346
 
        # Emit D-Bus signal
347
 
        self.PropertyChanged(dbus.String(u"checker_running"),
348
 
                             dbus.Boolean(False, variant_level=1))
349
 
        if (os.WIFEXITED(condition)
 
317
        if (os.WIFEXITED(condition) 
350
318
            and (os.WEXITSTATUS(condition) == 0)):
351
319
            logger.info(u"Checker for %(name)s succeeded",
352
320
                        vars(self))
353
321
            # Emit D-Bus signal
354
 
            self.CheckerCompleted(dbus.Boolean(True),
355
 
                                  dbus.UInt16(condition),
356
 
                                  dbus.String(command))
 
322
            self.CheckerCompleted(True)
357
323
            self.bump_timeout()
358
324
        elif not os.WIFEXITED(condition):
359
325
            logger.warning(u"Checker for %(name)s crashed?",
360
326
                           vars(self))
361
327
            # Emit D-Bus signal
362
 
            self.CheckerCompleted(dbus.Boolean(False),
363
 
                                  dbus.UInt16(condition),
364
 
                                  dbus.String(command))
 
328
            self.CheckerCompleted(False)
365
329
        else:
366
330
            logger.info(u"Checker for %(name)s failed",
367
331
                        vars(self))
368
332
            # Emit D-Bus signal
369
 
            self.CheckerCompleted(dbus.Boolean(False),
370
 
                                  dbus.UInt16(condition),
371
 
                                  dbus.String(command))
 
333
            self.CheckerCompleted(False)
372
334
    
373
335
    def bump_timeout(self):
374
336
        """Bump up the timeout for this client.
380
342
        self.stop_initiator_tag = (gobject.timeout_add
381
343
                                   (self._timeout_milliseconds,
382
344
                                    self.stop))
383
 
        self.PropertyChanged(dbus.String(u"last_checked_ok"),
384
 
                             (_datetime_to_dbus_struct
385
 
                              (self.last_checked_ok,
386
 
                               variant_level=1)))
387
345
    
388
346
    def start_checker(self):
389
347
        """Start a new checker subprocess if one is not running.
399
357
        # is as it should be.
400
358
        if self.checker is None:
401
359
            try:
402
 
                # In case checker_command has exactly one % operator
403
 
                command = self.checker_command % self.host
 
360
                # In case check_command has exactly one % operator
 
361
                command = self.check_command % self.host
404
362
            except TypeError:
405
363
                # Escape attributes for the shell
406
364
                escaped_attrs = dict((key, re.escape(str(val)))
407
365
                                     for key, val in
408
366
                                     vars(self).iteritems())
409
367
                try:
410
 
                    command = self.checker_command % escaped_attrs
 
368
                    command = self.check_command % escaped_attrs
411
369
                except TypeError, error:
412
370
                    logger.error(u'Could not format string "%s":'
413
 
                                 u' %s', self.checker_command, error)
 
371
                                 u' %s', self.check_command, error)
414
372
                    return True # Try again later
415
373
            try:
416
374
                logger.info(u"Starting checker %r for %s",
422
380
                self.checker = subprocess.Popen(command,
423
381
                                                close_fds=True,
424
382
                                                shell=True, cwd="/")
 
383
                self.checker_callback_tag = (gobject.child_watch_add
 
384
                                             (self.checker.pid,
 
385
                                              self.checker_callback))
425
386
                # Emit D-Bus signal
426
387
                self.CheckerStarted(command)
427
 
                self.PropertyChanged(dbus.String("checker_running"),
428
 
                                     dbus.Boolean(True, variant_level=1))
429
 
                self.checker_callback_tag = (gobject.child_watch_add
430
 
                                             (self.checker.pid,
431
 
                                              self.checker_callback,
432
 
                                              data=command))
433
388
            except OSError, error:
434
389
                logger.error(u"Failed to start subprocess: %s",
435
390
                             error)
453
408
            if error.errno != errno.ESRCH: # No such process
454
409
                raise
455
410
        self.checker = None
456
 
        self.PropertyChanged(dbus.String(u"checker_running"),
457
 
                             dbus.Boolean(False, variant_level=1))
458
411
    
459
412
    def still_valid(self):
460
413
        """Has the timeout not yet passed for this client?"""
461
 
        if not getattr(self, "started", False):
 
414
        if not self.started:
462
415
            return False
463
416
        now = datetime.datetime.utcnow()
464
417
        if self.last_checked_ok is None:
469
422
    ## D-Bus methods & signals
470
423
    _interface = u"org.mandos_system.Mandos.Client"
471
424
    
 
425
    def _datetime_to_dbus_struct(dt):
 
426
        return dbus.Struct(dt.year, dt.month, dt.day, dt.hour,
 
427
                           dt.minute, dt.second, dt.microsecond,
 
428
                           signature="nyyyyyu")
 
429
    
472
430
    # BumpTimeout - method
473
431
    BumpTimeout = dbus.service.method(_interface)(bump_timeout)
474
432
    BumpTimeout.__name__ = "BumpTimeout"
475
433
    
 
434
    # IntervalChanged - signal
 
435
    @dbus.service.signal(_interface, signature="t")
 
436
    def IntervalChanged(self, t):
 
437
        "D-Bus signal"
 
438
        pass
 
439
    
476
440
    # CheckerCompleted - signal
477
 
    @dbus.service.signal(_interface, signature="bqs")
478
 
    def CheckerCompleted(self, success, condition, command):
 
441
    @dbus.service.signal(_interface, signature="b")
 
442
    def CheckerCompleted(self, success):
479
443
        "D-Bus signal"
480
444
        pass
481
445
    
 
446
    # CheckerIsRunning - method
 
447
    @dbus.service.method(_interface, out_signature="b")
 
448
    def CheckerIsRunning(self):
 
449
        "D-Bus getter method"
 
450
        return self.checker is not None
 
451
    
482
452
    # CheckerStarted - signal
483
453
    @dbus.service.signal(_interface, signature="s")
484
454
    def CheckerStarted(self, command):
485
455
        "D-Bus signal"
486
456
        pass
487
457
    
488
 
    # GetAllProperties - method
489
 
    @dbus.service.method(_interface, out_signature="a{sv}")
490
 
    def GetAllProperties(self):
491
 
        "D-Bus method"
492
 
        return dbus.Dictionary({
493
 
                dbus.String("name"):
494
 
                    dbus.String(self.name, variant_level=1),
495
 
                dbus.String("fingerprint"):
496
 
                    dbus.String(self.fingerprint, variant_level=1),
497
 
                dbus.String("host"):
498
 
                    dbus.String(self.host, variant_level=1),
499
 
                dbus.String("created"):
500
 
                    _datetime_to_dbus_struct(self.created,
501
 
                                             variant_level=1),
502
 
                dbus.String("last_started"):
503
 
                    (_datetime_to_dbus_struct(self.last_started,
504
 
                                              variant_level=1)
505
 
                     if self.last_started is not None
506
 
                     else dbus.Boolean(False, variant_level=1)),
507
 
                dbus.String("started"):
508
 
                    dbus.Boolean(self.started, variant_level=1),
509
 
                dbus.String("last_checked_ok"):
510
 
                    (_datetime_to_dbus_struct(self.last_checked_ok,
511
 
                                              variant_level=1)
512
 
                     if self.last_checked_ok is not None
513
 
                     else dbus.Boolean (False, variant_level=1)),
514
 
                dbus.String("timeout"):
515
 
                    dbus.UInt64(self._timeout_milliseconds,
516
 
                                variant_level=1),
517
 
                dbus.String("interval"):
518
 
                    dbus.UInt64(self._interval_milliseconds,
519
 
                                variant_level=1),
520
 
                dbus.String("checker"):
521
 
                    dbus.String(self.checker_command,
522
 
                                variant_level=1),
523
 
                dbus.String("checker_running"):
524
 
                    dbus.Boolean(self.checker is not None,
525
 
                                 variant_level=1),
526
 
                }, signature="sv")
527
 
    
528
 
    # IsStillValid - method
529
 
    IsStillValid = (dbus.service.method(_interface, out_signature="b")
530
 
                    (still_valid))
531
 
    IsStillValid.__name__ = "IsStillValid"
532
 
    
533
 
    # PropertyChanged - signal
534
 
    @dbus.service.signal(_interface, signature="sv")
535
 
    def PropertyChanged(self, property, value):
536
 
        "D-Bus signal"
537
 
        pass
 
458
    # GetChecker - method
 
459
    @dbus.service.method(_interface, out_signature="s")
 
460
    def GetChecker(self):
 
461
        "D-Bus getter method"
 
462
        return self.checker_command
 
463
    
 
464
    # GetCreated - method
 
465
    @dbus.service.method(_interface, out_signature="(nyyyyyu)")
 
466
    def GetCreated(self):
 
467
        "D-Bus getter method"
 
468
        return datetime_to_dbus_struct(self.created)
 
469
    
 
470
    # GetFingerprint - method
 
471
    @dbus.service.method(_interface, out_signature="s")
 
472
    def GetFingerprint(self):
 
473
        "D-Bus getter method"
 
474
        return self.fingerprint
 
475
    
 
476
    # GetHost - method
 
477
    @dbus.service.method(_interface, out_signature="s")
 
478
    def GetHost(self):
 
479
        "D-Bus getter method"
 
480
        return self.host
 
481
    
 
482
    # GetInterval - method
 
483
    @dbus.service.method(_interface, out_signature="t")
 
484
    def GetInterval(self):
 
485
        "D-Bus getter method"
 
486
        return self._interval_milliseconds
 
487
    
 
488
    # GetName - method
 
489
    @dbus.service.method(_interface, out_signature="s")
 
490
    def GetName(self):
 
491
        "D-Bus getter method"
 
492
        return self.name
 
493
    
 
494
    # GetStarted - method
 
495
    @dbus.service.method(_interface, out_signature="(nyyyyyu)")
 
496
    def GetStarted(self):
 
497
        "D-Bus getter method"
 
498
        if self.started is not None:
 
499
            return datetime_to_dbus_struct(self.started)
 
500
        else:
 
501
            return dbus.Struct(0, 0, 0, 0, 0, 0, 0,
 
502
                               signature="nyyyyyu")
 
503
    
 
504
    # GetTimeout - method
 
505
    @dbus.service.method(_interface, out_signature="t")
 
506
    def GetTimeout(self):
 
507
        "D-Bus getter method"
 
508
        return self._timeout_milliseconds
538
509
    
539
510
    # SetChecker - method
540
511
    @dbus.service.method(_interface, in_signature="s")
553
524
    def SetInterval(self, milliseconds):
554
525
        self.interval = datetime.timdeelta(0, 0, 0, milliseconds)
555
526
    
 
527
    # SetTimeout - method
 
528
    @dbus.service.method(_interface, in_signature="t")
 
529
    def SetTimeout(self, milliseconds):
 
530
        self.timeout = datetime.timedelta(0, 0, 0, milliseconds)
 
531
    
556
532
    # SetSecret - method
557
533
    @dbus.service.method(_interface, in_signature="ay",
558
534
                         byte_arrays=True)
560
536
        "D-Bus setter method"
561
537
        self.secret = str(secret)
562
538
    
563
 
    # SetTimeout - method
564
 
    @dbus.service.method(_interface, in_signature="t")
565
 
    def SetTimeout(self, milliseconds):
566
 
        self.timeout = datetime.timedelta(0, 0, 0, milliseconds)
567
 
    
568
539
    # Start - method
569
540
    Start = dbus.service.method(_interface)(start)
570
541
    Start.__name__ = "Start"
571
542
    
572
543
    # StartChecker - method
573
 
    @dbus.service.method(_interface)
574
 
    def StartChecker(self):
575
 
        "D-Bus method"
576
 
        self.start_checker()
 
544
    StartChecker = dbus.service.method(_interface)(start_checker)
 
545
    StartChecker.__name__ = "StartChecker"
 
546
    
 
547
    # StateChanged - signal
 
548
    @dbus.service.signal(_interface, signature="b")
 
549
    def StateChanged(self, started):
 
550
        "D-Bus signal"
 
551
        pass
 
552
    
 
553
    # StillValid - method
 
554
    StillValid = (dbus.service.method(_interface, out_signature="b")
 
555
                  (still_valid))
 
556
    StillValid.__name__ = "StillValid"
577
557
    
578
558
    # Stop - method
579
 
    @dbus.service.method(_interface)
580
 
    def Stop(self):
581
 
        "D-Bus method"
582
 
        self.stop()
 
559
    Stop = dbus.service.method(_interface)(stop)
 
560
    Stop.__name__ = "Stop"
583
561
    
584
562
    # StopChecker - method
585
563
    StopChecker = dbus.service.method(_interface)(stop_checker)
586
564
    StopChecker.__name__ = "StopChecker"
587
565
    
 
566
    # TimeoutChanged - signal
 
567
    @dbus.service.signal(_interface, signature="t")
 
568
    def TimeoutChanged(self, t):
 
569
        "D-Bus signal"
 
570
        pass
 
571
    
 
572
    del _datetime_to_dbus_struct
588
573
    del _interface
589
574
 
590
575
 
986
971
    except IOError, error:
987
972
        logger.error("Could not open file %r", pidfilename)
988
973
    
989
 
    try:
990
 
        uid = pwd.getpwnam("_mandos").pw_uid
991
 
    except KeyError:
992
 
        try:
993
 
            uid = pwd.getpwnam("mandos").pw_uid
994
 
        except KeyError:
995
 
            try:
996
 
                uid = pwd.getpwnam("nobody").pw_uid
997
 
            except KeyError:
998
 
                uid = 65534
999
 
    try:
1000
 
        gid = pwd.getpwnam("_mandos").pw_gid
1001
 
    except KeyError:
1002
 
        try:
1003
 
            gid = pwd.getpwnam("mandos").pw_gid
1004
 
        except KeyError:
1005
 
            try:
1006
 
                gid = pwd.getpwnam("nogroup").pw_gid
1007
 
            except KeyError:
1008
 
                gid = 65534
 
974
    uid = 65534
 
975
    gid = 65534
 
976
    try:
 
977
        uid = pwd.getpwnam("mandos").pw_uid
 
978
    except KeyError:
 
979
        try:
 
980
            uid = pwd.getpwnam("nobody").pw_uid
 
981
        except KeyError:
 
982
            pass
 
983
    try:
 
984
        gid = pwd.getpwnam("mandos").pw_gid
 
985
    except KeyError:
 
986
        try:
 
987
            gid = pwd.getpwnam("nogroup").pw_gid
 
988
        except KeyError:
 
989
            pass
1009
990
    try:
1010
991
        os.setuid(uid)
1011
992
        os.setgid(gid)
1033
1014
    # End of Avahi example code
1034
1015
    bus_name = dbus.service.BusName(u"org.mandos-system.Mandos", bus)
1035
1016
    
 
1017
    def remove_from_clients(client):
 
1018
        clients.remove(client)
 
1019
        if not clients:
 
1020
            logger.critical(u"No clients left, exiting")
 
1021
            sys.exit()
 
1022
    
1036
1023
    clients.update(Set(Client(name = section,
 
1024
                              stop_hook = remove_from_clients,
1037
1025
                              config
1038
1026
                              = dict(client_config.items(section)))
1039
1027
                       for section in client_config.sections()))
1087
1075
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1088
1076
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1089
1077
    
1090
 
    class MandosServer(dbus.service.Object):
1091
 
        """A D-Bus proxy object"""
1092
 
        def __init__(self):
1093
 
            dbus.service.Object.__init__(self, bus,
1094
 
                                         "/Mandos")
1095
 
        _interface = u"org.mandos_system.Mandos"
1096
 
        
1097
 
        @dbus.service.signal(_interface, signature="oa{sv}")
1098
 
        def ClientAdded(self, objpath, properties):
1099
 
            "D-Bus signal"
1100
 
            pass
1101
 
        
1102
 
        @dbus.service.signal(_interface, signature="o")
1103
 
        def ClientRemoved(self, objpath):
1104
 
            "D-Bus signal"
1105
 
            pass
1106
 
        
1107
 
        @dbus.service.method(_interface, out_signature="ao")
1108
 
        def GetAllClients(self):
1109
 
            return dbus.Array(c.dbus_object_path for c in clients)
1110
 
        
1111
 
        @dbus.service.method(_interface, out_signature="a{oa{sv}}")
1112
 
        def GetAllClientsWithProperties(self):
1113
 
            return dbus.Dictionary(
1114
 
                ((c.dbus_object_path, c.GetAllProperties())
1115
 
                 for c in clients),
1116
 
                signature="oa{sv}")
1117
 
        
1118
 
        @dbus.service.method(_interface, in_signature="o")
1119
 
        def RemoveClient(self, object_path):
1120
 
            for c in clients:
1121
 
                if c.dbus_object_path == object_path:
1122
 
                    c.stop()
1123
 
                    clients.remove(c)
1124
 
                    return
1125
 
            raise KeyError
1126
 
        
1127
 
        del _interface
1128
 
    
1129
 
    mandos_server = MandosServer()
1130
 
    
1131
1078
    for client in clients:
1132
 
        # Emit D-Bus signal
1133
 
        mandos_server.ClientAdded(client.dbus_object_path,
1134
 
                                  client.GetAllProperties())
1135
1079
        client.start()
1136
1080
    
1137
1081
    tcp_server.enable()