Statistics
| Branch: | Tag: | Revision:

root / vncauthproxy / proxy.py @ 8d766971

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 8d766971 Stratos Psomadakis
import re
76 138d0e8b Faidon Liambotis
77 138d0e8b Faidon Liambotis
import rfb
78 39840bd3 Vangelis Koukis
79 138d0e8b Faidon Liambotis
try:
80 138d0e8b Faidon Liambotis
    import simplejson as json
81 138d0e8b Faidon Liambotis
except ImportError:
82 138d0e8b Faidon Liambotis
    import json
83 138d0e8b Faidon Liambotis
84 0b74ef50 Stratos Psomadakis
from gevent import socket, ssl
85 138d0e8b Faidon Liambotis
from signal import SIGINT, SIGTERM
86 138d0e8b Faidon Liambotis
from gevent.select import select
87 138d0e8b Faidon Liambotis
88 180a750f Vangelis Koukis
from lockfile import LockTimeout, AlreadyLocked
89 180a750f Vangelis Koukis
# Take care of differences between python-daemon versions.
90 180a750f Vangelis Koukis
try:
91 180a750f Vangelis Koukis
    from daemon import pidfile as pidlockfile
92 4331e4d8 Stratos Psomadakis
except ImportError:
93 180a750f Vangelis Koukis
    from daemon import pidlockfile
94 180a750f Vangelis Koukis
95 180a750f Vangelis Koukis
96 88420a63 Faidon Liambotis
logger = None
97 88420a63 Faidon Liambotis
98 31965126 Vangelis Koukis
99 4331e4d8 Stratos Psomadakis
class InternalError(Exception):
100 4331e4d8 Stratos Psomadakis
    """Exception for internal vncauthproxy errors"""
101 4331e4d8 Stratos Psomadakis
    pass
102 4331e4d8 Stratos Psomadakis
103 4331e4d8 Stratos Psomadakis
104 fe5fc466 Vangelis Koukis
# Currently, gevent uses libevent-dns for asynchronous DNS resolution,
105 376a8634 Vangelis Koukis
# which opens a socket upon initialization time. Since we can't get the fd
106 376a8634 Vangelis Koukis
# reliably, We have to maintain all file descriptors open (which won't harm
107 376a8634 Vangelis Koukis
# anyway)
108 376a8634 Vangelis Koukis
class AllFilesDaemonContext(daemon.DaemonContext):
109 376a8634 Vangelis Koukis
    """DaemonContext class keeping all file descriptors open"""
110 376a8634 Vangelis Koukis
    def _get_exclude_file_descriptors(self):
111 376a8634 Vangelis Koukis
        class All:
112 376a8634 Vangelis Koukis
            def __contains__(self, value):
113 376a8634 Vangelis Koukis
                return True
114 376a8634 Vangelis Koukis
        return All()
115 376a8634 Vangelis Koukis
116 376a8634 Vangelis Koukis
117 138d0e8b Faidon Liambotis
class VncAuthProxy(gevent.Greenlet):
118 138d0e8b Faidon Liambotis
    """
119 138d0e8b Faidon Liambotis
    Simple class implementing a VNC Forwarder with MITM authentication as a
120 138d0e8b Faidon Liambotis
    Greenlet
121 138d0e8b Faidon Liambotis

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

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

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

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

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

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

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

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

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

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

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

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

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