Revision 310ae019

b/vncauthproxy/proxy.py
20 20
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 21
# 02110-1301, USA.
22 22

  
23
DEFAULT_LISTEN_ADDRESS = None
24
DEFAULT_LISTEN_PORT = 24999
23
# Daemon files
25 24
DEFAULT_LOG_FILE = "/var/log/vncauthproxy/vncauthproxy.log"
26 25
DEFAULT_PID_FILE = "/var/run/vncauthproxy/vncauthproxy.pid"
27
DEFAULT_CONNECT_TIMEOUT = 30
26

  
27
# By default, bind / listen for control connections to TCP *:24999
28
# (both IPv4 and IPv6)
29
DEFAULT_LISTEN_ADDRESS = None
30
DEFAULT_LISTEN_PORT = 24999
31

  
32
# Backlog for the control socket
33
DEFAULT_BACKLOG = 256
34

  
35
# Timeout for the VNC server connection establishment / RFB handshake
36
DEFAULT_SERVER_TIMEOUT = 60.0
37

  
38
# Connect retries and delay between retries for the VNC server socket
28 39
DEFAULT_CONNECT_RETRIES = 3
29 40
DEFAULT_RETRY_WAIT = 0.1
30
DEFAULT_BACKLOG = 256
31
DEFAULT_SOCK_TIMEOUT = 60.0
41

  
42
# Connect timeout for the listening sockets
43
DEFAULT_CONNECT_TIMEOUT = 30
44

  
45
# Port range for the listening sockets
46
#
32 47
# We must take care not to fall into the ephemeral port range,
33 48
# this can lead to transient failures to bind a chosen port.
34 49
#
35 50
# By default, Linux uses 32768 to 61000, see:
36 51
# http://www.ncftp.com/ncftpd/doc/misc/ephemeral_ports.html#Linux
37 52
# so 25000-30000 seems to be a sensible default.
53
#
54
# We also take into account the ports that Ganeti daemons bind to, the port
55
# range used by DRBD etc.
38 56
DEFAULT_MIN_PORT = 25000
39 57
DEFAULT_MAX_PORT = 30000
40 58

  
......
98 116
    """
99 117
    id = 1
100 118

  
101
    def __init__(self, logger, listeners, pool, daddr, dport, server, password,
102
                 connect_timeout):
119
    def __init__(self, logger, client):
103 120
        """
104 121
        @type logger: logging.Logger
105 122
        @param logger: the logger to use
106
        @type listeners: list
107
        @param listeners: list of listening sockets to use for clients
108
        @type pool: list
109
        @param pool: if not None, return the client number into this port pool
110
        @type daddr: str
111
        @param daddr: destination address (IPv4, IPv6 or hostname)
112
        @type dport: int
113
        @param dport: destination port
114
        @type server: socket
115
        @param server: VNC server socket
116
        @type password: str
117
        @param password: password to request from the client
118
        @type connect_timeout: int
119
        @param connect_timeout: how long to wait for client connections
120
                                (seconds)
123
        @type client: socket.socket
124
        @param listeners: the client control connection socket
121 125

  
122 126
        """
123 127
        gevent.Greenlet.__init__(self)
124 128
        self.id = VncAuthProxy.id
125 129
        VncAuthProxy.id += 1
126 130
        self.log = logger
127
        self.listeners = listeners
131
        self.client = client
128 132
        # A list of worker/forwarder greenlets, one for each direction
129 133
        self.workers = []
130
        # All listening sockets are assumed to be on the same port
131
        self.sport = listeners[0].getsockname()[1]
132
        self.pool = pool
133
        self.daddr = daddr
134
        self.dport = dport
135
        self.server = server
136
        self.password = password
137
        self.client = None
138
        self.timeout = connect_timeout
134
        self.sport = None
135
        self.pool = None
136
        self.daddr = None
137
        self.dport = None
138
        self.server = None
139
        self.password = None
139 140

  
140 141
    def _cleanup(self):
141 142
        """Cleanup everything: workers, sockets, ports
......
197 198
        # No need to close the source and dest sockets here.
198 199
        # They are owned by and will be closed by the original greenlet.
199 200

  
201
    def _perform_server_handshake(self):
202
        """
203
        Initiate a connection with the backend server and perform basic
204
        RFB 3.8 handshake with it.
205

  
206
        Return a socket connected to the backend server.
207

  
208
        """
209
        server = None
210

  
211
        tries = self.retries
212
        while tries:
213
            tries -= 1
214

  
215
            # Initiate server connection
216
            for res in socket.getaddrinfo(self.addr, self.dport,
217
                                          socket.AF_UNSPEC,
218
                                          socket.SOCK_STREAM, 0,
219
                                          socket.AI_PASSIVE):
220
                af, socktype, proto, canonname, sa = res
221
                try:
222
                    server = socket.socket(af, socktype, proto)
223
                except socket.error:
224
                    server = None
225
                    continue
226

  
227
                # Set socket timeout for the initial handshake
228
                server.settimeout(self.server_timeout)
229

  
230
                try:
231
                    logger.debug("Connecting to %s:%s", *sa[:2])
232
                    server.connect(sa)
233
                    logger.debug("Connection to %s:%s successful", *sa[:2])
234
                except socket.error:
235
                    server.close()
236
                    server = None
237
                    continue
238

  
239
                # We succesfully connected to the server
240
                tries = 0
241
                break
242

  
243
            # Wait and retry
244
            gevent.sleep(self.retry_wait)
245

  
246
        if server is None:
247
            raise Exception("Failed to connect to server")
248

  
249
        version = server.recv(1024)
250
        if not rfb.check_version(version):
251
            raise Exception("Unsupported RFB version: %s" % version.strip())
252

  
253
        server.send(rfb.RFB_VERSION_3_8 + "\n")
254

  
255
        res = server.recv(1024)
256
        types = rfb.parse_auth_request(res)
257
        if not types:
258
            raise Exception("Error handshaking with the server")
259

  
260
        else:
261
            logger.debug("Supported authentication types: %s",
262
                         " ".join([str(x) for x in types]))
263

  
264
        if rfb.RFB_AUTHTYPE_NONE not in types:
265
            raise Exception("Error, server demands authentication")
266

  
267
        server.send(rfb.to_u8(rfb.RFB_AUTHTYPE_NONE))
268

  
269
        # Check authentication response
270
        res = server.recv(4)
271
        res = rfb.from_u32(res)
272

  
273
        if res != 0:
274
            raise Exception("Authentication error")
275

  
276
        # Reset the timeout for the rest of the session
277
        server.settimeout(None)
278

  
279
        self.server = server
280

  
281
    def _establish_connection(self):
282
        client = self.client
283
        ports = VncAuthProxy.ports
284

  
285
        # Receive and parse a client request.
286
        response = {
287
            "source_port": 0,
288
            "status": "FAILED",
289
        }
290
        try:
291
            # TODO: support multiple forwardings in the same message?
292
            #
293
            # Control request, in JSON:
294
            #
295
            # {
296
            #     "source_port":
297
            #         <source port or 0 for automatic allocation>,
298
            #     "destination_address":
299
            #         <destination address of backend server>,
300
            #     "destination_port":
301
            #         <destination port>
302
            #     "password":
303
            #         <the password to use to authenticate clients>
304
            # }
305
            #
306
            # The <password> is used for MITM authentication of clients
307
            # connecting to <source_port>, who will subsequently be
308
            # forwarded to a VNC server listening at
309
            # <destination_address>:<destination_port>
310
            #
311
            # Control reply, in JSON:
312
            # {
313
            #     "source_port": <the allocated source port>
314
            #     "status": <one of "OK" or "FAILED">
315
            # }
316
            #
317
            buf = client.recv(1024)
318
            req = json.loads(buf)
319

  
320
            sport_orig = int(req['source_port'])
321
            daddr = req['destination_address']
322
            dport = int(req['destination_port'])
323
            password = req['password']
324
        except Exception, e:
325
            logger.warn("Malformed request: %s", buf)
326
            client.send(json.dumps(response))
327
            client.close()
328
            raise gevent.GreenletExit
329

  
330
        server = None
331
        try:
332
            # If the client has so indicated, pick an ephemeral source port
333
            # randomly, and remove it from the port pool.
334
            if sport_orig == 0:
335
                while True:
336
                    try:
337
                        sport = random.choice(ports)
338
                        ports.remove(sport)
339
                        break
340
                    except ValueError:
341
                        logger.debug("Port %d already taken", sport)
342

  
343
                logger.debug("Got port %d from pool, %d remaining",
344
                             sport, len(ports))
345
                pool = ports
346
            else:
347
                sport = sport_orig
348
                pool = None
349

  
350
            self.sport = sport
351
            self.pool = pool
352

  
353
            self.listeners = get_listening_sockets(sport)
354
            perform_server_handshake()
355

  
356
            logger.info("New forwarding: %d (client req'd: %d) -> %s:%d",
357
                        sport, sport_orig, self.daddr, self.dport)
358
            response = {"source_port": sport,
359
                        "status": "OK"}
360
        except IndexError:
361
            logger.error(("FAILED forwarding, out of ports for [req'd by "
362
                          "client: %d -> %s:%d]"),
363
                         sport_orig, self.daddr, self.dport)
364
            raise gevent.GreenletExit
365
        except Exception, msg:
366
            logger.error(msg)
367
            logger.error(("FAILED forwarding: %d (client req'd: %d) -> "
368
                          "%s:%d"), sport, sport_orig, self.daddr, self.dport)
369
            if not pool is None:
370
                pool.append(sport)
371
                logger.debug("Returned port %d to pool, %d remanining",
372
                             sport, len(pool))
373
            if not server is None:
374
                server.close()
375
            raise gevent.GreenletExit
376
        finally:
377
            client.send(json.dumps(response))
378
            client.close()
379

  
200 380
    def _client_handshake(self):
201 381
        """
202 382
        Perform handshake/authentication with a connecting client
......
258 438
        # Accept the authentication
259 439
        self.client.send(rfb.to_u32(rfb.RFB_AUTH_SUCCESS))
260 440

  
261
    def _run(self):
441
    def _proxy(self):
262 442
        try:
263 443
            self.info("Waiting for a client to connect at %s",
264 444
                      ", ".join(["%s:%d" % s.getsockname()[:2]
......
320 500
        finally:
321 501
            self._cleanup()
322 502

  
503
    def _run(self):
504
        _establish_connection()
505
        _proxy()
506

  
323 507
# Logging support inside VncAuthproxy
324 508
# Wrap all common logging functions in logging-specific methods
325 509
for funcname in ["info", "debug", "warn", "error", "critical",
......
338 522
    raise SystemExit
339 523

  
340 524

  
341
def get_listening_sockets(sport):
525
def get_listening_sockets(sport, saddr=None):
342 526
    sockets = []
343 527

  
344 528
    # Use two sockets, one for IPv4, one for IPv6. IPv4-to-IPv6 mapped
345 529
    # addresses do not work reliably everywhere (under linux it may have
346 530
    # been disabled in /proc/sys/net/ipv6/bind_ipv6_only).
347
    for res in socket.getaddrinfo(None, sport, socket.AF_UNSPEC,
531
    for res in socket.getaddrinfo(saddr, sport, socket.AF_UNSPEC,
348 532
                                  socket.SOCK_STREAM, 0, socket.AI_PASSIVE):
349 533
        af, socktype, proto, canonname, sa = res
350 534
        try:
......
371 555
    return sockets
372 556

  
373 557

  
374
def perform_server_handshake(daddr, dport, tries, retry_wait, sock_timeout):
375
    """
376
    Initiate a connection with the backend server and perform basic
377
    RFB 3.8 handshake with it.
378

  
379
    Return a socket connected to the backend server.
380

  
381
    """
382
    server = None
383

  
384
    while tries:
385
        tries -= 1
386

  
387
        # Initiate server connection
388
        for res in socket.getaddrinfo(daddr, dport, socket.AF_UNSPEC,
389
                                      socket.SOCK_STREAM, 0,
390
                                      socket.AI_PASSIVE):
391
            af, socktype, proto, canonname, sa = res
392
            try:
393
                server = socket.socket(af, socktype, proto)
394
            except socket.error:
395
                server = None
396
                continue
397

  
398
            # Set socket timeout for the initial handshake
399
            server.settimeout(sock_timeout)
400

  
401
            try:
402
                logger.debug("Connecting to %s:%s", *sa[:2])
403
                server.connect(sa)
404
                logger.debug("Connection to %s:%s successful", *sa[:2])
405
            except socket.error:
406
                server.close()
407
                server = None
408
                continue
409

  
410
            # We succesfully connected to the server
411
            tries = 0
412
            break
413

  
414
        # Wait and retry
415
        gevent.sleep(retry_wait)
416

  
417
    if server is None:
418
        raise Exception("Failed to connect to server")
419

  
420
    version = server.recv(1024)
421
    if not rfb.check_version(version):
422
        raise Exception("Unsupported RFB version: %s" % version.strip())
423

  
424
    server.send(rfb.RFB_VERSION_3_8 + "\n")
425

  
426
    res = server.recv(1024)
427
    types = rfb.parse_auth_request(res)
428
    if not types:
429
        raise Exception("Error handshaking with the server")
430

  
431
    else:
432
        logger.debug("Supported authentication types: %s",
433
                     " ".join([str(x) for x in types]))
434

  
435
    if rfb.RFB_AUTHTYPE_NONE not in types:
436
        raise Exception("Error, server demands authentication")
437

  
438
    server.send(rfb.to_u8(rfb.RFB_AUTHTYPE_NONE))
439

  
440
    # Check authentication response
441
    res = server.recv(4)
442
    res = rfb.from_u32(res)
443

  
444
    if res != 0:
445
        raise Exception("Authentication error")
446

  
447
    # Reset the timeout for the rest of the session
448
    server.settimeout(None)
449

  
450
    return server
451

  
452

  
453 558
def parse_arguments(args):
454 559
    from optparse import OptionParser
455 560

  
456 561
    parser = OptionParser()
457
    parser.add_option("--listen-address", dest="listen_address",
458
                      default=DEFAULT_LISTEN_ADDRESS,
459
                      metavar="LISTEN_ADDRESS",
460
                      help=("Address to listen for control connections"))
461
    parser.add_option( "--listen-port", dest="listen_port",
462
                      default=DEFAULT_LISTEN_PORT,
463
                      metavar="LISTEN_PORT",
464
                      help=("Port to listen for control connections"))
465 562
    parser.add_option("-d", "--debug", action="store_true", dest="debug",
466 563
                      help="Enable debugging information")
467
    parser.add_option("-l", "--log", dest="log_file",
564
    parser.add_option("--log", dest="log_file",
468 565
                      default=DEFAULT_LOG_FILE,
469 566
                      metavar="FILE",
470
                      help=("Write log to FILE instead of %s" %
567
                      help=("Write log to FILE (default: %s)" %
471 568
                            DEFAULT_LOG_FILE))
472 569
    parser.add_option('--pid-file', dest="pid_file",
473 570
                      default=DEFAULT_PID_FILE,
474 571
                      metavar='PIDFILE',
475 572
                      help=("Save PID to file (default: %s)" %
476 573
                            DEFAULT_PID_FILE))
477
    parser.add_option("-t", "--connect-timeout", dest="connect_timeout",
478
                      default=DEFAULT_CONNECT_TIMEOUT, type="int",
479
                      metavar="SECONDS", help=("Wait SECONDS sec for a client "
480
                                               "to connect"))
481
    parser.add_option("-r", "--connect-retries", dest="connect_retries",
574
    parser.add_option("--listen-address", dest="listen_address",
575
                      default=DEFAULT_LISTEN_ADDRESS,
576
                      metavar="LISTEN_ADDRESS",
577
                      help=("Address to listen for control connections"
578
                            "(default: *)"))
579
    parser.add_option("--listen-port", dest="listen_port",
580
                      default=DEFAULT_LISTEN_PORT,
581
                      metavar="LISTEN_PORT",
582
                      help=("Port to listen for control connections"
583
                            "(default: %d)" % DEFAULT_LISTEN_PORT))
584
    parser.add_option("-b", "--backlog", dest="backlog",
585
                      default=DEFAULT_BACKLOG, type="int", metavar="N",
586
                      help=("Size of the backlog for the control connection "
587
                          "socket (default: %s)" % DEFAULT_BACKLOG))
588
    parser.add_option("--server-timeout", dest="server_timeout",
589
                      default=DEFAULT_SERVER_TIMEOUT, type="float",
590
                      metavar="N",
591
                      help=("Wait for N seconds for the VNC server RFB "
592
                            "handshake (default %s)" % DEFAULT_SERVER_TIMEOUT))
593
    parser.add_option("--connect-retries", dest="connect_retries",
482 594
                      default=DEFAULT_CONNECT_RETRIES, type="int",
483
                      metavar="RETRIES",
484
                      help="How many times to try to connect to the server")
485
    parser.add_option("-w", "--retry-wait", dest="retry_wait",
595
                      metavar="N",
596
                      help=("Retry N times to connect to the "
597
                            "server (default: %d)" %
598
                            DEFAULT_CONNECT_RETRIES))
599
    parser.add_option("--retry-wait", dest="retry_wait",
486 600
                      default=DEFAULT_RETRY_WAIT, type="float",
487
                      metavar="SECONDS", help=("Retry connection to server "
488
                                               "every SECONDS sec"))
601
                      metavar="N",
602
                      help=("Wait N seconds before retrying "
603
                            "to connect to the server (default: %s)" %
604
                            DEFAULT_RETRY_WAIT))
605
    parser.add_option("--connect-timeout", dest="connect_timeout",
606
                      default=DEFAULT_CONNECT_TIMEOUT, type="int",
607
                      metavar="N",
608
                      help=("Wait N seconds for a client "
609
                            "to connect (default: %d)"
610
                            % DEFAULT_CONNECT_TIMEOUT))
489 611
    parser.add_option("-p", "--min-port", dest="min_port",
490 612
                      default=DEFAULT_MIN_PORT, type="int", metavar="MIN_PORT",
491 613
                      help=("The minimum port number to use for automatically-"
492
                            "allocated ephemeral ports"))
614
                            "allocated ephemeral ports (default: %s)" %
615
                            DEFAULT_MIN_PORT))
493 616
    parser.add_option("-P", "--max-port", dest="max_port",
494 617
                      default=DEFAULT_MAX_PORT, type="int", metavar="MAX_PORT",
495 618
                      help=("The maximum port number to use for automatically-"
496
                            "allocated ephemeral ports"))
497
    parser.add_option("-b", "--backlog", dest="backlog",
498
                      default=DEFAULT_BACKLOG, type="int", metavar="BACKLOG",
499
                      help=("Length of the backlog queue for the control"
500
                            "connection socket"))
501
    parser.add_option("--socket-timeout", dest="sock_timeout",
502
                      default=DEFAULT_SOCK_TIMEOUT, type="float",
503
                      metavar="SOCK_TIMEOUT",
504
                      help=("Socket timeout for the server handshake"))
505

  
506
    return parser.parse_args(args)
507

  
508

  
509
def establish_connection(client, addr, ports, opts):
510
    # Receive and parse a client request.
511
    response = {
512
        "source_port": 0,
513
        "status": "FAILED",
514
    }
515
    try:
516
        # TODO: support multiple forwardings in the same message?
517
        #
518
        # Control request, in JSON:
519
        #
520
        # {
521
        #     "source_port":
522
        #         <source port or 0 for automatic allocation>,
523
        #     "destination_address":
524
        #         <destination address of backend server>,
525
        #     "destination_port":
526
        #         <destination port>
527
        #     "password":
528
        #         <the password to use to authenticate clients>
529
        # }
530
        #
531
        # The <password> is used for MITM authentication of clients
532
        # connecting to <source_port>, who will subsequently be
533
        # forwarded to a VNC server listening at
534
        # <destination_address>:<destination_port>
535
        #
536
        # Control reply, in JSON:
537
        # {
538
        #     "source_port": <the allocated source port>
539
        #     "status": <one of "OK" or "FAILED">
540
        # }
541
        #
542
        buf = client.recv(1024)
543
        req = json.loads(buf)
544

  
545
        sport_orig = int(req['source_port'])
546
        daddr = req['destination_address']
547
        dport = int(req['destination_port'])
548
        password = req['password']
549
    except Exception, e:
550
        logger.warn("Malformed request: %s", buf)
551
        client.send(json.dumps(response))
552
        client.close()
553

  
554
    # Spawn a new Greenlet to service the request.
555
    server = None
556
    try:
557
        # If the client has so indicated, pick an ephemeral source port
558
        # randomly, and remove it from the port pool.
559
        if sport_orig == 0:
560
            while True:
561
                try:
562
                    sport = random.choice(ports)
563
                    ports.remove(sport)
564
                    break
565
                except ValueError:
566
                    logger.debug("Port %d already taken", sport)
567

  
568
            logger.debug("Got port %d from pool, %d remaining",
569
                         sport, len(ports))
570
            pool = ports
571
        else:
572
            sport = sport_orig
573
            pool = None
574

  
575
        listeners = get_listening_sockets(sport)
576
        server = perform_server_handshake(daddr, dport,
577
                                          opts.connect_retries,
578
                                          opts.retry_wait, opts.sock_timeout)
579

  
580
        VncAuthProxy.spawn(logger, listeners, pool, daddr, dport,
581
                           server, password, opts.connect_timeout)
582

  
583
        logger.info("New forwarding: %d (client req'd: %d) -> %s:%d",
584
                    sport, sport_orig, daddr, dport)
585
        response = {"source_port": sport,
586
                    "status": "OK"}
587
    except IndexError:
588
        logger.error(("FAILED forwarding, out of ports for [req'd by "
589
                      "client: %d -> %s:%d]"),
590
                     sport_orig, daddr, dport)
591
    except Exception, msg:
592
        logger.error(msg)
593
        logger.error(("FAILED forwarding: %d (client req'd: %d) -> "
594
                      "%s:%d"), sport, sport_orig, daddr, dport)
595
        if not pool is None:
596
            pool.append(sport)
597
            logger.debug("Returned port %d to pool, %d remanining",
598
                         sport, len(pool))
599
        if not server is None:
600
            server.close()
601
    finally:
602
        client.send(json.dumps(response))
603
        client.close()
619
                            "allocated ephemeral ports (default: %s)" %
620
                            DEFAULT_MAX_PORT))
621

  
622
    (opts, args) = parser.parse_args(args)
623

  
624
    if args:
625
        parser.print_help()
626
        sys.exit(1)
604 627

  
605 628

  
606 629
def main():
607 630
    """Run the daemon from the command line"""
608 631

  
609
    (opts, args) = parse_arguments(sys.argv[1:])
632
    (opts, ) = parse_arguments(sys.argv[1:])
610 633

  
611 634
    # Create pidfile
612 635
    pidf = pidlockfile.TimeoutPIDLockFile(opts.pid_file, 10)
......
652 675
    # we *must* reinit gevent
653 676
    gevent.reinit()
654 677

  
655
    sockets = []
656
    for res in socket.getaddrinfo(opts.listen_address, opts.listen_port,
657
                             socket.AF_UNSPEC, socket.SOCK_STREAM, 0,
658
                             socket.AI_PASSIVE):
659
        af, socktype, proto, canonname, sa = res
660
        try:
661
            s = None
662
            s = socket.socket(af, socktype, proto)
663
            if af == socket.AF_INET6:
664
                # Bind v6 only when AF_INET6, otherwise either v4 or v6 bind
665
                # will fail.
666
                s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
667
            s.bind(sa)
668
            s.listen(opts.backlog)
669
            sockets.append(s)
670
            logger.info("Control socket listening on %s:%d", *sa[:2])
671
        except socket.error, msg:
672
            logger.critical("Error binding control socket to %s:%d: %s",
673
                         sa[0], sa[1], msg[1])
674
            if s:
675
                s.close()
676
            while sockets:
677
                sockets.pop.close()
678

  
679
            sys.exit(1)
680

  
681 678
    # Catch signals to ensure graceful shutdown,
682
    # e.g., to make sure the control socket gets unlink()ed.
683 679
    #
684 680
    # Uses gevent.signal so the handler fires even during
685 681
    # gevent.socket.accept()
......
689 685
    # Init ephemeral port pool
690 686
    ports = range(opts.min_port, opts.max_port + 1)
691 687

  
688
    # Init VncAuthProxy class attributes
689
    VncAuthProxy.server_timeout = opts.server_timeout
690
    VncAuthProxy.connect_retries = opts.connect_retries
691
    VncAuthProxy.retry_wait = opts.retry_wait
692
    VncAuthProxy.connect_timeout = opts.connect_timeout
693

  
694
    try:
695
        sockets = get_listening_sockets(opts.listen_address,
696
                                        opts.listen_port)
697
    except socket.error:
698
        logger.critical("Error binding control socket")
699
        sys.exit(1)
700

  
692 701
    while True:
693 702
        try:
694 703
            rlist, _, _ = select(sockets, [], [])
695 704
            for ctrl in rlist:
696
                client, addr = ctrl.accept()
705
                client, _ = ctrl.accept()
697 706
                logger.info("New control connection")
698 707

  
699
                gevent.Greenlet.spawn(establish_connection, client, addr,
700
                                      ports, opts)
708
                VncAuthProxy.spawn(logger, client)
701 709
        except Exception, e:
702 710
            logger.exception(e)
703 711
            continue

Also available in: Unified diff