Statistics
| Branch: | Tag: | Revision:

root / vncauthproxy / proxy.py @ 6149f03e

History | View | Annotate | Download (30.5 kB)

1 138d0e8b Faidon Liambotis
#!/usr/bin/env python
2 138d0e8b Faidon Liambotis
"""
3 138d0e8b Faidon Liambotis
vncauthproxy - a VNC authentication proxy
4 138d0e8b Faidon Liambotis
"""
5 138d0e8b Faidon Liambotis
#
6 31965126 Vangelis Koukis
# Copyright (c) 2010-2013 Greek Research and Technology Network S.A.
7 138d0e8b Faidon Liambotis
#
8 138d0e8b Faidon Liambotis
# This program is free software; you can redistribute it and/or modify
9 138d0e8b Faidon Liambotis
# it under the terms of the GNU General Public License as published by
10 138d0e8b Faidon Liambotis
# the Free Software Foundation; either version 2 of the License, or
11 138d0e8b Faidon Liambotis
# (at your option) any later version.
12 138d0e8b Faidon Liambotis
#
13 138d0e8b Faidon Liambotis
# This program is distributed in the hope that it will be useful, but
14 138d0e8b Faidon Liambotis
# WITHOUT ANY WARRANTY; without even the implied warranty of
15 138d0e8b Faidon Liambotis
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 138d0e8b Faidon Liambotis
# General Public License for more details.
17 138d0e8b Faidon Liambotis
#
18 138d0e8b Faidon Liambotis
# You should have received a copy of the GNU General Public License
19 138d0e8b Faidon Liambotis
# along with this program; if not, write to the Free Software
20 138d0e8b Faidon Liambotis
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 138d0e8b Faidon Liambotis
# 02110-1301, USA.
22 138d0e8b Faidon Liambotis
23 310ae019 Stratos Psomadakis
# Daemon files
24 138d0e8b Faidon Liambotis
DEFAULT_LOG_FILE = "/var/log/vncauthproxy/vncauthproxy.log"
25 138d0e8b Faidon Liambotis
DEFAULT_PID_FILE = "/var/run/vncauthproxy/vncauthproxy.pid"
26 310ae019 Stratos Psomadakis
27 310ae019 Stratos Psomadakis
# By default, bind / listen for control connections to TCP *:24999
28 310ae019 Stratos Psomadakis
# (both IPv4 and IPv6)
29 310ae019 Stratos Psomadakis
DEFAULT_LISTEN_ADDRESS = None
30 310ae019 Stratos Psomadakis
DEFAULT_LISTEN_PORT = 24999
31 310ae019 Stratos Psomadakis
32 310ae019 Stratos Psomadakis
# Backlog for the control socket
33 310ae019 Stratos Psomadakis
DEFAULT_BACKLOG = 256
34 310ae019 Stratos Psomadakis
35 310ae019 Stratos Psomadakis
# Timeout for the VNC server connection establishment / RFB handshake
36 310ae019 Stratos Psomadakis
DEFAULT_SERVER_TIMEOUT = 60.0
37 310ae019 Stratos Psomadakis
38 310ae019 Stratos Psomadakis
# Connect retries and delay between retries for the VNC server socket
39 7eb27319 Stratos Psomadakis
DEFAULT_CONNECT_RETRIES = 3
40 7eb27319 Stratos Psomadakis
DEFAULT_RETRY_WAIT = 0.1
41 310ae019 Stratos Psomadakis
42 310ae019 Stratos Psomadakis
# Connect timeout for the listening sockets
43 310ae019 Stratos Psomadakis
DEFAULT_CONNECT_TIMEOUT = 30
44 310ae019 Stratos Psomadakis
45 310ae019 Stratos Psomadakis
# Port range for the listening sockets
46 310ae019 Stratos Psomadakis
#
47 1e3d1c7d Vangelis Koukis
# We must take care not to fall into the ephemeral port range,
48 1e3d1c7d Vangelis Koukis
# this can lead to transient failures to bind a chosen port.
49 1e3d1c7d Vangelis Koukis
#
50 1e3d1c7d Vangelis Koukis
# By default, Linux uses 32768 to 61000, see:
51 1e3d1c7d Vangelis Koukis
# http://www.ncftp.com/ncftpd/doc/misc/ephemeral_ports.html#Linux
52 1e3d1c7d Vangelis Koukis
# so 25000-30000 seems to be a sensible default.
53 310ae019 Stratos Psomadakis
#
54 310ae019 Stratos Psomadakis
# We also take into account the ports that Ganeti daemons bind to, the port
55 310ae019 Stratos Psomadakis
# range used by DRBD etc.
56 1e3d1c7d Vangelis Koukis
DEFAULT_MIN_PORT = 25000
57 1e3d1c7d Vangelis Koukis
DEFAULT_MAX_PORT = 30000
58 138d0e8b Faidon Liambotis
59 0b74ef50 Stratos Psomadakis
# SSL certificate / key files
60 d49bd2fb Stratos Psomadakis
DEFAULT_CERT_FILE = "/var/lib/vncauthproxy/cert.pem"
61 d49bd2fb Stratos Psomadakis
DEFAULT_KEY_FILE = "/var/lib/vncauthproxy/key.pem"
62 d49bd2fb Stratos Psomadakis
63 d49bd2fb Stratos Psomadakis
# Auth file
64 d49bd2fb Stratos Psomadakis
DEFAULT_AUTH_FILE = "/var/lib/vncauthproxy/users"
65 0b74ef50 Stratos Psomadakis
66 138d0e8b Faidon Liambotis
import os
67 138d0e8b Faidon Liambotis
import sys
68 138d0e8b Faidon Liambotis
import logging
69 138d0e8b Faidon Liambotis
import gevent
70 020f4a9e Vangelis Koukis
import gevent.event
71 138d0e8b Faidon Liambotis
import daemon
72 138d0e8b Faidon Liambotis
import random
73 39840bd3 Vangelis Koukis
import daemon.runner
74 d49bd2fb Stratos Psomadakis
import hashlib
75 138d0e8b Faidon Liambotis
76 138d0e8b Faidon Liambotis
import rfb
77 39840bd3 Vangelis Koukis
78 138d0e8b Faidon Liambotis
try:
79 138d0e8b Faidon Liambotis
    import simplejson as json
80 138d0e8b Faidon Liambotis
except ImportError:
81 138d0e8b Faidon Liambotis
    import json
82 138d0e8b Faidon Liambotis
83 0b74ef50 Stratos Psomadakis
from gevent import socket, ssl
84 138d0e8b Faidon Liambotis
from signal import SIGINT, SIGTERM
85 138d0e8b Faidon Liambotis
from gevent.select import select
86 138d0e8b Faidon Liambotis
87 180a750f Vangelis Koukis
from lockfile import LockTimeout, AlreadyLocked
88 180a750f Vangelis Koukis
# Take care of differences between python-daemon versions.
89 180a750f Vangelis Koukis
try:
90 180a750f Vangelis Koukis
    from daemon import pidfile as pidlockfile
91 4331e4d8 Stratos Psomadakis
except ImportError:
92 180a750f Vangelis Koukis
    from daemon import pidlockfile
93 180a750f Vangelis Koukis
94 180a750f Vangelis Koukis
95 88420a63 Faidon Liambotis
logger = None
96 88420a63 Faidon Liambotis
97 31965126 Vangelis Koukis
98 4331e4d8 Stratos Psomadakis
class InternalError(Exception):
99 4331e4d8 Stratos Psomadakis
    """Exception for internal vncauthproxy errors"""
100 4331e4d8 Stratos Psomadakis
    pass
101 4331e4d8 Stratos Psomadakis
102 4331e4d8 Stratos Psomadakis
103 fe5fc466 Vangelis Koukis
# Currently, gevent uses libevent-dns for asynchronous DNS resolution,
104 376a8634 Vangelis Koukis
# which opens a socket upon initialization time. Since we can't get the fd
105 376a8634 Vangelis Koukis
# reliably, We have to maintain all file descriptors open (which won't harm
106 376a8634 Vangelis Koukis
# anyway)
107 376a8634 Vangelis Koukis
class AllFilesDaemonContext(daemon.DaemonContext):
108 376a8634 Vangelis Koukis
    """DaemonContext class keeping all file descriptors open"""
109 376a8634 Vangelis Koukis
    def _get_exclude_file_descriptors(self):
110 376a8634 Vangelis Koukis
        class All:
111 376a8634 Vangelis Koukis
            def __contains__(self, value):
112 376a8634 Vangelis Koukis
                return True
113 376a8634 Vangelis Koukis
        return All()
114 376a8634 Vangelis Koukis
115 376a8634 Vangelis Koukis
116 138d0e8b Faidon Liambotis
class VncAuthProxy(gevent.Greenlet):
117 138d0e8b Faidon Liambotis
    """
118 138d0e8b Faidon Liambotis
    Simple class implementing a VNC Forwarder with MITM authentication as a
119 138d0e8b Faidon Liambotis
    Greenlet
120 138d0e8b Faidon Liambotis

121 138d0e8b Faidon Liambotis
    VncAuthProxy forwards VNC traffic from a specified port of the local host
122 138d0e8b Faidon Liambotis
    to a specified remote host:port. Furthermore, it implements VNC
123 138d0e8b Faidon Liambotis
    Authentication, intercepting the client/server handshake and asking the
124 138d0e8b Faidon Liambotis
    client for authentication even if the backend requires none.
125 138d0e8b Faidon Liambotis

126 138d0e8b Faidon Liambotis
    It is primarily intended for use in virtualization environments, as a VNC
127 138d0e8b Faidon Liambotis
    ``switch''.
128 138d0e8b Faidon Liambotis

129 138d0e8b Faidon Liambotis
    """
130 138d0e8b Faidon Liambotis
    id = 1
131 138d0e8b Faidon Liambotis
132 310ae019 Stratos Psomadakis
    def __init__(self, logger, client):
133 138d0e8b Faidon Liambotis
        """
134 138d0e8b Faidon Liambotis
        @type logger: logging.Logger
135 138d0e8b Faidon Liambotis
        @param logger: the logger to use
136 310ae019 Stratos Psomadakis
        @type client: socket.socket
137 310ae019 Stratos Psomadakis
        @param listeners: the client control connection socket
138 138d0e8b Faidon Liambotis

139 138d0e8b Faidon Liambotis
        """
140 138d0e8b Faidon Liambotis
        gevent.Greenlet.__init__(self)
141 138d0e8b Faidon Liambotis
        self.id = VncAuthProxy.id
142 138d0e8b Faidon Liambotis
        VncAuthProxy.id += 1
143 138d0e8b Faidon Liambotis
        self.log = logger
144 310ae019 Stratos Psomadakis
        self.client = client
145 020f4a9e Vangelis Koukis
        # A list of worker/forwarder greenlets, one for each direction
146 020f4a9e Vangelis Koukis
        self.workers = []
147 310ae019 Stratos Psomadakis
        self.sport = None
148 310ae019 Stratos Psomadakis
        self.pool = None
149 310ae019 Stratos Psomadakis
        self.daddr = None
150 310ae019 Stratos Psomadakis
        self.dport = None
151 310ae019 Stratos Psomadakis
        self.server = None
152 310ae019 Stratos Psomadakis
        self.password = None
153 138d0e8b Faidon Liambotis
154 138d0e8b Faidon Liambotis
    def _cleanup(self):
155 020f4a9e Vangelis Koukis
        """Cleanup everything: workers, sockets, ports
156 020f4a9e Vangelis Koukis

157 020f4a9e Vangelis Koukis
        Kill all remaining forwarder greenlets, close all active sockets,
158 020f4a9e Vangelis Koukis
        return the source port to the pool if applicable, then exit
159 020f4a9e Vangelis Koukis
        gracefully.
160 020f4a9e Vangelis Koukis

161 020f4a9e Vangelis Koukis
        """
162 020f4a9e Vangelis Koukis
        # Make sure all greenlets are dead, then clean them up
163 020f4a9e Vangelis Koukis
        self.debug("Cleaning up %d workers", len(self.workers))
164 020f4a9e Vangelis Koukis
        for g in self.workers:
165 020f4a9e Vangelis Koukis
            g.kill()
166 020f4a9e Vangelis Koukis
        gevent.joinall(self.workers)
167 020f4a9e Vangelis Koukis
        del self.workers
168 020f4a9e Vangelis Koukis
169 020f4a9e Vangelis Koukis
        self.debug("Cleaning up sockets")
170 138d0e8b Faidon Liambotis
        while self.listeners:
171 0b74ef50 Stratos Psomadakis
            sock = self.listeners.pop().close()
172 0b74ef50 Stratos Psomadakis
173 138d0e8b Faidon Liambotis
        if self.server:
174 138d0e8b Faidon Liambotis
            self.server.close()
175 0b74ef50 Stratos Psomadakis
176 138d0e8b Faidon Liambotis
        if self.client:
177 138d0e8b Faidon Liambotis
            self.client.close()
178 138d0e8b Faidon Liambotis
179 f6eb1be8 Vangelis Koukis
        # Reintroduce the port number of the client socket in
180 f6eb1be8 Vangelis Koukis
        # the port pool, if applicable.
181 f6eb1be8 Vangelis Koukis
        if not self.pool is None:
182 f6eb1be8 Vangelis Koukis
            self.pool.append(self.sport)
183 f6eb1be8 Vangelis Koukis
            self.debug("Returned port %d to port pool, contains %d ports",
184 f6eb1be8 Vangelis Koukis
                       self.sport, len(self.pool))
185 f6eb1be8 Vangelis Koukis
186 020f4a9e Vangelis Koukis
        self.info("Cleaned up connection, all done")
187 138d0e8b Faidon Liambotis
        raise gevent.GreenletExit
188 138d0e8b Faidon Liambotis
189 138d0e8b Faidon Liambotis
    def __str__(self):
190 31965126 Vangelis Koukis
        return "VncAuthProxy: %d -> %s:%d" % (self.sport, self.daddr,
191 31965126 Vangelis Koukis
                                              self.dport)
192 138d0e8b Faidon Liambotis
193 138d0e8b Faidon Liambotis
    def _forward(self, source, dest):
194 138d0e8b Faidon Liambotis
        """
195 138d0e8b Faidon Liambotis
        Forward traffic from source to dest
196 138d0e8b Faidon Liambotis

197 138d0e8b Faidon Liambotis
        @type source: socket
198 138d0e8b Faidon Liambotis
        @param source: source socket
199 138d0e8b Faidon Liambotis
        @type dest: socket
200 138d0e8b Faidon Liambotis
        @param dest: destination socket
201 138d0e8b Faidon Liambotis

202 138d0e8b Faidon Liambotis
        """
203 138d0e8b Faidon Liambotis
204 138d0e8b Faidon Liambotis
        while True:
205 138d0e8b Faidon Liambotis
            d = source.recv(16384)
206 138d0e8b Faidon Liambotis
            if d == '':
207 138d0e8b Faidon Liambotis
                if source == self.client:
208 138d0e8b Faidon Liambotis
                    self.info("Client connection closed")
209 138d0e8b Faidon Liambotis
                else:
210 138d0e8b Faidon Liambotis
                    self.info("Server connection closed")
211 138d0e8b Faidon Liambotis
                break
212 138d0e8b Faidon Liambotis
            dest.sendall(d)
213 138d0e8b Faidon Liambotis
        # No need to close the source and dest sockets here.
214 138d0e8b Faidon Liambotis
        # They are owned by and will be closed by the original greenlet.
215 138d0e8b Faidon Liambotis
216 310ae019 Stratos Psomadakis
    def _perform_server_handshake(self):
217 310ae019 Stratos Psomadakis
        """
218 310ae019 Stratos Psomadakis
        Initiate a connection with the backend server and perform basic
219 310ae019 Stratos Psomadakis
        RFB 3.8 handshake with it.
220 310ae019 Stratos Psomadakis

221 310ae019 Stratos Psomadakis
        Return a socket connected to the backend server.
222 310ae019 Stratos Psomadakis

223 310ae019 Stratos Psomadakis
        """
224 310ae019 Stratos Psomadakis
        server = None
225 310ae019 Stratos Psomadakis
226 dd62f34b Stratos Psomadakis
        tries = VncAuthProxy.connect_retries
227 310ae019 Stratos Psomadakis
        while tries:
228 310ae019 Stratos Psomadakis
            tries -= 1
229 310ae019 Stratos Psomadakis
230 310ae019 Stratos Psomadakis
            # Initiate server connection
231 dd62f34b Stratos Psomadakis
            for res in socket.getaddrinfo(self.daddr, self.dport,
232 310ae019 Stratos Psomadakis
                                          socket.AF_UNSPEC,
233 310ae019 Stratos Psomadakis
                                          socket.SOCK_STREAM, 0,
234 310ae019 Stratos Psomadakis
                                          socket.AI_PASSIVE):
235 310ae019 Stratos Psomadakis
                af, socktype, proto, canonname, sa = res
236 310ae019 Stratos Psomadakis
                try:
237 310ae019 Stratos Psomadakis
                    server = socket.socket(af, socktype, proto)
238 310ae019 Stratos Psomadakis
                except socket.error:
239 310ae019 Stratos Psomadakis
                    server = None
240 310ae019 Stratos Psomadakis
                    continue
241 310ae019 Stratos Psomadakis
242 310ae019 Stratos Psomadakis
                # Set socket timeout for the initial handshake
243 dd62f34b Stratos Psomadakis
                server.settimeout(VncAuthProxy.server_timeout)
244 310ae019 Stratos Psomadakis
245 310ae019 Stratos Psomadakis
                try:
246 dd62f34b Stratos Psomadakis
                    self.debug("Connecting to %s:%s", *sa[:2])
247 310ae019 Stratos Psomadakis
                    server.connect(sa)
248 dd62f34b Stratos Psomadakis
                    self.debug("Connection to %s:%s successful", *sa[:2])
249 4331e4d8 Stratos Psomadakis
                except socket.error as err:
250 4331e4d8 Stratos Psomadakis
                    self.debug("Failed to perform sever hanshake, retrying...")
251 310ae019 Stratos Psomadakis
                    server.close()
252 310ae019 Stratos Psomadakis
                    server = None
253 310ae019 Stratos Psomadakis
                    continue
254 310ae019 Stratos Psomadakis
255 310ae019 Stratos Psomadakis
                # We succesfully connected to the server
256 310ae019 Stratos Psomadakis
                tries = 0
257 310ae019 Stratos Psomadakis
                break
258 310ae019 Stratos Psomadakis
259 310ae019 Stratos Psomadakis
            # Wait and retry
260 dd62f34b Stratos Psomadakis
            gevent.sleep(VncAuthProxy.retry_wait)
261 310ae019 Stratos Psomadakis
262 310ae019 Stratos Psomadakis
        if server is None:
263 4331e4d8 Stratos Psomadakis
            raise InternalError("Failed to connect to server")
264 310ae019 Stratos Psomadakis
265 310ae019 Stratos Psomadakis
        version = server.recv(1024)
266 310ae019 Stratos Psomadakis
        if not rfb.check_version(version):
267 4331e4d8 Stratos Psomadakis
            raise InternalError("Unsupported RFB version: %s"
268 4331e4d8 Stratos Psomadakis
                                % version.strip())
269 310ae019 Stratos Psomadakis
270 310ae019 Stratos Psomadakis
        server.send(rfb.RFB_VERSION_3_8 + "\n")
271 310ae019 Stratos Psomadakis
272 310ae019 Stratos Psomadakis
        res = server.recv(1024)
273 310ae019 Stratos Psomadakis
        types = rfb.parse_auth_request(res)
274 310ae019 Stratos Psomadakis
        if not types:
275 4331e4d8 Stratos Psomadakis
            raise InternalError("Error handshaking with the server")
276 310ae019 Stratos Psomadakis
277 310ae019 Stratos Psomadakis
        else:
278 dd62f34b Stratos Psomadakis
            self.debug("Supported authentication types: %s",
279 310ae019 Stratos Psomadakis
                         " ".join([str(x) for x in types]))
280 310ae019 Stratos Psomadakis
281 310ae019 Stratos Psomadakis
        if rfb.RFB_AUTHTYPE_NONE not in types:
282 4331e4d8 Stratos Psomadakis
            raise InternalError("Error, server demands authentication")
283 310ae019 Stratos Psomadakis
284 310ae019 Stratos Psomadakis
        server.send(rfb.to_u8(rfb.RFB_AUTHTYPE_NONE))
285 310ae019 Stratos Psomadakis
286 310ae019 Stratos Psomadakis
        # Check authentication response
287 310ae019 Stratos Psomadakis
        res = server.recv(4)
288 310ae019 Stratos Psomadakis
        res = rfb.from_u32(res)
289 310ae019 Stratos Psomadakis
290 310ae019 Stratos Psomadakis
        if res != 0:
291 4331e4d8 Stratos Psomadakis
            raise InternalError("Authentication error")
292 310ae019 Stratos Psomadakis
293 310ae019 Stratos Psomadakis
        # Reset the timeout for the rest of the session
294 310ae019 Stratos Psomadakis
        server.settimeout(None)
295 310ae019 Stratos Psomadakis
296 310ae019 Stratos Psomadakis
        self.server = server
297 310ae019 Stratos Psomadakis
298 310ae019 Stratos Psomadakis
    def _establish_connection(self):
299 310ae019 Stratos Psomadakis
        client = self.client
300 310ae019 Stratos Psomadakis
        ports = VncAuthProxy.ports
301 310ae019 Stratos Psomadakis
302 310ae019 Stratos Psomadakis
        # Receive and parse a client request.
303 310ae019 Stratos Psomadakis
        response = {
304 310ae019 Stratos Psomadakis
            "source_port": 0,
305 310ae019 Stratos Psomadakis
            "status": "FAILED",
306 310ae019 Stratos Psomadakis
        }
307 310ae019 Stratos Psomadakis
        try:
308 310ae019 Stratos Psomadakis
            # Control request, in JSON:
309 310ae019 Stratos Psomadakis
            #
310 310ae019 Stratos Psomadakis
            # {
311 310ae019 Stratos Psomadakis
            #     "source_port":
312 310ae019 Stratos Psomadakis
            #         <source port or 0 for automatic allocation>,
313 310ae019 Stratos Psomadakis
            #     "destination_address":
314 310ae019 Stratos Psomadakis
            #         <destination address of backend server>,
315 310ae019 Stratos Psomadakis
            #     "destination_port":
316 310ae019 Stratos Psomadakis
            #         <destination port>
317 310ae019 Stratos Psomadakis
            #     "password":
318 310ae019 Stratos Psomadakis
            #         <the password to use to authenticate clients>
319 d49bd2fb Stratos Psomadakis
            #     "auth_user":
320 d49bd2fb Stratos Psomadakis
            #         <user for control connection authentication>,
321 d49bd2fb Stratos Psomadakis
            #      "auth_password":
322 d49bd2fb Stratos Psomadakis
            #         <password for control connection authentication>,
323 310ae019 Stratos Psomadakis
            # }
324 310ae019 Stratos Psomadakis
            #
325 310ae019 Stratos Psomadakis
            # The <password> is used for MITM authentication of clients
326 310ae019 Stratos Psomadakis
            # connecting to <source_port>, who will subsequently be
327 310ae019 Stratos Psomadakis
            # forwarded to a VNC server listening at
328 310ae019 Stratos Psomadakis
            # <destination_address>:<destination_port>
329 310ae019 Stratos Psomadakis
            #
330 310ae019 Stratos Psomadakis
            # Control reply, in JSON:
331 310ae019 Stratos Psomadakis
            # {
332 310ae019 Stratos Psomadakis
            #     "source_port": <the allocated source port>
333 310ae019 Stratos Psomadakis
            #     "status": <one of "OK" or "FAILED">
334 310ae019 Stratos Psomadakis
            # }
335 310ae019 Stratos Psomadakis
            #
336 310ae019 Stratos Psomadakis
            buf = client.recv(1024)
337 310ae019 Stratos Psomadakis
            req = json.loads(buf)
338 310ae019 Stratos Psomadakis
339 d49bd2fb Stratos Psomadakis
            auth_user = req['auth_user']
340 d49bd2fb Stratos Psomadakis
            auth_password = req['auth_password']
341 310ae019 Stratos Psomadakis
            sport_orig = int(req['source_port'])
342 dd62f34b Stratos Psomadakis
            self.daddr = req['destination_address']
343 dd62f34b Stratos Psomadakis
            self.dport = int(req['destination_port'])
344 dd62f34b Stratos Psomadakis
            self.password = req['password']
345 d49bd2fb Stratos Psomadakis
346 d49bd2fb Stratos Psomadakis
            if auth_user not in VncAuthProxy.authdb:
347 d49bd2fb Stratos Psomadakis
                msg = "Authentication failure: user not found"
348 4331e4d8 Stratos Psomadakis
                raise InternalError(msg)
349 d49bd2fb Stratos Psomadakis
350 d49bd2fb Stratos Psomadakis
            (cipher, authdb_password) = VncAuthProxy.authdb[auth_user]
351 d49bd2fb Stratos Psomadakis
            if cipher == 'HA1':
352 d49bd2fb Stratos Psomadakis
                message = auth_user + ':vncauthproxy:' + auth_password
353 d49bd2fb Stratos Psomadakis
                auth_password = hashlib.md5(message).hexdigest()
354 d49bd2fb Stratos Psomadakis
355 d49bd2fb Stratos Psomadakis
            if auth_password != authdb_password:
356 d49bd2fb Stratos Psomadakis
                msg = "Authentication failure: wrong password"
357 4331e4d8 Stratos Psomadakis
                raise InternalError(msg)
358 d49bd2fb Stratos Psomadakis
        except KeyError:
359 d49bd2fb Stratos Psomadakis
            msg = "Malformed request: %s" % buf
360 4331e4d8 Stratos Psomadakis
            raise InternalError(msg)
361 4331e4d8 Stratos Psomadakis
        except InternalError as err:
362 4331e4d8 Stratos Psomadakis
            self.warn(err)
363 4331e4d8 Stratos Psomadakis
            response['reason'] = str(err)
364 4331e4d8 Stratos Psomadakis
            client.send(json.dumps(response))
365 4331e4d8 Stratos Psomadakis
            client.close()
366 4331e4d8 Stratos Psomadakis
            raise gevent.GreenletExit
367 d49bd2fb Stratos Psomadakis
        except Exception as err:
368 4331e4d8 Stratos Psomadakis
            self.exception(err)
369 4331e4d8 Stratos Psomadakis
            self.error("Unexpected error")
370 310ae019 Stratos Psomadakis
            client.send(json.dumps(response))
371 310ae019 Stratos Psomadakis
            client.close()
372 310ae019 Stratos Psomadakis
            raise gevent.GreenletExit
373 310ae019 Stratos Psomadakis
374 310ae019 Stratos Psomadakis
        server = None
375 4331e4d8 Stratos Psomadakis
        pool = None
376 4331e4d8 Stratos Psomadakis
        sport = sport_orig
377 310ae019 Stratos Psomadakis
        try:
378 310ae019 Stratos Psomadakis
            # If the client has so indicated, pick an ephemeral source port
379 310ae019 Stratos Psomadakis
            # randomly, and remove it from the port pool.
380 310ae019 Stratos Psomadakis
            if sport_orig == 0:
381 310ae019 Stratos Psomadakis
                while True:
382 310ae019 Stratos Psomadakis
                    try:
383 310ae019 Stratos Psomadakis
                        sport = random.choice(ports)
384 310ae019 Stratos Psomadakis
                        ports.remove(sport)
385 310ae019 Stratos Psomadakis
                        break
386 310ae019 Stratos Psomadakis
                    except ValueError:
387 dd62f34b Stratos Psomadakis
                        self.debug("Port %d already taken", sport)
388 310ae019 Stratos Psomadakis
389 dd62f34b Stratos Psomadakis
                self.debug("Got port %d from pool, %d remaining",
390 4331e4d8 Stratos Psomadakis
                           sport, len(ports))
391 310ae019 Stratos Psomadakis
                pool = ports
392 310ae019 Stratos Psomadakis
393 310ae019 Stratos Psomadakis
            self.sport = sport
394 310ae019 Stratos Psomadakis
            self.pool = pool
395 310ae019 Stratos Psomadakis
396 4331e4d8 Stratos Psomadakis
            self.listeners = get_listening_sockets(sport)
397 dd62f34b Stratos Psomadakis
            self._perform_server_handshake()
398 310ae019 Stratos Psomadakis
399 dd62f34b Stratos Psomadakis
            self.info("New forwarding: %d (client req'd: %d) -> %s:%d",
400 310ae019 Stratos Psomadakis
                        sport, sport_orig, self.daddr, self.dport)
401 310ae019 Stratos Psomadakis
            response = {"source_port": sport,
402 310ae019 Stratos Psomadakis
                        "status": "OK"}
403 310ae019 Stratos Psomadakis
        except IndexError:
404 dd62f34b Stratos Psomadakis
            self.error(("FAILED forwarding, out of ports for [req'd by "
405 310ae019 Stratos Psomadakis
                          "client: %d -> %s:%d]"),
406 310ae019 Stratos Psomadakis
                         sport_orig, self.daddr, self.dport)
407 310ae019 Stratos Psomadakis
            raise gevent.GreenletExit
408 4331e4d8 Stratos Psomadakis
        except InternalError as err:
409 4331e4d8 Stratos Psomadakis
            self.error(err)
410 4331e4d8 Stratos Psomadakis
            self.error(("FAILED forwarding: %d (client req'd: %d) -> "
411 4331e4d8 Stratos Psomadakis
                          "%s:%d"), sport, sport_orig, self.daddr, self.dport)
412 4331e4d8 Stratos Psomadakis
            if pool:
413 4331e4d8 Stratos Psomadakis
                pool.append(sport)
414 4331e4d8 Stratos Psomadakis
                self.debug("Returned port %d to pool, %d remanining",
415 4331e4d8 Stratos Psomadakis
                             sport, len(pool))
416 4331e4d8 Stratos Psomadakis
            if server:
417 4331e4d8 Stratos Psomadakis
                server.close()
418 4331e4d8 Stratos Psomadakis
            raise gevent.GreenletExit
419 4331e4d8 Stratos Psomadakis
        except Exception as err:
420 4331e4d8 Stratos Psomadakis
            self.exception(err)
421 4331e4d8 Stratos Psomadakis
            self.error("Unexpected error")
422 dd62f34b Stratos Psomadakis
            self.error(("FAILED forwarding: %d (client req'd: %d) -> "
423 310ae019 Stratos Psomadakis
                          "%s:%d"), sport, sport_orig, self.daddr, self.dport)
424 4331e4d8 Stratos Psomadakis
            if pool:
425 310ae019 Stratos Psomadakis
                pool.append(sport)
426 dd62f34b Stratos Psomadakis
                self.debug("Returned port %d to pool, %d remanining",
427 310ae019 Stratos Psomadakis
                             sport, len(pool))
428 4331e4d8 Stratos Psomadakis
            if server:
429 310ae019 Stratos Psomadakis
                server.close()
430 310ae019 Stratos Psomadakis
            raise gevent.GreenletExit
431 310ae019 Stratos Psomadakis
        finally:
432 310ae019 Stratos Psomadakis
            client.send(json.dumps(response))
433 310ae019 Stratos Psomadakis
            client.close()
434 310ae019 Stratos Psomadakis
435 512c571e Stratos Psomadakis
    def _client_handshake(self):
436 138d0e8b Faidon Liambotis
        """
437 138d0e8b Faidon Liambotis
        Perform handshake/authentication with a connecting client
438 138d0e8b Faidon Liambotis

439 138d0e8b Faidon Liambotis
        Outline:
440 138d0e8b Faidon Liambotis
        1. Client connects
441 31965126 Vangelis Koukis
        2. We fake RFB 3.8 protocol and require VNC authentication
442 31965126 Vangelis Koukis
           [processing also supports RFB 3.3]
443 138d0e8b Faidon Liambotis
        3. Client accepts authentication method
444 138d0e8b Faidon Liambotis
        4. We send an authentication challenge
445 138d0e8b Faidon Liambotis
        5. Client sends the authentication response
446 138d0e8b Faidon Liambotis
        6. We check the authentication
447 138d0e8b Faidon Liambotis

448 512c571e Stratos Psomadakis
        Upon return, self.client socket is connected to the client.
449 138d0e8b Faidon Liambotis

450 138d0e8b Faidon Liambotis
        """
451 138d0e8b Faidon Liambotis
        self.client.send(rfb.RFB_VERSION_3_8 + "\n")
452 138d0e8b Faidon Liambotis
        client_version_str = self.client.recv(1024)
453 138d0e8b Faidon Liambotis
        client_version = rfb.check_version(client_version_str)
454 138d0e8b Faidon Liambotis
        if not client_version:
455 c87d99e9 Vangelis Koukis
            self.error("Invalid version: %s", client_version_str)
456 138d0e8b Faidon Liambotis
            raise gevent.GreenletExit
457 138d0e8b Faidon Liambotis
458 138d0e8b Faidon Liambotis
        # Both for RFB 3.3 and 3.8
459 138d0e8b Faidon Liambotis
        self.debug("Requesting authentication")
460 138d0e8b Faidon Liambotis
        auth_request = rfb.make_auth_request(rfb.RFB_AUTHTYPE_VNC,
461 31965126 Vangelis Koukis
                                             version=client_version)
462 138d0e8b Faidon Liambotis
        self.client.send(auth_request)
463 138d0e8b Faidon Liambotis
464 138d0e8b Faidon Liambotis
        # The client gets to propose an authtype only for RFB 3.8
465 138d0e8b Faidon Liambotis
        if client_version == rfb.RFB_VERSION_3_8:
466 138d0e8b Faidon Liambotis
            res = self.client.recv(1024)
467 138d0e8b Faidon Liambotis
            type = rfb.parse_client_authtype(res)
468 138d0e8b Faidon Liambotis
            if type == rfb.RFB_AUTHTYPE_ERROR:
469 c87d99e9 Vangelis Koukis
                self.warn("Client refused authentication: %s", res[1:])
470 138d0e8b Faidon Liambotis
            else:
471 c87d99e9 Vangelis Koukis
                self.debug("Client requested authtype %x", type)
472 138d0e8b Faidon Liambotis
473 138d0e8b Faidon Liambotis
            if type != rfb.RFB_AUTHTYPE_VNC:
474 c87d99e9 Vangelis Koukis
                self.error("Wrong auth type: %d", type)
475 138d0e8b Faidon Liambotis
                self.client.send(rfb.to_u32(rfb.RFB_AUTH_ERROR))
476 138d0e8b Faidon Liambotis
                raise gevent.GreenletExit
477 39840bd3 Vangelis Koukis
478 138d0e8b Faidon Liambotis
        # Generate the challenge
479 138d0e8b Faidon Liambotis
        challenge = os.urandom(16)
480 138d0e8b Faidon Liambotis
        self.client.send(challenge)
481 138d0e8b Faidon Liambotis
        response = self.client.recv(1024)
482 138d0e8b Faidon Liambotis
        if len(response) != 16:
483 c87d99e9 Vangelis Koukis
            self.error("Wrong response length %d, should be 16", len(response))
484 138d0e8b Faidon Liambotis
            raise gevent.GreenletExit
485 138d0e8b Faidon Liambotis
486 5a196d84 Vangelis Koukis
        if rfb.check_password(challenge, response, self.password):
487 c87d99e9 Vangelis Koukis
            self.debug("Authentication successful")
488 138d0e8b Faidon Liambotis
        else:
489 138d0e8b Faidon Liambotis
            self.warn("Authentication failed")
490 138d0e8b Faidon Liambotis
            self.client.send(rfb.to_u32(rfb.RFB_AUTH_ERROR))
491 138d0e8b Faidon Liambotis
            raise gevent.GreenletExit
492 138d0e8b Faidon Liambotis
493 138d0e8b Faidon Liambotis
        # Accept the authentication
494 138d0e8b Faidon Liambotis
        self.client.send(rfb.to_u32(rfb.RFB_AUTH_SUCCESS))
495 39840bd3 Vangelis Koukis
496 310ae019 Stratos Psomadakis
    def _proxy(self):
497 138d0e8b Faidon Liambotis
        try:
498 c87d99e9 Vangelis Koukis
            self.info("Waiting for a client to connect at %s",
499 c87d99e9 Vangelis Koukis
                      ", ".join(["%s:%d" % s.getsockname()[:2]
500 c87d99e9 Vangelis Koukis
                                 for s in self.listeners]))
501 dd62f34b Stratos Psomadakis
            rlist, _, _ = select(self.listeners, [], [],
502 dd62f34b Stratos Psomadakis
                          timeout=VncAuthProxy.connect_timeout)
503 138d0e8b Faidon Liambotis
            if not rlist:
504 c87d99e9 Vangelis Koukis
                self.info("Timed out, no connection after %d sec",
505 dd62f34b Stratos Psomadakis
                          VncAuthProxy.connect_timeout)
506 138d0e8b Faidon Liambotis
                raise gevent.GreenletExit
507 138d0e8b Faidon Liambotis
508 138d0e8b Faidon Liambotis
            for sock in rlist:
509 138d0e8b Faidon Liambotis
                self.client, addrinfo = sock.accept()
510 d5705e2c Vangelis Koukis
                self.info("Connection from %s:%d", *addrinfo[:2])
511 138d0e8b Faidon Liambotis
512 31965126 Vangelis Koukis
                # Close all listening sockets, we only want a one-shot
513 31965126 Vangelis Koukis
                # connection from a single client.
514 138d0e8b Faidon Liambotis
                while self.listeners:
515 0b74ef50 Stratos Psomadakis
                    sock = self.listeners.pop().close()
516 138d0e8b Faidon Liambotis
                break
517 39840bd3 Vangelis Koukis
518 512c571e Stratos Psomadakis
            # Perform RFB handshake with the client.
519 512c571e Stratos Psomadakis
            self._client_handshake()
520 138d0e8b Faidon Liambotis
521 138d0e8b Faidon Liambotis
            # Bridge both connections through two "forwarder" greenlets.
522 020f4a9e Vangelis Koukis
            # This greenlet will wait until any of the workers dies.
523 020f4a9e Vangelis Koukis
            # Final cleanup will take place in _cleanup().
524 020f4a9e Vangelis Koukis
            dead = gevent.event.Event()
525 020f4a9e Vangelis Koukis
            dead.clear()
526 020f4a9e Vangelis Koukis
527 020f4a9e Vangelis Koukis
            # This callback will get called if any of the two workers dies.
528 020f4a9e Vangelis Koukis
            def callback(g):
529 020f4a9e Vangelis Koukis
                self.debug("Worker %d/%d died", self.workers.index(g),
530 020f4a9e Vangelis Koukis
                           len(self.workers))
531 020f4a9e Vangelis Koukis
                dead.set()
532 020f4a9e Vangelis Koukis
533 020f4a9e Vangelis Koukis
            self.workers.append(gevent.spawn(self._forward,
534 020f4a9e Vangelis Koukis
                                             self.client, self.server))
535 020f4a9e Vangelis Koukis
            self.workers.append(gevent.spawn(self._forward,
536 020f4a9e Vangelis Koukis
                                             self.server, self.client))
537 020f4a9e Vangelis Koukis
            for g in self.workers:
538 020f4a9e Vangelis Koukis
                g.link(callback)
539 020f4a9e Vangelis Koukis
540 020f4a9e Vangelis Koukis
            # Wait until any of the workers dies
541 020f4a9e Vangelis Koukis
            self.debug("Waiting for any of %d workers to die",
542 020f4a9e Vangelis Koukis
                       len(self.workers))
543 020f4a9e Vangelis Koukis
            dead.wait()
544 020f4a9e Vangelis Koukis
545 020f4a9e Vangelis Koukis
            # We can go now, _cleanup() will take care of
546 020f4a9e Vangelis Koukis
            # all worker, socket and port cleanup
547 020f4a9e Vangelis Koukis
            self.debug("A forwarder died, our work here is done")
548 138d0e8b Faidon Liambotis
            raise gevent.GreenletExit
549 4331e4d8 Stratos Psomadakis
        except Exception as err:
550 138d0e8b Faidon Liambotis
            # Any unhandled exception in the previous block
551 138d0e8b Faidon Liambotis
            # is an error and must be logged accordingly
552 138d0e8b Faidon Liambotis
            if not isinstance(e, gevent.GreenletExit):
553 4331e4d8 Stratos Psomadakis
                self.exception(err)
554 4331e4d8 Stratos Psomadakis
                self.error("Unexpected error")
555 4331e4d8 Stratos Psomadakis
            raise err
556 138d0e8b Faidon Liambotis
        finally:
557 138d0e8b Faidon Liambotis
            self._cleanup()
558 138d0e8b Faidon Liambotis
559 310ae019 Stratos Psomadakis
    def _run(self):
560 dd62f34b Stratos Psomadakis
        self._establish_connection()
561 dd62f34b Stratos Psomadakis
        self._proxy()
562 310ae019 Stratos Psomadakis
563 c87d99e9 Vangelis Koukis
# Logging support inside VncAuthproxy
564 c87d99e9 Vangelis Koukis
# Wrap all common logging functions in logging-specific methods
565 c87d99e9 Vangelis Koukis
for funcname in ["info", "debug", "warn", "error", "critical",
566 c87d99e9 Vangelis Koukis
                 "exception"]:
567 86d1202e Stratos Psomadakis
568 c87d99e9 Vangelis Koukis
    def gen(funcname):
569 c87d99e9 Vangelis Koukis
        def wrapped_log_func(self, *args, **kwargs):
570 c87d99e9 Vangelis Koukis
            func = getattr(self.log, funcname)
571 c87d99e9 Vangelis Koukis
            func("[C%d] %s" % (self.id, args[0]), *args[1:], **kwargs)
572 c87d99e9 Vangelis Koukis
        return wrapped_log_func
573 c87d99e9 Vangelis Koukis
    setattr(VncAuthProxy, funcname, gen(funcname))
574 c87d99e9 Vangelis Koukis
575 138d0e8b Faidon Liambotis
576 138d0e8b Faidon Liambotis
def fatal_signal_handler(signame):
577 c87d99e9 Vangelis Koukis
    logger.info("Caught %s, will raise SystemExit", signame)
578 138d0e8b Faidon Liambotis
    raise SystemExit
579 138d0e8b Faidon Liambotis
580 31965126 Vangelis Koukis
581 4331e4d8 Stratos Psomadakis
def get_listening_sockets(sport, saddr=None, reuse_addr=False):
582 138d0e8b Faidon Liambotis
    sockets = []
583 138d0e8b Faidon Liambotis
584 138d0e8b Faidon Liambotis
    # Use two sockets, one for IPv4, one for IPv6. IPv4-to-IPv6 mapped
585 138d0e8b Faidon Liambotis
    # addresses do not work reliably everywhere (under linux it may have
586 138d0e8b Faidon Liambotis
    # been disabled in /proc/sys/net/ipv6/bind_ipv6_only).
587 310ae019 Stratos Psomadakis
    for res in socket.getaddrinfo(saddr, sport, socket.AF_UNSPEC,
588 138d0e8b Faidon Liambotis
                                  socket.SOCK_STREAM, 0, socket.AI_PASSIVE):
589 138d0e8b Faidon Liambotis
        af, socktype, proto, canonname, sa = res
590 138d0e8b Faidon Liambotis
        try:
591 138d0e8b Faidon Liambotis
            s = None
592 138d0e8b Faidon Liambotis
            s = socket.socket(af, socktype, proto)
593 7af890c9 Stratos Psomadakis
594 138d0e8b Faidon Liambotis
            if af == socket.AF_INET6:
595 138d0e8b Faidon Liambotis
                # Bind v6 only when AF_INET6, otherwise either v4 or v6 bind
596 138d0e8b Faidon Liambotis
                # will fail.
597 138d0e8b Faidon Liambotis
                s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
598 7af890c9 Stratos Psomadakis
599 7af890c9 Stratos Psomadakis
            if reuse_addr:
600 7af890c9 Stratos Psomadakis
                s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
601 7af890c9 Stratos Psomadakis
602 138d0e8b Faidon Liambotis
            s.bind(sa)
603 138d0e8b Faidon Liambotis
            s.listen(1)
604 138d0e8b Faidon Liambotis
            sockets.append(s)
605 d5705e2c Vangelis Koukis
            logger.debug("Listening on %s:%d", *sa[:2])
606 4331e4d8 Stratos Psomadakis
        except socket.error as err:
607 4331e4d8 Stratos Psomadakis
            logger.error("Error binding to %s:%d: %s", sa[0], sa[1], err[1])
608 138d0e8b Faidon Liambotis
            if s:
609 138d0e8b Faidon Liambotis
                s.close()
610 138d0e8b Faidon Liambotis
            while sockets:
611 0b74ef50 Stratos Psomadakis
                sock = sockets.pop().close()
612 39840bd3 Vangelis Koukis
613 138d0e8b Faidon Liambotis
            # Make sure we fail immediately if we cannot get a socket
614 4331e4d8 Stratos Psomadakis
            raise InernalError(err)
615 39840bd3 Vangelis Koukis
616 138d0e8b Faidon Liambotis
    return sockets
617 138d0e8b Faidon Liambotis
618 31965126 Vangelis Koukis
619 d49bd2fb Stratos Psomadakis
def parse_auth_file(auth_file):
620 d49bd2fb Stratos Psomadakis
    supported_ciphers = ('cleartext', 'HA1')
621 d49bd2fb Stratos Psomadakis
622 4a1dd7af Stratos Psomadakis
    users = {}
623 4331e4d8 Stratos Psomadakis
    try:
624 4331e4d8 Stratos Psomadakis
        with open(auth_file) as f:
625 4331e4d8 Stratos Psomadakis
            lines = [l.strip().split() for l in f.readlines()]
626 d49bd2fb Stratos Psomadakis
627 4331e4d8 Stratos Psomadakis
            for line in lines:
628 4331e4d8 Stratos Psomadakis
                if not line or line[0][0] == '#':
629 4331e4d8 Stratos Psomadakis
                    continue
630 d49bd2fb Stratos Psomadakis
631 4331e4d8 Stratos Psomadakis
                if len(line) != 2:
632 4331e4d8 Stratos Psomadakis
                    raise InternalError("Invaild user entry in auth file")
633 d49bd2fb Stratos Psomadakis
634 4331e4d8 Stratos Psomadakis
                user = line[0]
635 4331e4d8 Stratos Psomadakis
                password = line[1]
636 d49bd2fb Stratos Psomadakis
637 4331e4d8 Stratos Psomadakis
                split_password = ('{cleartext}', password)
638 4331e4d8 Stratos Psomadakis
                if password[0] == '{':
639 4331e4d8 Stratos Psomadakis
                    split_password = password[1:].split('}')
640 4331e4d8 Stratos Psomadakis
                    if len(split_password) != 2 or not split_password[1] \
641 4331e4d8 Stratos Psomadakis
                            or split_password[0] not in supported_ciphers:
642 4331e4d8 Stratos Psomadakis
                        raise InternalError("Invalid password format "
643 4331e4d8 Stratos Psomadakis
                                            "in auth file")
644 d49bd2fb Stratos Psomadakis
645 4331e4d8 Stratos Psomadakis
                if user in users:
646 4331e4d8 Stratos Psomadakis
                    raise InternalError("Duplicate user entry in auth file")
647 d49bd2fb Stratos Psomadakis
648 4331e4d8 Stratos Psomadakis
                users[user] = split_password
649 4331e4d8 Stratos Psomadakis
    except IOError as err:
650 4331e4d8 Stratos Psomadakis
        logger.error("Couldn't read auth file")
651 4331e4d8 Stratos Psomadakis
        raise InternalError(err)
652 d49bd2fb Stratos Psomadakis
653 4a1dd7af Stratos Psomadakis
    if not users:
654 28a2d809 Stratos Psomadakis
        logger.warn("No users specified.")
655 4a1dd7af Stratos Psomadakis
656 d49bd2fb Stratos Psomadakis
    return users
657 d49bd2fb Stratos Psomadakis
658 d49bd2fb Stratos Psomadakis
659 138d0e8b Faidon Liambotis
def parse_arguments(args):
660 138d0e8b Faidon Liambotis
    from optparse import OptionParser
661 138d0e8b Faidon Liambotis
662 138d0e8b Faidon Liambotis
    parser = OptionParser()
663 138d0e8b Faidon Liambotis
    parser.add_option("-d", "--debug", action="store_true", dest="debug",
664 138d0e8b Faidon Liambotis
                      help="Enable debugging information")
665 310ae019 Stratos Psomadakis
    parser.add_option("--log", dest="log_file",
666 138d0e8b Faidon Liambotis
                      default=DEFAULT_LOG_FILE,
667 138d0e8b Faidon Liambotis
                      metavar="FILE",
668 310ae019 Stratos Psomadakis
                      help=("Write log to FILE (default: %s)" %
669 31965126 Vangelis Koukis
                            DEFAULT_LOG_FILE))
670 138d0e8b Faidon Liambotis
    parser.add_option('--pid-file', dest="pid_file",
671 138d0e8b Faidon Liambotis
                      default=DEFAULT_PID_FILE,
672 138d0e8b Faidon Liambotis
                      metavar='PIDFILE',
673 31965126 Vangelis Koukis
                      help=("Save PID to file (default: %s)" %
674 31965126 Vangelis Koukis
                            DEFAULT_PID_FILE))
675 310ae019 Stratos Psomadakis
    parser.add_option("--listen-address", dest="listen_address",
676 310ae019 Stratos Psomadakis
                      default=DEFAULT_LISTEN_ADDRESS,
677 310ae019 Stratos Psomadakis
                      metavar="LISTEN_ADDRESS",
678 310ae019 Stratos Psomadakis
                      help=("Address to listen for control connections"
679 310ae019 Stratos Psomadakis
                            "(default: *)"))
680 310ae019 Stratos Psomadakis
    parser.add_option("--listen-port", dest="listen_port",
681 310ae019 Stratos Psomadakis
                      default=DEFAULT_LISTEN_PORT,
682 310ae019 Stratos Psomadakis
                      metavar="LISTEN_PORT",
683 310ae019 Stratos Psomadakis
                      help=("Port to listen for control connections"
684 310ae019 Stratos Psomadakis
                            "(default: %d)" % DEFAULT_LISTEN_PORT))
685 310ae019 Stratos Psomadakis
    parser.add_option("--server-timeout", dest="server_timeout",
686 310ae019 Stratos Psomadakis
                      default=DEFAULT_SERVER_TIMEOUT, type="float",
687 310ae019 Stratos Psomadakis
                      metavar="N",
688 310ae019 Stratos Psomadakis
                      help=("Wait for N seconds for the VNC server RFB "
689 310ae019 Stratos Psomadakis
                            "handshake (default %s)" % DEFAULT_SERVER_TIMEOUT))
690 310ae019 Stratos Psomadakis
    parser.add_option("--connect-retries", dest="connect_retries",
691 7eb27319 Stratos Psomadakis
                      default=DEFAULT_CONNECT_RETRIES, type="int",
692 310ae019 Stratos Psomadakis
                      metavar="N",
693 310ae019 Stratos Psomadakis
                      help=("Retry N times to connect to the "
694 310ae019 Stratos Psomadakis
                            "server (default: %d)" %
695 310ae019 Stratos Psomadakis
                            DEFAULT_CONNECT_RETRIES))
696 310ae019 Stratos Psomadakis
    parser.add_option("--retry-wait", dest="retry_wait",
697 31965126 Vangelis Koukis
                      default=DEFAULT_RETRY_WAIT, type="float",
698 310ae019 Stratos Psomadakis
                      metavar="N",
699 310ae019 Stratos Psomadakis
                      help=("Wait N seconds before retrying "
700 310ae019 Stratos Psomadakis
                            "to connect to the server (default: %s)" %
701 310ae019 Stratos Psomadakis
                            DEFAULT_RETRY_WAIT))
702 310ae019 Stratos Psomadakis
    parser.add_option("--connect-timeout", dest="connect_timeout",
703 310ae019 Stratos Psomadakis
                      default=DEFAULT_CONNECT_TIMEOUT, type="int",
704 310ae019 Stratos Psomadakis
                      metavar="N",
705 310ae019 Stratos Psomadakis
                      help=("Wait N seconds for a client "
706 310ae019 Stratos Psomadakis
                            "to connect (default: %d)"
707 310ae019 Stratos Psomadakis
                            % DEFAULT_CONNECT_TIMEOUT))
708 138d0e8b Faidon Liambotis
    parser.add_option("-p", "--min-port", dest="min_port",
709 138d0e8b Faidon Liambotis
                      default=DEFAULT_MIN_PORT, type="int", metavar="MIN_PORT",
710 31965126 Vangelis Koukis
                      help=("The minimum port number to use for automatically-"
711 310ae019 Stratos Psomadakis
                            "allocated ephemeral ports (default: %s)" %
712 310ae019 Stratos Psomadakis
                            DEFAULT_MIN_PORT))
713 138d0e8b Faidon Liambotis
    parser.add_option("-P", "--max-port", dest="max_port",
714 138d0e8b Faidon Liambotis
                      default=DEFAULT_MAX_PORT, type="int", metavar="MAX_PORT",
715 31965126 Vangelis Koukis
                      help=("The maximum port number to use for automatically-"
716 310ae019 Stratos Psomadakis
                            "allocated ephemeral ports (default: %s)" %
717 310ae019 Stratos Psomadakis
                            DEFAULT_MAX_PORT))
718 d49bd2fb Stratos Psomadakis
    parser.add_option('--no-ssl', dest="no_ssl",
719 d49bd2fb Stratos Psomadakis
                      default=False, action='store_true',
720 d49bd2fb Stratos Psomadakis
                      help=("Disable SSL/TLS for control connections "
721 d49bd2fb Stratos Psomadakis
                            "(default: False"))
722 0b74ef50 Stratos Psomadakis
    parser.add_option('--cert-file', dest="cert_file",
723 0b74ef50 Stratos Psomadakis
                      default=DEFAULT_CERT_FILE,
724 0b74ef50 Stratos Psomadakis
                      metavar='CERTFILE',
725 0b74ef50 Stratos Psomadakis
                      help=("SSL certificate (default: %s)" %
726 0b74ef50 Stratos Psomadakis
                            DEFAULT_CERT_FILE))
727 0b74ef50 Stratos Psomadakis
    parser.add_option('--key-file', dest="key_file",
728 0b74ef50 Stratos Psomadakis
                      default=DEFAULT_KEY_FILE,
729 0b74ef50 Stratos Psomadakis
                      metavar='KEYFILE',
730 0b74ef50 Stratos Psomadakis
                      help=("SSL key (default: %s)" %
731 0b74ef50 Stratos Psomadakis
                            DEFAULT_KEY_FILE))
732 d49bd2fb Stratos Psomadakis
    parser.add_option('--auth-file', dest="auth_file",
733 d49bd2fb Stratos Psomadakis
                      default=DEFAULT_AUTH_FILE,
734 d49bd2fb Stratos Psomadakis
                      metavar='AUTHFILE',
735 d49bd2fb Stratos Psomadakis
                      help=("Authentication file (default: %s)" %
736 d49bd2fb Stratos Psomadakis
                            DEFAULT_AUTH_FILE))
737 310ae019 Stratos Psomadakis
738 310ae019 Stratos Psomadakis
    (opts, args) = parser.parse_args(args)
739 310ae019 Stratos Psomadakis
740 310ae019 Stratos Psomadakis
    if args:
741 310ae019 Stratos Psomadakis
        parser.print_help()
742 310ae019 Stratos Psomadakis
        sys.exit(1)
743 86d1202e Stratos Psomadakis
744 dd62f34b Stratos Psomadakis
    return opts
745 dd62f34b Stratos Psomadakis
746 86d1202e Stratos Psomadakis
747 138d0e8b Faidon Liambotis
def main():
748 d5705e2c Vangelis Koukis
    """Run the daemon from the command line"""
749 138d0e8b Faidon Liambotis
750 dd62f34b Stratos Psomadakis
    opts = parse_arguments(sys.argv[1:])
751 138d0e8b Faidon Liambotis
752 138d0e8b Faidon Liambotis
    # Create pidfile
753 180a750f Vangelis Koukis
    pidf = pidlockfile.TimeoutPIDLockFile(opts.pid_file, 10)
754 39840bd3 Vangelis Koukis
755 138d0e8b Faidon Liambotis
    # Initialize logger
756 138d0e8b Faidon Liambotis
    lvl = logging.DEBUG if opts.debug else logging.INFO
757 88420a63 Faidon Liambotis
758 88420a63 Faidon Liambotis
    global logger
759 138d0e8b Faidon Liambotis
    logger = logging.getLogger("vncauthproxy")
760 138d0e8b Faidon Liambotis
    logger.setLevel(lvl)
761 31965126 Vangelis Koukis
    formatter = logging.Formatter(("%(asctime)s %(module)s[%(process)d] "
762 31965126 Vangelis Koukis
                                   " %(levelname)s: %(message)s"),
763 31965126 Vangelis Koukis
                                  "%Y-%m-%d %H:%M:%S")
764 138d0e8b Faidon Liambotis
    handler = logging.FileHandler(opts.log_file)
765 138d0e8b Faidon Liambotis
    handler.setFormatter(formatter)
766 138d0e8b Faidon Liambotis
    logger.addHandler(handler)
767 138d0e8b Faidon Liambotis
768 138d0e8b Faidon Liambotis
    # Become a daemon:
769 138d0e8b Faidon Liambotis
    # Redirect stdout and stderr to handler.stream to catch
770 138d0e8b Faidon Liambotis
    # early errors in the daemonization process [e.g., pidfile creation]
771 138d0e8b Faidon Liambotis
    # which will otherwise go to /dev/null.
772 376a8634 Vangelis Koukis
    daemon_context = AllFilesDaemonContext(
773 138d0e8b Faidon Liambotis
        pidfile=pidf,
774 138d0e8b Faidon Liambotis
        umask=0022,
775 138d0e8b Faidon Liambotis
        stdout=handler.stream,
776 138d0e8b Faidon Liambotis
        stderr=handler.stream,
777 138d0e8b Faidon Liambotis
        files_preserve=[handler.stream])
778 39840bd3 Vangelis Koukis
779 39840bd3 Vangelis Koukis
    # Remove any stale PID files, left behind by previous invocations
780 39840bd3 Vangelis Koukis
    if daemon.runner.is_pidfile_stale(pidf):
781 75eed2cf Vangelis Koukis
        logger.warning("Removing stale PID lock file %s", pidf.path)
782 39840bd3 Vangelis Koukis
        pidf.break_lock()
783 39840bd3 Vangelis Koukis
784 39840bd3 Vangelis Koukis
    try:
785 39840bd3 Vangelis Koukis
        daemon_context.open()
786 180a750f Vangelis Koukis
    except (AlreadyLocked, LockTimeout):
787 31965126 Vangelis Koukis
        logger.critical(("Failed to lock PID file %s, another instance "
788 31965126 Vangelis Koukis
                         "running?"), pidf.path)
789 39840bd3 Vangelis Koukis
        sys.exit(1)
790 138d0e8b Faidon Liambotis
    logger.info("Became a daemon")
791 138d0e8b Faidon Liambotis
792 138d0e8b Faidon Liambotis
    # A fork() has occured while daemonizing,
793 138d0e8b Faidon Liambotis
    # we *must* reinit gevent
794 138d0e8b Faidon Liambotis
    gevent.reinit()
795 138d0e8b Faidon Liambotis
796 138d0e8b Faidon Liambotis
    # Catch signals to ensure graceful shutdown,
797 138d0e8b Faidon Liambotis
    #
798 138d0e8b Faidon Liambotis
    # Uses gevent.signal so the handler fires even during
799 138d0e8b Faidon Liambotis
    # gevent.socket.accept()
800 138d0e8b Faidon Liambotis
    gevent.signal(SIGINT, fatal_signal_handler, "SIGINT")
801 138d0e8b Faidon Liambotis
    gevent.signal(SIGTERM, fatal_signal_handler, "SIGTERM")
802 138d0e8b Faidon Liambotis
803 138d0e8b Faidon Liambotis
    # Init ephemeral port pool
804 39840bd3 Vangelis Koukis
    ports = range(opts.min_port, opts.max_port + 1)
805 138d0e8b Faidon Liambotis
806 310ae019 Stratos Psomadakis
    # Init VncAuthProxy class attributes
807 310ae019 Stratos Psomadakis
    VncAuthProxy.server_timeout = opts.server_timeout
808 310ae019 Stratos Psomadakis
    VncAuthProxy.connect_retries = opts.connect_retries
809 310ae019 Stratos Psomadakis
    VncAuthProxy.retry_wait = opts.retry_wait
810 310ae019 Stratos Psomadakis
    VncAuthProxy.connect_timeout = opts.connect_timeout
811 dd62f34b Stratos Psomadakis
    VncAuthProxy.ports = ports
812 310ae019 Stratos Psomadakis
813 310ae019 Stratos Psomadakis
    try:
814 d49bd2fb Stratos Psomadakis
        VncAuthProxy.authdb = parse_auth_file(opts.auth_file)
815 4331e4d8 Stratos Psomadakis
    except InternalError as err:
816 4331e4d8 Stratos Psomadakis
        logger.critical(err)
817 4331e4d8 Stratos Psomadakis
        sys.exit(1)
818 4331e4d8 Stratos Psomadakis
    except Exception as err:
819 d49bd2fb Stratos Psomadakis
        logger.exception(err)
820 4331e4d8 Stratos Psomadakis
        logger.error("Unexpected error")
821 4331e4d8 Stratos Psomadakis
        sys.exit(1)
822 4331e4d8 Stratos Psomadakis
823 4331e4d8 Stratos Psomadakis
    try:
824 4331e4d8 Stratos Psomadakis
        sockets = get_listening_sockets(opts.listen_port, opts.listen_address,
825 4331e4d8 Stratos Psomadakis
                                        reuse_addr=True)
826 4331e4d8 Stratos Psomadakis
    except InternalError as err:
827 310ae019 Stratos Psomadakis
        logger.critical("Error binding control socket")
828 310ae019 Stratos Psomadakis
        sys.exit(1)
829 d49bd2fb Stratos Psomadakis
    except Exception as err:
830 d49bd2fb Stratos Psomadakis
        logger.exception(err)
831 4331e4d8 Stratos Psomadakis
        logger.error("Unexpected error")
832 d49bd2fb Stratos Psomadakis
        sys.exit(1)
833 310ae019 Stratos Psomadakis
834 138d0e8b Faidon Liambotis
    while True:
835 138d0e8b Faidon Liambotis
        try:
836 0b74ef50 Stratos Psomadakis
            client = None
837 b129b0c0 Stratos Psomadakis
            rlist, _, _ = select(sockets, [], [])
838 b129b0c0 Stratos Psomadakis
            for ctrl in rlist:
839 d49bd2fb Stratos Psomadakis
                client, _ = ctrl.accept()
840 28a2d809 Stratos Psomadakis
                if not opts.no_ssl:
841 d49bd2fb Stratos Psomadakis
                    client = ssl.wrap_socket(client,
842 d49bd2fb Stratos Psomadakis
                                             server_side=True,
843 d49bd2fb Stratos Psomadakis
                                             keyfile=opts.key_file,
844 d49bd2fb Stratos Psomadakis
                                             certfile=opts.cert_file,
845 d49bd2fb Stratos Psomadakis
                                             ssl_version=ssl.PROTOCOL_TLSv1)
846 b129b0c0 Stratos Psomadakis
                logger.info("New control connection")
847 39840bd3 Vangelis Koukis
848 310ae019 Stratos Psomadakis
                VncAuthProxy.spawn(logger, client)
849 0b74ef50 Stratos Psomadakis
            continue
850 138d0e8b Faidon Liambotis
        except Exception, e:
851 138d0e8b Faidon Liambotis
            logger.exception(e)
852 4331e4d8 Stratos Psomadakis
            logger.error("Unexpected error")
853 0b74ef50 Stratos Psomadakis
            if client:
854 0b74ef50 Stratos Psomadakis
                client.close()
855 138d0e8b Faidon Liambotis
            continue
856 138d0e8b Faidon Liambotis
        except SystemExit:
857 138d0e8b Faidon Liambotis
            break
858 39840bd3 Vangelis Koukis
859 b129b0c0 Stratos Psomadakis
    logger.info("Closing control sockets")
860 b129b0c0 Stratos Psomadakis
    while sockets:
861 0b74ef50 Stratos Psomadakis
        sock = sockets.pop()
862 0b74ef50 Stratos Psomadakis
        sock.close()
863 0b74ef50 Stratos Psomadakis
864 138d0e8b Faidon Liambotis
    daemon_context.close()
865 138d0e8b Faidon Liambotis
    sys.exit(0)