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