/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: 2012-06-23 00:58:49 UTC
  • Revision ID: teddy@recompile.se-20120623005849-02wj82cng433rt2k
* clients.conf: Convert all time intervals to new RFC 3339 syntax.
* mandos: All client options for time intervals now take an RFC 3339
          duration.
  (rfc3339_duration_to_delta): New function.
  (string_to_delta): Try rfc3339_duration_to_delta first.
* mandos-clients.conf.xml (OPTIONS/timeout): Document new format.
  (EXAMPLE): Update to new interval format.
  (SEE ALSO): Reference RFC 3339.

Show diffs side-by-side

added added

removed removed

Lines of Context:
11
11
# "AvahiService" class, and some lines in "main".
12
12
13
13
# Everything else is
14
 
# Copyright © 2008-2014 Teddy Hogeborn
15
 
# Copyright © 2008-2014 Björn Påhlsson
 
14
# Copyright © 2008-2012 Teddy Hogeborn
 
15
# Copyright © 2008-2012 Björn Påhlsson
16
16
17
17
# This program is free software: you can redistribute it and/or modify
18
18
# it under the terms of the GNU General Public License as published by
79
79
import ctypes.util
80
80
import xml.dom.minidom
81
81
import inspect
 
82
import GnuPGInterface
82
83
 
83
84
try:
84
85
    SO_BINDTODEVICE = socket.SO_BINDTODEVICE
88
89
    except ImportError:
89
90
        SO_BINDTODEVICE = None
90
91
 
91
 
version = "1.6.5"
 
92
version = "1.6.0"
92
93
stored_state_file = "clients.pickle"
93
94
 
94
95
logger = logging.getLogger()
95
 
syslogger = None
 
96
syslogger = (logging.handlers.SysLogHandler
 
97
             (facility = logging.handlers.SysLogHandler.LOG_DAEMON,
 
98
              address = str("/dev/log")))
96
99
 
97
100
try:
98
101
    if_nametoindex = (ctypes.cdll.LoadLibrary
114
117
def initlogger(debug, level=logging.WARNING):
115
118
    """init logger and add loglevel"""
116
119
    
117
 
    global syslogger
118
 
    syslogger = (logging.handlers.SysLogHandler
119
 
                 (facility =
120
 
                  logging.handlers.SysLogHandler.LOG_DAEMON,
121
 
                  address = str("/dev/log")))
122
120
    syslogger.setFormatter(logging.Formatter
123
121
                           ('Mandos [%(process)d]: %(levelname)s:'
124
122
                            ' %(message)s'))
142
140
class PGPEngine(object):
143
141
    """A simple class for OpenPGP symmetric encryption & decryption"""
144
142
    def __init__(self):
 
143
        self.gnupg = GnuPGInterface.GnuPG()
145
144
        self.tempdir = tempfile.mkdtemp(prefix="mandos-")
146
 
        self.gnupgargs = ['--batch',
147
 
                          '--home', self.tempdir,
148
 
                          '--force-mdc',
149
 
                          '--quiet',
150
 
                          '--no-use-agent']
 
145
        self.gnupg = GnuPGInterface.GnuPG()
 
146
        self.gnupg.options.meta_interactive = False
 
147
        self.gnupg.options.homedir = self.tempdir
 
148
        self.gnupg.options.extra_args.extend(['--force-mdc',
 
149
                                              '--quiet',
 
150
                                              '--no-use-agent'])
151
151
    
152
152
    def __enter__(self):
153
153
        return self
175
175
    def password_encode(self, password):
176
176
        # Passphrase can not be empty and can not contain newlines or
177
177
        # NUL bytes.  So we prefix it and hex encode it.
178
 
        encoded = b"mandos" + binascii.hexlify(password)
179
 
        if len(encoded) > 2048:
180
 
            # GnuPG can't handle long passwords, so encode differently
181
 
            encoded = (b"mandos" + password.replace(b"\\", b"\\\\")
182
 
                       .replace(b"\n", b"\\n")
183
 
                       .replace(b"\0", b"\\x00"))
184
 
        return encoded
 
178
        return b"mandos" + binascii.hexlify(password)
185
179
    
186
180
    def encrypt(self, data, password):
187
 
        passphrase = self.password_encode(password)
188
 
        with tempfile.NamedTemporaryFile(dir=self.tempdir
189
 
                                         ) as passfile:
190
 
            passfile.write(passphrase)
191
 
            passfile.flush()
192
 
            proc = subprocess.Popen(['gpg', '--symmetric',
193
 
                                     '--passphrase-file',
194
 
                                     passfile.name]
195
 
                                    + self.gnupgargs,
196
 
                                    stdin = subprocess.PIPE,
197
 
                                    stdout = subprocess.PIPE,
198
 
                                    stderr = subprocess.PIPE)
199
 
            ciphertext, err = proc.communicate(input = data)
200
 
        if proc.returncode != 0:
201
 
            raise PGPError(err)
 
181
        self.gnupg.passphrase = self.password_encode(password)
 
182
        with open(os.devnull, "w") as devnull:
 
183
            try:
 
184
                proc = self.gnupg.run(['--symmetric'],
 
185
                                      create_fhs=['stdin', 'stdout'],
 
186
                                      attach_fhs={'stderr': devnull})
 
187
                with contextlib.closing(proc.handles['stdin']) as f:
 
188
                    f.write(data)
 
189
                with contextlib.closing(proc.handles['stdout']) as f:
 
190
                    ciphertext = f.read()
 
191
                proc.wait()
 
192
            except IOError as e:
 
193
                raise PGPError(e)
 
194
        self.gnupg.passphrase = None
202
195
        return ciphertext
203
196
    
204
197
    def decrypt(self, data, password):
205
 
        passphrase = self.password_encode(password)
206
 
        with tempfile.NamedTemporaryFile(dir = self.tempdir
207
 
                                         ) as passfile:
208
 
            passfile.write(passphrase)
209
 
            passfile.flush()
210
 
            proc = subprocess.Popen(['gpg', '--decrypt',
211
 
                                     '--passphrase-file',
212
 
                                     passfile.name]
213
 
                                    + self.gnupgargs,
214
 
                                    stdin = subprocess.PIPE,
215
 
                                    stdout = subprocess.PIPE,
216
 
                                    stderr = subprocess.PIPE)
217
 
            decrypted_plaintext, err = proc.communicate(input
218
 
                                                        = data)
219
 
        if proc.returncode != 0:
220
 
            raise PGPError(err)
 
198
        self.gnupg.passphrase = self.password_encode(password)
 
199
        with open(os.devnull, "w") as devnull:
 
200
            try:
 
201
                proc = self.gnupg.run(['--decrypt'],
 
202
                                      create_fhs=['stdin', 'stdout'],
 
203
                                      attach_fhs={'stderr': devnull})
 
204
                with contextlib.closing(proc.handles['stdin']) as f:
 
205
                    f.write(data)
 
206
                with contextlib.closing(proc.handles['stdout']) as f:
 
207
                    decrypted_plaintext = f.read()
 
208
                proc.wait()
 
209
            except IOError as e:
 
210
                raise PGPError(e)
 
211
        self.gnupg.passphrase = None
221
212
        return decrypted_plaintext
222
213
 
223
214
 
243
234
               Used to optionally bind to the specified interface.
244
235
    name: string; Example: 'Mandos'
245
236
    type: string; Example: '_mandos._tcp'.
246
 
     See <https://www.iana.org/assignments/service-names-port-numbers>
 
237
                  See <http://www.dns-sd.org/ServiceTypes.html>
247
238
    port: integer; what port to announce
248
239
    TXT: list of strings; TXT record for the service
249
240
    domain: string; Domain to publish on, default to .local if empty.
449
440
    runtime_expansions: Allowed attributes for runtime expansion.
450
441
    expires:    datetime.datetime(); time (UTC) when a client will be
451
442
                disabled, or None
452
 
    server_settings: The server_settings dict from main()
453
443
    """
454
444
    
455
445
    runtime_expansions = ("approval_delay", "approval_duration",
530
520
        
531
521
        return settings
532
522
    
533
 
    def __init__(self, settings, name = None, server_settings=None):
 
523
    def __init__(self, settings, name = None):
534
524
        self.name = name
535
 
        if server_settings is None:
536
 
            server_settings = {}
537
 
        self.server_settings = server_settings
538
525
        # adding all client settings
539
526
        for setting, value in settings.iteritems():
540
527
            setattr(self, setting, value)
693
680
        # If a checker exists, make sure it is not a zombie
694
681
        try:
695
682
            pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
696
 
        except AttributeError:
697
 
            pass
698
 
        except OSError as error:
699
 
            if error.errno != errno.ECHILD:
700
 
                raise
 
683
        except (AttributeError, OSError) as error:
 
684
            if (isinstance(error, OSError)
 
685
                and error.errno != errno.ECHILD):
 
686
                raise error
701
687
        else:
702
688
            if pid:
703
689
                logger.warning("Checker was a zombie")
725
711
                # in normal mode, that is already done by daemon(),
726
712
                # and in debug mode we don't want to.  (Stdin is
727
713
                # always replaced by /dev/null.)
728
 
                # The exception is when not debugging but nevertheless
729
 
                # running in the foreground; use the previously
730
 
                # created wnull.
731
 
                popen_args = {}
732
 
                if (not self.server_settings["debug"]
733
 
                    and self.server_settings["foreground"]):
734
 
                    popen_args.update({"stdout": wnull,
735
 
                                       "stderr": wnull })
736
714
                self.checker = subprocess.Popen(command,
737
715
                                                close_fds=True,
738
 
                                                shell=True, cwd="/",
739
 
                                                **popen_args)
 
716
                                                shell=True, cwd="/")
740
717
            except OSError as error:
741
718
                logger.error("Failed to start subprocess",
742
719
                             exc_info=error)
743
 
                return True
744
720
            self.checker_callback_tag = (gobject.child_watch_add
745
721
                                         (self.checker.pid,
746
722
                                          self.checker_callback,
747
723
                                          data=command))
748
724
            # The checker may have completed before the gobject
749
725
            # watch was added.  Check for this.
750
 
            try:
751
 
                pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
752
 
            except OSError as error:
753
 
                if error.errno == errno.ECHILD:
754
 
                    # This should never happen
755
 
                    logger.error("Child process vanished",
756
 
                                 exc_info=error)
757
 
                    return True
758
 
                raise
 
726
            pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
759
727
            if pid:
760
728
                gobject.source_remove(self.checker_callback_tag)
761
729
                self.checker_callback(pid, status, command)
937
905
            # The byte_arrays option is not supported yet on
938
906
            # signatures other than "ay".
939
907
            if prop._dbus_signature != "ay":
940
 
                raise ValueError("Byte arrays not supported for non-"
941
 
                                 "'ay' signature {0!r}"
942
 
                                 .format(prop._dbus_signature))
 
908
                raise ValueError
943
909
            value = dbus.ByteArray(b''.join(chr(byte)
944
910
                                            for byte in value))
945
911
        prop(value)
1103
1069
                interface_names.add(alt_interface)
1104
1070
                # Is this a D-Bus signal?
1105
1071
                if getattr(attribute, "_dbus_is_signal", False):
1106
 
                    # Extract the original non-method undecorated
1107
 
                    # function by black magic
 
1072
                    # Extract the original non-method function by
 
1073
                    # black magic
1108
1074
                    nonmethod_func = (dict(
1109
1075
                            zip(attribute.func_code.co_freevars,
1110
1076
                                attribute.__closure__))["func"]
1353
1319
                                       *args, **kwargs)
1354
1320
    
1355
1321
    def start_checker(self, *args, **kwargs):
1356
 
        old_checker_pid = getattr(self.checker, "pid", None)
 
1322
        old_checker = self.checker
 
1323
        if self.checker is not None:
 
1324
            old_checker_pid = self.checker.pid
 
1325
        else:
 
1326
            old_checker_pid = None
1357
1327
        r = Client.start_checker(self, *args, **kwargs)
1358
1328
        # Only if new checker process was started
1359
1329
        if (self.checker is not None
1704
1674
            logger.debug("Protocol version: %r", line)
1705
1675
            try:
1706
1676
                if int(line.strip().split()[0]) > 1:
1707
 
                    raise RuntimeError(line)
 
1677
                    raise RuntimeError
1708
1678
            except (ValueError, IndexError, RuntimeError) as error:
1709
1679
                logger.error("Unknown protocol version: %s", error)
1710
1680
                return
1917
1887
    
1918
1888
    def add_pipe(self, parent_pipe, proc):
1919
1889
        """Dummy function; override as necessary"""
1920
 
        raise NotImplementedError()
 
1890
        raise NotImplementedError
1921
1891
 
1922
1892
 
1923
1893
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1999
1969
                if self.address_family == socket.AF_INET6:
2000
1970
                    any_address = "::" # in6addr_any
2001
1971
                else:
2002
 
                    any_address = "0.0.0.0" # INADDR_ANY
 
1972
                    any_address = socket.INADDR_ANY
2003
1973
                self.server_address = (any_address,
2004
1974
                                       self.server_address[1])
2005
1975
            elif not self.server_address[1]:
2260
2230
            else:
2261
2231
                raise ValueError("Unknown suffix {0!r}"
2262
2232
                                 .format(suffix))
2263
 
        except IndexError as e:
 
2233
        except (ValueError, IndexError) as e:
2264
2234
            raise ValueError(*(e.args))
2265
2235
        timevalue += delta
2266
2236
    return timevalue
2310
2280
                        help="Run self-test")
2311
2281
    parser.add_argument("--debug", action="store_true",
2312
2282
                        help="Debug mode; run in foreground and log"
2313
 
                        " to terminal", default=None)
 
2283
                        " to terminal")
2314
2284
    parser.add_argument("--debuglevel", metavar="LEVEL",
2315
2285
                        help="Debug level for stdout output")
2316
2286
    parser.add_argument("--priority", help="GnuTLS"
2323
2293
                        " files")
2324
2294
    parser.add_argument("--no-dbus", action="store_false",
2325
2295
                        dest="use_dbus", help="Do not provide D-Bus"
2326
 
                        " system bus interface", default=None)
 
2296
                        " system bus interface")
2327
2297
    parser.add_argument("--no-ipv6", action="store_false",
2328
 
                        dest="use_ipv6", help="Do not use IPv6",
2329
 
                        default=None)
 
2298
                        dest="use_ipv6", help="Do not use IPv6")
2330
2299
    parser.add_argument("--no-restore", action="store_false",
2331
2300
                        dest="restore", help="Do not restore stored"
2332
 
                        " state", default=None)
 
2301
                        " state")
2333
2302
    parser.add_argument("--socket", type=int,
2334
2303
                        help="Specify a file descriptor to a network"
2335
2304
                        " socket to use instead of creating one")
2336
2305
    parser.add_argument("--statedir", metavar="DIR",
2337
2306
                        help="Directory to save/restore state in")
2338
2307
    parser.add_argument("--foreground", action="store_true",
2339
 
                        help="Run in foreground", default=None)
2340
 
    parser.add_argument("--no-zeroconf", action="store_false",
2341
 
                        dest="zeroconf", help="Do not use Zeroconf",
2342
 
                        default=None)
 
2308
                        help="Run in foreground")
2343
2309
    
2344
2310
    options = parser.parse_args()
2345
2311
    
2346
2312
    if options.check:
2347
2313
        import doctest
2348
 
        fail_count, test_count = doctest.testmod()
2349
 
        sys.exit(os.EX_OK if fail_count == 0 else 1)
 
2314
        doctest.testmod()
 
2315
        sys.exit()
2350
2316
    
2351
2317
    # Default values for config file for server-global settings
2352
2318
    server_defaults = { "interface": "",
2354
2320
                        "port": "",
2355
2321
                        "debug": "False",
2356
2322
                        "priority":
2357
 
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:+SIGN-RSA-SHA224:+SIGN-RSA-RMD160",
 
2323
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
2358
2324
                        "servicename": "Mandos",
2359
2325
                        "use_dbus": "True",
2360
2326
                        "use_ipv6": "True",
2363
2329
                        "socket": "",
2364
2330
                        "statedir": "/var/lib/mandos",
2365
2331
                        "foreground": "False",
2366
 
                        "zeroconf": "True",
2367
2332
                        }
2368
2333
    
2369
2334
    # Parse config file for server-global settings
2396
2361
    for option in ("interface", "address", "port", "debug",
2397
2362
                   "priority", "servicename", "configdir",
2398
2363
                   "use_dbus", "use_ipv6", "debuglevel", "restore",
2399
 
                   "statedir", "socket", "foreground", "zeroconf"):
 
2364
                   "statedir", "socket", "foreground"):
2400
2365
        value = getattr(options, option)
2401
2366
        if value is not None:
2402
2367
            server_settings[option] = value
2405
2370
    for option in server_settings.keys():
2406
2371
        if type(server_settings[option]) is str:
2407
2372
            server_settings[option] = unicode(server_settings[option])
2408
 
    # Force all boolean options to be boolean
2409
 
    for option in ("debug", "use_dbus", "use_ipv6", "restore",
2410
 
                   "foreground", "zeroconf"):
2411
 
        server_settings[option] = bool(server_settings[option])
2412
2373
    # Debug implies foreground
2413
2374
    if server_settings["debug"]:
2414
2375
        server_settings["foreground"] = True
2416
2377
    
2417
2378
    ##################################################################
2418
2379
    
2419
 
    if (not server_settings["zeroconf"] and
2420
 
        not (server_settings["port"]
2421
 
             or server_settings["socket"] != "")):
2422
 
            parser.error("Needs port or socket to work without"
2423
 
                         " Zeroconf")
2424
 
    
2425
2380
    # For convenience
2426
2381
    debug = server_settings["debug"]
2427
2382
    debuglevel = server_settings["debuglevel"]
2430
2385
    stored_state_path = os.path.join(server_settings["statedir"],
2431
2386
                                     stored_state_file)
2432
2387
    foreground = server_settings["foreground"]
2433
 
    zeroconf = server_settings["zeroconf"]
2434
2388
    
2435
2389
    if debug:
2436
2390
        initlogger(debug, logging.DEBUG)
2457
2411
    global mandos_dbus_service
2458
2412
    mandos_dbus_service = None
2459
2413
    
2460
 
    socketfd = None
2461
 
    if server_settings["socket"] != "":
2462
 
        socketfd = server_settings["socket"]
2463
2414
    tcp_server = MandosServer((server_settings["address"],
2464
2415
                               server_settings["port"]),
2465
2416
                              ClientHandler,
2469
2420
                              gnutls_priority=
2470
2421
                              server_settings["priority"],
2471
2422
                              use_dbus=use_dbus,
2472
 
                              socketfd=socketfd)
 
2423
                              socketfd=(server_settings["socket"]
 
2424
                                        or None))
2473
2425
    if not foreground:
2474
 
        pidfilename = "/run/mandos.pid"
2475
 
        if not os.path.isdir("/run/."):
2476
 
            pidfilename = "/var/run/mandos.pid"
 
2426
        pidfilename = "/var/run/mandos.pid"
2477
2427
        pidfile = None
2478
2428
        try:
2479
2429
            pidfile = open(pidfilename, "w")
2496
2446
        os.setuid(uid)
2497
2447
    except OSError as error:
2498
2448
        if error.errno != errno.EPERM:
2499
 
            raise
 
2449
            raise error
2500
2450
    
2501
2451
    if debug:
2502
2452
        # Enable all possible GnuTLS debugging
2545
2495
            use_dbus = False
2546
2496
            server_settings["use_dbus"] = False
2547
2497
            tcp_server.use_dbus = False
2548
 
    if zeroconf:
2549
 
        protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2550
 
        service = AvahiServiceToSyslog(name =
2551
 
                                       server_settings["servicename"],
2552
 
                                       servicetype = "_mandos._tcp",
2553
 
                                       protocol = protocol, bus = bus)
2554
 
        if server_settings["interface"]:
2555
 
            service.interface = (if_nametoindex
2556
 
                                 (str(server_settings["interface"])))
 
2498
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
 
2499
    service = AvahiServiceToSyslog(name =
 
2500
                                   server_settings["servicename"],
 
2501
                                   servicetype = "_mandos._tcp",
 
2502
                                   protocol = protocol, bus = bus)
 
2503
    if server_settings["interface"]:
 
2504
        service.interface = (if_nametoindex
 
2505
                             (str(server_settings["interface"])))
2557
2506
    
2558
2507
    global multiprocessing_manager
2559
2508
    multiprocessing_manager = multiprocessing.Manager()
2566
2515
    old_client_settings = {}
2567
2516
    clients_data = {}
2568
2517
    
2569
 
    # This is used to redirect stdout and stderr for checker processes
2570
 
    global wnull
2571
 
    wnull = open(os.devnull, "w") # A writable /dev/null
2572
 
    # Only used if server is running in foreground but not in debug
2573
 
    # mode
2574
 
    if debug or not foreground:
2575
 
        wnull.close()
2576
 
    
2577
2518
    # Get client data and settings from last running state.
2578
2519
    if server_settings["restore"]:
2579
2520
        try:
2595
2536
    
2596
2537
    with PGPEngine() as pgp:
2597
2538
        for client_name, client in clients_data.iteritems():
2598
 
            # Skip removed clients
2599
 
            if client_name not in client_settings:
2600
 
                continue
2601
 
            
2602
2539
            # Decide which value to use after restoring saved state.
2603
2540
            # We have three different values: Old config file,
2604
2541
            # new config file, and saved state.
2666
2603
    # Create all client objects
2667
2604
    for client_name, client in clients_data.iteritems():
2668
2605
        tcp_server.clients[client_name] = client_class(
2669
 
            name = client_name, settings = client,
2670
 
            server_settings = server_settings)
 
2606
            name = client_name, settings = client)
2671
2607
    
2672
2608
    if not tcp_server.clients:
2673
2609
        logger.warning("No clients defined")
2753
2689
    
2754
2690
    def cleanup():
2755
2691
        "Cleanup function; run on exit"
2756
 
        if zeroconf:
2757
 
            service.cleanup()
 
2692
        service.cleanup()
2758
2693
        
2759
2694
        multiprocessing.active_children()
2760
 
        wnull.close()
2761
2695
        if not (tcp_server.clients or client_settings):
2762
2696
            return
2763
2697
        
2775
2709
                # A list of attributes that can not be pickled
2776
2710
                # + secret.
2777
2711
                exclude = set(("bus", "changedstate", "secret",
2778
 
                               "checker", "server_settings"))
 
2712
                               "checker"))
2779
2713
                for name, typ in (inspect.getmembers
2780
2714
                                  (dbus.service.Object)):
2781
2715
                    exclude.add(name)
2809
2743
            else:
2810
2744
                logger.warning("Could not save persistent state:",
2811
2745
                               exc_info=e)
2812
 
                raise
 
2746
                raise e
2813
2747
        
2814
2748
        # Delete all clients, and settings from config
2815
2749
        while tcp_server.clients:
2839
2773
    tcp_server.server_activate()
2840
2774
    
2841
2775
    # Find out what port we got
2842
 
    if zeroconf:
2843
 
        service.port = tcp_server.socket.getsockname()[1]
 
2776
    service.port = tcp_server.socket.getsockname()[1]
2844
2777
    if use_ipv6:
2845
2778
        logger.info("Now listening on address %r, port %d,"
2846
2779
                    " flowinfo %d, scope_id %d",
2852
2785
    #service.interface = tcp_server.socket.getsockname()[3]
2853
2786
    
2854
2787
    try:
2855
 
        if zeroconf:
2856
 
            # From the Avahi example code
2857
 
            try:
2858
 
                service.activate()
2859
 
            except dbus.exceptions.DBusException as error:
2860
 
                logger.critical("D-Bus Exception", exc_info=error)
2861
 
                cleanup()
2862
 
                sys.exit(1)
2863
 
            # End of Avahi example code
 
2788
        # From the Avahi example code
 
2789
        try:
 
2790
            service.activate()
 
2791
        except dbus.exceptions.DBusException as error:
 
2792
            logger.critical("D-Bus Exception", exc_info=error)
 
2793
            cleanup()
 
2794
            sys.exit(1)
 
2795
        # End of Avahi example code
2864
2796
        
2865
2797
        gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
2866
2798
                             lambda *args, **kwargs: