1 //////////////////////////////////////////////////////////////////////////
2 //
3 // pgAdmin III - PostgreSQL Tools
4 //
5 // Copyright (C) 2002 - 2016, The pgAdmin Development Team
6 // This software is released under the PostgreSQL Licence
7 //
8 // sshTunnel.cpp - Used to create SSH Tunnels
9 //
10 //////////////////////////////////////////////////////////////////////////
11
12 // App headers
13 #include "pgAdmin3.h"
14 #include <wx/dynlib.h>
15
16 #undef ssize_t
17 #define ssize_t long
18 #include "libssh2/libssh2.h"
19 #include "utils/sshTunnel.h"
20 #include "frm/frmMain.h"
21
22 #ifdef WIN32
23 #pragma comment (lib, "Ws2_32.lib")
24 #endif
25
26 typedef const char *(*inet_ntop_t) (int af, const void *src, char *dst, socklen_t size);
27
28 #ifdef WIN32
custom_inet_ntop(int af,const void * src,char * dst,int cnt)29 const char *custom_inet_ntop(int af, const void *src, char *dst, int cnt)
30 {
31 struct sockaddr_in srcaddr;
32
33 memset(&srcaddr, 0, sizeof(struct sockaddr_in));
34 memcpy(&(srcaddr.sin_addr), src, sizeof(srcaddr.sin_addr));
35
36 srcaddr.sin_family = af;
37 if (WSAAddressToStringA((struct sockaddr *) &srcaddr, sizeof(struct sockaddr_in), 0, dst, (LPDWORD) &cnt) != 0)
38 {
39
40 wxLogInfo(wxT("SSH error: WSAAddressToStringA failed with error code %d"), WSAGetLastError());
41 return NULL;
42 }
43 return dst;
44 }
45
46 static inet_ntop_t gs_fnPtr_inet_ntop = &custom_inet_ntop;
47 #else
48 static inet_ntop_t gs_fnPtr_inet_ntop = &inet_ntop;
49 #endif
50
51 char CSSHTunnelThread::m_keyboard_interactive_pwd[SSH_MAX_PASSWORD_LEN];
52
CSSHTunnelThread(const wxString tunnelhost,const wxString remote_desthost,const unsigned int remote_destport,const wxString username,const wxString password,const wxString publickey,const wxString privatekey,const enAuthenticationMethod & enAuthMethod,const unsigned int tunnelPort)53 CSSHTunnelThread::CSSHTunnelThread(const wxString tunnelhost, const wxString remote_desthost, const unsigned int remote_destport,
54 const wxString username, const wxString password, const wxString publickey, const wxString privatekey,
55 const enAuthenticationMethod &enAuthMethod, const unsigned int tunnelPort)
56 : m_tunnelhost(tunnelhost), m_remote_desthost(remote_desthost), m_remote_destport(remote_destport), m_username(username),
57 m_password(password), m_publickey(publickey), m_privatekey(privatekey), m_enAuthMethod(enAuthMethod), m_tunnelPort(tunnelPort)
58 {
59 m_local_listenip = wxEmptyString;
60 m_local_listenport = 0;
61 m_listensock = -1, m_sock = -1;
62 m_session = NULL;
63
64 memset(m_keyboard_interactive_pwd, 0 , strlen(m_keyboard_interactive_pwd));
65 strncpy(m_keyboard_interactive_pwd, (const char *)password.mb_str(wxConvUTF8), password.Length());
66 }
67
~CSSHTunnelThread(void)68 CSSHTunnelThread::~CSSHTunnelThread(void)
69 {
70 }
71
Initialize()72 bool CSSHTunnelThread::Initialize()
73 {
74 int rc, auth = AUTH_NONE;
75 const char *fingerprint;
76 char *userauthlist;
77
78 #ifdef WIN32
79 char sockopt;
80 WSADATA wsadata;
81 int err;
82
83 err = WSAStartup(MAKEWORD(2, 0), &wsadata);
84 if(err != 0)
85 {
86 wxLogInfo(wxT("WSAStartup failed with error: %d"), err);
87 return false;
88 }
89 #else
90 int sockopt;
91 #endif
92
93 wxArrayString arrTunnelHostIP;
94
95 if (resolveDNS(m_tunnelhost.mb_str(), arrTunnelHostIP))
96 {
97 rc = libssh2_init (0);
98
99 if (rc != 0)
100 {
101 LogSSHTunnelErrors(wxString::Format(_("libssh2 initialization failed with error code %d"), rc), GetId());
102 return false;
103 }
104
105 /* Connect to SSH server */
106 m_sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
107 m_sin.sin_family = AF_INET;
108 if (INADDR_NONE == (m_sin.sin_addr.s_addr = inet_addr(arrTunnelHostIP.Item(0).mb_str())))
109 {
110 LogSSHTunnelErrors(wxString::Format(_("SSH error: Error in inet address with error code %d"), wxSysErrorCode()), GetId());
111 return false;
112 }
113 m_sin.sin_port = htons(m_tunnelPort);
114 if (connect(m_sock, (struct sockaddr *)(&m_sin),
115 sizeof(struct sockaddr_in)) != 0)
116 {
117 LogSSHTunnelErrors(wxString::Format(_("SSH error: Could not connect to socket with error code %d"), wxSysErrorCode()), GetId());
118 return false;
119 }
120
121 /* Create a session instance */
122 m_session = libssh2_session_init();
123
124 if (!m_session)
125 {
126 LogSSHTunnelErrors(_("SSH error: Could not initialize SSH session!"), GetId());
127 return false;
128 }
129
130 /* ... start it up. This will trade welcome banners, exchange keys,
131 * and setup crypto, compression, and MAC layers
132 */
133 rc = libssh2_session_handshake(m_session, m_sock);
134 if (rc)
135 {
136 LogSSHTunnelErrors(wxString::Format(_("SSH error: Error when starting up SSH session with error code %d"), rc), GetId(), m_session);
137 return false;
138 }
139
140 /* At this point we havn't yet authenticated. The first thing to do
141 * is check the hostkey's fingerprint against our known hosts Your app
142 * may have it hard coded, may go to a file, may present it to the
143 * user, that's your call
144 */
145 fingerprint = libssh2_hostkey_hash(m_session, LIBSSH2_HOSTKEY_HASH_SHA1);
146 wxString newHostKey = wxEmptyString;
147 for(int i = 0; i < 20; i++)
148 {
149 newHostKey += wxString::Format(wxT("%02X "), (unsigned char)fingerprint[i]);
150 }
151
152 // Check if the SSH Host Key is verified
153 if(!IsHostKeyVerified(newHostKey))
154 {
155 Cleanup();
156 return false;
157 }
158
159
160 /* check what authentication methods are available */
161 userauthlist = libssh2_userauth_list(m_session, m_username.mb_str(), strlen(m_username.mb_str()));
162
163 if (strstr(userauthlist, "password"))
164 auth |= AUTH_PASSWORD;
165 if(strstr(userauthlist, "keyboard-interactive"))
166 auth |= AUTH_KEYBOARD_INTERACTIVE;
167 if (strstr(userauthlist, "publickey"))
168 auth |= AUTH_PUBLICKEY;
169
170 if ((auth & AUTH_PASSWORD) && (m_enAuthMethod == AUTH_PASSWORD))
171 auth = AUTH_PASSWORD;
172 else if ((auth & AUTH_KEYBOARD_INTERACTIVE) && (m_enAuthMethod == AUTH_PASSWORD))
173 auth = AUTH_KEYBOARD_INTERACTIVE;
174 if ((auth & AUTH_PUBLICKEY) && (m_enAuthMethod == AUTH_PUBLICKEY))
175 auth = AUTH_PUBLICKEY;
176
177 if (auth & AUTH_PASSWORD)
178 {
179 rc = libssh2_userauth_password(m_session, m_username.mb_str(), m_password.mb_str());
180 if (rc)
181 {
182 LogSSHTunnelErrors(wxString::Format(_("SSH error: Authentication by password failed with error code %d"), rc), GetId(), m_session);
183 Cleanup();
184 return false;
185 }
186 }
187 else if (auth & AUTH_KEYBOARD_INTERACTIVE)
188 {
189 rc = libssh2_userauth_keyboard_interactive(m_session, m_username.mb_str(), &CSSHTunnelThread::keyboard_interactive);
190 if (rc)
191 {
192 LogSSHTunnelErrors(wxString::Format(_("SSH error: Authentication by password failed with error code %d"), rc), GetId(), m_session);
193 Cleanup();
194 return false;
195 }
196 }
197 else if (auth & AUTH_PUBLICKEY)
198 {
199 #ifdef HAVE_GCRYPT
200 rc = libssh2_userauth_publickey_fromfile(m_session, m_username.mb_str(), m_publickey.mb_str(), m_privatekey.mb_str(), m_password.mb_str());
201 #else
202 rc = libssh2_userauth_publickey_fromfile(m_session, m_username.mb_str(), NULL, m_privatekey.mb_str(), m_password.mb_str());
203 #endif
204 if (rc)
205 {
206 LogSSHTunnelErrors(wxString::Format(_("SSH error: Authentication by identity file failed with error code %d"), rc), GetId(), m_session);
207 Cleanup();
208 return false;
209 }
210 }
211 else
212 {
213 LogSSHTunnelErrors(_("SSH error: No supported authentication methods found!"), GetId());
214 Cleanup();
215 return false;
216 }
217
218 // Get the IP Address of local machine
219 wxArrayString arrLocalIP;
220 if(resolveDNS("localhost", arrLocalIP))
221 {
222 m_listensock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
223 memset(&m_sin, 0 , sizeof(m_sin));
224 m_sin.sin_family = AF_INET;
225
226 // Give port no to 0 so that bind will automatically select the available port.
227 m_sin.sin_port = htons(0);
228 if (INADDR_NONE == (m_sin.sin_addr.s_addr = inet_addr(arrLocalIP.Item(0).mb_str())))
229 {
230 Cleanup();
231 return false;
232 }
233
234 sockopt = 1;
235 setsockopt(m_listensock, SOL_SOCKET, SO_REUSEADDR, &sockopt, sizeof(sockopt));
236 m_sinlen = sizeof(m_sin);
237 if (-1 == bind(m_listensock, (struct sockaddr *)&m_sin, m_sinlen))
238 {
239 LogSSHTunnelErrors(wxString::Format(_("SSH error: bind failed with error code %d"), wxSysErrorCode()), GetId());
240 Cleanup();
241 return false;
242 }
243
244 if (getsockname(m_listensock, (struct sockaddr *)&m_sin, &m_sinlen) == -1)
245 {
246 LogSSHTunnelErrors(wxString::Format(_("SSH error: getsockname() failed with error code %d"), wxSysErrorCode()), GetId());
247 Cleanup();
248 return false;
249 }
250
251 if (-1 == listen(m_listensock, 2))
252 {
253 LogSSHTunnelErrors(wxString::Format(_("SSH error: listen failed with error code %d"), wxSysErrorCode()), GetId());
254 Cleanup();
255 return false;
256 }
257
258 m_local_listenip = wxString(inet_ntoa(m_sin.sin_addr), wxConvLibc);
259 m_local_listenport = ntohs(m_sin.sin_port);
260
261 wxLogInfo(wxT("Waiting for TCP connection on %s:%d..."), m_local_listenip.c_str(), m_local_listenport);
262 return true;
263 }
264 else
265 {
266 LogSSHTunnelErrors(_("SSH error: Unable to resolve localhost"), GetId());
267 }
268 }
269 else
270 {
271 LogSSHTunnelErrors(wxString::Format(_("SSH error: Unable to resolve host: %s"), m_tunnelhost.c_str()), GetId());
272 }
273
274 return false;
275 }
276
Entry()277 void *CSSHTunnelThread::Entry()
278 {
279 while (1)
280 {
281 int forwardsock = accept(m_listensock, (struct sockaddr *) & m_sin, &m_sinlen);
282 if (-1 == forwardsock)
283 {
284 #ifdef WIN32
285 if(wxSysErrorCode() != WSAEINTR && wxSysErrorCode() != WSAEBADF)
286 #else
287 if(wxSysErrorCode() != EINTR && wxSysErrorCode() != EBADF && wxSysErrorCode() != ECONNABORTED)
288 #endif
289 {
290 LogSSHTunnelErrors(wxString::Format(_("SSH error: accept failed with error code %d"), wxSysErrorCode()), GetId());
291 Cleanup();
292 }
293 break;
294 }
295
296 // Create thread for read/write.
297 CSubThread *subThread = new CSubThread(m_sin, m_remote_desthost, m_remote_destport, m_session, forwardsock);
298 if ( subThread->Create() != wxTHREAD_NO_ERROR )
299 {
300 delete subThread;
301 subThread = NULL;
302 }
303 else
304 {
305 if (subThread->Run() != wxTHREAD_NO_ERROR )
306 {
307 delete subThread;
308 subThread = NULL;
309 }
310 else
311 {
312 g_SSHThreadMutex.Lock();
313 g_setSocketDescriptor.insert(forwardsock);
314 g_SSHThreadMutex.Unlock();
315 }
316 }
317 }
318
319 return NULL;
320 }
321
Cleanup()322 void CSSHTunnelThread::Cleanup()
323 {
324 // Close all the sockets
325 g_SSHThreadMutex.Lock();
326 subThreadSDSet::iterator it;
327 for( it = g_setSocketDescriptor.begin(); it != g_setSocketDescriptor.end(); ++it )
328 {
329 int socketDescriptor = *it;
330 #ifdef WIN32
331 closesocket(socketDescriptor);
332 #else
333 close(socketDescriptor);
334 #endif
335 }
336 g_SSHThreadMutex.Unlock();
337
338 Sleep(1000);
339
340 if(m_session)
341 {
342 libssh2_session_disconnect(m_session, "Client disconnecting normally");
343 libssh2_session_free(m_session);
344 m_session = NULL;
345 }
346
347 if (m_listensock)
348 {
349 #ifdef WIN32
350 closesocket(m_listensock);
351 #else
352 close(m_listensock);
353 #endif
354 m_listensock = 0;
355 }
356
357 if (m_sock)
358 {
359 #ifdef WIN32
360 closesocket(m_sock);
361 #else
362 close(m_sock);
363 #endif
364 m_sock = 0;
365 }
366
367 libssh2_exit();
368 }
369
resolveDNS(const char * host,wxArrayString & arrIPAddress)370 bool CSSHTunnelThread::resolveDNS(const char *host, wxArrayString &arrIPAddress)
371 {
372 struct addrinfo hints, *res, *p;
373 int status;
374 char ipstr[INET6_ADDRSTRLEN];
375
376 memset(&hints, 0, sizeof hints);
377 memset(&ipstr, 0, sizeof ipstr);
378 hints.ai_family = AF_UNSPEC; // AF_INET or AF_INET6 to force version
379 hints.ai_socktype = SOCK_STREAM;
380 hints.ai_flags = AI_ADDRCONFIG | AI_PASSIVE;
381
382 if ((status = getaddrinfo(host, NULL, &hints, &res)) != 0)
383 {
384 wxLogInfo(wxT("getaddrinfo failed with error code: %d"), status);
385 return false;
386 }
387
388 for(p = res; p != NULL; p = p->ai_next)
389 {
390 void *addr;
391 if (p->ai_family == AF_INET)
392 {
393 struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr;
394 addr = &(ipv4->sin_addr);
395
396 /* convert the IP to a string*/
397 if(NULL != gs_fnPtr_inet_ntop(p->ai_family, addr, ipstr, sizeof ipstr))
398 arrIPAddress.Add(wxString(ipstr, wxConvLocal));
399 }
400 }
401
402 if(res)
403 {
404 freeaddrinfo(res); // free the linked list
405 }
406
407 if(arrIPAddress.Count() > 0)
408 {
409 return true;
410 }
411
412 return false;
413 }
414
keyboard_interactive(const char * name,int name_len,const char * instr,int instr_len,int num_prompts,const struct _LIBSSH2_USERAUTH_KBDINT_PROMPT * prompts,struct _LIBSSH2_USERAUTH_KBDINT_RESPONSE * res,void ** abstract)415 void CSSHTunnelThread::keyboard_interactive(const char *name, int name_len, const char *instr, int instr_len,
416 int num_prompts, const struct _LIBSSH2_USERAUTH_KBDINT_PROMPT *prompts, struct _LIBSSH2_USERAUTH_KBDINT_RESPONSE *res,
417 void **abstract)
418 {
419 if (num_prompts == 1)
420 {
421 res[0].text = strdup(m_keyboard_interactive_pwd);
422 res[0].length = strlen(m_keyboard_interactive_pwd);
423 }
424 }
425
IsHostKeyVerified(const wxString & newHostKey)426 bool CSSHTunnelThread::IsHostKeyVerified(const wxString &newHostKey)
427 {
428 bool bIsVerified = false;
429 wxString cachedHostKey = settings->Read(wxT("HostKeys/") + m_tunnelhost, wxEmptyString);
430
431 // If cached host key is empty then ask user to accept or reject
432 if (cachedHostKey == wxEmptyString)
433 {
434 // Prompt User to accept or reject
435 wxString msg = wxString::Format(wxT("Host key received for the SSH server \"%s\" is \n\n%s\n\nWould you like to accept it and continue with the connection?"), m_tunnelhost.c_str(), newHostKey.c_str());
436 int answer = wxMessageBox(msg, wxT("Host key verification"), wxYES_NO | wxNO_DEFAULT);
437 if (answer == wxYES)
438 {
439 // Write the host key with respect to tunnel host
440 settings->Write(wxT("HostKeys/") + m_tunnelhost, newHostKey);
441 bIsVerified = true;
442 }
443 }
444 else
445 {
446 // Compare the cached host key with the new key
447 if (cachedHostKey.compare(newHostKey) == 0)
448 bIsVerified = true;
449 else
450 {
451 // Prompt user to accept or reject the new host key received for the tunnel host
452 wxString msg = wxString::Format(wxT("The host key received from the server \"%s\" is\n\n%s\n\nbut the stored key is\n\n%s\n\nThis may indicate that this is not the same server that was previously used.\n\nDo you wish to accept and store the new key, and continue with the connection?"), m_tunnelhost.c_str(), newHostKey.c_str(), cachedHostKey.c_str());
453 int answer = wxMessageBox(msg, wxT("Host key verification - WARNING"), wxYES_NO | wxNO_DEFAULT);
454 if (answer == wxYES)
455 {
456 // Write the host key with respect to tunnel host
457 settings->Write(wxT("HostKeys/") + m_tunnelhost, newHostKey);
458 bIsVerified = true;
459 }
460 }
461 }
462
463 return bIsVerified;
464 }
465
CSubThread(const struct sockaddr_in sin,const wxString remote_desthost,const unsigned int remote_destport,LIBSSH2_SESSION * session,int forwardsock)466 CSubThread::CSubThread(const struct sockaddr_in sin, const wxString remote_desthost, const unsigned int remote_destport,
467 LIBSSH2_SESSION *session, int forwardsock)
468 : m_sin(sin), m_remote_desthost(remote_desthost), m_remote_destport(remote_destport),
469 m_subThreadSession(session), m_forwardsock(forwardsock)
470 {
471 }
472
~CSubThread(void)473 CSubThread::~CSubThread(void)
474 {
475 g_SSHThreadMutex.Lock();
476 g_setSocketDescriptor.erase(m_forwardsock);
477 g_SSHThreadMutex.Unlock();
478 }
479
480 void *
Entry()481 CSubThread::Entry()
482 {
483 fd_set fds;
484 struct timeval tv;
485 ssize_t len, wr;
486 char buf[20480];
487 int rc, i = 0;
488
489 const char *shost = inet_ntoa(m_sin.sin_addr);
490 unsigned int sport = ntohs(m_sin.sin_port);
491
492 wxLogInfo(wxT("Forwarding connection from %s:%d to %s:%d"), wxString(inet_ntoa(m_sin.sin_addr), wxConvLibc).c_str(),
493 sport, m_remote_desthost.c_str(), m_remote_destport);
494
495 /* Must use blocking here to avoid connect errors */
496 //libssh2_session_set_blocking(m_subThreadSession, 1);
497
498 while((m_channel = libssh2_channel_direct_tcpip_ex(m_subThreadSession, m_remote_desthost.mb_str(),
499 m_remote_destport, shost, sport)) == NULL)
500 {
501 rc = libssh2_session_last_error(m_subThreadSession, NULL, NULL, 0);
502 if (rc == LIBSSH2_ERROR_EAGAIN)
503 Sleep(10);
504 else
505 break;
506 }
507
508 /* Must use non-blocking IO hereafter due to the current libssh2 API */
509 libssh2_session_set_blocking(m_subThreadSession, 0);
510
511 if (!m_channel)
512 {
513 wxLogInfo(_("SSH error: Could not open a direct-tcpip channel!"));
514 goto shutdown;
515 }
516
517 while (1)
518 {
519 FD_ZERO(&fds);
520 FD_SET(m_forwardsock, &fds);
521 tv.tv_sec = 0;
522 tv.tv_usec = 100000;
523 rc = select(m_forwardsock + 1, &fds, NULL, NULL, &tv);
524 memset(buf, 0, sizeof(buf));
525 if (-1 == rc)
526 {
527 wxLogInfo(_("SSH error: select failed with error code %d"), wxSysErrorCode());
528 goto shutdown;
529 }
530 if (rc && FD_ISSET(m_forwardsock, &fds))
531 {
532 len = recv(m_forwardsock, buf, sizeof(buf), 0);
533 if (len < 0)
534 {
535 wxLogInfo(_("SSH error: read failed with error code %d"), wxSysErrorCode());
536 goto shutdown;
537 }
538 else if (0 == len)
539 {
540 wxLogInfo(_("The client at %s:%d disconnected!"), wxString(inet_ntoa(m_sin.sin_addr), wxConvLibc).c_str(), sport);
541 goto shutdown;
542 }
543 wr = 0;
544 do
545 {
546 i = libssh2_channel_write(m_channel, buf, len);
547
548 if (i < 0)
549 {
550 wxLogInfo(_("SSH error: libssh2_channel_write with error code %d"), i);
551 goto shutdown;
552 }
553 wr += i;
554 }
555 while (i > 0 && wr < len);
556 }
557 while (1)
558 {
559 len = libssh2_channel_read(m_channel, buf, sizeof(buf));
560
561 if (LIBSSH2_ERROR_EAGAIN == len)
562 break;
563 else if (len < 0)
564 {
565 wxLogInfo(_("SSH error: libssh2_channel_read with error code %d"), (int)len);
566 goto shutdown;
567 }
568 wr = 0;
569 while (wr < len)
570 {
571 i = send(m_forwardsock, buf + wr, len - wr, 0);
572 if (i <= 0)
573 {
574 wxLogInfo(_("SSH error: write failed with error code %d"), wxSysErrorCode());
575 goto shutdown;
576 }
577 wr += i;
578 }
579 if (libssh2_channel_eof(m_channel))
580 {
581 wxLogInfo(_("Connection at %s:%d disconnected by server"),
582 wxString(inet_ntoa(m_sin.sin_addr), wxConvLibc).c_str(), sport);
583 goto shutdown;
584 }
585 }
586 }
587 shutdown:
588 #ifdef WIN32
589 closesocket(m_forwardsock);
590 #else
591 close(m_forwardsock);
592 #endif
593
594 if (m_channel)
595 {
596 libssh2_channel_close(m_channel);
597 libssh2_channel_free(m_channel);
598 m_channel = NULL;
599 }
600
601 return NULL;
602 }
603
LogSSHTunnelErrors(const wxString & msg,const int & id,struct _LIBSSH2_SESSION * session)604 void LogSSHTunnelErrors(const wxString &msg, const int &id, struct _LIBSSH2_SESSION *session)
605 {
606 g_SSHThreadMutex.TryLock();
607
608 wxString errorMsg = msg;
609 // If session is not NULL then fetch the last error on that session
610 if (session)
611 {
612 char *errmsg;
613 int errmsg_len;
614 libssh2_session_last_error(session, &errmsg, &errmsg_len, 0);
615 if (errmsg_len > 0)
616 {
617 wxString errmsg_s(errmsg, wxConvLibc);
618 errorMsg += wxString::Format(_(" [%s]"), errmsg_s.c_str());
619 }
620 }
621
622 wxCommandEvent event(SSH_TUNNEL_ERROR_EVENT, id);
623 // Give it some contents
624 event.SetString(errorMsg);
625
626 // Do send it
627 wxPostEvent(winMain, event);
628
629 g_SSHThreadMutex.Unlock();
630 }
631