157.1.3
by Adam Kruger
Add PsiphonX source code. |
1 |
/*
|
2 |
* Copyright (c) 2010, Psiphon Inc.
|
|
3 |
* All rights reserved.
|
|
4 |
*
|
|
5 |
* This program is free software: you can redistribute it and/or modify
|
|
6 |
* it under the terms of the GNU General Public License as published by
|
|
7 |
* the Free Software Foundation, either version 3 of the License, or
|
|
8 |
* (at your option) any later version.
|
|
9 |
*
|
|
10 |
* This program is distributed in the hope that it will be useful,
|
|
11 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13 |
* GNU General Public License for more details.
|
|
14 |
*
|
|
15 |
* You should have received a copy of the GNU General Public License
|
|
16 |
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
17 |
*
|
|
18 |
*/
|
|
19 |
||
20 |
#include "stdafx.h" |
|
21 |
#include "psiclient.h" |
|
22 |
#include "sshtunnel.h" |
|
23 |
#include "locallistener.h" |
|
24 |
#include "libssh2.h" |
|
25 |
#include "sshsession.h" |
|
26 |
#include "config.h" |
|
27 |
#include "embeddedconfigvalues.h" |
|
28 |
||
29 |
#include <ws2tcpip.h> |
|
30 |
#include <winsock2.h> |
|
31 |
||
32 |
struct Connection; |
|
33 |
DWORD WINAPI listen(LPVOID pData); |
|
34 |
void process_connection(Connection connection, bool readFromClient); |
|
35 |
||
36 |
SSHTunnel::SSHTunnel(void) |
|
37 |
{
|
|
38 |
m_listenThread = 0; |
|
39 |
m_shuttingDown = false; |
|
40 |
}
|
|
41 |
||
42 |
SSHTunnel::~SSHTunnel(void) |
|
43 |
{
|
|
44 |
TearDown(); |
|
45 |
}
|
|
46 |
||
47 |
void SSHTunnel::StartUp(void) |
|
48 |
{
|
|
49 |
TearDown(); |
|
50 |
||
51 |
m_shuttingDown = false; |
|
52 |
||
53 |
int rc = libssh2_init(0); |
|
54 |
if (rc) |
|
55 |
{
|
|
56 |
my_print(false, _T("libssh2 initialization failed: %d"), rc); |
|
57 |
return; |
|
58 |
}
|
|
59 |
||
60 |
// Start the listener
|
|
61 |
m_listenThread = CreateThread(NULL, 0, listen, (LPVOID)this, 0, NULL); |
|
62 |
||
63 |
// TODO: should wait for listener to start before changing proxy settings and launching IE?
|
|
64 |
||
65 |
// Configure the proxy settings
|
|
66 |
if (!m_proxySettings.Configure()) |
|
67 |
{
|
|
68 |
my_print(false, _T("error configuring proxy settings")); |
|
69 |
}
|
|
70 |
else
|
|
71 |
{
|
|
72 |
// Start IE
|
|
73 |
m_webBrowser.Open(); |
|
74 |
||
75 |
my_print(false, _T("PsiphonX started")); |
|
76 |
}
|
|
77 |
}
|
|
78 |
||
79 |
void SSHTunnel::ShutDown(void) |
|
80 |
{
|
|
81 |
TearDown(); |
|
82 |
||
83 |
my_print(false, _T("PsiphonX stopped")); |
|
84 |
}
|
|
85 |
||
86 |
bool SSHTunnel::ShuttingDown(void) |
|
87 |
{
|
|
88 |
return m_shuttingDown; |
|
89 |
}
|
|
90 |
||
91 |
void SSHTunnel::TearDown(void) |
|
92 |
{
|
|
93 |
// Stop IE
|
|
94 |
m_webBrowser.Close(); |
|
95 |
||
96 |
// Reset the proxy settings
|
|
97 |
m_proxySettings.Revert(); |
|
98 |
||
99 |
// Stop the listener by setting close flag
|
|
100 |
// TODO: signal event?
|
|
101 |
||
102 |
m_shuttingDown = true; |
|
103 |
||
104 |
if (m_listenThread) |
|
105 |
{
|
|
106 |
WaitForSingleObject(m_listenThread, INFINITE); |
|
107 |
||
108 |
m_listenThread = 0; |
|
109 |
}
|
|
110 |
||
111 |
m_shuttingDown = false; |
|
112 |
||
113 |
libssh2_exit(); |
|
114 |
}
|
|
115 |
||
116 |
struct Connection |
|
117 |
{
|
|
118 |
int socket; |
|
119 |
LIBSSH2_CHANNEL *channel; |
|
120 |
};
|
|
121 |
typedef vector<Connection>::const_iterator connection_iter; |
|
122 |
||
123 |
DWORD WINAPI listen(LPVOID pData) |
|
124 |
{
|
|
125 |
SSHTunnel* pTunnel = (SSHTunnel*)pData; |
|
126 |
LocalListener listener(LOCAL_LISTEN_IP, LOCAL_LISTEN_PORT); |
|
127 |
ssh_credentials credentials = { |
|
128 |
UnpadValue(SSH_SERVER_HOST), |
|
129 |
atoi(UnpadValue(SSH_SERVER_PORT_NUMBER).c_str()), |
|
130 |
UnpadValue(SSH_USER_NAME), |
|
131 |
UnpadValue(SSH_USER_PASSWORD), |
|
132 |
UnpadValue(SSH_SERVER_FINGERPRINT)}; |
|
133 |
SSHSession ssh_session(credentials); |
|
134 |
vector<Connection> connections; |
|
135 |
bool bTearDownSession = false; |
|
136 |
||
137 |
WSADATA wsadata; |
|
138 |
int rc = WSAStartup(MAKEWORD(2,0), &wsadata); |
|
139 |
||
140 |
if (rc) |
|
141 |
{
|
|
142 |
my_print(false, _T("WSAStartup failed: %d"), rc); |
|
143 |
return 0; |
|
144 |
}
|
|
145 |
||
146 |
try
|
|
147 |
{
|
|
148 |
// initialize local listener
|
|
149 |
||
150 |
listener.Initialize(); |
|
151 |
||
152 |
while (true) |
|
153 |
{
|
|
154 |
if (pTunnel->ShuttingDown()) |
|
155 |
{
|
|
156 |
// The main thread is signalling us to shut down
|
|
157 |
throw 0; |
|
158 |
}
|
|
159 |
||
160 |
// to recover from major session errors, reset everything
|
|
161 |
||
162 |
if (bTearDownSession) |
|
163 |
{
|
|
164 |
my_print(false, _T("reset session")); |
|
165 |
||
166 |
// destroy an existing session (and connections)
|
|
167 |
for (connection_iter conn = connections.begin(); conn != connections.end(); ++conn) |
|
168 |
{
|
|
169 |
libssh2_channel_free(conn->channel); |
|
170 |
closesocket(conn->socket); |
|
171 |
}
|
|
172 |
connections.clear(); |
|
173 |
||
174 |
ssh_session.TearDown(); |
|
175 |
||
176 |
bTearDownSession = false; |
|
177 |
}
|
|
178 |
||
179 |
fd_set fds; |
|
180 |
FD_ZERO(&fds); |
|
181 |
||
182 |
// - select on the listen socket
|
|
183 |
FD_SET(listener.ListenSocket(), &fds); |
|
184 |
||
185 |
// - select on all connection sockets to see if there's anything to read from the clients
|
|
186 |
for (connection_iter conn = connections.begin(); conn != connections.end(); ++conn) |
|
187 |
{
|
|
188 |
FD_SET(conn->socket, &fds); |
|
189 |
}
|
|
190 |
||
191 |
struct timeval tv; |
|
192 |
tv.tv_sec = 0; |
|
193 |
tv.tv_usec = 1; |
|
194 |
int rc = select(0, &fds, NULL, NULL, &tv); |
|
195 |
if (SOCKET_ERROR == rc) |
|
196 |
{
|
|
197 |
my_print(false, _T("select error: %d"), WSAGetLastError()); |
|
198 |
throw 0; |
|
199 |
}
|
|
200 |
||
201 |
// accept connections and add to connection list
|
|
202 |
||
203 |
if (FD_ISSET(listener.ListenSocket(), &fds)) |
|
204 |
{
|
|
205 |
int forwardSocket = -1; |
|
206 |
listener.Accept(forwardSocket); |
|
207 |
||
208 |
// initialize ssh connection/session on demand
|
|
209 |
||
210 |
if (!ssh_session.IsConnected() && !ssh_session.Connect()) |
|
211 |
{
|
|
212 |
my_print(false, _T("ssh connection failed")); |
|
213 |
||
214 |
// wait for more work (or shutdown signal) before retrying to connect
|
|
215 |
continue; |
|
216 |
}
|
|
217 |
||
218 |
// create channel for this connection
|
|
219 |
||
220 |
// in libssh2, session needs to be in blocking mode for this operation
|
|
221 |
libssh2_session_set_blocking(ssh_session.GetSession(), 1); |
|
222 |
||
223 |
// TODO: do we need a new channel each time?
|
|
224 |
LIBSSH2_CHANNEL *channel = libssh2_channel_direct_tcpip_ex( |
|
225 |
ssh_session.GetSession(), |
|
226 |
HTTP_PROXY_HOST, |
|
227 |
atoi(UnpadValue(HTTP_PROXY_PORT_NUMBER).c_str()), |
|
228 |
LOCAL_LISTEN_IP, |
|
229 |
LOCAL_LISTEN_PORT); |
|
230 |
||
231 |
if (!channel) |
|
232 |
{
|
|
233 |
// after "Could not open the direct-tcpip channel" the session seems broken,
|
|
234 |
// so force a complete reset
|
|
235 |
bTearDownSession = true; |
|
236 |
||
237 |
my_print(false, _T("Could not open the direct-tcpip channel")); |
|
238 |
//"(Note that this can be a problem at the server!"
|
|
239 |
//" Please review the server logs.)");
|
|
240 |
||
241 |
// go to the top of the loop where the session will be restarted
|
|
242 |
continue; |
|
243 |
}
|
|
244 |
||
245 |
// Must use non-blocking IO hereafter due to the current libssh2 API
|
|
246 |
libssh2_session_set_blocking(ssh_session.GetSession(), 0); |
|
247 |
||
248 |
// add this connection to list for processing
|
|
249 |
||
250 |
Connection connection = {forwardSocket, channel}; |
|
251 |
connections.push_back(connection); |
|
252 |
}
|
|
253 |
||
254 |
// process each connection:
|
|
255 |
// - the check for browser-side reads has already been done by select above
|
|
256 |
// - also, check for ssh-side reads
|
|
257 |
||
258 |
connection_iter conn = connections.begin(); |
|
259 |
while (conn != connections.end()) |
|
260 |
{
|
|
261 |
// try block for errors related to this connection: will reset connection "conn" only
|
|
262 |
||
263 |
try
|
|
264 |
{
|
|
265 |
process_connection(*conn, 0 != FD_ISSET(conn->socket, &fds)); |
|
266 |
++conn; |
|
267 |
}
|
|
268 |
catch (...) |
|
269 |
{
|
|
270 |
// connection was closed or error; shutdown and remove from list
|
|
271 |
||
272 |
my_print(true, _T("closing connection")); |
|
273 |
||
274 |
libssh2_channel_free(conn->channel); |
|
275 |
||
276 |
closesocket(conn->socket); |
|
277 |
||
278 |
conn = connections.erase(conn); |
|
279 |
}
|
|
280 |
}
|
|
281 |
}
|
|
282 |
}
|
|
283 |
catch (...) |
|
284 |
{
|
|
285 |
}
|
|
286 |
// TODO: Don't throw 0. Throw and catch a std::exception.
|
|
287 |
// Maybe put the error message in the exception.
|
|
288 |
||
289 |
// close all connections
|
|
290 |
||
291 |
for (connection_iter conn = connections.begin(); conn != connections.end(); ++conn) |
|
292 |
{
|
|
293 |
libssh2_channel_free(conn->channel); |
|
294 |
closesocket(conn->socket); |
|
295 |
}
|
|
296 |
connections.clear(); |
|
297 |
||
298 |
return 0; |
|
299 |
}
|
|
300 |
||
301 |
void process_connection(Connection connection, bool readFromClient) |
|
302 |
{
|
|
303 |
ssize_t len, wr; |
|
304 |
char buf[16384]; |
|
305 |
||
306 |
if (readFromClient) |
|
307 |
{
|
|
308 |
len = recv(connection.socket, buf, sizeof(buf), 0); |
|
309 |
if (len < 0) |
|
310 |
{
|
|
311 |
// errno=11 corresponds to "resource is not available" which apparently means
|
|
312 |
// the peer closed the the socket seems to be reported a lot; perhaps IE is abandoning
|
|
313 |
// the request due to delays. for now, since IE seems to retry, only showing this
|
|
314 |
// error in debug
|
|
315 |
my_print((errno == 11), _T("read error: %s: %d"), _tcserror(errno), WSAGetLastError()); |
|
316 |
throw 0; |
|
317 |
}
|
|
318 |
else if (0 == len) |
|
319 |
{
|
|
320 |
// TODO: correctly detect client shutdown?
|
|
321 |
//my_print(_T("the client at %hs:%d disconnected"), shost, sport);
|
|
322 |
my_print(true, _T("the client disconnected")); |
|
323 |
throw 0; |
|
324 |
}
|
|
325 |
else
|
|
326 |
{
|
|
327 |
wr = 0; |
|
328 |
while (wr < len) |
|
329 |
{
|
|
330 |
int i = libssh2_channel_write(connection.channel, buf, len); |
|
331 |
if (i == LIBSSH2_ERROR_EAGAIN) |
|
332 |
{
|
|
333 |
// TODO: wait?
|
|
334 |
continue; |
|
335 |
}
|
|
336 |
else if (i < 0) |
|
337 |
{
|
|
338 |
my_print(false, _T("libssh2_channel_write error: %d"), i); |
|
339 |
throw 0; |
|
340 |
}
|
|
341 |
else if (i == 0) |
|
342 |
{
|
|
343 |
// TODO: need this condition?
|
|
344 |
break; |
|
345 |
}
|
|
346 |
wr += i; |
|
347 |
}
|
|
348 |
}
|
|
349 |
}
|
|
350 |
while (true) |
|
351 |
{
|
|
352 |
len = libssh2_channel_read(connection.channel, buf, sizeof(buf)); |
|
353 |
||
354 |
if (LIBSSH2_ERROR_EAGAIN == len) |
|
355 |
{
|
|
356 |
break; |
|
357 |
}
|
|
358 |
else if (len < 0) |
|
359 |
{
|
|
360 |
my_print(false, _T("libssh2_channel_read error: %d"), len); |
|
361 |
throw 0; |
|
362 |
}
|
|
363 |
wr = 0; |
|
364 |
while (wr < len) |
|
365 |
{
|
|
366 |
int i = send(connection.socket, buf + wr, len - wr, 0); |
|
367 |
if (i <= 0) |
|
368 |
{
|
|
369 |
// see comment above about errno=11
|
|
370 |
my_print((errno == 11), _T("write error: %s: %d"), _tcserror(errno), WSAGetLastError()); |
|
371 |
throw 0; |
|
372 |
}
|
|
373 |
wr += i; |
|
374 |
}
|
|
375 |
if (libssh2_channel_eof(connection.channel)) |
|
376 |
{
|
|
377 |
my_print(true, |
|
378 |
_T("the server at %hs:%hs disconnected"), |
|
379 |
HTTP_PROXY_HOST, |
|
380 |
UnpadValue(HTTP_PROXY_PORT_NUMBER).c_str()); |
|
381 |
throw 0; |
|
382 |
}
|
|
383 |
}
|
|
384 |
}
|