source: trunk/scripts/vnc-client/invirt-vnc-client @ 1972

Last change on this file since 1972 was 1972, checked in by quentin, 15 years ago

Standalone VNC client fixes

  • Correct OpenSSL verification
  • Pass data after initial handshake (Oops!)
  • Find a free port to listen on, if one is not specified
  • Warn users when the authentication token expires
  • Property svn:executable set to *
File size: 7.2 KB
Line 
1#!/usr/bin/python
2from twisted.internet import reactor, ssl, protocol, error
3from OpenSSL import SSL
4import base64, pickle
5import getopt, sys, os, time
6
7verbose = False
8
9def usage():
10    print """%s [-v] [-l [HOST:]PORT] {-a AUTHTOKEN|VMNAME}
11 -l, --listen [HOST:]PORT  port (and optionally host) to listen on for
12                           connections (default is 127.0.0.1 and a randomly
13                           chosen port)
14 -a, --authtoken AUTHTOKEN Authentication token for connecting to the VNC server
15 VMNAME                    VM name to connect to (automatically fetches an
16                           authentication token using remctl)
17 -v                        verbose status messages""" % (sys.argv[0])
18
19class ClientContextFactory(ssl.ClientContextFactory):
20
21    def _verify(self, connection, x509, errnum, errdepth, ok):
22        if verbose:
23            print '_verify (ok=%d):' % ok
24            print '  subject:', x509.get_subject()
25            print '  issuer:', x509.get_issuer()
26            print '  errnum %s, errdepth %d' % (errnum, errdepth)
27        if errnum == 10:
28            print 'The VNC server certificate has expired. Please contact xvm@mit.edu.'
29        return ok
30
31    def getContext(self):
32        ctx = ssl.ClientContextFactory.getContext(self)
33
34        certFile = '/mit/xvm/vnc/servers.cert'
35        if verbose: print "Loading certificates from %s" % certFile
36        ctx.load_verify_locations(certFile)
37        ctx.set_verify(SSL.VERIFY_PEER|SSL.VERIFY_FAIL_IF_NO_PEER_CERT,
38                       self._verify)
39
40        return ctx
41
42class Proxy(protocol.Protocol):
43    peer = None
44
45    def setPeer(self, peer):
46        self.peer = peer
47
48    def connectionLost(self, reason):
49        if self.peer is not None:
50            self.peer.transport.loseConnection()
51            self.peer = None
52
53    def dataReceived(self, data):
54        self.peer.transport.write(data)
55
56class ProxyClient(Proxy):
57    ready = False
58
59    def connectionMade(self):
60        self.peer.setPeer(self)
61        data = "CONNECTVNC %s VNCProxy/1.0\r\nAuth-token: %s\r\n\r\n" % (self.factory.machine, self.factory.authtoken)
62        self.transport.write(data)
63        if verbose: print "ProxyClient: connection made"
64    def dataReceived(self, data):
65        if not self.ready:
66            if verbose: print 'ProxyClient: received data "%s"' % data
67            if data.startswith("VNCProxy/1.0 200 "):
68                self.ready = True
69                if "\n" in data:
70                    self.peer.transport.write(data[data.find("\n")+3:])
71                self.peer.transport.resumeProducing() # Allow reading
72            else:
73                print "Failed to connect: %s" % data
74                self.transport.loseConnection()
75        else:
76            self.peer.transport.write(data)
77
78class ProxyClientFactory(protocol.ClientFactory):
79    protocol = ProxyClient
80   
81    def __init__(self, authtoken, machine):
82        self.authtoken = authtoken
83        self.machine = machine
84
85    def setServer(self, server):
86        self.server = server
87
88    def buildProtocol(self, *args, **kw):
89        prot = protocol.ClientFactory.buildProtocol(self, *args, **kw)
90        prot.setPeer(self.server)
91        return prot
92
93    def clientConnectionFailed(self, connector, reason):
94        self.server.transport.loseConnection()
95
96
97class ProxyServer(Proxy):
98    clientProtocolFactory = ProxyClientFactory
99    authtoken = None
100    machine = None
101
102    def connectionMade(self):
103        # Don't read anything from the connecting client until we have
104        # somewhere to send it to.
105        self.transport.pauseProducing()
106       
107        if verbose: print "ProxyServer: connection made"
108
109        client = self.clientProtocolFactory(self.factory.authtoken, self.factory.machine)
110        client.setServer(self)
111
112        reactor.connectSSL(self.factory.host, self.factory.port, client, ClientContextFactory())
113       
114
115class ProxyFactory(protocol.Factory):
116    protocol = ProxyServer
117
118    def __init__(self, host, port, authtoken, machine):
119        self.host = host
120        self.port = port
121        self.authtoken = authtoken
122        self.machine = machine
123
124def main():
125    global verbose
126    try:
127        opts, args = getopt.gnu_getopt(sys.argv[1:], "hl:a:v",
128                                       ["help", "listen=", "authtoken="])
129    except getopt.GetoptError, err:
130        print str(err) # will print something like "option -a not recognized"
131        usage()
132        sys.exit(2)
133    listen = ["127.0.0.1", None]
134    authtoken = None
135    for o, a in opts:
136        if o == "-v":
137            verbose = True
138        elif o in ("-h", "--help"):
139            usage()
140            sys.exit()
141        elif o in ("-l", "--listen"):
142            if ":" in a:
143                listen = a.split(":", 2)
144            else:
145                listen[1] = a
146        elif o in ("-a", "--authtoken"):
147            authtoken = a
148        else:
149            assert False, "unhandled option"
150
151    # Get authentication token
152    if authtoken is None:
153        # User didn't give us an authentication token, so we need to get one
154        if len(args) != 1:
155            print "VMNAME not given or too many arguments"
156            usage()
157            sys.exit(2)
158        from subprocess import PIPE, Popen
159        try:
160            p = Popen(["remctl", "remote", "control", args[0], "vnctoken"],
161                      stdout=PIPE)
162        except OSError:
163            if verbose: print "remctl not found in path. Trying remctl locker."
164            p = Popen(["athrun", "remctl", "remctl",
165                       "remote", "control", args[0], "vnctoken"],
166                      stdout=PIPE)
167        authtoken = p.communicate()[0]
168        if p.returncode != 0:
169            print "Unable to get authentication token"
170            sys.exit(1)
171        if verbose: print 'Got authentication token "%s" for VM %s' % \
172                          (authtoken, args[0])
173
174    # Unpack authentication token
175    try:
176        token_outer = base64.urlsafe_b64decode(authtoken)
177        token_outer = pickle.loads(token_outer)
178        token_inner = pickle.loads(token_outer["data"])
179        machine = token_inner["machine"]
180        connect_host = token_inner["connect_host"]
181        connect_port = token_inner["connect_port"]
182        token_expires = token_inner["expires"]
183        if verbose: print "Unpacked authentication token:\n%s" % \
184                          repr(token_inner)
185    except:
186        print "Invalid authentication token"
187        sys.exit(1)
188   
189    if verbose: print "Will connect to %s:%s" % (connect_host, connect_port) 
190    if listen[1] is None:
191        listen[1] = 5900
192        ready = False
193        while not ready:
194            try:
195                reactor.listenTCP(listen[1], ProxyFactory(connect_host, connect_port, authtoken, machine))
196                ready = True
197            except error.CannotListenError:
198                listen[1] += 1
199    else:
200        reactor.listenTCP(listen[1], ProxyFactory(connect_host, connect_port, authtoken, machine))
201   
202    print "Ready to connect. Connect to %s:%s (display %d) now with your VNC client. The password is 'moocow'." % (listen[0], listen[1], listen[1]-5900)
203    print "You must connect before your authentication token expires at %s." % \
204          (time.ctime(token_expires))
205   
206    reactor.run()
207
208if '__main__' == __name__:
209    main()
Note: See TracBrowser for help on using the repository browser.