Statistics
| Branch: | Tag: | Revision:

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)