Revision d49bd2fb

b/vncauthproxy/client.py
65 65
                      metavar="PASSWORD",
66 66
                      help=("Use password PASSWD to authenticate incoming "
67 67
                            "VNC connections"))
68
    parser.add_option("--auth-user", dest="auth_user",
69
                      default=None,
70
                      metavar="AUTH_USER",
71
                      help=("User to authenticate as, for the control "
72
                            "connection"))
73
    parser.add_option("--auth-password", dest="auth_password",
74
                      default=None,
75
                      metavar="AUTH_PASSWORD",
76
                      help=("User password for the control connection "
77
                            "authentication"))
78
    parser.add_option("--no-ssl", dest="no_ssl",
79
                      action='store_false', default=False,
80
                      help=("Disable SSL/TLS for control connecions "
81
                            "(default: %s)" % False))
68 82

  
69 83
    (opts, args) = parser.parse_args(args)
70 84

  
......
75 89
        parser.error("The -d/--dest argument is mandatory.")
76 90
    if not opts.dport:
77 91
        parser.error("The -p/--dport argument is mandatory.")
92
    if not opts.auth_user:
93
        parser.error("The --auth-user argument is mandatory.")
94
    if not opts.auth_password:
95
        parser.error("The --auth-password argument is mandatory.")
78 96

  
79 97
    return (opts, args)
80 98

  
81 99

  
82 100
def request_forwarding(sport, daddr, dport, password,
101
                       auth_user, auth_password,
83 102
                       server_address=DEFAULT_SERVER_ADDRESS,
84
                       server_port=DEFAULT_SERVER_PORT, ssl_sock=True):
103
                       server_port=DEFAULT_SERVER_PORT, no_ssl=False):
85 104
    """Connect to vncauthproxy and request a VNC forwarding."""
86 105
    if not password:
87 106
        raise ValueError("You must specify a non-empty password")
......
91 110
        "destination_address": daddr,
92 111
        "destination_port": int(dport),
93 112
        "password": password,
113
        "auth_user": auth_user,
114
        "auth_password": auth_password,
94 115
    }
95 116

  
96 117
    retries = 5
......
107 128
                server = None
108 129
                continue
109 130

  
110
            if ssl_sock:
131
            if not no_ssl:
111 132
                server = ssl.wrap_socket(
112 133
                      server, cert_reqs=ssl.CERT_NONE,
113 134
                      ssl_version=ssl.PROTOCOL_TLSv1)
......
119 140
            except socket.error:
120 141
                server.close()
121 142
                server = None
143
                retries -= 1
122 144
                continue
123 145

  
124 146
            retries = 0
......
141 163
    (opts, args) = parse_arguments(sys.argv[1:])
142 164

  
143 165
    res = request_forwarding(sport=opts.sport, daddr=opts.daddr,
144
                             dport=opts.dport, password=opts.password)
145

  
146
    sys.stderr.write("Forwaring %s -> %s:%s: %s\n" % (res['source_port'],
166
                             dport=opts.dport, password=opts.password,
167
                             auth_user=opts.auth_user,
168
                             auth_password=opts.auth_password,
169
                             no_ssl=opts.no_ssl)
170

  
171
    reason = None
172
    if 'reason' in res:
173
        reason = 'Reason: %s\n' % res['reason']
174
    sys.stderr.write("Forwaring %s -> %s:%s: %s\n%s" % (res['source_port'],
147 175
                                                      opts.daddr, opts.dport,
148
                                                      res['status']))
176
                                                      res['status'], reason))
149 177

  
150 178
    if res['status'] == "OK":
151 179
        sys.exit(0)
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