1 /**
2  * telnetcon.cpp - Class dealing with telnet connections,
3  *                 parsing telnet commands.
4  *
5  * Copyright (c) 2011 Kan-Ru Chen <kanru@kanru.info>
6  * Copyright (c) 2005 PCMan <pcman.tw@gmail.com>
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software Foundation,
20  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21  */
22 
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26 
27 #ifdef __GNUG__
28   #pragma implementation "telnetcon.h"
29 #endif
30 
31 #include "telnetcon.h"
32 #include "telnetview.h"
33 
34 #if !defined(MOZ_PLUGIN)
35 #include "mainframe.h"
36 
37 #ifdef USE_NOTIFIER
38 #ifdef USE_LIBNOTIFY
39 #include <libnotify/notify.h>
40 #else
41 #include "notifier/api.h"
42 #endif
43 #endif
44 
45 #ifdef USE_SCRIPT
46 #include "script/api.h"
47 #endif
48 
49 #endif /* !defined(MOZ_PLUGIN) */
50 
51 #include <cstring>
52 #include <cstdio>
53 #include <glib/gi18n.h>
54 
55 #include "stringutil.h"
56 
57 #if !defined(MOZ_PLUGIN)
58 #include "appconfig.h"
59 #endif /* !defined(MOZ_PLUGIN) */
60 
61 // socket related headers
62 #include <sys/select.h>
63 
64 #include <sys/types.h>
65 #include <sys/socket.h>
66 #include <netinet/in.h>
67 #include <arpa/inet.h>
68 #include <netinet/tcp.h>
69 #include <netdb.h>
70 #include <unistd.h>
71 #include <fcntl.h>
72 #include <errno.h>
73 
74 #include <sys/time.h>
75 
76 // pseudo tty headers
77 #ifdef USING_LINUX
78 #include <pty.h>
79 #endif
80 
81 #include <signal.h>
82 #include <sys/wait.h>
83 #include <sys/types.h>
84 
85 #if defined(USING_FREEBSD) || defined(CSRG_BASED)
86 #include <sys/ioctl.h>
87 #include <termios.h>
88 
89 #if (defined(__APPLE__)) && (defined(__MACH__))
90 #include <util.h>
91 #else
92 #include <libutil.h>
93 #endif
94 
95 #endif
96 
97 #ifdef USE_PROXY
98 #include "proxy.h"
99 #endif
100 
101 #define RECV_BUF_SIZE (4097)
102 
103 #if !defined(MOZ_PLUGIN)
104 #ifdef USE_NANCY
105 bool CTelnetCon::with_nancy_support = true;  // start new connections with nancy support.
106 #endif
107 #endif /* !defined(MOZ_PLUGIN) */
108 
109 // class constructor
CTelnetCon(CTermView * pView,CSite & SiteInfo)110 CTelnetCon::CTelnetCon(CTermView* pView, CSite& SiteInfo)
111 	: CTermData(pView), m_Site(SiteInfo)
112 {
113 #if !defined(MOZ_PLUGIN)
114 #ifdef USE_NANCY
115 	use_nancy = false;  // Dynamic open or close it.
116 	if (with_nancy_support) {
117 		/* XXX:
118 		 * We should assign the path via built-in configurator.
119 		 */
120 		string path_home = getenv("HOME");
121         	bot = new NancyBot("default", (path_home + "/.pcmanx/nancy_bot/").c_str());
122 	}
123 #endif
124 #endif /* !defined(MOZ_PLUGIN) */
125 
126 	m_pBuf = m_pLastByte = m_pRecvBuf = NULL;
127     m_pCmdLine = m_CmdLine;
128 	m_pCmdLine[0] = '\0';
129 
130 	m_State = TS_CONNECTING;
131 	m_Duration = 0;
132 	m_IdleTime = 0;
133 	m_AutoLoginStage = ALS_OFF;
134 
135 	m_SockFD = -1;
136 	m_IOChannel = 0;
137 	m_IOChannelID = 0;
138 	m_Pid = 0;
139 
140 	m_BellTimeout = 0;
141 	m_IsLastLineModified = false;
142 
143 	// Cache the sockaddr_in which can be used to reconnect.
144 	memset(&m_SockAddr, 0, sizeof(m_SockAddr));
145 	m_SockAddr.ss_family = AF_UNSPEC;
146 	m_Port = "23";
147 
148 	gchar* locale_str;
149 	gsize l;
150 	if( !m_Site.GetPreLoginPrompt().empty() )
151 	{
152 		if( (locale_str = g_convert(m_Site.GetPreLoginPrompt().c_str(),
153 									m_Site.GetPreLoginPrompt().length(),
154 									m_Site.m_Encoding.c_str(),
155 									"UTF-8", NULL, &l, NULL)) )
156 		{
157 			m_PreLoginPrompt = locale_str;
158 			g_free(locale_str);
159 		}
160 	}
161 	if( !m_Site.GetLoginPrompt().empty() )
162 	{
163 		if( (locale_str = g_convert(m_Site.GetLoginPrompt().c_str(),
164 									m_Site.GetLoginPrompt().length(),
165 									m_Site.m_Encoding.c_str(),
166 									"UTF-8", NULL, &l, NULL)) )
167 		{
168 			m_LoginPrompt = locale_str;
169 			g_free(locale_str);
170 		}
171 	}
172 	if( !m_Site.GetPasswdPrompt().empty() )
173 	{
174 		if( (locale_str = g_convert(m_Site.GetPasswdPrompt().c_str(),
175 									m_Site.GetPasswdPrompt().length(),
176 									m_Site.m_Encoding.c_str(),
177 									"UTF-8", NULL, &l, NULL)) )
178 		{
179 			m_PasswdPrompt = locale_str;
180 			g_free(locale_str);
181 		}
182 	}
183 }
184 
185 GThread* CTelnetCon::m_DNSThread = NULL;
186 int CTelnetCon::m_SocketTimeout = 30;
187 GMutex* CTelnetCon::m_DNSMutex = NULL;
188 
189 // class destructor
~CTelnetCon()190 CTelnetCon::~CTelnetCon()
191 {
192 #if !defined(MOZ_PLUGIN)
193 #ifdef USE_NANCY
194 	if(bot)
195 		delete bot;
196 #endif
197 #endif /* !defined(MOZ_PLUGIN) */
198 
199 	Close();
200 	INFO("CTelnetCon::~CTelnetCon");
201 	list<CDNSRequest*>::iterator it;
202 	if(m_DNSMutex)
203 		g_mutex_lock(m_DNSMutex);
204 	for( it = m_DNSQueue.begin(); it != m_DNSQueue.end(); ++it)
205 	{
206 		CDNSRequest* thread = *it;
207 		if( thread->m_pCon == this )
208 		{
209 			if( thread->m_Running )
210 				thread->m_pCon = NULL;
211 			else
212 			{
213 				delete thread;
214 				m_DNSQueue.erase(it);
215 				INFO("thread obj deleted in CTelnet::~CTelnet()");
216 			}
217 			break;
218 		}
219 	}
220 	if(m_DNSMutex)
221 		g_mutex_unlock(m_DNSMutex);
222 
223 	if( m_BellTimeout )
224 		g_source_remove( m_BellTimeout );
225 }
226 
227 #ifdef USE_MOUSE
GetMenuChar(int y)228 char CTelnetCon::GetMenuChar(int y)
229 {
230 	gchar* str = m_Screen[y];
231 	for (int i = 0; ; i++)
232 	{
233 		if (str[ i ] !=' ')
234 		{
235 			if ( g_ascii_isalpha(str[i]) )
236 				return str[ i ];
237 			return str[i + 1];
238 		}
239 	}
240 }
241 #endif
242 
OnSocket(GIOChannel * channel UNUSED,GIOCondition type,CTelnetCon * _this)243 gboolean CTelnetCon::OnSocket(GIOChannel *channel UNUSED, GIOCondition type, CTelnetCon* _this)
244 {
245 	bool ret = false;
246 	if( type & G_IO_IN )
247 		ret = _this->OnRecv();
248 	if( type & G_IO_HUP )
249 	{
250 		_this->OnClose();
251 		ret = false;
252 	}
253 	return ret;
254 }
255 
256 // No description
Connect()257 bool CTelnetCon::Connect()
258 {
259 	m_State = TS_CONNECTING;
260 
261 	string address;
262 	m_Port = "23";
263 	PreConnect( address, m_Port );
264 
265 	// If this site has auto-login settings, activate auto-login
266 	// and set it to stage 1, waiting for prelogin prompt or stage 2,
267 	// waiting for login prompt.
268 	INFO("login = %s", m_Site.GetLogin().c_str());
269 	if( !m_Site.GetLogin().empty() /*&& AppConfig.IsLoggedIn()*/ )
270 		m_AutoLoginStage = m_Site.GetPreLogin().empty() ? ALS_LOGIN : ALS_PRELOGIN ;
271 	else if ( !m_Site.GetPasswd().empty() ) /* in case we only need password (ssh) */
272 		m_AutoLoginStage = ALS_PASSWD;
273 	else
274 		m_AutoLoginStage = ALS_OFF;
275 
276 #ifdef USE_EXTERNAL
277 	// Run external program to handle connection.
278 
279 	/* external telnet */
280 	if ( m_Port == "23" && m_Site.m_UseExternalTelnet )
281 	{
282 		// Suggestion from kyl <kylinx@gmail.com>
283 		// Call forkpty() to use pseudo terminal and run an external program.
284 		const char* prog = "telnet";
285 		setenv("TERM", m_Site.m_TermType.c_str() , 1);
286 		// Current terminal emulation is buggy and only suitable for BBS browsing.
287 		// Both xterm or vt??? terminal emulation has not been fully implemented.
288 		m_Pid = forkpty (& m_SockFD, NULL, NULL, NULL );
289 		if ( m_Pid == 0 )
290 		{
291 			// Child Process;
292 			close(m_SockFD);
293 			execlp ( prog, prog, "-8", address.c_str(), NULL ) ;
294 			exit(EXIT_FAILURE);
295 		}
296 		else
297 		{
298 			// Parent process
299 			int flags = fcntl(m_SockFD, F_GETFD);
300 			fcntl(m_SockFD, F_SETFD,
301 				flags | FD_CLOEXEC); /* make m_SockFD
302 							auto close on exec */
303 		}
304 		OnConnect(0);
305 	}
306 	/* external ssh */
307 	else if ( m_Port == "22" && m_Site.m_UseExternalSSH )
308 	{
309 		// Suggestion from kyl <kylinx@gmail.com>
310 		// Call forkpty() to use pseudo terminal and run
311 		// an external program.
312 		const char* prog = "ssh";
313 		setenv("TERM", m_Site.m_TermType.c_str() , 1);
314 		// Current terminal emulation is buggy and only suitable
315 		// for BBS browsing. Both xterm or vt??? terminal emulation
316 		// has not been fully implemented.
317 		m_Pid = forkpty (& m_SockFD, NULL, NULL, NULL );
318 		if ( m_Pid == 0 )
319 		{
320 			// Child Process;
321 			close(m_SockFD);
322 			execlp ( prog, prog, address.c_str(), NULL ) ;
323 			exit(EXIT_FAILURE);
324 		}
325 		else
326 		{
327 			// Parent process
328 			int flags = fcntl(m_SockFD, F_GETFD);
329 			fcntl(m_SockFD, F_SETFD,
330 				flags | FD_CLOEXEC); /* make m_SockFD
331 							auto close on exec */
332 		}
333 		OnConnect(0);
334 	}
335 	else	// Use built-in telnet command handler
336 #endif
337 	{
338 		if( m_SockAddr.ss_family != AF_UNSPEC )
339 			ConnectAsync();
340 		else	// Lookup DNS first.
341 		{
342 			g_mutex_lock(m_DNSMutex);
343 			CDNSRequest* dns_request = new CDNSRequest(this, address, m_Port);
344 			m_DNSQueue.push_back( dns_request );
345 			if( !m_DNSThread ) // There isn't any runnung thread.
346 #if defined(GLIB_VERSION_2_32)
347 				m_DNSThread = g_thread_new( _("Process DNS Queue"), (GThreadFunc)&CTelnetCon::ProcessDNSQueue, NULL);
348 #else
349 				m_DNSThread = g_thread_create( (GThreadFunc)&CTelnetCon::ProcessDNSQueue, NULL, true, NULL);
350 #endif
351 			g_mutex_unlock(m_DNSMutex);
352 		}
353 	}
354 
355     return true;
356 }
357 
358 // No description
OnRecv()359 bool CTelnetCon::OnRecv()
360 {
361 	static unsigned char recv_buf[RECV_BUF_SIZE];
362 	m_pRecvBuf = recv_buf;
363 
364 	if( !m_IOChannel || m_SockFD == -1 )
365 		return false;
366 
367 	gsize rlen = 0;
368 	g_io_channel_read_chars(m_IOChannel, (char*)m_pRecvBuf, (RECV_BUF_SIZE - 1), &rlen, NULL);
369 	if(rlen == 0 && !(m_State & TS_CLOSED) )
370 	{
371 		OnClose();
372 		return false;
373 	}
374 
375     m_pRecvBuf[rlen] = '\0';
376     m_pBuf = m_pRecvBuf;
377     m_pLastByte = m_pRecvBuf + rlen;
378 //	printf("recv (%d): %s\n\n", rlen, m_pRecvBuf);
379     ParseReceivedData();
380 
381 #ifdef USE_MOUSE
382     SetPageState();
383 #endif
384 	UpdateDisplay();
385 
386 //	((CTelnetView*)m_pView)->GetParentFrame()->OnTelnetConRecv((CTelnetView*)m_pView);
387 	return true;
388 }
389 
OnConnect(int code)390 void CTelnetCon::OnConnect(int code)
391 {
392 	if( 0 == code )
393 	{
394 		m_State = TS_CONNECTED;
395 #if !defined(MOZ_PLUGIN)
396 		((CTelnetView*)m_pView)->GetParentFrame()->OnTelnetConConnect((CTelnetView*)m_pView);
397 #endif
398 		m_IOChannel = g_io_channel_unix_new(m_SockFD);
399 		m_IOChannelID = g_io_add_watch( m_IOChannel,
400 		GIOCondition(G_IO_ERR|G_IO_HUP|G_IO_IN), (GIOFunc)CTelnetCon::OnSocket, this );
401 		g_io_channel_set_encoding(m_IOChannel, NULL, NULL);
402 		g_io_channel_set_buffered(m_IOChannel, false);
403 	}
404 	else
405 	{
406 		m_State = TS_CLOSED;
407 		Close();
408 #if !defined(MOZ_PLUGIN)
409 		((CTelnetView*)m_pView)->GetParentFrame()->OnTelnetConClose((CTelnetView*)m_pView);
410 #endif
411 		const char failed_msg[] = "Unable to connect.";
412 		memcpy( m_Screen[0], failed_msg, sizeof(failed_msg) );
413 #if !defined(MOZ_PLUGIN)
414 		if( GetView()->GetParentFrame()->GetCurView() == m_pView )
415 		{
416 			for( unsigned int col = 0; col < sizeof(failed_msg); )
417 				col += m_pView->DrawChar( 0, col );
418 		}
419 #endif
420 	}
421 }
422 
OnClose()423 void CTelnetCon::OnClose()
424 {
425 	m_State = TS_CLOSED;
426 	Close();
427 #if !defined(MOZ_PLUGIN)
428 	((CTelnetView*)m_pView)->GetParentFrame()->OnTelnetConClose((CTelnetView*)m_pView);
429 #endif
430 	//	if disconnected by the server too soon, reconnect automatically.
431 	if( m_Site.m_AutoReconnect > 0 && m_Duration < m_Site.m_AutoReconnect )
432 		Reconnect();
433 }
434 
435 /*
436  * Parse received data, process telnet command
437  * and ANSI escape sequence.
438  */
ParseReceivedData()439 void CTelnetCon::ParseReceivedData()
440 {
441     for( m_pBuf = m_pRecvBuf; m_pBuf < m_pLastByte; m_pBuf++ )
442     {
443 		if (m_Pid == 0) // No external program.  Handle telnet commands ourselves.
444 		{
445 			if( m_CmdLine[0] == TC_IAC )	// IAC, in telnet command mode.
446 			{
447 				ParseTelnetCommand();
448 				continue;
449 			}
450 
451 			if( *m_pBuf == TC_IAC )    // IAC, in telnet command mode.
452 			{
453 				m_CmdLine[0] = TC_IAC;
454 				m_pCmdLine = &m_CmdLine[1];
455 				continue;
456 			}
457 		}
458 		// *m_pBuf is not a telnet command, let genic terminal process it.
459 		CTermData::PutChar( *m_pBuf );
460     }
461 }
462 
463 // Process telnet command.
ParseTelnetCommand()464 void CTelnetCon::ParseTelnetCommand()
465 {
466     *m_pCmdLine = *m_pBuf;
467     m_pCmdLine++;
468 	switch( m_CmdLine[1] )
469 	{
470 	case TC_WILL:
471 		{
472 			if( 3 > (m_pCmdLine-m_CmdLine) )
473 				return;
474 			unsigned char ret[]={TC_IAC,TC_DONT,*m_pBuf};
475 			switch(*m_pBuf)
476 			{
477 			case TO_ECHO:
478 			case TO_SUPRESS_GO_AHEAD:
479 				ret[1] = TC_DO;
480 				break;
481 			}
482 			SendRawString(reinterpret_cast<char*>(ret), 3);
483 			break;
484 		}
485 	case TC_DO:
486 		{
487 			if( 3 > (m_pCmdLine-m_CmdLine) )
488 				return;
489 			unsigned char ret[]={TC_IAC,TC_WILL,*m_pBuf};
490 			switch(*m_pBuf)
491 			{
492 			case TO_TERMINAL_TYPE:
493 			case TO_NAWS:
494 				break;
495 			default:
496 				ret[1] = TC_WONT;
497 			}
498 			SendRawString(reinterpret_cast<char*>(ret),3);
499 			if( TO_NAWS == *m_pBuf )	// Send NAWS
500 			{
501 				unsigned char naws[]={TC_IAC,TC_SB,TO_NAWS,0,80,0,24,TC_IAC,TC_SE};
502 				naws[3] = m_ColsPerPage >>8;	// higher byte
503 				naws[4] = m_ColsPerPage & 0xff; // lower byte
504 				naws[5] = m_RowsPerPage >> 8;	// higher byte
505 				naws[6] = m_RowsPerPage & 0xff; // lower byte
506 				SendRawString( (const char*)naws,sizeof(naws));
507 			}
508 			break;
509 		}
510 	case TC_WONT:
511 	case TC_DONT:
512 		if( 3 > (m_pCmdLine-m_CmdLine) )
513 			return;
514 		break;
515 	case TC_SB:	// sub negotiation
516 		if( *m_pBuf == TC_SE )	// end of sub negotiation
517 		{
518 			switch( m_CmdLine[2] )
519 			{
520 			case TO_TERMINAL_TYPE:
521 				{
522 					// Return terminal type.  2004.08.05 modified by PCMan.
523 					unsigned char ret_head[] = { TC_IAC, TC_SB, TO_TERMINAL_TYPE, TO_IS };
524 					unsigned char ret_tail[] = { TC_IAC, TC_SE };
525 					int ret_len = 4 + 2 + m_Site.m_TermType.length();
526 					unsigned char *ret = new unsigned char[ret_len];
527 					memcpy( ret, ret_head, 4);
528 					memcpy( ret + 4, m_Site.m_TermType.c_str(), m_Site.m_TermType.length() );
529 					memcpy( ret + 4 + m_Site.m_TermType.length() , ret_tail, 2);
530 					SendRawString( (const char*)ret, ret_len);
531 					delete []ret;
532 				}
533 			}
534 		}
535 		else
536 			return;	// prevent m_CmdLine from being cleard.
537 	}
538 	m_CmdLine[0] = '\0';
539 	m_pCmdLine = m_CmdLine;
540 }
541 
Disconnect()542 void CTelnetCon::Disconnect()
543 {
544 	if( m_State != TS_CONNECTED )
545 		return;
546 	Close();
547 }
548 
OnTimer()549 void CTelnetCon::OnTimer()
550 {
551 	if( m_State == TS_CLOSED )
552 		return;
553 	m_Duration++;
554 	m_IdleTime++;
555 //	Note by PCMan:
556 //	Here is a little trick.
557 //	Since we have increased m_IdleTime by 1, it's impossible for
558 //	m_IdleTime to equal zero.
559 //	When 'Anti Idle' is disabled, m_Site.m_AntiIdle must = 0.
560 //	So m_Site.m_AntiIdle != m_IdleTimeand, and the following SendRawString() won't be called.
561 //	Hence we don't need to check if 'Anti Idle' is enabled or not.
562 	if( m_Site.m_AntiIdle == m_IdleTime )
563 	{
564 		//	2004.8.5 Added by PCMan.	Convert non-printable control characters.
565 		INFO("AntiIdle: %s", m_Site.m_AntiIdleStr.c_str() );
566 		string aistr = UnEscapeStr( m_Site.m_AntiIdleStr.c_str() );
567 		SendRawString( aistr.c_str(), aistr.length() );
568 	}
569 	//	When SendSRawtring() is called, m_IdleTime is set to 0 automatically.
570 }
571 
572 
573 //	Virtual function called from parent class to let us determine
574 //	whether to beep or show a visual indication instead.
Bell()575 void CTelnetCon::Bell()
576 {
577 #if !defined(MOZ_PLUGIN)
578 	((CTelnetView*)m_pView)->GetParentFrame()->OnTelnetConBell((CTelnetView*)m_pView);
579 #endif
580 	if( m_BellTimeout )
581 		g_source_remove( m_BellTimeout );
582 
583 	m_BellTimeout = g_timeout_add( 500, (GSourceFunc)CTelnetCon::OnBellTimeout, this );
584 }
585 
586 
OnBellTimeout(CTelnetCon * _this)587 bool CTelnetCon::OnBellTimeout( CTelnetCon* _this )
588 {
589 	INFO("on bell timer");
590 	if( _this->m_IsLastLineModified )
591 	{
592 		char* line = _this->m_Screen[ _this->m_RowsPerPage-1 ];
593 		// Convert received message to UTF-8
594 		gsize l;
595 		gchar *utf8_text = g_convert(
596 			line, strlen(line),
597 			"UTF-8", _this->m_Site.m_Encoding.c_str(),
598 			NULL, &l, NULL);
599 
600 		if(utf8_text)
601 		{
602 			_this->OnNewIncomingMessage( utf8_text );
603 			g_free(utf8_text);
604 		}
605 		_this->m_IsLastLineModified = false;
606 	}
607 	_this->m_BellTimeout = 0;
608 	return false;
609 }
610 
611 
CheckAutoLogin(int row)612 void CTelnetCon::CheckAutoLogin(int row)
613 {
614 	if( m_AutoLoginStage > ALS_PASSWD )	// This shouldn't happen, but just in case.
615 		return;
616 	INFO("check auto login: %d", row);
617 
618 	const char* prompts[] = {
619 		NULL,	//	Just used to increase array indices by one.
620 		m_PreLoginPrompt.c_str(),	//	m_AutoLoginStage = 1 = ALS_PROMPT
621 		m_LoginPrompt.c_str(),	//	m_AutoLoginStage = 2 = ALS_LOGIN
622 		m_PasswdPrompt.c_str() };	//	m_AutoLoginStage = 3 = ALS_PASSWD
623 
624 	if( strstr(m_Screen[row], prompts[m_AutoLoginStage] ) )
625 	{
626 		const char* responds[] = {
627 			NULL,	//	Just used to increase array indices by one.
628 			m_Site.GetPreLogin().c_str(),	//	m_AutoLoginStage = 1
629 			m_Site.GetLogin().c_str(),	//	m_AutoLoginStage = 2
630 			m_Site.GetPasswd().c_str(),	//	m_AutoLoginStage = 3
631 			""	//	m_AutoLoginStage = 4, turn off auto-login
632 			};
633 
634 		string respond = responds[m_AutoLoginStage];
635 		UnEscapeStr(respond);
636 		respond += '\n';
637 		SendString(respond);	// '\n' will be converted to m_Site.GetCRLF() here.
638 
639 		if( (++m_AutoLoginStage) >= ALS_END )	// Go to next stage.
640 		{
641 			m_AutoLoginStage = ALS_OFF;	// turn off auto-login after all stages end.
642 			respond = m_Site.GetPostLogin();	// Send post-login string
643 			if( respond.length() > 0 )
644 			{
645 				// Unescape all control characters.
646 				UnEscapeStr(respond);
647 				SendString(respond);
648 			}
649 		}
650 	}
651 }
652 
SendUnEscapedString(string str)653 void CTelnetCon::SendUnEscapedString(string str)
654 {
655 	UnEscapeStr(str);
656 	SendString(str);
657 }
658 
SendString(string str)659 void CTelnetCon::SendString(string str)
660 {
661 //	str.Replace( "\n", m_Site.GetCRLF(), true);
662 	string str2;
663 	const char* crlf = m_Site.GetCRLF();
664 	for( const char* pstr = str.c_str(); *pstr; ++pstr )
665 		if( *pstr == '\n' )
666 			str2 += crlf;
667 		else
668 			str2 += *pstr;
669 	gsize l;
670 	gchar* _text = g_convert(str2.c_str(), str2.length(), m_Site.m_Encoding.c_str(), "UTF-8", NULL, &l, NULL);
671 	if( _text )
672 	{
673 		SendRawString(_text, strlen(_text));
674 		g_free(_text);
675 	}
676 }
677 
678 
PreConnect(string & address,string & port)679 void CTelnetCon::PreConnect(string& address, string& port)
680 {
681 	m_Duration = 0;
682 	m_IdleTime = 0;
683 	m_State = TS_CONNECTING;
684 
685 	string::size_type lbracket = m_Site.m_URL.find_first_of('[');
686 	string::size_type rbracket = m_Site.m_URL.find_last_of(']');
687 	// IPv6 address literal, see RFC 3986 3.2.2
688 	// We do not do any validation though.
689 	if ( lbracket != string::npos && rbracket != string::npos )
690 	{
691 		address = m_Site.m_URL.substr(lbracket+1, rbracket-lbracket-1 );
692 		string::size_type p = m_Site.m_URL.find_first_of(':', rbracket+1);
693 		if( p != string::npos )		// use port other then 23;
694 			port = m_Site.m_URL.substr(p+1);
695 	}
696 	else
697 	{
698 		string::size_type p = m_Site.m_URL.find_last_of(':');
699 		if( p != string::npos )		// use port other then 23;
700 		{
701 			port = m_Site.m_URL.substr(p+1);
702 			address = m_Site.m_URL.substr(0, p);
703 		}
704 		else
705 			address = m_Site.m_URL;
706 	}
707 }
708 
Send(void * buf,int len)709 int CTelnetCon::Send(void *buf, int len)
710 {
711 	if( m_IOChannel && m_State & TS_CONNECTED )
712 	{
713 		gsize wlen = 0;
714 		g_io_channel_write_chars(m_IOChannel, (char*)buf, len, &wlen, NULL);
715 
716 		if( wlen > 0 )
717 			m_IdleTime = 0;	// Since data has been sent, we are not idle.
718 
719 		return wlen;
720 	}
721 	return 0;
722 }
723 
724 list<CDNSRequest*> CTelnetCon::m_DNSQueue;
725 
DoDNSLookup(CDNSRequest * data)726 void CTelnetCon::DoDNSLookup( CDNSRequest* data )
727 {
728 	struct addrinfo *res = NULL;
729 	struct addrinfo hints;
730 
731 	memset( &hints, 0, sizeof(struct addrinfo) );
732 #ifdef USE_PROXY
733 	if ( &data->m_pCon->m_Site.m_ProxyType != PROXY_NONE ) // use proxy server
734 	{
735 // TODO: Socks5 accepts IPv6, but we only use IPv4 for now
736 		hints.ai_family = AF_INET;
737 	}
738 #endif
739 //  Because of the usage of thread pool, all DNS requests are queued
740 //  and be executed one by one.  So no mutex lock is needed anymore.
741 	int ret = getaddrinfo(data->m_Address.c_str()
742 			      , data->m_pCon->m_Port.c_str()
743 			      , &hints
744 			      , &res);
745 
746 	g_mutex_lock(m_DNSMutex);
747 	if( data && data->m_pCon)
748 	{
749 		if ( !ret )
750 		{
751 			memcpy(&data->m_pCon->m_SockAddr, res->ai_addr, res->ai_addrlen);
752 			freeaddrinfo(res);
753 		}
754 		g_idle_add((GSourceFunc)OnDNSLookupEnd, data->m_pCon);
755 	}
756 	g_mutex_unlock(m_DNSMutex);
757 }
758 
Close()759 void CTelnetCon::Close()
760 {
761 	m_State = TS_CLOSED;
762 
763 	if( m_IOChannel )
764 	{
765 		g_source_remove(m_IOChannelID);
766 		m_IOChannelID = 0;
767 		g_io_channel_shutdown(m_IOChannel, true, NULL);
768 		g_io_channel_unref(m_IOChannel);
769 		m_IOChannel = NULL;
770 	}
771 
772 	if( m_SockFD != -1 )
773 	{
774 		close( m_SockFD ); /* FIXME: actually unnecessary, since
775 				      g_io_channel operations will take care
776 				      of this. */
777 		m_SockFD = -1;
778 		if( m_Pid )
779 		{
780 			/* FIXME: unnecessary again, since child has already
781 			 * received SIGHUP when m_SockFD was closed. */
782 			int kill_ret = kill( m_Pid, 1 ); // SIG_HUP Is this correct?
783 			int status = 0;
784 			pid_t wait_ret = waitpid(m_Pid, &status, 0);
785 			(void) kill_ret; (void) wait_ret; // suppress warnings
786 			DEBUG("pid=%d, kill=%d, wait=%d", m_Pid, kill_ret, wait_ret);
787 			m_Pid = 0;
788 		}
789 	}
790 }
791 
Init()792 void CTelnetCon::Init()
793 {
794 	if (m_DNSMutex == NULL)
795 	{
796 #if defined(GLIB_VERSION_2_32)
797 		m_DNSMutex = g_new (GMutex, 1);
798 		g_mutex_init(m_DNSMutex);
799 #else
800 		m_DNSMutex = g_mutex_new();
801 #endif
802 	}
803 }
804 
Cleanup()805 void CTelnetCon::Cleanup()
806 {
807 	if( m_DNSThread )
808 		g_thread_join(m_DNSThread);
809 
810 	if(m_DNSMutex)
811 	{
812 #if defined(GLIB_VERSION_2_32)
813 		g_free(m_DNSMutex);
814 #else
815 		g_mutex_free(m_DNSMutex);
816 #endif
817 		m_DNSMutex = NULL;
818 	}
819 }
820 
Reconnect()821 void CTelnetCon::Reconnect()
822 {
823 	ClearScreen(2);
824 	m_CaretPos.x = m_CaretPos.y = 0;
825 	Connect();
826 }
827 
OnLineModified(int row)828 void CTelnetCon::OnLineModified(int row)
829 {
830     /// @todo implement me
831 	if( m_AutoLoginStage > ALS_OFF )
832 		CheckAutoLogin(row);
833 	INFO("line %d is modified", row);
834 	if( row == (m_RowsPerPage-1) )	// If last line is modified
835 		m_IsLastLineModified = true;
836 }
837 
838 #if !defined(MOZ_PLUGIN)
839 
840 #ifdef USE_NOTIFIER
841 
popup_win_clicked(GtkWidget * widget,CTelnetCon * con)842 void popup_win_clicked(GtkWidget* widget, CTelnetCon* con)
843 {
844 	INFO("popup clicked");
845 	CMainFrame* mainfrm = con->GetView()->GetParentFrame();
846 	mainfrm->SwitchToCon(con);
847 	gtk_widget_destroy( gtk_widget_get_parent(widget) );
848 	gtk_window_present(GTK_WINDOW(mainfrm->m_Widget));
849 	return;
850 }
851 
852 #endif
853 
854 #endif /* !defined(MOZ_PLUGIN) */
855 
856 // When new incoming message is detected, this function gets called with the
857 // received message encoded in UTF-8 passed in 'char* line'.
OnNewIncomingMessage(const char * line)858 void CTelnetCon::OnNewIncomingMessage(const char* line)	// line is already a UTF-8 string.
859 {
860 #if !defined(MOZ_PLUGIN)
861 #ifdef USE_NANCY
862 	if( bot && use_nancy )
863 	{
864 		if ( !*line )
865 			return;
866 
867 		string sub;
868 		string sub2;
869 		string str(line);
870 
871 		unsigned int n = str.find_first_of("  ");  // cut userid and spaces at head
872 		if( n != string::npos) // found
873 			sub = str.substr(n+1);
874 
875 		while(sub[0] == ' ')
876 			sub = sub.substr(1);
877 
878 		int m = sub.find_last_not_of(" ");  // cut spaces at tail
879 		if( n != string::npos)
880 			sub2 = sub.erase(m+1);
881 		string str_to_send = "\022" + bot->askNancy(sub2)
882 			+ "\015y\015\033[A\033[B";
883 		SendString( str_to_send.c_str() );
884 	} // end if
885 #endif  // USE_NANCY
886 
887 #ifdef USE_NOTIFIER
888 	if ( !AppConfig.PopupNotifier || !*line )
889 		return;
890 
891 	/*
892 	   We don't need to convert the incoming message into UTF-8 encoding here.
893 	   This is already done before CTelnetCon::OnNewIncomingMessage is called.
894 	 */
895 #ifdef USE_SCRIPT
896 	ScriptOnNewIncomingMessage(this, line);
897 #endif
898 
899 	CMainFrame* mainfrm = ((CTelnetView*)m_pView)->GetParentFrame();
900 	if( mainfrm->IsActivated() && mainfrm->GetCurCon() == this )
901 		return;
902 
903 	gchar **column = g_strsplit(line, " ", 2);
904 #ifdef USE_LIBNOTIFY
905 	gchar *t;
906 	char body[256];
907 	char summary[256];
908 	t = g_markup_escape_text(column[0], -1);
909 	g_snprintf(summary, 256, "%s - %s",
910 		   m_Site.m_Name.c_str(),
911 		   g_strchomp(t));
912     g_free(t);
913 	t = g_markup_escape_text(column[1], -1);
914 	g_snprintf(body, 256, "%s", g_strchomp(t));
915 	g_free(t);
916 	NotifyNotification *notification =
917 	  notify_notification_new(
918 				  summary,
919 				  body,
920 #if !defined(NOTIFY_CHECK_VERSION)
921 				  NULL,
922 #endif
923 				  NULL);
924 	notify_notification_set_timeout(notification,
925 					AppConfig.PopupTimeout*1000);
926 	notify_notification_set_icon_from_pixbuf(notification,
927 						 mainfrm->GetMainIcon());
928 	notify_notification_show(notification,
929 				 NULL);
930 	g_object_unref(G_OBJECT(notification));
931 #else
932 	/*GtkWidget* popup_win = */ popup_notifier_notify(
933 		g_strdup_printf("%s - %s",
934 			m_Site.m_Name.c_str(),
935 			g_strchomp(column[0])),
936 		g_strchomp(column[1]),
937 		m_pView->m_Widget,
938 		G_CALLBACK(popup_win_clicked),
939 		this);
940 #endif
941 	g_strfreev(column);
942 #endif
943 
944 #endif /* !defined(MOZ_PLUGIN) */
945 }
946 
OnDNSLookupEnd(CTelnetCon * _this)947 gboolean CTelnetCon::OnDNSLookupEnd(CTelnetCon* _this)
948 {
949 	INFO("CTelnetCon::OnDNSLookupEnd");
950 	g_mutex_lock(m_DNSMutex);
951 	if( _this->m_SockAddr.ss_family != AF_UNSPEC )
952 		_this->ConnectAsync();
953 	g_mutex_unlock(m_DNSMutex);
954 	return false;
955 }
956 
957 
OnConnectCB(GIOChannel * channel,GIOCondition type,CTelnetCon * _this)958 gboolean CTelnetCon::OnConnectCB(GIOChannel *channel, GIOCondition type, CTelnetCon* _this)
959 {
960 //	g_source_remove(m_IOChannelID);
961 	_this->m_IOChannelID = 0;
962 	g_io_channel_unref(channel);
963 	_this->m_IOChannel = NULL;
964 	_this->OnConnect( (type & G_IO_OUT) ? 0 : -1);
965 	return false;	// The event source will be removed.
966 }
967 
ConnectAsync()968 void CTelnetCon::ConnectAsync()
969 {
970 	int err;
971 
972 #ifdef USE_PROXY
973 	if ( m_Site.m_ProxyType == PROXY_NONE ) // don't use proxy server
974 	{
975 #endif
976 		m_SockFD = socket(m_SockAddr.ss_family, SOCK_STREAM, 0);
977 		int sock_flags = fcntl(m_SockFD, F_GETFL, 0);
978 		fcntl(m_SockFD, F_SETFL, sock_flags | O_NONBLOCK);
979 		/* Disable the Nagle (TCP No Delay) algorithm
980 		 *
981 		 * Nagle algorithm works well to minimize small packets by
982 		 * concatenating them into larger ones. However, for telnet
983 		 * application, the experience would be less than desirable
984 		 * if the user were required to fill a segment with typed
985 		 * characters before the packet was sent.
986 		 */
987 		setsockopt(m_SockFD, IPPROTO_TCP, TCP_NODELAY, (char *)&sock_flags, sizeof(sock_flags));
988 		if ( m_SockAddr.ss_family == AF_INET )
989 			err = connect( m_SockFD, (sockaddr*)&m_SockAddr, sizeof(sockaddr_in) );
990 		else
991 			err = connect( m_SockFD, (sockaddr*)&m_SockAddr, sizeof(sockaddr_in6) );
992 		fcntl(m_SockFD, F_SETFL, sock_flags );
993 #ifdef USE_PROXY
994 	}
995 	else // use proxy server
996 	{
997 		struct addrinfo *res = NULL;
998 		struct addrinfo hints;
999 		struct sockaddr_in proxy_addr;
1000 		memset( &hints, 0, sizeof(struct addrinfo) );
1001 		// TODO: Socks5 accepts IPv6, but we only use IPv4 for now
1002 		hints.ai_family = AF_INET;
1003 		err = getaddrinfo( m_Site.m_ProxyAddr.c_str()
1004 				   , m_Site.m_ProxyPort.c_str()
1005 				   , &hints
1006 				   , &res );
1007 		if ( !err )
1008 		{
1009 			memcpy( &proxy_addr, res->ai_addr, res->ai_addrlen );
1010 			m_SockFD = proxy_connect( &m_SockAddr
1011 						  , m_Site.m_ProxyType
1012 						  , &proxy_addr
1013 						  , m_Site.m_ProxyUser.c_str()
1014 						  , m_Site.m_ProxyPass.c_str() );
1015 			err = m_SockFD == -1 ? -1:0;
1016 			freeaddrinfo( res );
1017 		}
1018 	}
1019 #endif
1020 
1021 	if( err == 0 )
1022 		OnConnect( 0 );
1023 	else if( errno == EINPROGRESS )
1024 	{
1025 		m_IOChannel = g_io_channel_unix_new(m_SockFD);
1026 		m_IOChannelID = g_io_add_watch( m_IOChannel,
1027 			GIOCondition(G_IO_ERR|G_IO_HUP|G_IO_OUT|G_IO_IN|G_IO_NVAL), (GIOFunc)CTelnetCon::OnConnectCB, this );
1028 	}
1029 	else
1030 		OnConnect(-1);
1031 }
1032 
ProcessDNSQueue(gpointer unused UNUSED)1033 void CTelnetCon::ProcessDNSQueue(gpointer unused UNUSED)
1034 {
1035 	INFO("begin run dns threads");
1036 	g_mutex_lock(m_DNSMutex);
1037 	list<CDNSRequest*>::iterator it = m_DNSQueue.begin();
1038 	while( it != m_DNSQueue.end() )
1039 	{
1040 		CDNSRequest* data = *it;
1041 		data->m_Running = true;
1042 		if( data->m_pCon )
1043 		{
1044 			g_mutex_unlock(m_DNSMutex);
1045 			DoDNSLookup(data);
1046 			g_mutex_lock(m_DNSMutex);
1047 			data->m_Running = false;
1048 		}
1049 		delete *it;
1050 		it = m_DNSQueue.erase(it);
1051 		INFO("thread obj deleted in CTelnetCon::ProcessDNSQueue()");
1052 	}
1053 	g_idle_add((GSourceFunc)&CTelnetCon::OnProcessDNSQueueExit, NULL);
1054 	g_mutex_unlock(m_DNSMutex);
1055 	INFO("CTelnetCon::ProcessDNSQueue() returns");
1056 }
1057 
OnProcessDNSQueueExit(gpointer unused UNUSED)1058 bool CTelnetCon::OnProcessDNSQueueExit(gpointer unused UNUSED)
1059 {
1060 	g_mutex_lock(m_DNSMutex);
1061 	g_thread_join( m_DNSThread );
1062 
1063 	m_DNSThread = NULL;
1064 	if( !m_DNSQueue.empty() )
1065 	{
1066 		INFO("A new thread has to be started");
1067 #if defined(GLIB_VERSION_2_32)
1068 		m_DNSThread = g_thread_new( _("Process DNS Queue"), (GThreadFunc)&CTelnetCon::ProcessDNSQueue, NULL);
1069 #else
1070 		m_DNSThread = g_thread_create( (GThreadFunc)&CTelnetCon::ProcessDNSQueue, NULL, true, NULL);
1071 #endif
1072 		// If some DNS requests are queued just before the thread exits,
1073 		// we should start a new thread.
1074 	}
1075 	g_mutex_unlock(m_DNSMutex);
1076 	INFO("all threads end");
1077 	return false;
1078 }
1079 
1080 #ifdef USE_MOUSE
SetPageState()1081 void CTelnetCon::SetPageState()
1082 {
1083 	m_nPageState = -1; //NORMAL
1084 
1085 	char* pLine = m_Screen[m_FirstLine];
1086 
1087 	if( IsUnicolor(pLine, 0, m_ColsPerPage / 2) )
1088 	{
1089 		pLine = m_Screen[m_FirstLine + 2];
1090 		if(IsUnicolor(pLine,0,m_ColsPerPage / 2))
1091 			m_nPageState = 1; // LIST
1092 		else
1093 			m_nPageState = 0; // MENU
1094 	}
1095 	else
1096 	{
1097 		pLine = m_Screen[m_FirstLine + m_RowsPerPage - 1];
1098 		if( IsUnicolor(pLine, m_ColsPerPage / 3, m_ColsPerPage * 2 / 3) )
1099 			m_nPageState = 2; // READING
1100 	}
1101 
1102 }
1103 
IsUnicolor(char * pLine,int start,int end)1104 bool CTelnetCon::IsUnicolor(char* pLine, int start, int end)
1105 {
1106 	CTermCharAttr* pAttr = GetLineAttr(pLine);
1107 	GdkColor* clr = pAttr[start].GetBgColor( CTermCharAttr::GetDefaultColorTable() );
1108 
1109 	// a dirty hacking, because of the difference between maple and firebird bbs.
1110 	for ( int i = start; i < end; i++)
1111 	{
1112 		GdkColor* clr1 = pAttr[i].GetBgColor( CTermCharAttr::GetDefaultColorTable() );
1113 		if (clr1 != clr || clr1 == CTermCharAttr::GetDefaultColorTable(0))
1114 		{
1115 			return false;
1116 		}
1117 	}
1118 
1119 	return true;
1120 }
1121 #endif
1122