~psiphon-inc/psiphon/trunk

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
}