root / vncauthproxy / proxy.py @ 7eb27319
History | View | Annotate | Download (21 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 | 138d0e8b | Faidon Liambotis | # Copyright (c) 2010-2011 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 | 138d0e8b | Faidon Liambotis | DEFAULT_CTRL_SOCKET = "/var/run/vncauthproxy/ctrl.sock"
|
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 | 138d0e8b | Faidon Liambotis | DEFAULT_CONNECT_TIMEOUT = 30
|
27 | 7eb27319 | Stratos Psomadakis | DEFAULT_CONNECT_RETRIES = 3
|
28 | 7eb27319 | Stratos Psomadakis | DEFAULT_RETRY_WAIT = 0.1
|
29 | 138d0e8b | Faidon Liambotis | # Default values per http://www.iana.org/assignments/port-numbers
|
30 | 138d0e8b | Faidon Liambotis | DEFAULT_MIN_PORT = 49152
|
31 | 138d0e8b | Faidon Liambotis | DEFAULT_MAX_PORT = 65535
|
32 | 138d0e8b | Faidon Liambotis | |
33 | 138d0e8b | Faidon Liambotis | import os |
34 | 138d0e8b | Faidon Liambotis | import sys |
35 | 138d0e8b | Faidon Liambotis | import logging |
36 | 138d0e8b | Faidon Liambotis | import gevent |
37 | 138d0e8b | Faidon Liambotis | import daemon |
38 | 138d0e8b | Faidon Liambotis | import random |
39 | 138d0e8b | Faidon Liambotis | import daemon.pidlockfile |
40 | 138d0e8b | Faidon Liambotis | |
41 | 138d0e8b | Faidon Liambotis | import rfb |
42 | 138d0e8b | Faidon Liambotis | |
43 | 138d0e8b | Faidon Liambotis | try:
|
44 | 138d0e8b | Faidon Liambotis | import simplejson as json |
45 | 138d0e8b | Faidon Liambotis | except ImportError: |
46 | 138d0e8b | Faidon Liambotis | import json |
47 | 138d0e8b | Faidon Liambotis | |
48 | 138d0e8b | Faidon Liambotis | from gevent import socket |
49 | 138d0e8b | Faidon Liambotis | from signal import SIGINT, SIGTERM |
50 | 138d0e8b | Faidon Liambotis | from gevent import signal |
51 | 138d0e8b | Faidon Liambotis | from gevent.select import select |
52 | 512c571e | Stratos Psomadakis | from time import sleep |
53 | 138d0e8b | Faidon Liambotis | |
54 | 88420a63 | Faidon Liambotis | logger = None
|
55 | 88420a63 | Faidon Liambotis | |
56 | 376a8634 | Vangelis Koukis | # Currently, gevent uses libevent-dns for asynchornous DNS resolution,
|
57 | 376a8634 | Vangelis Koukis | # which opens a socket upon initialization time. Since we can't get the fd
|
58 | 376a8634 | Vangelis Koukis | # reliably, We have to maintain all file descriptors open (which won't harm
|
59 | 376a8634 | Vangelis Koukis | # anyway)
|
60 | 376a8634 | Vangelis Koukis | |
61 | 376a8634 | Vangelis Koukis | class AllFilesDaemonContext(daemon.DaemonContext): |
62 | 376a8634 | Vangelis Koukis | """DaemonContext class keeping all file descriptors open"""
|
63 | 376a8634 | Vangelis Koukis | def _get_exclude_file_descriptors(self): |
64 | 376a8634 | Vangelis Koukis | class All: |
65 | 376a8634 | Vangelis Koukis | def __contains__(self, value): |
66 | 376a8634 | Vangelis Koukis | return True |
67 | 376a8634 | Vangelis Koukis | return All()
|
68 | 376a8634 | Vangelis Koukis | |
69 | 376a8634 | Vangelis Koukis | |
70 | 138d0e8b | Faidon Liambotis | class VncAuthProxy(gevent.Greenlet): |
71 | 138d0e8b | Faidon Liambotis | """
|
72 | 138d0e8b | Faidon Liambotis | Simple class implementing a VNC Forwarder with MITM authentication as a
|
73 | 138d0e8b | Faidon Liambotis | Greenlet
|
74 | 138d0e8b | Faidon Liambotis |
|
75 | 138d0e8b | Faidon Liambotis | VncAuthProxy forwards VNC traffic from a specified port of the local host
|
76 | 138d0e8b | Faidon Liambotis | to a specified remote host:port. Furthermore, it implements VNC
|
77 | 138d0e8b | Faidon Liambotis | Authentication, intercepting the client/server handshake and asking the
|
78 | 138d0e8b | Faidon Liambotis | client for authentication even if the backend requires none.
|
79 | 138d0e8b | Faidon Liambotis |
|
80 | 138d0e8b | Faidon Liambotis | It is primarily intended for use in virtualization environments, as a VNC
|
81 | 138d0e8b | Faidon Liambotis | ``switch''.
|
82 | 138d0e8b | Faidon Liambotis |
|
83 | 138d0e8b | Faidon Liambotis | """
|
84 | 138d0e8b | Faidon Liambotis | id = 1
|
85 | 138d0e8b | Faidon Liambotis | |
86 | 512c571e | Stratos Psomadakis | def __init__(self, logger, listeners, pool, daddr, dport, server, password, connect_timeout): |
87 | 138d0e8b | Faidon Liambotis | """
|
88 | 138d0e8b | Faidon Liambotis | @type logger: logging.Logger
|
89 | 138d0e8b | Faidon Liambotis | @param logger: the logger to use
|
90 | 138d0e8b | Faidon Liambotis | @type listeners: list
|
91 | 138d0e8b | Faidon Liambotis | @param listeners: list of listening sockets to use for client connections
|
92 | 138d0e8b | Faidon Liambotis | @type pool: list
|
93 | 138d0e8b | Faidon Liambotis | @param pool: if not None, return the client port number into this port pool
|
94 | 138d0e8b | Faidon Liambotis | @type daddr: str
|
95 | 138d0e8b | Faidon Liambotis | @param daddr: destination address (IPv4, IPv6 or hostname)
|
96 | 138d0e8b | Faidon Liambotis | @type dport: int
|
97 | 138d0e8b | Faidon Liambotis | @param dport: destination port
|
98 | 512c571e | Stratos Psomadakis | @type server: socket
|
99 | 512c571e | Stratos Psomadakis | @param server: VNC server socket
|
100 | 138d0e8b | Faidon Liambotis | @type password: str
|
101 | 138d0e8b | Faidon Liambotis | @param password: password to request from the client
|
102 | 138d0e8b | Faidon Liambotis | @type connect_timeout: int
|
103 | 138d0e8b | Faidon Liambotis | @param connect_timeout: how long to wait for client connections
|
104 | 138d0e8b | Faidon Liambotis | (seconds)
|
105 | 138d0e8b | Faidon Liambotis |
|
106 | 138d0e8b | Faidon Liambotis | """
|
107 | 138d0e8b | Faidon Liambotis | gevent.Greenlet.__init__(self)
|
108 | 138d0e8b | Faidon Liambotis | self.id = VncAuthProxy.id
|
109 | 138d0e8b | Faidon Liambotis | VncAuthProxy.id += 1
|
110 | 138d0e8b | Faidon Liambotis | self.log = logger
|
111 | 138d0e8b | Faidon Liambotis | self.listeners = listeners
|
112 | 138d0e8b | Faidon Liambotis | # All listening sockets are assumed to be on the same port
|
113 | 138d0e8b | Faidon Liambotis | self.sport = listeners[0].getsockname()[1] |
114 | 138d0e8b | Faidon Liambotis | self.pool = pool
|
115 | 138d0e8b | Faidon Liambotis | self.daddr = daddr
|
116 | 138d0e8b | Faidon Liambotis | self.dport = dport
|
117 | 512c571e | Stratos Psomadakis | self.server = server
|
118 | 138d0e8b | Faidon Liambotis | self.password = password
|
119 | 138d0e8b | Faidon Liambotis | self.client = None |
120 | 138d0e8b | Faidon Liambotis | self.timeout = connect_timeout
|
121 | 138d0e8b | Faidon Liambotis | |
122 | 138d0e8b | Faidon Liambotis | def _cleanup(self): |
123 | 138d0e8b | Faidon Liambotis | """Close all active sockets and exit gracefully"""
|
124 | 138d0e8b | Faidon Liambotis | # Reintroduce the port number of the client socket in
|
125 | 138d0e8b | Faidon Liambotis | # the port pool, if applicable.
|
126 | 138d0e8b | Faidon Liambotis | if not self.pool is None: |
127 | 138d0e8b | Faidon Liambotis | self.pool.append(self.sport) |
128 | 138d0e8b | Faidon Liambotis | self.log.debug("Returned port %d to port pool, contains %d ports", |
129 | 138d0e8b | Faidon Liambotis | self.sport, len(self.pool)) |
130 | 138d0e8b | Faidon Liambotis | |
131 | 138d0e8b | Faidon Liambotis | while self.listeners: |
132 | 138d0e8b | Faidon Liambotis | self.listeners.pop().close()
|
133 | 138d0e8b | Faidon Liambotis | if self.server: |
134 | 138d0e8b | Faidon Liambotis | self.server.close()
|
135 | 138d0e8b | Faidon Liambotis | if self.client: |
136 | 138d0e8b | Faidon Liambotis | self.client.close()
|
137 | 138d0e8b | Faidon Liambotis | |
138 | 138d0e8b | Faidon Liambotis | raise gevent.GreenletExit
|
139 | 138d0e8b | Faidon Liambotis | |
140 | 138d0e8b | Faidon Liambotis | def info(self, msg): |
141 | 138d0e8b | Faidon Liambotis | self.log.info("[C%d] %s" % (self.id, msg)) |
142 | 138d0e8b | Faidon Liambotis | |
143 | 138d0e8b | Faidon Liambotis | def debug(self, msg): |
144 | 138d0e8b | Faidon Liambotis | self.log.debug("[C%d] %s" % (self.id, msg)) |
145 | 138d0e8b | Faidon Liambotis | |
146 | 138d0e8b | Faidon Liambotis | def warn(self, msg): |
147 | 138d0e8b | Faidon Liambotis | self.log.warn("[C%d] %s" % (self.id, msg)) |
148 | 138d0e8b | Faidon Liambotis | |
149 | 138d0e8b | Faidon Liambotis | def error(self, msg): |
150 | 138d0e8b | Faidon Liambotis | self.log.error("[C%d] %s" % (self.id, msg)) |
151 | 138d0e8b | Faidon Liambotis | |
152 | 138d0e8b | Faidon Liambotis | def critical(self, msg): |
153 | 138d0e8b | Faidon Liambotis | self.log.critical("[C%d] %s" % (self.id, msg)) |
154 | 138d0e8b | Faidon Liambotis | |
155 | 138d0e8b | Faidon Liambotis | def __str__(self): |
156 | 138d0e8b | Faidon Liambotis | return "VncAuthProxy: %d -> %s:%d" % (self.sport, self.daddr, self.dport) |
157 | 138d0e8b | Faidon Liambotis | |
158 | 138d0e8b | Faidon Liambotis | def _forward(self, source, dest): |
159 | 138d0e8b | Faidon Liambotis | """
|
160 | 138d0e8b | Faidon Liambotis | Forward traffic from source to dest
|
161 | 138d0e8b | Faidon Liambotis |
|
162 | 138d0e8b | Faidon Liambotis | @type source: socket
|
163 | 138d0e8b | Faidon Liambotis | @param source: source socket
|
164 | 138d0e8b | Faidon Liambotis | @type dest: socket
|
165 | 138d0e8b | Faidon Liambotis | @param dest: destination socket
|
166 | 138d0e8b | Faidon Liambotis |
|
167 | 138d0e8b | Faidon Liambotis | """
|
168 | 138d0e8b | Faidon Liambotis | |
169 | 138d0e8b | Faidon Liambotis | while True: |
170 | 138d0e8b | Faidon Liambotis | d = source.recv(16384)
|
171 | 138d0e8b | Faidon Liambotis | if d == '': |
172 | 138d0e8b | Faidon Liambotis | if source == self.client: |
173 | 138d0e8b | Faidon Liambotis | self.info("Client connection closed") |
174 | 138d0e8b | Faidon Liambotis | else:
|
175 | 138d0e8b | Faidon Liambotis | self.info("Server connection closed") |
176 | 138d0e8b | Faidon Liambotis | break
|
177 | 138d0e8b | Faidon Liambotis | dest.sendall(d) |
178 | 138d0e8b | Faidon Liambotis | # No need to close the source and dest sockets here.
|
179 | 138d0e8b | Faidon Liambotis | # They are owned by and will be closed by the original greenlet.
|
180 | 138d0e8b | Faidon Liambotis | |
181 | 512c571e | Stratos Psomadakis | def _client_handshake(self): |
182 | 138d0e8b | Faidon Liambotis | """
|
183 | 138d0e8b | Faidon Liambotis | Perform handshake/authentication with a connecting client
|
184 | 138d0e8b | Faidon Liambotis |
|
185 | 138d0e8b | Faidon Liambotis | Outline:
|
186 | 138d0e8b | Faidon Liambotis | 1. Client connects
|
187 | 138d0e8b | Faidon Liambotis | 2. We fake RFB 3.8 protocol and require VNC authentication [also supports RFB 3.3]
|
188 | 138d0e8b | Faidon Liambotis | 3. Client accepts authentication method
|
189 | 138d0e8b | Faidon Liambotis | 4. We send an authentication challenge
|
190 | 138d0e8b | Faidon Liambotis | 5. Client sends the authentication response
|
191 | 138d0e8b | Faidon Liambotis | 6. We check the authentication
|
192 | 138d0e8b | Faidon Liambotis |
|
193 | 512c571e | Stratos Psomadakis | Upon return, self.client socket is connected to the client.
|
194 | 138d0e8b | Faidon Liambotis |
|
195 | 138d0e8b | Faidon Liambotis | """
|
196 | 138d0e8b | Faidon Liambotis | self.client.send(rfb.RFB_VERSION_3_8 + "\n") |
197 | 138d0e8b | Faidon Liambotis | client_version_str = self.client.recv(1024) |
198 | 138d0e8b | Faidon Liambotis | client_version = rfb.check_version(client_version_str) |
199 | 138d0e8b | Faidon Liambotis | if not client_version: |
200 | 138d0e8b | Faidon Liambotis | self.error("Invalid version: %s" % client_version_str) |
201 | 138d0e8b | Faidon Liambotis | raise gevent.GreenletExit
|
202 | 138d0e8b | Faidon Liambotis | |
203 | 138d0e8b | Faidon Liambotis | # Both for RFB 3.3 and 3.8
|
204 | 138d0e8b | Faidon Liambotis | self.debug("Requesting authentication") |
205 | 138d0e8b | Faidon Liambotis | auth_request = rfb.make_auth_request(rfb.RFB_AUTHTYPE_VNC, |
206 | 138d0e8b | Faidon Liambotis | version=client_version) |
207 | 138d0e8b | Faidon Liambotis | self.client.send(auth_request)
|
208 | 138d0e8b | Faidon Liambotis | |
209 | 138d0e8b | Faidon Liambotis | # The client gets to propose an authtype only for RFB 3.8
|
210 | 138d0e8b | Faidon Liambotis | if client_version == rfb.RFB_VERSION_3_8:
|
211 | 138d0e8b | Faidon Liambotis | res = self.client.recv(1024) |
212 | 138d0e8b | Faidon Liambotis | type = rfb.parse_client_authtype(res) |
213 | 138d0e8b | Faidon Liambotis | if type == rfb.RFB_AUTHTYPE_ERROR:
|
214 | 138d0e8b | Faidon Liambotis | self.warn("Client refused authentication: %s" % res[1:]) |
215 | 138d0e8b | Faidon Liambotis | else:
|
216 | 138d0e8b | Faidon Liambotis | self.debug("Client requested authtype %x" % type) |
217 | 138d0e8b | Faidon Liambotis | |
218 | 138d0e8b | Faidon Liambotis | if type != rfb.RFB_AUTHTYPE_VNC: |
219 | 138d0e8b | Faidon Liambotis | self.error("Wrong auth type: %d" % type) |
220 | 138d0e8b | Faidon Liambotis | self.client.send(rfb.to_u32(rfb.RFB_AUTH_ERROR))
|
221 | 138d0e8b | Faidon Liambotis | raise gevent.GreenletExit
|
222 | 138d0e8b | Faidon Liambotis | |
223 | 138d0e8b | Faidon Liambotis | # Generate the challenge
|
224 | 138d0e8b | Faidon Liambotis | challenge = os.urandom(16)
|
225 | 138d0e8b | Faidon Liambotis | self.client.send(challenge)
|
226 | 138d0e8b | Faidon Liambotis | response = self.client.recv(1024) |
227 | 138d0e8b | Faidon Liambotis | if len(response) != 16: |
228 | 138d0e8b | Faidon Liambotis | self.error("Wrong response length %d, should be 16" % len(response)) |
229 | 138d0e8b | Faidon Liambotis | raise gevent.GreenletExit
|
230 | 138d0e8b | Faidon Liambotis | |
231 | 5a196d84 | Vangelis Koukis | if rfb.check_password(challenge, response, self.password): |
232 | 138d0e8b | Faidon Liambotis | self.debug("Authentication successful!") |
233 | 138d0e8b | Faidon Liambotis | else:
|
234 | 138d0e8b | Faidon Liambotis | self.warn("Authentication failed") |
235 | 138d0e8b | Faidon Liambotis | self.client.send(rfb.to_u32(rfb.RFB_AUTH_ERROR))
|
236 | 138d0e8b | Faidon Liambotis | raise gevent.GreenletExit
|
237 | 138d0e8b | Faidon Liambotis | |
238 | 138d0e8b | Faidon Liambotis | # Accept the authentication
|
239 | 138d0e8b | Faidon Liambotis | self.client.send(rfb.to_u32(rfb.RFB_AUTH_SUCCESS))
|
240 | 138d0e8b | Faidon Liambotis | |
241 | 138d0e8b | Faidon Liambotis | def _run(self): |
242 | 138d0e8b | Faidon Liambotis | try:
|
243 | 138d0e8b | Faidon Liambotis | self.log.debug("Waiting for client to connect") |
244 | 5a196d84 | Vangelis Koukis | rlist, _, _ = select(self.listeners, [], [], timeout=self.timeout) |
245 | 138d0e8b | Faidon Liambotis | |
246 | 138d0e8b | Faidon Liambotis | if not rlist: |
247 | 138d0e8b | Faidon Liambotis | self.info("Timed out, no connection after %d sec" % self.timeout) |
248 | 138d0e8b | Faidon Liambotis | raise gevent.GreenletExit
|
249 | 138d0e8b | Faidon Liambotis | |
250 | 138d0e8b | Faidon Liambotis | for sock in rlist: |
251 | 138d0e8b | Faidon Liambotis | self.client, addrinfo = sock.accept()
|
252 | 138d0e8b | Faidon Liambotis | self.info("Connection from %s:%d" % addrinfo[:2]) |
253 | 138d0e8b | Faidon Liambotis | |
254 | 138d0e8b | Faidon Liambotis | # Close all listening sockets, we only want a one-shot connection
|
255 | 138d0e8b | Faidon Liambotis | # from a single client.
|
256 | 138d0e8b | Faidon Liambotis | while self.listeners: |
257 | 138d0e8b | Faidon Liambotis | self.listeners.pop().close()
|
258 | 138d0e8b | Faidon Liambotis | break
|
259 | 138d0e8b | Faidon Liambotis | |
260 | 512c571e | Stratos Psomadakis | # Perform RFB handshake with the client.
|
261 | 512c571e | Stratos Psomadakis | self._client_handshake()
|
262 | 138d0e8b | Faidon Liambotis | |
263 | 138d0e8b | Faidon Liambotis | # Bridge both connections through two "forwarder" greenlets.
|
264 | 138d0e8b | Faidon Liambotis | self.workers = [gevent.spawn(self._forward, self.client, self.server), |
265 | 138d0e8b | Faidon Liambotis | gevent.spawn(self._forward, self.server, self.client)] |
266 | 138d0e8b | Faidon Liambotis | |
267 | 138d0e8b | Faidon Liambotis | # If one greenlet goes, the other has to go too.
|
268 | 138d0e8b | Faidon Liambotis | self.workers[0].link(self.workers[1]) |
269 | 138d0e8b | Faidon Liambotis | self.workers[1].link(self.workers[0]) |
270 | 138d0e8b | Faidon Liambotis | gevent.joinall(self.workers)
|
271 | 138d0e8b | Faidon Liambotis | del self.workers |
272 | 138d0e8b | Faidon Liambotis | raise gevent.GreenletExit
|
273 | 138d0e8b | Faidon Liambotis | except Exception, e: |
274 | 138d0e8b | Faidon Liambotis | # Any unhandled exception in the previous block
|
275 | 138d0e8b | Faidon Liambotis | # is an error and must be logged accordingly
|
276 | 138d0e8b | Faidon Liambotis | if not isinstance(e, gevent.GreenletExit): |
277 | 138d0e8b | Faidon Liambotis | self.log.exception(e)
|
278 | 138d0e8b | Faidon Liambotis | raise e
|
279 | 138d0e8b | Faidon Liambotis | finally:
|
280 | 138d0e8b | Faidon Liambotis | self._cleanup()
|
281 | 138d0e8b | Faidon Liambotis | |
282 | 138d0e8b | Faidon Liambotis | |
283 | 138d0e8b | Faidon Liambotis | def fatal_signal_handler(signame): |
284 | 138d0e8b | Faidon Liambotis | logger.info("Caught %s, will raise SystemExit" % signame)
|
285 | 138d0e8b | Faidon Liambotis | raise SystemExit |
286 | 138d0e8b | Faidon Liambotis | |
287 | 138d0e8b | Faidon Liambotis | def get_listening_sockets(sport): |
288 | 138d0e8b | Faidon Liambotis | sockets = [] |
289 | 138d0e8b | Faidon Liambotis | |
290 | 138d0e8b | Faidon Liambotis | # Use two sockets, one for IPv4, one for IPv6. IPv4-to-IPv6 mapped
|
291 | 138d0e8b | Faidon Liambotis | # addresses do not work reliably everywhere (under linux it may have
|
292 | 138d0e8b | Faidon Liambotis | # been disabled in /proc/sys/net/ipv6/bind_ipv6_only).
|
293 | 138d0e8b | Faidon Liambotis | for res in socket.getaddrinfo(None, sport, socket.AF_UNSPEC, |
294 | 138d0e8b | Faidon Liambotis | socket.SOCK_STREAM, 0, socket.AI_PASSIVE):
|
295 | 138d0e8b | Faidon Liambotis | af, socktype, proto, canonname, sa = res |
296 | 138d0e8b | Faidon Liambotis | try:
|
297 | 138d0e8b | Faidon Liambotis | s = None
|
298 | 138d0e8b | Faidon Liambotis | s = socket.socket(af, socktype, proto) |
299 | 138d0e8b | Faidon Liambotis | if af == socket.AF_INET6:
|
300 | 138d0e8b | Faidon Liambotis | # Bind v6 only when AF_INET6, otherwise either v4 or v6 bind
|
301 | 138d0e8b | Faidon Liambotis | # will fail.
|
302 | 138d0e8b | Faidon Liambotis | s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
|
303 | 138d0e8b | Faidon Liambotis | s.bind(sa) |
304 | 138d0e8b | Faidon Liambotis | s.listen(1)
|
305 | 138d0e8b | Faidon Liambotis | sockets.append(s) |
306 | 138d0e8b | Faidon Liambotis | logger.debug("Listening on %s:%d" % sa[:2]) |
307 | 138d0e8b | Faidon Liambotis | except socket.error, msg:
|
308 | 138d0e8b | Faidon Liambotis | logger.error("Error binding to %s:%d: %s" %
|
309 | 138d0e8b | Faidon Liambotis | (sa[0], sa[1], msg[1])) |
310 | 138d0e8b | Faidon Liambotis | if s:
|
311 | 138d0e8b | Faidon Liambotis | s.close() |
312 | 138d0e8b | Faidon Liambotis | while sockets:
|
313 | 138d0e8b | Faidon Liambotis | sockets.pop().close() |
314 | 138d0e8b | Faidon Liambotis | |
315 | 138d0e8b | Faidon Liambotis | # Make sure we fail immediately if we cannot get a socket
|
316 | 138d0e8b | Faidon Liambotis | raise msg
|
317 | 138d0e8b | Faidon Liambotis | |
318 | 138d0e8b | Faidon Liambotis | return sockets
|
319 | 138d0e8b | Faidon Liambotis | |
320 | 7eb27319 | Stratos Psomadakis | def perform_server_handshake(daddr, dport, tries, retry_wait): |
321 | 512c571e | Stratos Psomadakis | """
|
322 | 512c571e | Stratos Psomadakis | Initiate a connection with the backend server and perform basic
|
323 | 512c571e | Stratos Psomadakis | RFB 3.8 handshake with it.
|
324 | 512c571e | Stratos Psomadakis |
|
325 | 512c571e | Stratos Psomadakis | Returns a socket connected to the backend server.
|
326 | 512c571e | Stratos Psomadakis |
|
327 | 512c571e | Stratos Psomadakis | """
|
328 | 512c571e | Stratos Psomadakis | server = None
|
329 | 512c571e | Stratos Psomadakis | |
330 | 512c571e | Stratos Psomadakis | while tries:
|
331 | 512c571e | Stratos Psomadakis | tries -= 1
|
332 | 512c571e | Stratos Psomadakis | |
333 | 512c571e | Stratos Psomadakis | # Initiate server connection
|
334 | 512c571e | Stratos Psomadakis | for res in socket.getaddrinfo(daddr, dport, socket.AF_UNSPEC, |
335 | 512c571e | Stratos Psomadakis | socket.SOCK_STREAM, 0, socket.AI_PASSIVE):
|
336 | 512c571e | Stratos Psomadakis | af, socktype, proto, canonname, sa = res |
337 | 512c571e | Stratos Psomadakis | try:
|
338 | 512c571e | Stratos Psomadakis | server = socket.socket(af, socktype, proto) |
339 | 512c571e | Stratos Psomadakis | except socket.error, msg:
|
340 | 512c571e | Stratos Psomadakis | server = None
|
341 | 512c571e | Stratos Psomadakis | continue
|
342 | 512c571e | Stratos Psomadakis | |
343 | 512c571e | Stratos Psomadakis | try:
|
344 | 512c571e | Stratos Psomadakis | logger.debug("Connecting to %s:%s" % sa[:2]) |
345 | 512c571e | Stratos Psomadakis | server.connect(sa) |
346 | 512c571e | Stratos Psomadakis | logger.debug("Connection to %s:%s successful" % sa[:2]) |
347 | 512c571e | Stratos Psomadakis | except socket.error, msg:
|
348 | 512c571e | Stratos Psomadakis | server.close() |
349 | 512c571e | Stratos Psomadakis | server = None
|
350 | 512c571e | Stratos Psomadakis | continue
|
351 | 512c571e | Stratos Psomadakis | |
352 | 512c571e | Stratos Psomadakis | # We succesfully connected to the server
|
353 | 512c571e | Stratos Psomadakis | tries = 0
|
354 | 512c571e | Stratos Psomadakis | break
|
355 | 512c571e | Stratos Psomadakis | |
356 | 512c571e | Stratos Psomadakis | # Wait and retry
|
357 | 7eb27319 | Stratos Psomadakis | sleep(retry_wait) |
358 | 512c571e | Stratos Psomadakis | |
359 | 512c571e | Stratos Psomadakis | if server is None: |
360 | 512c571e | Stratos Psomadakis | raise Exception("Failed to connect to server") |
361 | 512c571e | Stratos Psomadakis | |
362 | 512c571e | Stratos Psomadakis | version = server.recv(1024)
|
363 | 512c571e | Stratos Psomadakis | if not rfb.check_version(version): |
364 | 512c571e | Stratos Psomadakis | raise Exception("Unsupported RFB version: %s" % version.strip()) |
365 | 512c571e | Stratos Psomadakis | |
366 | 512c571e | Stratos Psomadakis | server.send(rfb.RFB_VERSION_3_8 + "\n")
|
367 | 512c571e | Stratos Psomadakis | |
368 | 512c571e | Stratos Psomadakis | res = server.recv(1024)
|
369 | 512c571e | Stratos Psomadakis | types = rfb.parse_auth_request(res) |
370 | 512c571e | Stratos Psomadakis | if not types: |
371 | 512c571e | Stratos Psomadakis | raise Exception("Error handshaking with the server") |
372 | 512c571e | Stratos Psomadakis | |
373 | 512c571e | Stratos Psomadakis | else:
|
374 | 512c571e | Stratos Psomadakis | logger.debug("Supported authentication types: %s" %
|
375 | 512c571e | Stratos Psomadakis | " ".join([str(x) for x in types])) |
376 | 512c571e | Stratos Psomadakis | |
377 | 512c571e | Stratos Psomadakis | if rfb.RFB_AUTHTYPE_NONE not in types: |
378 | 512c571e | Stratos Psomadakis | raise Exception("Error, server demands authentication") |
379 | 512c571e | Stratos Psomadakis | |
380 | 512c571e | Stratos Psomadakis | server.send(rfb.to_u8(rfb.RFB_AUTHTYPE_NONE)) |
381 | 512c571e | Stratos Psomadakis | |
382 | 512c571e | Stratos Psomadakis | # Check authentication response
|
383 | 512c571e | Stratos Psomadakis | res = server.recv(4)
|
384 | 512c571e | Stratos Psomadakis | res = rfb.from_u32(res) |
385 | 512c571e | Stratos Psomadakis | |
386 | 512c571e | Stratos Psomadakis | if res != 0: |
387 | 512c571e | Stratos Psomadakis | raise Exception("Authentication error") |
388 | 512c571e | Stratos Psomadakis | |
389 | 512c571e | Stratos Psomadakis | return server
|
390 | 512c571e | Stratos Psomadakis | |
391 | 138d0e8b | Faidon Liambotis | def parse_arguments(args): |
392 | 138d0e8b | Faidon Liambotis | from optparse import OptionParser |
393 | 138d0e8b | Faidon Liambotis | |
394 | 138d0e8b | Faidon Liambotis | parser = OptionParser() |
395 | 138d0e8b | Faidon Liambotis | parser.add_option("-s", "--socket", dest="ctrl_socket", |
396 | 138d0e8b | Faidon Liambotis | default=DEFAULT_CTRL_SOCKET, |
397 | 138d0e8b | Faidon Liambotis | metavar="PATH",
|
398 | 138d0e8b | Faidon Liambotis | help="UNIX socket path for control connections (default: %s" %
|
399 | 138d0e8b | Faidon Liambotis | DEFAULT_CTRL_SOCKET) |
400 | 138d0e8b | Faidon Liambotis | parser.add_option("-d", "--debug", action="store_true", dest="debug", |
401 | 138d0e8b | Faidon Liambotis | help="Enable debugging information")
|
402 | 138d0e8b | Faidon Liambotis | parser.add_option("-l", "--log", dest="log_file", |
403 | 138d0e8b | Faidon Liambotis | default=DEFAULT_LOG_FILE, |
404 | 138d0e8b | Faidon Liambotis | metavar="FILE",
|
405 | 138d0e8b | Faidon Liambotis | help="Write log to FILE instead of %s" % DEFAULT_LOG_FILE),
|
406 | 138d0e8b | Faidon Liambotis | parser.add_option('--pid-file', dest="pid_file", |
407 | 138d0e8b | Faidon Liambotis | default=DEFAULT_PID_FILE, |
408 | 138d0e8b | Faidon Liambotis | metavar='PIDFILE',
|
409 | 138d0e8b | Faidon Liambotis | help="Save PID to file (default: %s)" %
|
410 | 138d0e8b | Faidon Liambotis | DEFAULT_PID_FILE) |
411 | 138d0e8b | Faidon Liambotis | parser.add_option("-t", "--connect-timeout", dest="connect_timeout", |
412 | 138d0e8b | Faidon Liambotis | default=DEFAULT_CONNECT_TIMEOUT, type="int", metavar="SECONDS", |
413 | 138d0e8b | Faidon Liambotis | help="How long to listen for clients to forward")
|
414 | 7eb27319 | Stratos Psomadakis | parser.add_option("-r", "--connect-retries", dest="connect_retries", |
415 | 7eb27319 | Stratos Psomadakis | default=DEFAULT_CONNECT_RETRIES, type="int",
|
416 | 7eb27319 | Stratos Psomadakis | metavar="RETRIES",
|
417 | 7eb27319 | Stratos Psomadakis | help="How many times to try to connect to the server")
|
418 | 7eb27319 | Stratos Psomadakis | parser.add_option("-w", "--retry-wait", dest="retry_wait", |
419 | 7eb27319 | Stratos Psomadakis | default=DEFAULT_RETRY_WAIT, type="float", metavar="SECONDS", |
420 | 7eb27319 | Stratos Psomadakis | help="How long to wait between retrying to connect to the server")
|
421 | 138d0e8b | Faidon Liambotis | parser.add_option("-p", "--min-port", dest="min_port", |
422 | 138d0e8b | Faidon Liambotis | default=DEFAULT_MIN_PORT, type="int", metavar="MIN_PORT", |
423 | 138d0e8b | Faidon Liambotis | help="The minimum port to use for automatically-allocated ephemeral ports")
|
424 | 138d0e8b | Faidon Liambotis | parser.add_option("-P", "--max-port", dest="max_port", |
425 | 138d0e8b | Faidon Liambotis | default=DEFAULT_MAX_PORT, type="int", metavar="MAX_PORT", |
426 | 138d0e8b | Faidon Liambotis | help="The minimum port to use for automatically-allocated ephemeral ports")
|
427 | 138d0e8b | Faidon Liambotis | |
428 | 138d0e8b | Faidon Liambotis | return parser.parse_args(args)
|
429 | 138d0e8b | Faidon Liambotis | |
430 | 138d0e8b | Faidon Liambotis | |
431 | 138d0e8b | Faidon Liambotis | def main(): |
432 | 138d0e8b | Faidon Liambotis | """Run the daemon from the command line."""
|
433 | 138d0e8b | Faidon Liambotis | |
434 | 138d0e8b | Faidon Liambotis | (opts, args) = parse_arguments(sys.argv[1:])
|
435 | 138d0e8b | Faidon Liambotis | |
436 | 138d0e8b | Faidon Liambotis | # Create pidfile
|
437 | 138d0e8b | Faidon Liambotis | pidf = daemon.pidlockfile.TimeoutPIDLockFile( |
438 | 138d0e8b | Faidon Liambotis | opts.pid_file, 10)
|
439 | 138d0e8b | Faidon Liambotis | |
440 | 138d0e8b | Faidon Liambotis | # Initialize logger
|
441 | 138d0e8b | Faidon Liambotis | lvl = logging.DEBUG if opts.debug else logging.INFO |
442 | 88420a63 | Faidon Liambotis | |
443 | 88420a63 | Faidon Liambotis | global logger
|
444 | 138d0e8b | Faidon Liambotis | logger = logging.getLogger("vncauthproxy")
|
445 | 138d0e8b | Faidon Liambotis | logger.setLevel(lvl) |
446 | 138d0e8b | Faidon Liambotis | formatter = logging.Formatter("%(asctime)s %(module)s[%(process)d] %(levelname)s: %(message)s",
|
447 | 138d0e8b | Faidon Liambotis | "%Y-%m-%d %H:%M:%S")
|
448 | 138d0e8b | Faidon Liambotis | handler = logging.FileHandler(opts.log_file) |
449 | 138d0e8b | Faidon Liambotis | handler.setFormatter(formatter) |
450 | 138d0e8b | Faidon Liambotis | logger.addHandler(handler) |
451 | 138d0e8b | Faidon Liambotis | |
452 | 138d0e8b | Faidon Liambotis | # Become a daemon:
|
453 | 138d0e8b | Faidon Liambotis | # Redirect stdout and stderr to handler.stream to catch
|
454 | 138d0e8b | Faidon Liambotis | # early errors in the daemonization process [e.g., pidfile creation]
|
455 | 138d0e8b | Faidon Liambotis | # which will otherwise go to /dev/null.
|
456 | 376a8634 | Vangelis Koukis | daemon_context = AllFilesDaemonContext( |
457 | 138d0e8b | Faidon Liambotis | pidfile=pidf, |
458 | 138d0e8b | Faidon Liambotis | umask=0022,
|
459 | 138d0e8b | Faidon Liambotis | stdout=handler.stream, |
460 | 138d0e8b | Faidon Liambotis | stderr=handler.stream, |
461 | 138d0e8b | Faidon Liambotis | files_preserve=[handler.stream]) |
462 | 138d0e8b | Faidon Liambotis | daemon_context.open() |
463 | 138d0e8b | Faidon Liambotis | logger.info("Became a daemon")
|
464 | 138d0e8b | Faidon Liambotis | |
465 | 138d0e8b | Faidon Liambotis | # A fork() has occured while daemonizing,
|
466 | 138d0e8b | Faidon Liambotis | # we *must* reinit gevent
|
467 | 138d0e8b | Faidon Liambotis | gevent.reinit() |
468 | 138d0e8b | Faidon Liambotis | |
469 | 138d0e8b | Faidon Liambotis | if os.path.exists(opts.ctrl_socket):
|
470 | 138d0e8b | Faidon Liambotis | logger.critical("Socket '%s' already exists" % opts.ctrl_socket)
|
471 | 138d0e8b | Faidon Liambotis | sys.exit(1)
|
472 | 138d0e8b | Faidon Liambotis | |
473 | 138d0e8b | Faidon Liambotis | # TODO: make this tunable? chgrp as well?
|
474 | 1c241b27 | Faidon Liambotis | old_umask = os.umask(0007)
|
475 | 138d0e8b | Faidon Liambotis | |
476 | 138d0e8b | Faidon Liambotis | ctrl = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) |
477 | 138d0e8b | Faidon Liambotis | ctrl.bind(opts.ctrl_socket) |
478 | 138d0e8b | Faidon Liambotis | |
479 | 138d0e8b | Faidon Liambotis | os.umask(old_umask) |
480 | 138d0e8b | Faidon Liambotis | |
481 | 138d0e8b | Faidon Liambotis | ctrl.listen(1)
|
482 | 138d0e8b | Faidon Liambotis | logger.info("Initialized, waiting for control connections at %s" %
|
483 | 138d0e8b | Faidon Liambotis | opts.ctrl_socket) |
484 | 138d0e8b | Faidon Liambotis | |
485 | 138d0e8b | Faidon Liambotis | # Catch signals to ensure graceful shutdown,
|
486 | 138d0e8b | Faidon Liambotis | # e.g., to make sure the control socket gets unlink()ed.
|
487 | 138d0e8b | Faidon Liambotis | #
|
488 | 138d0e8b | Faidon Liambotis | # Uses gevent.signal so the handler fires even during
|
489 | 138d0e8b | Faidon Liambotis | # gevent.socket.accept()
|
490 | 138d0e8b | Faidon Liambotis | gevent.signal(SIGINT, fatal_signal_handler, "SIGINT")
|
491 | 138d0e8b | Faidon Liambotis | gevent.signal(SIGTERM, fatal_signal_handler, "SIGTERM")
|
492 | 138d0e8b | Faidon Liambotis | |
493 | 138d0e8b | Faidon Liambotis | # Init ephemeral port pool
|
494 | 138d0e8b | Faidon Liambotis | ports = range(opts.min_port, opts.max_port + 1) |
495 | 138d0e8b | Faidon Liambotis | |
496 | 138d0e8b | Faidon Liambotis | while True: |
497 | 138d0e8b | Faidon Liambotis | try:
|
498 | 138d0e8b | Faidon Liambotis | client, addr = ctrl.accept() |
499 | 138d0e8b | Faidon Liambotis | logger.info("New control connection")
|
500 | 138d0e8b | Faidon Liambotis | |
501 | 138d0e8b | Faidon Liambotis | # Receive and parse a client request.
|
502 | 138d0e8b | Faidon Liambotis | response = { |
503 | 138d0e8b | Faidon Liambotis | "source_port": 0, |
504 | 138d0e8b | Faidon Liambotis | "status": "FAILED" |
505 | 138d0e8b | Faidon Liambotis | } |
506 | 138d0e8b | Faidon Liambotis | try:
|
507 | 138d0e8b | Faidon Liambotis | # TODO: support multiple forwardings in the same message?
|
508 | 138d0e8b | Faidon Liambotis | #
|
509 | 138d0e8b | Faidon Liambotis | # Control request, in JSON:
|
510 | 138d0e8b | Faidon Liambotis | #
|
511 | 138d0e8b | Faidon Liambotis | # {
|
512 | 138d0e8b | Faidon Liambotis | # "source_port": <source port or 0 for automatic allocation>,
|
513 | 138d0e8b | Faidon Liambotis | # "destination_address": <destination address of backend server>,
|
514 | 138d0e8b | Faidon Liambotis | # "destination_port": <destination port>
|
515 | 138d0e8b | Faidon Liambotis | # "password": <the password to use for MITM authentication of clients>
|
516 | 138d0e8b | Faidon Liambotis | # }
|
517 | 138d0e8b | Faidon Liambotis | #
|
518 | 138d0e8b | Faidon Liambotis | # The <password> is used for MITM authentication of clients
|
519 | 138d0e8b | Faidon Liambotis | # connecting to <source_port>, who will subsequently be forwarded
|
520 | 138d0e8b | Faidon Liambotis | # to a VNC server at <destination_address>:<destination_port>
|
521 | 138d0e8b | Faidon Liambotis | #
|
522 | 138d0e8b | Faidon Liambotis | # Control reply, in JSON:
|
523 | 138d0e8b | Faidon Liambotis | # {
|
524 | 138d0e8b | Faidon Liambotis | # "source_port": <the allocated source port>
|
525 | 138d0e8b | Faidon Liambotis | # "status": <one of "OK" or "FAILED">
|
526 | 138d0e8b | Faidon Liambotis | # }
|
527 | 138d0e8b | Faidon Liambotis | buf = client.recv(1024)
|
528 | 138d0e8b | Faidon Liambotis | req = json.loads(buf) |
529 | 138d0e8b | Faidon Liambotis | |
530 | 138d0e8b | Faidon Liambotis | sport_orig = int(req['source_port']) |
531 | 138d0e8b | Faidon Liambotis | daddr = req['destination_address']
|
532 | 138d0e8b | Faidon Liambotis | dport = int(req['destination_port']) |
533 | 138d0e8b | Faidon Liambotis | password = req['password']
|
534 | 138d0e8b | Faidon Liambotis | except Exception, e: |
535 | 138d0e8b | Faidon Liambotis | logger.warn("Malformed request: %s" % buf)
|
536 | 138d0e8b | Faidon Liambotis | client.send(json.dumps(response)) |
537 | 138d0e8b | Faidon Liambotis | client.close() |
538 | 138d0e8b | Faidon Liambotis | continue
|
539 | 138d0e8b | Faidon Liambotis | |
540 | 138d0e8b | Faidon Liambotis | # Spawn a new Greenlet to service the request.
|
541 | 512c571e | Stratos Psomadakis | server = None
|
542 | 138d0e8b | Faidon Liambotis | try:
|
543 | 138d0e8b | Faidon Liambotis | # If the client has so indicated, pick an ephemeral source port
|
544 | 138d0e8b | Faidon Liambotis | # randomly, and remove it from the port pool.
|
545 | 138d0e8b | Faidon Liambotis | if sport_orig == 0: |
546 | 138d0e8b | Faidon Liambotis | sport = random.choice(ports) |
547 | 138d0e8b | Faidon Liambotis | ports.remove(sport) |
548 | 138d0e8b | Faidon Liambotis | logger.debug("Got port %d from port pool, contains %d ports",
|
549 | 138d0e8b | Faidon Liambotis | sport, len(ports))
|
550 | 138d0e8b | Faidon Liambotis | pool = ports |
551 | 138d0e8b | Faidon Liambotis | else:
|
552 | 138d0e8b | Faidon Liambotis | sport = sport_orig |
553 | 138d0e8b | Faidon Liambotis | pool = None
|
554 | 512c571e | Stratos Psomadakis | |
555 | 138d0e8b | Faidon Liambotis | listeners = get_listening_sockets(sport) |
556 | 7eb27319 | Stratos Psomadakis | server = perform_server_handshake(daddr, dport, |
557 | 7eb27319 | Stratos Psomadakis | opts.connect_retries, opts.retry_wait) |
558 | 512c571e | Stratos Psomadakis | |
559 | 138d0e8b | Faidon Liambotis | VncAuthProxy.spawn(logger, listeners, pool, daddr, dport, |
560 | 512c571e | Stratos Psomadakis | server, password, opts.connect_timeout) |
561 | 512c571e | Stratos Psomadakis | |
562 | 138d0e8b | Faidon Liambotis | logger.info("New forwarding [%d (req'd by client: %d) -> %s:%d]" %
|
563 | 138d0e8b | Faidon Liambotis | (sport, sport_orig, daddr, dport)) |
564 | 138d0e8b | Faidon Liambotis | response = { |
565 | 138d0e8b | Faidon Liambotis | "source_port": sport,
|
566 | 138d0e8b | Faidon Liambotis | "status": "OK" |
567 | 138d0e8b | Faidon Liambotis | } |
568 | 138d0e8b | Faidon Liambotis | except IndexError: |
569 | 138d0e8b | Faidon Liambotis | logger.error("FAILED forwarding, out of ports for [req'd by "
|
570 | 138d0e8b | Faidon Liambotis | "client: %d -> %s:%d]" % (sport_orig, daddr, dport))
|
571 | 512c571e | Stratos Psomadakis | except Exception, msg: |
572 | 512c571e | Stratos Psomadakis | logger.error(msg) |
573 | 138d0e8b | Faidon Liambotis | logger.error("FAILED forwarding [%d (req'd by client: %d) -> %s:%d]" %
|
574 | 138d0e8b | Faidon Liambotis | (sport, sport_orig, daddr, dport)) |
575 | 138d0e8b | Faidon Liambotis | if not pool is None: |
576 | 138d0e8b | Faidon Liambotis | pool.append(sport) |
577 | 138d0e8b | Faidon Liambotis | logger.debug("Returned port %d to port pool, contains %d ports",
|
578 | 138d0e8b | Faidon Liambotis | sport, len(pool))
|
579 | 512c571e | Stratos Psomadakis | if not server is None: |
580 | 512c571e | Stratos Psomadakis | server.close() |
581 | 138d0e8b | Faidon Liambotis | finally:
|
582 | 138d0e8b | Faidon Liambotis | client.send(json.dumps(response)) |
583 | 138d0e8b | Faidon Liambotis | client.close() |
584 | 138d0e8b | Faidon Liambotis | except Exception, e: |
585 | 138d0e8b | Faidon Liambotis | logger.exception(e) |
586 | 138d0e8b | Faidon Liambotis | continue
|
587 | 138d0e8b | Faidon Liambotis | except SystemExit: |
588 | 138d0e8b | Faidon Liambotis | break
|
589 | 138d0e8b | Faidon Liambotis | |
590 | 138d0e8b | Faidon Liambotis | logger.info("Unlinking control socket at %s" %
|
591 | 138d0e8b | Faidon Liambotis | opts.ctrl_socket) |
592 | 138d0e8b | Faidon Liambotis | os.unlink(opts.ctrl_socket) |
593 | 138d0e8b | Faidon Liambotis | daemon_context.close() |
594 | 138d0e8b | Faidon Liambotis | sys.exit(0) |