/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

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
 
173
188
class Client(dbus.service.Object):
174
189
    """A representation of a client host served by this server.
175
190
    Attributes:
176
 
    name:      string; from the config file, used in log messages
 
191
    name:       string; from the config file, used in log messages
177
192
    fingerprint: string (40 or 32 hexadecimal digits); used to
178
193
                 uniquely identify the client
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
 
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()
183
199
    last_checked_ok: datetime.datetime(); (UTC) or None
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.
 
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.
191
207
    checker_initiator_tag: a gobject event source tag, or None
192
208
    stop_initiator_tag:    - '' -
193
209
    checker_callback_tag:  - '' -
195
211
                     client lives.  %() expansions are done at
196
212
                     runtime with vars(self) as dict, so that for
197
213
                     instance %(name)s can be used in the command.
 
214
    dbus_object_path: dbus.ObjectPath
198
215
    Private attibutes:
199
216
    _timeout: Real variable for 'timeout'
200
217
    _interval: Real variable for 'interval'
210
227
                                      + (self.timeout.microseconds
211
228
                                         // 1000))
212
229
        # Emit D-Bus signal
213
 
        self.TimeoutChanged(self._timeout_milliseconds)
 
230
        self.PropertyChanged(dbus.String(u"timeout"),
 
231
                             (dbus.UInt64(self._timeout_milliseconds,
 
232
                                          variant_level=1)))
214
233
    timeout = property(lambda self: self._timeout, _set_timeout)
215
234
    del _set_timeout
216
235
    
224
243
                                       + (self.interval.microseconds
225
244
                                          // 1000))
226
245
        # Emit D-Bus signal
227
 
        self.IntervalChanged(self._interval_milliseconds)
 
246
        self.PropertyChanged(dbus.String(u"interval"),
 
247
                             (dbus.UInt64(self._interval_milliseconds,
 
248
                                          variant_level=1)))
228
249
    interval = property(lambda self: self._interval, _set_interval)
229
250
    del _set_interval
230
251
    
232
253
        """Note: the 'checker' key in 'config' sets the
233
254
        'checker_command' attribute and *not* the 'checker'
234
255
        attribute."""
 
256
        self.dbus_object_path = (dbus.ObjectPath
 
257
                                 ("/Mandos/clients/"
 
258
                                  + name.replace(".", "_")))
235
259
        dbus.service.Object.__init__(self, bus,
236
 
                                     "/Mandos/clients/%s"
237
 
                                     % name.replace(".", "_"))
 
260
                                     self.dbus_object_path)
238
261
        if config is None:
239
262
            config = {}
240
263
        self.name = name
257
280
                            % self.name)
258
281
        self.host = config.get("host", "")
259
282
        self.created = datetime.datetime.utcnow()
260
 
        self.started = None
 
283
        self.started = False
 
284
        self.last_started = None
261
285
        self.last_checked_ok = None
262
286
        self.timeout = string_to_delta(config["timeout"])
263
287
        self.interval = string_to_delta(config["interval"])
266
290
        self.checker_initiator_tag = None
267
291
        self.stop_initiator_tag = None
268
292
        self.checker_callback_tag = None
269
 
        self.check_command = config["checker"]
 
293
        self.checker_command = config["checker"]
270
294
    
271
295
    def start(self):
272
296
        """Start this client's checker and timeout hooks"""
273
 
        self.started = datetime.datetime.utcnow()
 
297
        self.last_started = datetime.datetime.utcnow()
274
298
        # Schedule a new checker to be started an 'interval' from now,
275
299
        # and every interval from then on.
276
300
        self.checker_initiator_tag = (gobject.timeout_add
282
306
        self.stop_initiator_tag = (gobject.timeout_add
283
307
                                   (self._timeout_milliseconds,
284
308
                                    self.stop))
 
309
        self.started = True
285
310
        # Emit D-Bus signal
286
 
        self.StateChanged(True)
 
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)))
287
316
    
288
317
    def stop(self):
289
318
        """Stop this client."""
290
 
        if getattr(self, "started", None) is not None:
291
 
            logger.info(u"Stopping client %s", self.name)
292
 
        else:
 
319
        if not getattr(self, "started", False):
293
320
            return False
 
321
        logger.info(u"Stopping client %s", self.name)
294
322
        if getattr(self, "stop_initiator_tag", False):
295
323
            gobject.source_remove(self.stop_initiator_tag)
296
324
            self.stop_initiator_tag = None
300
328
        self.stop_checker()
301
329
        if self.stop_hook:
302
330
            self.stop_hook(self)
303
 
        self.started = None
 
331
        self.started = False
304
332
        # Emit D-Bus signal
305
 
        self.StateChanged(False)
 
333
        self.PropertyChanged(dbus.String(u"started"),
 
334
                             dbus.Boolean(False, variant_level=1))
306
335
        # Do not run this again if called by a gobject.timeout_add
307
336
        return False
308
337
    
310
339
        self.stop_hook = None
311
340
        self.stop()
312
341
    
313
 
    def checker_callback(self, pid, condition):
 
342
    def checker_callback(self, pid, condition, command):
314
343
        """The checker has completed, so take appropriate actions."""
315
344
        self.checker_callback_tag = None
316
345
        self.checker = None
317
 
        if (os.WIFEXITED(condition) 
 
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)
318
350
            and (os.WEXITSTATUS(condition) == 0)):
319
351
            logger.info(u"Checker for %(name)s succeeded",
320
352
                        vars(self))
321
353
            # Emit D-Bus signal
322
 
            self.CheckerCompleted(True)
 
354
            self.CheckerCompleted(dbus.Boolean(True),
 
355
                                  dbus.UInt16(condition),
 
356
                                  dbus.String(command))
323
357
            self.bump_timeout()
324
358
        elif not os.WIFEXITED(condition):
325
359
            logger.warning(u"Checker for %(name)s crashed?",
326
360
                           vars(self))
327
361
            # Emit D-Bus signal
328
 
            self.CheckerCompleted(False)
 
362
            self.CheckerCompleted(dbus.Boolean(False),
 
363
                                  dbus.UInt16(condition),
 
364
                                  dbus.String(command))
329
365
        else:
330
366
            logger.info(u"Checker for %(name)s failed",
331
367
                        vars(self))
332
368
            # Emit D-Bus signal
333
 
            self.CheckerCompleted(False)
 
369
            self.CheckerCompleted(dbus.Boolean(False),
 
370
                                  dbus.UInt16(condition),
 
371
                                  dbus.String(command))
334
372
    
335
373
    def bump_timeout(self):
336
374
        """Bump up the timeout for this client.
342
380
        self.stop_initiator_tag = (gobject.timeout_add
343
381
                                   (self._timeout_milliseconds,
344
382
                                    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)))
345
387
    
346
388
    def start_checker(self):
347
389
        """Start a new checker subprocess if one is not running.
357
399
        # is as it should be.
358
400
        if self.checker is None:
359
401
            try:
360
 
                # In case check_command has exactly one % operator
361
 
                command = self.check_command % self.host
 
402
                # In case checker_command has exactly one % operator
 
403
                command = self.checker_command % self.host
362
404
            except TypeError:
363
405
                # Escape attributes for the shell
364
406
                escaped_attrs = dict((key, re.escape(str(val)))
365
407
                                     for key, val in
366
408
                                     vars(self).iteritems())
367
409
                try:
368
 
                    command = self.check_command % escaped_attrs
 
410
                    command = self.checker_command % escaped_attrs
369
411
                except TypeError, error:
370
412
                    logger.error(u'Could not format string "%s":'
371
 
                                 u' %s', self.check_command, error)
 
413
                                 u' %s', self.checker_command, error)
372
414
                    return True # Try again later
373
415
            try:
374
416
                logger.info(u"Starting checker %r for %s",
380
422
                self.checker = subprocess.Popen(command,
381
423
                                                close_fds=True,
382
424
                                                shell=True, cwd="/")
 
425
                # Emit D-Bus signal
 
426
                self.CheckerStarted(command)
 
427
                self.PropertyChanged(dbus.String("checker_running"),
 
428
                                     dbus.Boolean(True, variant_level=1))
383
429
                self.checker_callback_tag = (gobject.child_watch_add
384
430
                                             (self.checker.pid,
385
 
                                              self.checker_callback))
386
 
                # Emit D-Bus signal
387
 
                self.CheckerStarted(command)
 
431
                                              self.checker_callback,
 
432
                                              data=command))
388
433
            except OSError, error:
389
434
                logger.error(u"Failed to start subprocess: %s",
390
435
                             error)
408
453
            if error.errno != errno.ESRCH: # No such process
409
454
                raise
410
455
        self.checker = None
 
456
        self.PropertyChanged(dbus.String(u"checker_running"),
 
457
                             dbus.Boolean(False, variant_level=1))
411
458
    
412
459
    def still_valid(self):
413
460
        """Has the timeout not yet passed for this client?"""
414
 
        if not self.started:
 
461
        if not getattr(self, "started", False):
415
462
            return False
416
463
        now = datetime.datetime.utcnow()
417
464
        if self.last_checked_ok is None:
422
469
    ## D-Bus methods & signals
423
470
    _interface = u"org.mandos_system.Mandos.Client"
424
471
    
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
 
    
430
472
    # BumpTimeout - method
431
473
    BumpTimeout = dbus.service.method(_interface)(bump_timeout)
432
474
    BumpTimeout.__name__ = "BumpTimeout"
433
475
    
434
 
    # IntervalChanged - signal
435
 
    @dbus.service.signal(_interface, signature="t")
436
 
    def IntervalChanged(self, t):
437
 
        "D-Bus signal"
438
 
        pass
439
 
    
440
476
    # CheckerCompleted - signal
441
 
    @dbus.service.signal(_interface, signature="b")
442
 
    def CheckerCompleted(self, success):
 
477
    @dbus.service.signal(_interface, signature="bqs")
 
478
    def CheckerCompleted(self, success, condition, command):
443
479
        "D-Bus signal"
444
480
        pass
445
481
    
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
 
    
452
482
    # CheckerStarted - signal
453
483
    @dbus.service.signal(_interface, signature="s")
454
484
    def CheckerStarted(self, command):
455
485
        "D-Bus signal"
456
486
        pass
457
487
    
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
 
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
509
538
    
510
539
    # SetChecker - method
511
540
    @dbus.service.method(_interface, in_signature="s")
524
553
    def SetInterval(self, milliseconds):
525
554
        self.interval = datetime.timdeelta(0, 0, 0, milliseconds)
526
555
    
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
 
    
532
556
    # SetSecret - method
533
557
    @dbus.service.method(_interface, in_signature="ay",
534
558
                         byte_arrays=True)
536
560
        "D-Bus setter method"
537
561
        self.secret = str(secret)
538
562
    
 
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
    
539
568
    # Start - method
540
569
    Start = dbus.service.method(_interface)(start)
541
570
    Start.__name__ = "Start"
542
571
    
543
572
    # StartChecker - method
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"
 
573
    @dbus.service.method(_interface)
 
574
    def StartChecker(self):
 
575
        "D-Bus method"
 
576
        self.start_checker()
557
577
    
558
578
    # Stop - method
559
 
    Stop = dbus.service.method(_interface)(stop)
560
 
    Stop.__name__ = "Stop"
 
579
    @dbus.service.method(_interface)
 
580
    def Stop(self):
 
581
        "D-Bus method"
 
582
        self.stop()
561
583
    
562
584
    # StopChecker - method
563
585
    StopChecker = dbus.service.method(_interface)(stop_checker)
564
586
    StopChecker.__name__ = "StopChecker"
565
587
    
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
573
588
    del _interface
574
589
 
575
590
 
971
986
    except IOError, error:
972
987
        logger.error("Could not open file %r", pidfilename)
973
988
    
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
 
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
990
1009
    try:
991
1010
        os.setuid(uid)
992
1011
        os.setgid(gid)
1014
1033
    # End of Avahi example code
1015
1034
    bus_name = dbus.service.BusName(u"org.mandos-system.Mandos", bus)
1016
1035
    
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
 
    
1023
1036
    clients.update(Set(Client(name = section,
1024
 
                              stop_hook = remove_from_clients,
1025
1037
                              config
1026
1038
                              = dict(client_config.items(section)))
1027
1039
                       for section in client_config.sections()))
1075
1087
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1076
1088
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1077
1089
    
 
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
    
1078
1131
    for client in clients:
 
1132
        # Emit D-Bus signal
 
1133
        mandos_server.ClientAdded(client.dbus_object_path,
 
1134
                                  client.GetAllProperties())
1079
1135
        client.start()
1080
1136
    
1081
1137
    tcp_server.enable()