root / vncauthproxy / vncauthproxy.py @ b216cb77
History | View | Annotate | Download (14.8 kB)
1 | 07b0130f | Vangelis Koukis | #!/usr/bin/env python
|
---|---|---|---|
2 | 07b0130f | Vangelis Koukis | #
|
3 | 07b0130f | Vangelis Koukis | |
4 | 07b0130f | Vangelis Koukis | # Copyright (c) 2010 GRNET SA
|
5 | 07b0130f | Vangelis Koukis | #
|
6 | 07b0130f | Vangelis Koukis | # This program is free software; you can redistribute it and/or modify
|
7 | 07b0130f | Vangelis Koukis | # it under the terms of the GNU General Public License as published by
|
8 | 07b0130f | Vangelis Koukis | # the Free Software Foundation; either version 2 of the License, or
|
9 | 07b0130f | Vangelis Koukis | # (at your option) any later version.
|
10 | 07b0130f | Vangelis Koukis | #
|
11 | 07b0130f | Vangelis Koukis | # This program is distributed in the hope that it will be useful, but
|
12 | 07b0130f | Vangelis Koukis | # WITHOUT ANY WARRANTY; without even the implied warranty of
|
13 | 07b0130f | Vangelis Koukis | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
14 | 07b0130f | Vangelis Koukis | # General Public License for more details.
|
15 | 07b0130f | Vangelis Koukis | #
|
16 | 07b0130f | Vangelis Koukis | # You should have received a copy of the GNU General Public License
|
17 | 07b0130f | Vangelis Koukis | # along with this program; if not, write to the Free Software
|
18 | 07b0130f | Vangelis Koukis | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
19 | 07b0130f | Vangelis Koukis | # 02110-1301, USA.
|
20 | 07b0130f | Vangelis Koukis | |
21 | 4af60ef0 | Vangelis Koukis | DEFAULT_CTRL_SOCKET = "/tmp/vncproxy.sock"
|
22 | 4af60ef0 | Vangelis Koukis | DEFAULT_LOG_FILE = "/var/log/vncauthproxy/vncauthproxy.log"
|
23 | 4af60ef0 | Vangelis Koukis | DEFAULT_PID_FILE = "/var/run/vncauthproxy/vncauthproxy.pid"
|
24 | 4af60ef0 | Vangelis Koukis | DEFAULT_CONNECT_TIMEOUT = 30
|
25 | 07b0130f | Vangelis Koukis | |
26 | 07b0130f | Vangelis Koukis | import os |
27 | 07b0130f | Vangelis Koukis | import sys |
28 | 07b0130f | Vangelis Koukis | import logging |
29 | 07b0130f | Vangelis Koukis | import gevent |
30 | 4af60ef0 | Vangelis Koukis | import daemon |
31 | 4af60ef0 | Vangelis Koukis | import daemon.pidlockfile |
32 | 07b0130f | Vangelis Koukis | |
33 | 07b0130f | Vangelis Koukis | import rfb |
34 | 07b0130f | Vangelis Koukis | |
35 | 07b0130f | Vangelis Koukis | from gevent import socket |
36 | 4af60ef0 | Vangelis Koukis | from signal import SIGINT, SIGTERM |
37 | 33c82017 | Vangelis Koukis | from gevent import signal |
38 | 07b0130f | Vangelis Koukis | from gevent.select import select |
39 | 07b0130f | Vangelis Koukis | |
40 | 33c82017 | Vangelis Koukis | |
41 | 07b0130f | Vangelis Koukis | class VncAuthProxy(gevent.Greenlet): |
42 | 07b0130f | Vangelis Koukis | """
|
43 | 07b0130f | Vangelis Koukis | Simple class implementing a VNC Forwarder with MITM authentication as a
|
44 | 07b0130f | Vangelis Koukis | Greenlet
|
45 | 07b0130f | Vangelis Koukis |
|
46 | 07b0130f | Vangelis Koukis | VncAuthProxy forwards VNC traffic from a specified port of the local host
|
47 | 07b0130f | Vangelis Koukis | to a specified remote host:port. Furthermore, it implements VNC
|
48 | 07b0130f | Vangelis Koukis | Authentication, intercepting the client/server handshake and asking the
|
49 | 07b0130f | Vangelis Koukis | client for authentication even if the backend requires none.
|
50 | 07b0130f | Vangelis Koukis |
|
51 | 07b0130f | Vangelis Koukis | It is primarily intended for use in virtualization environments, as a VNC
|
52 | 07b0130f | Vangelis Koukis | ``switch''.
|
53 | 07b0130f | Vangelis Koukis |
|
54 | 07b0130f | Vangelis Koukis | """
|
55 | 07b0130f | Vangelis Koukis | id = 1
|
56 | 07b0130f | Vangelis Koukis | |
57 | 4af60ef0 | Vangelis Koukis | def __init__(self, logger, sport, daddr, dport, password, connect_timeout): |
58 | 07b0130f | Vangelis Koukis | """
|
59 | 4af60ef0 | Vangelis Koukis | @type logger: logging.Logger
|
60 | 4af60ef0 | Vangelis Koukis | @param logger: the logger to use
|
61 | 07b0130f | Vangelis Koukis | @type sport: int
|
62 | 07b0130f | Vangelis Koukis | @param sport: source port
|
63 | 07b0130f | Vangelis Koukis | @type daddr: str
|
64 | 07b0130f | Vangelis Koukis | @param daddr: destination address (IPv4, IPv6 or hostname)
|
65 | 07b0130f | Vangelis Koukis | @type dport: int
|
66 | 07b0130f | Vangelis Koukis | @param dport: destination port
|
67 | 07b0130f | Vangelis Koukis | @type password: str
|
68 | 07b0130f | Vangelis Koukis | @param password: password to request from the client
|
69 | 07b0130f | Vangelis Koukis | @type connect_timeout: int
|
70 | 07b0130f | Vangelis Koukis | @param connect_timeout: how long to wait for client connections
|
71 | 07b0130f | Vangelis Koukis | (seconds)
|
72 | 07b0130f | Vangelis Koukis |
|
73 | 07b0130f | Vangelis Koukis | """
|
74 | 07b0130f | Vangelis Koukis | gevent.Greenlet.__init__(self)
|
75 | 07b0130f | Vangelis Koukis | self.id = VncAuthProxy.id
|
76 | 07b0130f | Vangelis Koukis | VncAuthProxy.id += 1
|
77 | 07b0130f | Vangelis Koukis | self.sport = sport
|
78 | 07b0130f | Vangelis Koukis | self.daddr = daddr
|
79 | 07b0130f | Vangelis Koukis | self.dport = dport
|
80 | 07b0130f | Vangelis Koukis | self.password = password
|
81 | 4af60ef0 | Vangelis Koukis | self.log = logger
|
82 | 07b0130f | Vangelis Koukis | self.server = None |
83 | 07b0130f | Vangelis Koukis | self.client = None |
84 | 07b0130f | Vangelis Koukis | self.timeout = connect_timeout
|
85 | 07b0130f | Vangelis Koukis | |
86 | 07b0130f | Vangelis Koukis | def _cleanup(self): |
87 | 07b0130f | Vangelis Koukis | """Close all active sockets and exit gracefully"""
|
88 | 07b0130f | Vangelis Koukis | if self.server: |
89 | 07b0130f | Vangelis Koukis | self.server.close()
|
90 | 07b0130f | Vangelis Koukis | if self.client: |
91 | 07b0130f | Vangelis Koukis | self.client.close()
|
92 | 07b0130f | Vangelis Koukis | raise gevent.GreenletExit
|
93 | 07b0130f | Vangelis Koukis | |
94 | 07b0130f | Vangelis Koukis | def info(self, msg): |
95 | 4af60ef0 | Vangelis Koukis | self.log.info("[C%d] %s" % (self.id, msg)) |
96 | 07b0130f | Vangelis Koukis | |
97 | 07b0130f | Vangelis Koukis | def debug(self, msg): |
98 | 4af60ef0 | Vangelis Koukis | self.log.debug("[C%d] %s" % (self.id, msg)) |
99 | 07b0130f | Vangelis Koukis | |
100 | 07b0130f | Vangelis Koukis | def warn(self, msg): |
101 | 4af60ef0 | Vangelis Koukis | self.log.warn("[C%d] %s" % (self.id, msg)) |
102 | 07b0130f | Vangelis Koukis | |
103 | 07b0130f | Vangelis Koukis | def error(self, msg): |
104 | 4af60ef0 | Vangelis Koukis | self.log.error("[C%d] %s" % (self.id, msg)) |
105 | 07b0130f | Vangelis Koukis | |
106 | 07b0130f | Vangelis Koukis | def critical(self, msg): |
107 | 4af60ef0 | Vangelis Koukis | self.log.critical("[C%d] %s" % (self.id, msg)) |
108 | 07b0130f | Vangelis Koukis | |
109 | 07b0130f | Vangelis Koukis | def __str__(self): |
110 | 07b0130f | Vangelis Koukis | return "VncAuthProxy: %d -> %s:%d" % (self.sport, self.daddr, self.dport) |
111 | 07b0130f | Vangelis Koukis | |
112 | 07b0130f | Vangelis Koukis | def _forward(self, source, dest): |
113 | 07b0130f | Vangelis Koukis | """
|
114 | 07b0130f | Vangelis Koukis | Forward traffic from source to dest
|
115 | 07b0130f | Vangelis Koukis |
|
116 | 07b0130f | Vangelis Koukis | @type source: socket
|
117 | 07b0130f | Vangelis Koukis | @param source: source socket
|
118 | 07b0130f | Vangelis Koukis | @type dest: socket
|
119 | 07b0130f | Vangelis Koukis | @param dest: destination socket
|
120 | 07b0130f | Vangelis Koukis |
|
121 | 07b0130f | Vangelis Koukis | """
|
122 | 07b0130f | Vangelis Koukis | |
123 | 07b0130f | Vangelis Koukis | while True: |
124 | 07b0130f | Vangelis Koukis | d = source.recv(8096)
|
125 | 07b0130f | Vangelis Koukis | if d == '': |
126 | 07b0130f | Vangelis Koukis | if source == self.client: |
127 | 07b0130f | Vangelis Koukis | self.info("Client connection closed") |
128 | 07b0130f | Vangelis Koukis | else:
|
129 | 07b0130f | Vangelis Koukis | self.info("Server connection closed") |
130 | 07b0130f | Vangelis Koukis | break
|
131 | 07b0130f | Vangelis Koukis | dest.sendall(d) |
132 | 07b0130f | Vangelis Koukis | source.close() |
133 | 07b0130f | Vangelis Koukis | dest.close() |
134 | 07b0130f | Vangelis Koukis | |
135 | 07b0130f | Vangelis Koukis | |
136 | 07b0130f | Vangelis Koukis | def _handshake(self): |
137 | 07b0130f | Vangelis Koukis | """
|
138 | 07b0130f | Vangelis Koukis | Perform handshake/authentication with a connecting client
|
139 | 07b0130f | Vangelis Koukis |
|
140 | 07b0130f | Vangelis Koukis | Outline:
|
141 | 07b0130f | Vangelis Koukis | 1. Client connects
|
142 | 07b0130f | Vangelis Koukis | 2. We fake RFB 3.8 protocol and require VNC authentication
|
143 | 07b0130f | Vangelis Koukis | 3. Client accepts authentication method
|
144 | 07b0130f | Vangelis Koukis | 4. We send an authentication challenge
|
145 | 07b0130f | Vangelis Koukis | 5. Client sends the authentication response
|
146 | 07b0130f | Vangelis Koukis | 6. We check the authentication
|
147 | 07b0130f | Vangelis Koukis | 7. We initiate a connection with the backend server and perform basic
|
148 | 07b0130f | Vangelis Koukis | RFB 3.8 handshake with it.
|
149 | 07b0130f | Vangelis Koukis | 8. If the above is successful, "bridge" both connections through two
|
150 | 07b0130f | Vangelis Koukis | "fowrarder" greenlets.
|
151 | 07b0130f | Vangelis Koukis |
|
152 | 07b0130f | Vangelis Koukis | """
|
153 | 07b0130f | Vangelis Koukis | self.client.send(rfb.RFB_VERSION_3_8 + "\n") |
154 | 07b0130f | Vangelis Koukis | client_version = self.client.recv(1024) |
155 | 07b0130f | Vangelis Koukis | if not rfb.check_version(client_version): |
156 | 07b0130f | Vangelis Koukis | self.error("Invalid version: %s" % client_version) |
157 | 07b0130f | Vangelis Koukis | self._cleanup()
|
158 | 07b0130f | Vangelis Koukis | self.debug("Requesting authentication") |
159 | 07b0130f | Vangelis Koukis | auth_request = rfb.make_auth_request(rfb.RFB_AUTHTYPE_VNC) |
160 | 07b0130f | Vangelis Koukis | self.client.send(auth_request)
|
161 | 07b0130f | Vangelis Koukis | res = self.client.recv(1024) |
162 | 07b0130f | Vangelis Koukis | type = rfb.parse_client_authtype(res) |
163 | 07b0130f | Vangelis Koukis | if type == rfb.RFB_AUTHTYPE_ERROR:
|
164 | 07b0130f | Vangelis Koukis | self.warn("Client refused authentication: %s" % res[1:]) |
165 | 07b0130f | Vangelis Koukis | else:
|
166 | 07b0130f | Vangelis Koukis | self.debug("Client requested authtype %x" % type) |
167 | 07b0130f | Vangelis Koukis | |
168 | 07b0130f | Vangelis Koukis | if type != rfb.RFB_AUTHTYPE_VNC: |
169 | 07b0130f | Vangelis Koukis | self.error("Wrong auth type: %d" % type) |
170 | 07b0130f | Vangelis Koukis | self.client.send(rfb.to_u32(rfb.RFB_AUTH_ERROR))
|
171 | 07b0130f | Vangelis Koukis | self._cleanup()
|
172 | 07b0130f | Vangelis Koukis | |
173 | 07b0130f | Vangelis Koukis | # Generate the challenge
|
174 | 07b0130f | Vangelis Koukis | challenge = os.urandom(16)
|
175 | 07b0130f | Vangelis Koukis | self.client.send(challenge)
|
176 | 07b0130f | Vangelis Koukis | response = self.client.recv(1024) |
177 | 07b0130f | Vangelis Koukis | if len(response) != 16: |
178 | 07b0130f | Vangelis Koukis | self.error("Wrong response length %d, should be 16" % len(response)) |
179 | 07b0130f | Vangelis Koukis | self._cleanup()
|
180 | 07b0130f | Vangelis Koukis | |
181 | 07b0130f | Vangelis Koukis | if rfb.check_password(challenge, response, password):
|
182 | 07b0130f | Vangelis Koukis | self.debug("Authentication successful!") |
183 | 07b0130f | Vangelis Koukis | else:
|
184 | 07b0130f | Vangelis Koukis | self.warn("Authentication failed") |
185 | 07b0130f | Vangelis Koukis | self.client.send(rfb.to_u32(rfb.RFB_AUTH_ERROR))
|
186 | 07b0130f | Vangelis Koukis | self._cleanup()
|
187 | 07b0130f | Vangelis Koukis | |
188 | 07b0130f | Vangelis Koukis | # Accept the authentication
|
189 | 07b0130f | Vangelis Koukis | self.client.send(rfb.to_u32(rfb.RFB_AUTH_SUCCESS))
|
190 | 07b0130f | Vangelis Koukis | |
191 | 07b0130f | Vangelis Koukis | # Try to connect to the server
|
192 | 07b0130f | Vangelis Koukis | tries = 50
|
193 | 07b0130f | Vangelis Koukis | |
194 | 07b0130f | Vangelis Koukis | while tries:
|
195 | 07b0130f | Vangelis Koukis | tries -= 1
|
196 | 07b0130f | Vangelis Koukis | |
197 | 07b0130f | Vangelis Koukis | # Initiate server connection
|
198 | 07b0130f | Vangelis Koukis | for res in socket.getaddrinfo(self.daddr, self.dport, socket.AF_UNSPEC, |
199 | 07b0130f | Vangelis Koukis | socket.SOCK_STREAM, 0, socket.AI_PASSIVE):
|
200 | 07b0130f | Vangelis Koukis | af, socktype, proto, canonname, sa = res |
201 | 07b0130f | Vangelis Koukis | try:
|
202 | 07b0130f | Vangelis Koukis | self.server = socket.socket(af, socktype, proto)
|
203 | 07b0130f | Vangelis Koukis | except socket.error, msg:
|
204 | 07b0130f | Vangelis Koukis | self.server = None |
205 | 07b0130f | Vangelis Koukis | continue
|
206 | 07b0130f | Vangelis Koukis | |
207 | 07b0130f | Vangelis Koukis | try:
|
208 | 07b0130f | Vangelis Koukis | self.debug("Connecting to %s:%s" % sa[:2]) |
209 | 07b0130f | Vangelis Koukis | self.server.connect(sa)
|
210 | 07b0130f | Vangelis Koukis | self.debug("Connection to %s:%s successful" % sa[:2]) |
211 | 07b0130f | Vangelis Koukis | except socket.error, msg:
|
212 | 07b0130f | Vangelis Koukis | self.server.close()
|
213 | 07b0130f | Vangelis Koukis | self.server = None |
214 | 07b0130f | Vangelis Koukis | continue
|
215 | 07b0130f | Vangelis Koukis | |
216 | 07b0130f | Vangelis Koukis | # We succesfully connected to the server
|
217 | 07b0130f | Vangelis Koukis | tries = 0
|
218 | 07b0130f | Vangelis Koukis | break
|
219 | 07b0130f | Vangelis Koukis | |
220 | 07b0130f | Vangelis Koukis | # Wait and retry
|
221 | 07b0130f | Vangelis Koukis | gevent.sleep(0.2)
|
222 | 07b0130f | Vangelis Koukis | |
223 | 07b0130f | Vangelis Koukis | if self.server is None: |
224 | 07b0130f | Vangelis Koukis | self.error("Failed to connect to server") |
225 | 07b0130f | Vangelis Koukis | self._cleanup()
|
226 | 07b0130f | Vangelis Koukis | |
227 | 07b0130f | Vangelis Koukis | version = self.server.recv(1024) |
228 | 07b0130f | Vangelis Koukis | if not rfb.check_version(version): |
229 | 07b0130f | Vangelis Koukis | self.error("Unsupported RFB version: %s" % version.strip()) |
230 | 07b0130f | Vangelis Koukis | self._cleanup()
|
231 | 07b0130f | Vangelis Koukis | |
232 | 07b0130f | Vangelis Koukis | self.server.send(rfb.RFB_VERSION_3_8 + "\n") |
233 | 07b0130f | Vangelis Koukis | |
234 | 07b0130f | Vangelis Koukis | res = self.server.recv(1024) |
235 | 07b0130f | Vangelis Koukis | types = rfb.parse_auth_request(res) |
236 | 07b0130f | Vangelis Koukis | if not types: |
237 | 07b0130f | Vangelis Koukis | self.error("Error handshaking with the server") |
238 | 07b0130f | Vangelis Koukis | self._cleanup()
|
239 | 07b0130f | Vangelis Koukis | |
240 | 07b0130f | Vangelis Koukis | else:
|
241 | 07b0130f | Vangelis Koukis | self.debug("Supported authentication types: %s" % |
242 | 07b0130f | Vangelis Koukis | " ".join([str(x) for x in types])) |
243 | 07b0130f | Vangelis Koukis | |
244 | 07b0130f | Vangelis Koukis | if rfb.RFB_AUTHTYPE_NONE not in types: |
245 | 07b0130f | Vangelis Koukis | self.error("Error, server demands authentication") |
246 | 07b0130f | Vangelis Koukis | self._cleanup()
|
247 | 07b0130f | Vangelis Koukis | |
248 | 07b0130f | Vangelis Koukis | self.server.send(rfb.to_u8(rfb.RFB_AUTHTYPE_NONE))
|
249 | 07b0130f | Vangelis Koukis | |
250 | 07b0130f | Vangelis Koukis | # Check authentication response
|
251 | 07b0130f | Vangelis Koukis | res = self.server.recv(4) |
252 | 07b0130f | Vangelis Koukis | res = rfb.from_u32(res) |
253 | 07b0130f | Vangelis Koukis | |
254 | 07b0130f | Vangelis Koukis | if res != 0: |
255 | 07b0130f | Vangelis Koukis | self.error("Authentication error") |
256 | 07b0130f | Vangelis Koukis | self._cleanup()
|
257 | 07b0130f | Vangelis Koukis | |
258 | 07b0130f | Vangelis Koukis | # Bridge client/server connections
|
259 | 07b0130f | Vangelis Koukis | self.workers = [gevent.spawn(self._forward, self.client, self.server), |
260 | 07b0130f | Vangelis Koukis | gevent.spawn(self._forward, self.server, self.client)] |
261 | 07b0130f | Vangelis Koukis | gevent.joinall(self.workers)
|
262 | 07b0130f | Vangelis Koukis | |
263 | 07b0130f | Vangelis Koukis | del self.workers |
264 | 07b0130f | Vangelis Koukis | self._cleanup()
|
265 | 07b0130f | Vangelis Koukis | |
266 | 07b0130f | Vangelis Koukis | def _run(self): |
267 | 07b0130f | Vangelis Koukis | sockets = [] |
268 | 07b0130f | Vangelis Koukis | |
269 | 07b0130f | Vangelis Koukis | # Use two sockets, one for IPv4, one for IPv6. IPv4-to-IPv6 mapped
|
270 | 07b0130f | Vangelis Koukis | # addresses do not work reliably everywhere (under linux it may have
|
271 | 07b0130f | Vangelis Koukis | # been disabled in /proc/sys/net/ipv6/bind_ipv6_only).
|
272 | 07b0130f | Vangelis Koukis | for res in socket.getaddrinfo(None, self.sport, socket.AF_UNSPEC, |
273 | 07b0130f | Vangelis Koukis | socket.SOCK_STREAM, 0, socket.AI_PASSIVE):
|
274 | 07b0130f | Vangelis Koukis | af, socktype, proto, canonname, sa = res |
275 | 07b0130f | Vangelis Koukis | try:
|
276 | 07b0130f | Vangelis Koukis | s = socket.socket(af, socktype, proto) |
277 | 07b0130f | Vangelis Koukis | if af == socket.AF_INET6:
|
278 | 07b0130f | Vangelis Koukis | # Bind v6 only when AF_INET6, otherwise either v4 or v6 bind
|
279 | 07b0130f | Vangelis Koukis | # will fail.
|
280 | 07b0130f | Vangelis Koukis | s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
|
281 | 07b0130f | Vangelis Koukis | except socket.error, msg:
|
282 | 07b0130f | Vangelis Koukis | s = None
|
283 | 07b0130f | Vangelis Koukis | continue;
|
284 | 07b0130f | Vangelis Koukis | |
285 | 07b0130f | Vangelis Koukis | try:
|
286 | 07b0130f | Vangelis Koukis | s.bind(sa) |
287 | 07b0130f | Vangelis Koukis | s.listen(1)
|
288 | 07b0130f | Vangelis Koukis | self.debug("Listening on %s:%d" % sa[:2]) |
289 | 07b0130f | Vangelis Koukis | except socket.error, msg:
|
290 | 07b0130f | Vangelis Koukis | self.error("Error binding to %s:%d: %s" % |
291 | 07b0130f | Vangelis Koukis | (sa[0], sa[1], msg[1])) |
292 | 07b0130f | Vangelis Koukis | s.close() |
293 | 07b0130f | Vangelis Koukis | s = None
|
294 | 07b0130f | Vangelis Koukis | continue
|
295 | 07b0130f | Vangelis Koukis | |
296 | 07b0130f | Vangelis Koukis | if s:
|
297 | 07b0130f | Vangelis Koukis | sockets.append(s) |
298 | 07b0130f | Vangelis Koukis | |
299 | 07b0130f | Vangelis Koukis | if not sockets: |
300 | 07b0130f | Vangelis Koukis | self.error("Failed to listen for connections") |
301 | 07b0130f | Vangelis Koukis | self._cleanup()
|
302 | 07b0130f | Vangelis Koukis | |
303 | 07b0130f | Vangelis Koukis | self.log.debug("Waiting for client to connect") |
304 | 07b0130f | Vangelis Koukis | rlist, _, _ = select(sockets, [], [], timeout=self.timeout)
|
305 | 07b0130f | Vangelis Koukis | |
306 | 07b0130f | Vangelis Koukis | if not rlist: |
307 | 07b0130f | Vangelis Koukis | self.info("Timed out, no connection after %d sec" % self.timeout) |
308 | 07b0130f | Vangelis Koukis | self._cleanup()
|
309 | 07b0130f | Vangelis Koukis | |
310 | 07b0130f | Vangelis Koukis | for sock in rlist: |
311 | 07b0130f | Vangelis Koukis | self.client, addrinfo = sock.accept()
|
312 | 07b0130f | Vangelis Koukis | self.info("Connection from %s:%d" % addrinfo[:2]) |
313 | 07b0130f | Vangelis Koukis | |
314 | 07b0130f | Vangelis Koukis | # Close all listening sockets, we only want a one-shot connection
|
315 | 07b0130f | Vangelis Koukis | # from a single client.
|
316 | 07b0130f | Vangelis Koukis | for listener in sockets: |
317 | 07b0130f | Vangelis Koukis | listener.close() |
318 | 07b0130f | Vangelis Koukis | break
|
319 | 07b0130f | Vangelis Koukis | |
320 | 07b0130f | Vangelis Koukis | self._handshake()
|
321 | 07b0130f | Vangelis Koukis | |
322 | 07b0130f | Vangelis Koukis | |
323 | 33c82017 | Vangelis Koukis | def fatal_signal_handler(signame): |
324 | 4af60ef0 | Vangelis Koukis | logger.info("Caught %s, will raise SystemExit" % signame)
|
325 | 33c82017 | Vangelis Koukis | raise SystemExit |
326 | 33c82017 | Vangelis Koukis | |
327 | 33c82017 | Vangelis Koukis | |
328 | 07b0130f | Vangelis Koukis | if __name__ == '__main__': |
329 | 07b0130f | Vangelis Koukis | from optparse import OptionParser |
330 | 07b0130f | Vangelis Koukis | |
331 | 07b0130f | Vangelis Koukis | parser = OptionParser() |
332 | 07b0130f | Vangelis Koukis | parser.add_option("-s", "--socket", dest="ctrl_socket", |
333 | 4af60ef0 | Vangelis Koukis | default=DEFAULT_CTRL_SOCKET, |
334 | 4af60ef0 | Vangelis Koukis | metavar="PATH",
|
335 | 4af60ef0 | Vangelis Koukis | help="UNIX socket path for control connections (default: %s" %
|
336 | 4af60ef0 | Vangelis Koukis | DEFAULT_CTRL_SOCKET) |
337 | 07b0130f | Vangelis Koukis | parser.add_option("-d", "--debug", action="store_true", dest="debug", |
338 | 07b0130f | Vangelis Koukis | help="Enable debugging information")
|
339 | 4af60ef0 | Vangelis Koukis | parser.add_option("-l", "--log", dest="log_file", |
340 | 4af60ef0 | Vangelis Koukis | default=DEFAULT_LOG_FILE, |
341 | 4af60ef0 | Vangelis Koukis | metavar="FILE",
|
342 | 4af60ef0 | Vangelis Koukis | help="Write log to FILE instead of %s" % DEFAULT_LOG_FILE),
|
343 | 4af60ef0 | Vangelis Koukis | parser.add_option('--pid-file', dest="pid_file", |
344 | 4af60ef0 | Vangelis Koukis | default=DEFAULT_PID_FILE, |
345 | 4af60ef0 | Vangelis Koukis | metavar='PIDFILE',
|
346 | 4af60ef0 | Vangelis Koukis | help="Save PID to file (default: %s)" %
|
347 | 4af60ef0 | Vangelis Koukis | DEFAULT_PID_FILE) |
348 | 07b0130f | Vangelis Koukis | parser.add_option("-t", "--connect-timeout", dest="connect_timeout", |
349 | 4af60ef0 | Vangelis Koukis | default=DEFAULT_CONNECT_TIMEOUT, type="int", metavar="SECONDS", |
350 | 07b0130f | Vangelis Koukis | help="How long to listen for clients to forward")
|
351 | 07b0130f | Vangelis Koukis | |
352 | 07b0130f | Vangelis Koukis | (opts, args) = parser.parse_args(sys.argv[1:])
|
353 | 07b0130f | Vangelis Koukis | |
354 | 4af60ef0 | Vangelis Koukis | # Create pidfile
|
355 | 4af60ef0 | Vangelis Koukis | pidf = daemon.pidlockfile.TimeoutPIDLockFile( |
356 | 4af60ef0 | Vangelis Koukis | opts.pid_file, 10)
|
357 | 4af60ef0 | Vangelis Koukis | |
358 | 4af60ef0 | Vangelis Koukis | # Initialize logger
|
359 | 07b0130f | Vangelis Koukis | lvl = logging.DEBUG if opts.debug else logging.INFO |
360 | 4af60ef0 | Vangelis Koukis | logger = logging.getLogger("vncauthproxy")
|
361 | 4af60ef0 | Vangelis Koukis | logger.setLevel(lvl) |
362 | 4af60ef0 | Vangelis Koukis | formatter = logging.Formatter("%(asctime)s vncauthproxy[%(process)d] %(levelname)s: %(message)s",
|
363 | 4af60ef0 | Vangelis Koukis | "%Y-%m-%d %H:%M:%S")
|
364 | 4af60ef0 | Vangelis Koukis | handler = logging.FileHandler(opts.log_file) |
365 | 4af60ef0 | Vangelis Koukis | handler.setFormatter(formatter) |
366 | 4af60ef0 | Vangelis Koukis | logger.addHandler(handler) |
367 | 4af60ef0 | Vangelis Koukis | |
368 | 4af60ef0 | Vangelis Koukis | # Become a daemon
|
369 | 4af60ef0 | Vangelis Koukis | # Redirecting stdout and stderr to handler.stream to catch
|
370 | 4af60ef0 | Vangelis Koukis | # early errors in the daemonization process [e.g., pidfile creation]
|
371 | 4af60ef0 | Vangelis Koukis | # which will otherwise go to /dev/null.
|
372 | 4af60ef0 | Vangelis Koukis | daemon_context = daemon.DaemonContext( |
373 | 4af60ef0 | Vangelis Koukis | pidfile=pidf, |
374 | 4af60ef0 | Vangelis Koukis | umask=0o0022,
|
375 | 4af60ef0 | Vangelis Koukis | stdout=handler.stream, |
376 | 4af60ef0 | Vangelis Koukis | stderr=handler.stream, |
377 | 4af60ef0 | Vangelis Koukis | files_preserve=[handler.stream]) |
378 | 4af60ef0 | Vangelis Koukis | daemon_context.open() |
379 | 4af60ef0 | Vangelis Koukis | logger.info("Became a daemon")
|
380 | 4af60ef0 | Vangelis Koukis | |
381 | 4af60ef0 | Vangelis Koukis | # A fork() has occured while daemonizing,
|
382 | 4af60ef0 | Vangelis Koukis | # we *must* reinit gevent
|
383 | 4af60ef0 | Vangelis Koukis | gevent.reinit() |
384 | 07b0130f | Vangelis Koukis | |
385 | 07b0130f | Vangelis Koukis | if os.path.exists(opts.ctrl_socket):
|
386 | 4af60ef0 | Vangelis Koukis | logger.critical("Socket '%s' already exists" % opts.ctrl_socket)
|
387 | 07b0130f | Vangelis Koukis | sys.exit(1)
|
388 | 07b0130f | Vangelis Koukis | |
389 | 07b0130f | Vangelis Koukis | # TODO: make this tunable? chgrp as well?
|
390 | 07b0130f | Vangelis Koukis | old_umask = os.umask(0077)
|
391 | 07b0130f | Vangelis Koukis | |
392 | 07b0130f | Vangelis Koukis | ctrl = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) |
393 | 07b0130f | Vangelis Koukis | ctrl.bind(opts.ctrl_socket) |
394 | 07b0130f | Vangelis Koukis | |
395 | 07b0130f | Vangelis Koukis | os.umask(old_umask) |
396 | 07b0130f | Vangelis Koukis | |
397 | 07b0130f | Vangelis Koukis | ctrl.listen(1)
|
398 | 4af60ef0 | Vangelis Koukis | logger.info("Initialized, waiting for control connections at %s" %
|
399 | 07b0130f | Vangelis Koukis | opts.ctrl_socket) |
400 | 07b0130f | Vangelis Koukis | |
401 | 4af60ef0 | Vangelis Koukis | # Catch signals to ensure graceful shutdown,
|
402 | 33c82017 | Vangelis Koukis | # e.g., to make sure the control socket gets unlink()ed.
|
403 | 33c82017 | Vangelis Koukis | #
|
404 | 33c82017 | Vangelis Koukis | # Uses gevent.signal so the handler fires even during
|
405 | 33c82017 | Vangelis Koukis | # gevent.socket.accept()
|
406 | 4af60ef0 | Vangelis Koukis | gevent.signal(SIGINT, fatal_signal_handler, "SIGINT")
|
407 | 33c82017 | Vangelis Koukis | gevent.signal(SIGTERM, fatal_signal_handler, "SIGTERM")
|
408 | 07b0130f | Vangelis Koukis | while True: |
409 | 07b0130f | Vangelis Koukis | try:
|
410 | 07b0130f | Vangelis Koukis | client, addr = ctrl.accept() |
411 | 4af60ef0 | Vangelis Koukis | except SystemExit: |
412 | 07b0130f | Vangelis Koukis | break
|
413 | 07b0130f | Vangelis Koukis | |
414 | 4af60ef0 | Vangelis Koukis | logger.info("New control connection")
|
415 | 07b0130f | Vangelis Koukis | line = client.recv(1024).strip()
|
416 | 07b0130f | Vangelis Koukis | try:
|
417 | 07b0130f | Vangelis Koukis | # Control message format:
|
418 | 07b0130f | Vangelis Koukis | # TODO: make this json-based?
|
419 | 07b0130f | Vangelis Koukis | # TODO: support multiple forwardings in the same message?
|
420 | 07b0130f | Vangelis Koukis | # <source_port>:<destination_address>:<destination_port>:<password>
|
421 | 07b0130f | Vangelis Koukis | # <password> will be used for MITM authentication of clients
|
422 | 07b0130f | Vangelis Koukis | # connecting to <source_port>, who will subsequently be forwarded
|
423 | 07b0130f | Vangelis Koukis | # to a VNC server at <destination_address>:<destination_port>
|
424 | 07b0130f | Vangelis Koukis | sport, daddr, dport, password = line.split(':', 3) |
425 | 4af60ef0 | Vangelis Koukis | logger.info("New forwarding [%d -> %s:%d]" %
|
426 | 07b0130f | Vangelis Koukis | (int(sport), daddr, int(dport))) |
427 | 07b0130f | Vangelis Koukis | except:
|
428 | 4af60ef0 | Vangelis Koukis | logger.warn("Malformed request: %s" % line)
|
429 | 07b0130f | Vangelis Koukis | client.send("FAILED\n")
|
430 | 07b0130f | Vangelis Koukis | client.close() |
431 | 07b0130f | Vangelis Koukis | continue
|
432 | 07b0130f | Vangelis Koukis | |
433 | 07b0130f | Vangelis Koukis | client.send("OK\n")
|
434 | 4af60ef0 | Vangelis Koukis | VncAuthProxy.spawn(logger, sport, daddr, dport, password, opts.connect_timeout) |
435 | 07b0130f | Vangelis Koukis | client.close() |
436 | 07b0130f | Vangelis Koukis | |
437 | 4af60ef0 | Vangelis Koukis | logger.info("Unlinking control socket at %s" %
|
438 | 33c82017 | Vangelis Koukis | opts.ctrl_socket) |
439 | 07b0130f | Vangelis Koukis | os.unlink(opts.ctrl_socket) |
440 | 4af60ef0 | Vangelis Koukis | daemon_context.close() |
441 | 07b0130f | Vangelis Koukis | sys.exit(0) |