Revision d49bd2fb vncauthproxy/proxy.py

b/vncauthproxy/proxy.py
57 57
DEFAULT_MAX_PORT = 30000
58 58

  
59 59
# SSL certificate / key files
60
DEFAULT_CERT_FILE = "/etc/ssl/certs/cert.pem"
61
DEFAULT_KEY_FILE = "/etc/ssl/certs/key.pem"
60
DEFAULT_CERT_FILE = "/var/lib/vncauthproxy/cert.pem"
61
DEFAULT_KEY_FILE = "/var/lib/vncauthproxy/key.pem"
62

  
63
# Auth file
64
DEFAULT_AUTH_FILE = "/var/lib/vncauthproxy/users"
62 65

  
63 66
import os
64 67
import sys
......
68 71
import daemon
69 72
import random
70 73
import daemon.runner
74
import hashlib
71 75

  
72 76
import rfb
73 77

  
......
307 311
            #         <destination port>
308 312
            #     "password":
309 313
            #         <the password to use to authenticate clients>
314
            #     "auth_user":
315
            #         <user for control connection authentication>,
316
            #      "auth_password":
317
            #         <password for control connection authentication>,
310 318
            # }
311 319
            #
312 320
            # The <password> is used for MITM authentication of clients
......
323 331
            buf = client.recv(1024)
324 332
            req = json.loads(buf)
325 333

  
334
            auth_user = req['auth_user']
335
            auth_password = req['auth_password']
326 336
            sport_orig = int(req['source_port'])
327 337
            self.daddr = req['destination_address']
328 338
            self.dport = int(req['destination_port'])
329 339
            self.password = req['password']
330
        except Exception, e:
331
            self.warn("Malformed request: %s", buf)
340

  
341
            if auth_user not in VncAuthProxy.authdb:
342
                msg = "Authentication failure: user not found"
343
                raise Exception(msg)
344

  
345
            (cipher, authdb_password) = VncAuthProxy.authdb[auth_user]
346
            if cipher == 'HA1':
347
                message = auth_user + ':vncauthproxy:' + auth_password
348
                auth_password = hashlib.md5(message).hexdigest()
349

  
350
            if auth_password != authdb_password:
351
                msg = "Authentication failure: wrong password"
352
                raise Exception(msg)
353
        except KeyError:
354
            msg = "Malformed request: %s" % buf
355
            raise Exception(msg)
356
        except Exception as err:
357
            logger.exception(err)
358
            msg = err.args
359
            self.warn(msg)
360
            response['reason'] = msg
332 361
            client.send(json.dumps(response))
333 362
            client.close()
334 363
            raise gevent.GreenletExit
......
566 595
    return sockets
567 596

  
568 597

  
598
def parse_auth_file(auth_file):
599
    supported_ciphers = ('cleartext', 'HA1')
600

  
601
    with open(auth_file) as f:
602
        users = {}
603
        lines = [l.strip().split() for l in f.readlines()]
604

  
605
        for line in lines:
606
            if not line or line[0][0] == '#':
607
                continue
608

  
609
            if len(line) != 2:
610
                raise Exception("Invaild user entry in auth file")
611

  
612
            user = line[0]
613
            password = line[1]
614

  
615
            split_password = ('cleartext', password)
616
            if password[0] == '{':
617
                split_password = password[1:].split('}')
618
                if len(split_password) != 2 or not split_password[1] \
619
                        or split_password[0] not in supported_ciphers:
620
                    raise Exception("Invalid password format in auth file")
621

  
622
            if user in users:
623
                raise Exception("Duplicate user entry in auth file")
624

  
625
            users[user] = password
626

  
627
    return users
628

  
629

  
569 630
def parse_arguments(args):
570 631
    from optparse import OptionParser
571 632

  
......
625 686
                      help=("The maximum port number to use for automatically-"
626 687
                            "allocated ephemeral ports (default: %s)" %
627 688
                            DEFAULT_MAX_PORT))
689
    parser.add_option('--no-ssl', dest="no_ssl",
690
                      default=False, action='store_true',
691
                      help=("Disable SSL/TLS for control connections "
692
                            "(default: False"))
628 693
    parser.add_option('--cert-file', dest="cert_file",
629 694
                      default=DEFAULT_CERT_FILE,
630 695
                      metavar='CERTFILE',
......
635 700
                      metavar='KEYFILE',
636 701
                      help=("SSL key (default: %s)" %
637 702
                            DEFAULT_KEY_FILE))
703
    parser.add_option('--auth-file', dest="auth_file",
704
                      default=DEFAULT_AUTH_FILE,
705
                      metavar='AUTHFILE',
706
                      help=("Authentication file (default: %s)" %
707
                            DEFAULT_AUTH_FILE))
638 708

  
639 709
    (opts, args) = parser.parse_args(args)
640 710

  
......
712 782
    VncAuthProxy.ports = ports
713 783

  
714 784
    try:
785
        VncAuthProxy.authdb = parse_auth_file(opts.auth_file)
715 786
        sockets = get_listening_sockets(logger, opts.listen_port,
716 787
                                        opts.listen_address, reuse_addr=True)
717
    except socket.error:
788
    except socket.error as err:
789
        logger.exception(err)
718 790
        logger.critical("Error binding control socket")
719 791
        sys.exit(1)
792
    except Exception as err:
793
        logger.exception(err)
794
        logger.critical("Unexpected error: %s", err.args)
795
        sys.exit(1)
720 796

  
721 797
    while True:
722 798
        try:
723 799
            client = None
724
            client_sock = None
725 800
            rlist, _, _ = select(sockets, [], [])
726 801
            for ctrl in rlist:
727
                client_sock, _ = ctrl.accept()
728
                client = ssl.wrap_socket(client_sock,
729
                                         server_side=True,
730
                                         keyfile=opts.key_file,
731
                                         certfile=opts.cert_file,
732
                                         ssl_version=ssl.PROTOCOL_TLSv1)
802
                client, _ = ctrl.accept()
803
                if not no_ssl:
804
                    client = ssl.wrap_socket(client,
805
                                             server_side=True,
806
                                             keyfile=opts.key_file,
807
                                             certfile=opts.cert_file,
808
                                             ssl_version=ssl.PROTOCOL_TLSv1)
733 809
                logger.info("New control connection")
734 810

  
735 811
                VncAuthProxy.spawn(logger, client)
......
738 814
            logger.exception(e)
739 815
            if client:
740 816
                client.close()
741
            elif client_sock:
742
                client_sock.close()
743 817
            continue
744 818
        except SystemExit:
745 819
            break

Also available in: Unified diff