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