2 from twisted.internet import reactor, ssl, protocol, error
3 from OpenSSL import SSL
5 import getopt, sys, os, time
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). Use an empty HOST to listen on all
14 interfaces (INSECURE!)
15 -a, --authtoken AUTHTOKEN Authentication token for connecting to the VNC server
16 VMNAME VM name to connect to (automatically fetches an
17 authentication token using remctl)
18 -v verbose status messages""" % (sys.argv[0])
20 class ClientContextFactory(ssl.ClientContextFactory):
22 def _verify(self, connection, x509, errnum, errdepth, ok):
24 print '_verify (ok=%d):' % ok
25 print ' subject:', x509.get_subject()
26 print ' issuer:', x509.get_issuer()
27 print ' errnum %s, errdepth %d' % (errnum, errdepth)
29 print 'The VNC server certificate has expired. Please contact xvm@mit.edu.'
33 ctx = ssl.ClientContextFactory.getContext(self)
35 certFile = '/mit/xvm/vnc/servers.cert'
36 if verbose: print "Loading certificates from %s" % certFile
37 ctx.load_verify_locations(certFile)
38 ctx.set_verify(SSL.VERIFY_PEER|SSL.VERIFY_FAIL_IF_NO_PEER_CERT,
43 class Proxy(protocol.Protocol):
46 def setPeer(self, peer):
49 def connectionLost(self, reason):
50 if self.peer is not None:
51 self.peer.transport.loseConnection()
54 def dataReceived(self, data):
55 self.peer.transport.write(data)
57 class ProxyClient(Proxy):
60 def connectionMade(self):
61 self.peer.setPeer(self)
62 data = "CONNECTVNC %s VNCProxy/1.0\r\nAuth-token: %s\r\n\r\n" % (self.factory.machine, self.factory.authtoken)
63 self.transport.write(data)
64 if verbose: print "ProxyClient: connection made"
65 def dataReceived(self, data):
67 if verbose: print 'ProxyClient: received data "%s"' % data
68 if data.startswith("VNCProxy/1.0 200 "):
71 self.peer.transport.write(data[data.find("\n")+3:])
72 self.peer.transport.resumeProducing() # Allow reading
74 print "Failed to connect: %s" % data
75 self.transport.loseConnection()
77 self.peer.transport.write(data)
79 class ProxyClientFactory(protocol.ClientFactory):
80 protocol = ProxyClient
82 def __init__(self, authtoken, machine):
83 self.authtoken = authtoken
84 self.machine = machine
86 def setServer(self, server):
89 def buildProtocol(self, *args, **kw):
90 prot = protocol.ClientFactory.buildProtocol(self, *args, **kw)
91 prot.setPeer(self.server)
94 def clientConnectionFailed(self, connector, reason):
95 self.server.transport.loseConnection()
98 class ProxyServer(Proxy):
99 clientProtocolFactory = ProxyClientFactory
103 def connectionMade(self):
104 # Don't read anything from the connecting client until we have
105 # somewhere to send it to.
106 self.transport.pauseProducing()
108 if verbose: print "ProxyServer: connection made"
110 client = self.clientProtocolFactory(self.factory.authtoken, self.factory.machine)
111 client.setServer(self)
113 reactor.connectSSL(self.factory.host, self.factory.port, client, ClientContextFactory())
116 class ProxyFactory(protocol.Factory):
117 protocol = ProxyServer
119 def __init__(self, host, port, authtoken, machine):
122 self.authtoken = authtoken
123 self.machine = machine
128 opts, args = getopt.gnu_getopt(sys.argv[1:], "hl:a:v",
129 ["help", "listen=", "authtoken="])
130 except getopt.GetoptError, err:
131 print str(err) # will print something like "option -a not recognized"
134 listen = ["127.0.0.1", None]
139 elif o in ("-h", "--help"):
142 elif o in ("-l", "--listen"):
144 listen = a.split(":", 2)
145 listen[1] = int(listen[1])
148 elif o in ("-a", "--authtoken"):
151 assert False, "unhandled option"
153 # Get authentication token
154 if authtoken is None:
155 # User didn't give us an authentication token, so we need to get one
157 print "VMNAME not given or too many arguments"
160 from subprocess import PIPE, Popen
162 p = Popen(["remctl", "xvm-remote.mit.edu", "control", args[0], "vnctoken"],
165 if verbose: print "remctl not found in path. Trying remctl locker."
166 p = Popen(["athrun", "remctl", "remctl",
167 "xvm-remote.mit.edu", "control", args[0], "vnctoken"],
169 authtoken = p.communicate()[0]
170 if p.returncode != 0:
171 print "Unable to get authentication token"
173 if verbose: print 'Got authentication token "%s" for VM %s' % \
176 # Unpack authentication token
178 token_outer = base64.urlsafe_b64decode(authtoken)
179 token_outer = pickle.loads(token_outer)
180 token_inner = pickle.loads(token_outer["data"])
181 machine = token_inner["machine"]
182 connect_host = token_inner["connect_host"]
183 connect_port = token_inner["connect_port"]
184 token_expires = token_inner["expires"]
185 if verbose: print "Unpacked authentication token:\n%s" % \
188 print "Invalid authentication token"
191 if verbose: print "Will connect to %s:%s" % (connect_host, connect_port)
192 if listen[1] is None:
195 while not ready and listen[1] < 6000:
197 reactor.listenTCP(listen[1], ProxyFactory(connect_host, connect_port, authtoken, machine), interface=listen[0])
199 except error.CannotListenError:
202 reactor.listenTCP(listen[1], ProxyFactory(connect_host, connect_port, authtoken, machine))
204 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)
205 print "You must connect before your authentication token expires at %s." % \
206 (time.ctime(token_expires))
210 if '__main__' == __name__: