File indexing completed on 2024-03-24 04:05:24
0001 /*************************************************************************** 0002 ctelnet.cpp - telnet... 0003 This file is a part of KMuddy distribution. 0004 ------------------- 0005 begin : Pi Jun 14 2002 0006 copyright : (C) 2002-2008 by Tomas Mecir 0007 email : kmuddy@kmuddy.com 0008 ***************************************************************************/ 0009 0010 /*************************************************************************** 0011 * * 0012 * This program is free software; you can redistribute it and/or modify * 0013 * it under the terms of the GNU General Public License as published by * 0014 * the Free Software Foundation; either version 2 of the License, or * 0015 * (at your option) any later version. * 0016 * * 0017 ***************************************************************************/ 0018 0019 #define CTELNET_CPP 0020 0021 #include "ctelnet.h" 0022 0023 #include "cactionmanager.h" 0024 #include "cglobalsettings.h" 0025 #include "cmccp.h" 0026 #include "cprofilesettings.h" 0027 #include "cmsp.h" 0028 0029 // needed for removeSession which is called in disconnect 0030 #include "csessionmanager.h" 0031 0032 #ifdef HAVE_MXP 0033 #include "cmxpmanager.h" 0034 #endif 0035 0036 #include <KLocalizedString> 0037 #include <kmessagebox.h> 0038 0039 #include <QTextCodec> 0040 #include <QTcpSocket> 0041 0042 #include <stdio.h> 0043 0044 using namespace std; 0045 0046 struct cTelnetPrivate { 0047 /** socket */ 0048 QTcpSocket *socket; 0049 QString hostName; 0050 int hostPort; 0051 0052 QString encoding; 0053 0054 QTextCodec *codec; 0055 QTextDecoder *inCoder; 0056 QTextEncoder *outCoder; 0057 0058 /** object that handles MCCP */ 0059 cMCCP *MCCP; 0060 0061 /** object that handles MSP */ 0062 cMSP *MSP; 0063 bool usingmsp; 0064 0065 #ifdef HAVE_MXP 0066 bool usingmxp, mxpNegotiated; 0067 int mxpallow; 0068 #endif 0069 0070 //iac: last char was IAC 0071 //iac2: last char was DO, DONT, WILL or WONT 0072 //insb: we're in IAC SB, waiting for IAC SE 0073 string command; 0074 bool iac, iac2, insb; 0075 0076 /** has any data arrived since last call to waitingForData() */ 0077 bool newdata; 0078 /** are we connected or commecting? */ 0079 bool _connected, _connecting; 0080 0081 /** current state of options on our side and on server side */ 0082 bool myOptionState[256], hisOptionState[256]; 0083 /** whether we have announced WILL/WON'T for that option (if we have, we don't 0084 respond to DO/DON'T sent by the server -- see implementation and RFC 854 0085 for more information... */ 0086 bool announcedState[256]; 0087 /** whether the server has already announced his WILL/WON'T */ 0088 bool heAnnouncedState[256]; 0089 /** whether we have tried to enable this option */ 0090 bool triedToEnable[256]; 0091 /** amount of bytes sent up to now */ 0092 int sentbytes; 0093 /** have we received the GA signal? */ 0094 bool recvdGA; 0095 /** should we prepend newline after receving a GA */ 0096 bool prependGANewLine; 0097 bool t_cmdEcho; 0098 bool t_lpmudstyle; 0099 bool _startupneg; 0100 /** current dimensions */ 0101 int curX, curY; 0102 /** offline connection */ 0103 bool offLineConnection; 0104 0105 QString termType; 0106 }; 0107 0108 #define DEFAULT_ENCODING "ISO 8859-1" 0109 0110 cTelnet::cTelnet (int sess) : cActionBase ("telnet", sess) 0111 { 0112 d = new cTelnetPrivate; 0113 0114 d->socket = nullptr; 0115 d->termType = "KMuddy"; 0116 0117 d->codec = nullptr; 0118 d->inCoder = nullptr; 0119 d->outCoder = nullptr; 0120 0121 d->iac = d->iac2 = d->insb = false; 0122 d->command = ""; 0123 0124 d->sentbytes = 0; 0125 d->offLineConnection=false; 0126 d->_connected = false; 0127 d->_connecting = false; 0128 d->curX = 0; 0129 d->curY = 0; 0130 d->_startupneg = true; 0131 d->encoding = DEFAULT_ENCODING; 0132 0133 reset (); 0134 0135 d->MCCP = new cMCCP (this); 0136 0137 d->MSP = new cMSP (sess); 0138 d->usingmsp = false; 0139 0140 #ifdef HAVE_MXP 0141 d->usingmxp = false; 0142 d->mxpallow = 3; //auto-detect 0143 d->mxpNegotiated = false; 0144 #endif 0145 0146 addEventHandler ("dimensions-changed", 20, PT_INT); 0147 addEventHandler ("settings-changed", 50, PT_NOTHING); 0148 addGlobalEventHandler ("global-settings-changed", 50, PT_NOTHING); 0149 } 0150 0151 cTelnet::~cTelnet() 0152 { 0153 if (isConnected ()) 0154 disconnect (); 0155 0156 removeEventHandler ("dimensions-changed"); 0157 removeEventHandler ("settings-changed"); 0158 removeGlobalEventHandler ("global-settings-changed"); 0159 0160 delete d->socket; 0161 delete d->MCCP; 0162 delete d->MSP; 0163 0164 delete d->inCoder; 0165 delete d->outCoder; 0166 0167 delete d; 0168 d = nullptr; 0169 } 0170 0171 void cTelnet::eventNothingHandler (QString event, int) 0172 { 0173 if (event == "global-settings-changed") { 0174 cGlobalSettings *gs = cGlobalSettings::self(); 0175 //Command echo parameter passed onwards ot cTelnet 0176 setCommandEcho (gs->getBool ("command-echo")); 0177 0178 // LPMUD style prompt handling too... 0179 setLPMudStyle (gs->getBool ("lpmud-style")); 0180 0181 //MUD Sound Protocol 0182 QStringList sounddirs; 0183 for (int i = 0; i < gs->getInt ("snd-path-count"); ++i) 0184 sounddirs << gs->getString ("snd-path-" + QString::number(i)); 0185 setMSPGlobalPaths (sounddirs); 0186 setMSPAllowed (gs->getBool ("msp-allow")); 0187 setDownloadAllowed (gs->getBool ("msp-allow-downloads")); 0188 0189 } 0190 else if (event == "settings-changed") { 0191 cProfileSettings *sett = settings(); 0192 if (!sett) return; 0193 // check if we need to update the codec 0194 QString enc = sett->getString ("encoding"); 0195 if (enc != d->encoding) { 0196 d->encoding = enc; 0197 setupEncoding (); 0198 } 0199 #ifdef HAVE_MXP 0200 // MXP settings 0201 setMXPAllowed (sett->getInt ("use-mxp")); 0202 #endif 0203 // telnet negotiation on startup 0204 setNegotiateOnStartup (sett->getBool ("startup-negotiate")); 0205 setLPMudStyle (sett->getBool ("lpmud-style")); 0206 } 0207 } 0208 0209 void cTelnet::eventIntHandler (QString event, int, int par1, int par2) 0210 { 0211 if (event == "dimensions-changed") { 0212 windowSizeChanged (par1, par2); 0213 } 0214 } 0215 0216 void cTelnet::socketFailed () 0217 { 0218 if (d->_connected) return; // nothing if we already are connected 0219 QString err = d->socket->errorString(); 0220 cActionManager::self()->invokeEvent ("connection-failed", sess(), err); 0221 d->_connected = false; 0222 d->_connecting = false; 0223 d->socket->deleteLater (); 0224 d->socket = nullptr; 0225 } 0226 0227 /** establishes a new connection */ 0228 void cTelnet::connectIt (const QString &address, int port, cProfileSettings *sett) 0229 { 0230 // close existing connection first (if any) 0231 if (isConnected ()) 0232 disconnect (); 0233 0234 // handle offline connection 0235 if (isOffLineConnection()) 0236 { 0237 d->_connected = true; 0238 d->_connecting = false; 0239 invokeEvent ("message", sess(), i18n ("--- A connection has been established ---")); 0240 invokeEvent ("connected", sess()); 0241 return; 0242 } 0243 0244 // set up encoding 0245 d->encoding = sett ? sett->getString ("encoding") : DEFAULT_ENCODING; 0246 setupEncoding (); 0247 0248 d->_connecting = true; 0249 cActionManager::self()->invokeEvent ("message", sess(), i18n ("Connecting...")); 0250 d->hostName = address; 0251 d->hostPort = port; 0252 // TODO add QSslSocket support 0253 d->socket = new QTcpSocket(this); 0254 d->socket->connectToHost(address, port); 0255 d->socket->setSocketOption (QAbstractSocket::KeepAliveOption, 1); 0256 setupSocketHandlers (); 0257 } 0258 0259 void cTelnet::setupSocketHandlers () 0260 { 0261 if (!d->socket) return; 0262 connect (d->socket, &QTcpSocket::connected, this, &cTelnet::socketConnected); 0263 connect (d->socket, &QTcpSocket::errorOccurred, this, &cTelnet::socketFailed); 0264 connect (d->socket, &QTcpSocket::readyRead, this, &cTelnet::socketRead); 0265 connect (d->socket, &QTcpSocket::disconnected, this, &cTelnet::socketClosed); 0266 connect (d->socket, &QTcpSocket::hostFound, this, &cTelnet::socketHostFound); 0267 } 0268 0269 void cTelnet::setupEncoding () 0270 { 0271 delete d->inCoder; 0272 delete d->outCoder; 0273 0274 d->codec = QTextCodec::codecForName (d->encoding.toLatin1().data()); 0275 if (!d->codec) { // unable to create codec - use latin1 0276 d->codec = QTextCodec::codecForName (DEFAULT_ENCODING); 0277 } 0278 d->inCoder = d->codec->makeDecoder (); 0279 d->outCoder = d->codec->makeEncoder (); 0280 } 0281 0282 void cTelnet::reset () 0283 { 0284 //prepare option variables 0285 for (int i = 0; i < 256; i++) 0286 { 0287 d->myOptionState[i] = false; 0288 d->hisOptionState[i] = false; 0289 d->announcedState[i] = false; 0290 d->heAnnouncedState[i] = false; 0291 d->triedToEnable[i] = false; 0292 } 0293 //reset telnet status 0294 d->iac = d->iac2 = d->insb = false; 0295 d->command = ""; 0296 // reset these so that we report dimensions correctly 0297 d->curX = 0; 0298 d->curY = 0; 0299 } 0300 0301 void cTelnet::socketConnected () 0302 { 0303 if (d->_connected) return; 0304 0305 d->_connected = true; 0306 d->_connecting = false; 0307 0308 //reset MCCP, MSP and byte counters 0309 d->MCCP->reset (); 0310 d->MSP->reset (d->hostName); 0311 0312 reset (); 0313 0314 //now we should be connected 0315 invokeEvent ("message", sess(), i18n ("--- A connection has been established ---")); 0316 cActionManager::self()->invokeEvent ("connected", sess()); 0317 0318 #ifdef HAVE_MXP 0319 // MXP -must- be initialized AFTER the connected message, else it won't work 0320 setMXPAllowed (d->mxpallow); //initialize MXP properly 0321 #endif 0322 d->sentbytes = 0; 0323 0324 //negotiate some telnet options, if allowed 0325 if (d->_startupneg) 0326 { 0327 //NAWS (used to send info about window size) 0328 sendTelnetOption (TN_WILL, OPT_NAWS); 0329 //do not allow server to echo our text! 0330 sendTelnetOption (TN_DONT, OPT_ECHO); 0331 } 0332 } 0333 0334 /** closes connection */ 0335 void cTelnet::disconnect () 0336 { 0337 if (!d->_connected) return; 0338 0339 d->_connected = false; 0340 d->_connecting = false; 0341 0342 reset (); 0343 0344 if (isOffLineConnection()) 0345 d->offLineConnection=false; 0346 0347 if (d->socket) { 0348 d->socket->flush (); 0349 d->socket->close (); 0350 0351 // schedule socket deletion 0352 d->socket->deleteLater (); 0353 d->socket = nullptr; 0354 } 0355 0356 //alright - we're disconnected 0357 cActionManager::self()->invokeEvent ("disconnected", sess()); 0358 0359 // ------------------ 0360 // TODO: the rest will not be here, but in their respective objects, hooked on the event 0361 0362 //remove the session - should now be safe 0363 cSessionManager::self()->removeSession (sess(), true); 0364 } 0365 0366 void cTelnet::setOffLineConnection (bool type) 0367 { 0368 d->offLineConnection = type; 0369 } 0370 0371 /** are we connected (offline connection is counted as connected) ? */ 0372 bool cTelnet::isConnected () 0373 { 0374 return (isOffLineConnection() || d->_connected); 0375 } 0376 0377 void cTelnet::setMSPGlobalPaths (const QStringList &paths) 0378 { 0379 if (d->MSP) 0380 d->MSP->setGlobalPaths (paths); 0381 } 0382 0383 bool cTelnet::usingMSP () 0384 { 0385 return d->usingmsp; 0386 } 0387 0388 void cTelnet::setMSPAllowed (bool allow) 0389 { 0390 if (d->MSP) 0391 d->MSP->setMSPAllowed (allow); 0392 } 0393 0394 void cTelnet::setDownloadAllowed (bool allow) 0395 { 0396 if (d->MSP) 0397 d->MSP->setDownloadAllowed (allow); 0398 } 0399 0400 void cTelnet::processSoundRequest (bool isSOUND, QString fName, int volume, int repeats, 0401 int priority, QString type, QString url) 0402 { 0403 if (d->MSP) 0404 d->MSP->processRequest (isSOUND, fName, volume, repeats, priority, type, url); 0405 } 0406 0407 #ifdef HAVE_MXP 0408 0409 bool cTelnet::usingMXP () { 0410 return d->usingmxp; 0411 } 0412 0413 int cTelnet::MXPAllowed () { 0414 return d->mxpallow; 0415 } 0416 0417 void cTelnet::setMXPAllowed (int allow) 0418 { 0419 //update actual MXP usage... 0420 switch (allow) { 0421 case 1: //Never 0422 { 0423 callAction ("mxpmanager", "set-active", sess(), 0); 0424 //ask server to disable MXP 0425 sendTelnetOption (TN_DONT, OPT_MXP); 0426 } 0427 break; 0428 case 2: //If negotiated 0429 { 0430 callAction ("mxpmanager", "set-active", sess(), d->mxpNegotiated ? 1 : 0); 0431 callAction ("mxpmanager", "switch-open", sess()); 0432 } 0433 break; 0434 case 3: //Auto-detect 0435 { 0436 callAction ("mxpmanager", "set-active", sess(), 1); 0437 } 0438 break; 0439 case 4: //Always 0440 { 0441 callAction ("mxpmanager", "set-active", sess(), 1); 0442 callAction ("mxpmanager", "switch-open", sess()); 0443 } 0444 break; 0445 }; 0446 d->mxpallow = allow; 0447 } 0448 0449 #endif //HAVE_MXP 0450 0451 bool cTelnet::sendData (const QString &data) 0452 { 0453 if (!(isConnected())) 0454 return false; 0455 // return true and dont send, since offline is used 0456 if (isOffLineConnection()) 0457 return true; 0458 0459 if (d->t_cmdEcho == true && d->t_lpmudstyle) 0460 d->prependGANewLine = false; 0461 0462 string outdata = (d->outCoder->fromUnicode(data)).data(); 0463 0464 // IAC byte must be doubled 0465 int len = outdata.length(); 0466 bool gotIAC = false; 0467 for (int i = 0; i < len; i++) 0468 if ((unsigned char) outdata[i] == TN_IAC) { 0469 gotIAC = true; 0470 break; 0471 } 0472 if (gotIAC) { 0473 string d; 0474 // double IACs 0475 for (int i = 0; i < len; i++) 0476 { 0477 d += outdata[i]; 0478 if ((unsigned char) outdata[i] == TN_IAC) 0479 d += outdata[i]; //double IAC 0480 } 0481 outdata = d; 0482 } 0483 0484 //data ready, send it 0485 return doSendData (outdata); 0486 } 0487 0488 void cTelnet::waitingForData () { 0489 d->newdata = false; 0490 } 0491 0492 bool cTelnet::newData () { 0493 return d->newdata; 0494 } 0495 0496 bool cTelnet::doSendData (const string &data) 0497 { 0498 if (!(isConnected())) 0499 return false; 0500 if (isOffLineConnection()) 0501 return true; 0502 //write data to socket - it's so complicated because sometimes only a part of data 0503 //is accepted at a time 0504 int dataLength = data.length (); 0505 const char *dd = data.c_str(); 0506 int written = 0; 0507 do { 0508 int w = d->socket->write (dd + written, dataLength - written); 0509 // TODO: need some error diagnostics 0510 if (w == -1) // buffer full - try again 0511 continue; 0512 written += w; 0513 } while (written < dataLength); 0514 0515 //update counter 0516 d->sentbytes += dataLength; 0517 return true; 0518 } 0519 0520 void cTelnet::windowSizeChanged (int x, int y) 0521 { 0522 //remember the size - we'll need it if NAWS is currently disabled but will 0523 //be enabled. Also remember it if no connection exists at the moment; 0524 //we won't be called again when connecting 0525 if (!(isConnected())) 0526 return; 0527 if (!d->myOptionState[OPT_NAWS]) return; //only if we have negotiated this option 0528 if ((x == d->curX) && (y == d->curY)) return; // don't spam sizes if we have sent the current one already 0529 0530 string s; 0531 s = TN_IAC; 0532 s += TN_SB; 0533 s += OPT_NAWS; 0534 unsigned char x1, x2, y1, y2; 0535 x1 = (unsigned char) x / 256; 0536 x2 = (unsigned char) x % 256; 0537 y1 = (unsigned char) y / 256; 0538 y2 = (unsigned char) y % 256; 0539 //IAC must be doubled 0540 s += x1; 0541 if (x1 == TN_IAC) 0542 s += TN_IAC; 0543 s += x2; 0544 if (x2 == TN_IAC) 0545 s += TN_IAC; 0546 s += y1; 0547 if (y1 == TN_IAC) 0548 s += TN_IAC; 0549 s += y2; 0550 if (y2 == TN_IAC) 0551 s += TN_IAC; 0552 0553 s += TN_IAC; 0554 s += TN_SE; 0555 doSendData (s); 0556 } 0557 0558 void cTelnet::sendTelnetOption (unsigned char type, unsigned char option) 0559 { 0560 string s; 0561 s = TN_IAC; 0562 s += (unsigned char) type; 0563 s += (unsigned char) option; 0564 doSendData (s); 0565 } 0566 0567 // TODO: sort out this mess, allow custom protocol handlers, etc. 0568 void cTelnet::processTelnetCommand (const string &command) 0569 { 0570 unsigned char ch = command[1]; 0571 unsigned char option; 0572 switch (ch) { 0573 case TN_AYT: 0574 doSendData ("I'm here! Please be more patient!\r\n"); 0575 //well, this should never be executed, as the response would probably 0576 //be treated as a command. But that's server's problem, not ours... 0577 //If the server wasn't capable of handling this, it wouldn't have 0578 //sent us the AYT command, would it? Impatient server = bad server. 0579 //Let it suffer! ;-) 0580 break; 0581 case TN_GA: 0582 d->recvdGA = true; 0583 //signal will be emitted later 0584 break; 0585 case TN_WILL: 0586 //server wants to enable some option (or he sends a timing-mark)... 0587 option = command[2]; 0588 0589 d->heAnnouncedState[option] = true; 0590 if (d->triedToEnable[option]) 0591 { 0592 d->hisOptionState[option] = true; 0593 d->triedToEnable[option] = false; 0594 } 0595 else 0596 { 0597 if (!d->hisOptionState[option]) 0598 //only if this is not set; if it's set, something's wrong wth the server 0599 //(according to telnet specification, option announcement may not be 0600 //unless explicitly requested) 0601 { 0602 if ((option == OPT_SUPPRESS_GA) || (option == OPT_STATUS) || 0603 (option == OPT_TERMINAL_TYPE) || (option == OPT_NAWS)) 0604 //these options are supported; compression is handled 0605 //separately 0606 { 0607 sendTelnetOption (TN_DO, option); 0608 d->hisOptionState[option] = true; 0609 } 0610 #ifdef HAVE_MXP 0611 else 0612 if (option == OPT_MXP) 0613 { 0614 // allow or disallow, MXP depending on whether it's disabled 0615 sendTelnetOption ((d->mxpallow >= 2) ? TN_DO : TN_DONT, option); 0616 d->hisOptionState[option] = true; 0617 0618 //MXP is now negotiated 0619 d->mxpNegotiated = true; 0620 if (d->mxpallow == 2) //MXP: if negotiated 0621 { 0622 cMXPManager *mm = dynamic_cast<cMXPManager *>(object ("mxpmanager")); 0623 mm->setMXPActive (true); 0624 puts ("KMuddy: MXP enabled !"); 0625 } 0626 } 0627 #endif 0628 else 0629 if (option == OPT_MSP) 0630 { 0631 sendTelnetOption (TN_DO, option); 0632 d->hisOptionState[option] = true; 0633 0634 //MSP is now enabled 0635 d->usingmsp = true; 0636 d->MSP->enableMSP (); 0637 puts ("KMuddy: MSP enabled !"); 0638 } 0639 else 0640 if ((option == OPT_COMPRESS) || (option == OPT_COMPRESS2)) 0641 //these are handled separately, as they're a bit special 0642 { 0643 if ((option == OPT_COMPRESS) && (d->hisOptionState[OPT_COMPRESS2])) 0644 { 0645 //protocol says: reject MCCP v1 if you have previously accepted 0646 //MCCP v2... 0647 sendTelnetOption (TN_DONT, option); 0648 d->hisOptionState[option] = false; 0649 puts ("KMuddy: Rejecting MCCP v1, because v2 is already used !"); 0650 } 0651 else 0652 { 0653 sendTelnetOption (TN_DO, option); 0654 d->hisOptionState[option] = true; 0655 //inform MCCP object about the change 0656 if ((option == OPT_COMPRESS)) { 0657 d->MCCP->setMCCP1 (true); 0658 puts ("KMuddy: MCCP v1 enabled !"); 0659 } 0660 else { 0661 d->MCCP->setMCCP2 (true); 0662 puts ("KMuddy: MCCP v2 enabled !"); 0663 } 0664 } 0665 } 0666 else 0667 { 0668 sendTelnetOption (TN_DONT, option); 0669 d->hisOptionState[option] = false; 0670 } 0671 } 0672 } 0673 break; 0674 case TN_WONT: 0675 //server refuses to enable some option... 0676 option = command[2]; 0677 if (d->triedToEnable[option]) 0678 { 0679 d->hisOptionState[option] = false; 0680 d->triedToEnable[option] = false; 0681 d->heAnnouncedState[option] = true; 0682 } 0683 else 0684 { 0685 //send DONT if needed (see RFC 854 for details) 0686 if (d->hisOptionState[option] || (!d->heAnnouncedState[option])) 0687 { 0688 sendTelnetOption (TN_DONT, option); 0689 d->hisOptionState[option] = false; 0690 //inform MCCP object about the change - won't cause problems in 0691 //cMCCP - see cmccp.cpp for more info 0692 if ((option == OPT_COMPRESS)) { 0693 d->MCCP->setMCCP1 (false); 0694 puts ("KMuddy: MCCP v1 disabled !"); 0695 } 0696 if ((option == OPT_COMPRESS2)) { 0697 d->MCCP->setMCCP2 (false); 0698 puts ("KMuddy: MCCP v1 disabled !"); 0699 } 0700 } 0701 d->heAnnouncedState[option] = true; 0702 } 0703 if (option == OPT_MSP) 0704 { 0705 //MSP is now disabled 0706 d->usingmsp = false; 0707 d->MSP->disableMSP (); 0708 puts ("KMuddy: MSP disabled !"); 0709 } 0710 #ifdef HAVE_MXP 0711 if (option == OPT_MXP) 0712 { 0713 //MXP is now disabled 0714 cMXPManager *mm = dynamic_cast<cMXPManager *>(object ("mxpmanager")); 0715 mm->setMXPActive (false); 0716 puts ("KMuddy: MXP disabled !"); 0717 } 0718 #endif 0719 break; 0720 case TN_DO: 0721 //server wants us to enable some option 0722 option = command[2]; 0723 if (option == OPT_TIMING_MARK) 0724 { 0725 //send WILL TIMING_MARK 0726 sendTelnetOption (TN_WILL, option); 0727 } 0728 #ifdef HAVE_MXP 0729 else 0730 if (option == OPT_MXP) 0731 { 0732 // allow or disallow, MXP depending on whether it's disabled 0733 sendTelnetOption ((d->mxpallow >= 2) ? TN_WILL : TN_WONT, option); 0734 d->hisOptionState[option] = true; 0735 0736 //MXP is now negotiated 0737 d->mxpNegotiated = true; 0738 if (d->mxpallow == 2) //MXP: if negotiated 0739 { 0740 cMXPManager *mm = dynamic_cast<cMXPManager *>(object ("mxpmanager")); 0741 mm->setMXPActive (true); 0742 puts ("KMuddy: MXP enabled !"); 0743 } 0744 } 0745 #endif 0746 0747 else if (!d->myOptionState[option]) 0748 //only if the option is currently disabled 0749 { 0750 if ((option == OPT_SUPPRESS_GA) || (option == OPT_STATUS) || 0751 (option == OPT_TERMINAL_TYPE) || (option == OPT_NAWS)) 0752 { 0753 sendTelnetOption (TN_WILL, option); 0754 d->myOptionState[option] = true; 0755 d->announcedState[option] = true; 0756 } 0757 else 0758 { 0759 sendTelnetOption (TN_WONT, option); 0760 d->myOptionState[option] = false; 0761 d->announcedState[option] = true; 0762 } 0763 } 0764 if (option == OPT_NAWS) //NAWS here - window size info must be sent 0765 windowSizeChanged (d->curX, d->curY); 0766 break; 0767 case TN_DONT: 0768 //only respond if value changed or if this option has not been announced yet 0769 option = command[2]; 0770 #ifdef HAVE_MXP 0771 if (option == OPT_MXP) 0772 { 0773 //MXP is now disabled 0774 cMXPManager *mm = dynamic_cast<cMXPManager *>(object ("mxpmanager")); 0775 mm->setMXPActive (false); 0776 puts ("KMuddy: MXP disabled !"); 0777 } 0778 #endif 0779 if (d->myOptionState[option] || (!d->announcedState[option])) 0780 { 0781 sendTelnetOption (TN_WONT, option); 0782 d->announcedState[option] = true; 0783 } 0784 d->myOptionState[option] = false; 0785 break; 0786 case TN_SB: 0787 //subcommand - we analyze and respond... 0788 option = command[2]; 0789 switch (option) { 0790 case OPT_STATUS: 0791 //see OPT_TERMINAL_TYPE for explanation why I'm doing this 0792 if (true /*myOptionState[OPT_STATUS]*/) 0793 { 0794 if (command[3] == TNSB_SEND) 0795 //request to send all enabled commands; if server sends his 0796 //own list of commands, we just ignore it (well, he shouldn't 0797 //send anything, as we do not request anything, but there are 0798 //so many servers out there, that you can never be sure...) 0799 { 0800 string s; 0801 s = TN_IAC; 0802 s += TN_SB; 0803 s += OPT_STATUS; 0804 s += TNSB_IS; 0805 for (int i = 0; i < 256; i++) 0806 { 0807 if (d->myOptionState[i]) 0808 { 0809 s += TN_WILL; 0810 s += (unsigned char) i; 0811 } 0812 if (d->hisOptionState[i]) 0813 { 0814 s += TN_DO; 0815 s += (unsigned char) i; 0816 } 0817 } 0818 s += TN_IAC; 0819 s += TN_SE; 0820 doSendData (s); 0821 } 0822 } 0823 break; 0824 case OPT_TERMINAL_TYPE: 0825 if (d->myOptionState[OPT_TERMINAL_TYPE]) 0826 { 0827 if (command[3] == TNSB_SEND) 0828 //server wants us to send terminal type; he can send his own type 0829 //too, but we just ignore it, as we have no use for it... 0830 { 0831 string s; 0832 s = TN_IAC; 0833 s += TN_SB; 0834 s += OPT_TERMINAL_TYPE; 0835 s += TNSB_IS; 0836 s += d->termType.toLatin1().data(); 0837 s += TN_IAC; 0838 s += TN_SE; 0839 doSendData (s); 0840 } 0841 } 0842 break; 0843 //other cmds should not arrive, as they were not negotiated. 0844 //if they do, they are merely ignored 0845 }; 0846 break; 0847 //other commands are simply ignored (NOP and such, see .h file for list) 0848 }; 0849 } 0850 0851 void cTelnet::socketRead () 0852 { 0853 /* 0854 This function makes heavy use of char* instead of relying on QString, 0855 because otherwise there are problems with locale-based data and/or 0856 telnet commands. 0857 */ 0858 0859 // Looks like we often get this before getting the connected event ... 0860 // TODO: is this still true with the Qt4/KDE4 stuff ? 0861 if (d->_connecting && (!d->_connected)) 0862 socketConnected(); 0863 0864 char buffer[32769]; //we should never receive such a big amount of data, 0865 //but we want to be sure that we read all we can... 0866 0867 char data[32769]; //clean data after decompression 0868 0869 int amount = d->socket->read (buffer, 32768); 0870 if (amount == -1) 0871 return; //something is wrong (no data?) 0872 if (amount == 0) 0873 return; //0 means socket has been closed; maybe I'll do something more? 0874 buffer[amount] = '\0'; //just to be sure - mark end of string 0875 0876 invokeEvent ("raw-data-comp", sess(), QString (buffer)); 0877 0878 //we'll need cProfileSettings later on 0879 cProfileSettings *sett = settings(); 0880 0881 //MCCP will be handled first. This is done in a separate class, which 0882 //detects all MCCP-related codes itself. I know that this involves some 0883 //code duplication (two telnet option parsers), but it's needed because 0884 //telnet sequences may (and will) appear inside the compressed streams, 0885 //so it's best to get plain uncompressed data first. Another reason is 0886 //improper design of MCCP v1, which uses unterminated telnet subsequences. 0887 //This has been fixed in MCCP v2, but some MUDs may still use the old 0888 //version. 0889 0890 d->MCCP->prepareDecompression (buffer, data, amount, 32768); 0891 int datalen; 0892 while ((datalen = d->MCCP->uncompressNext ()) != -1) 0893 { 0894 //inform plug-ins about uncompressed data (can be equal to raw data if MCCP isn't used 0895 invokeEvent ("raw-data", sess(), QString (data)); 0896 0897 #if 0 0898 printf ("DATA IN: "); 0899 for (int i = 0; i < datalen; ++i) printf ("%02x ", (unsigned char) data[i]); 0900 printf ("\n"); 0901 #endif 0902 0903 data[datalen] = '\0'; 0904 string cleandata; 0905 0906 //we have some data... 0907 d->newdata = true; 0908 0909 //clear the GO-AHEAD flag 0910 d->recvdGA = false; 0911 0912 //now we have the data, but we cannot forward it to next stage of processing, 0913 //because the data contains telnet commands 0914 //so we parse the text and process all telnet commands: 0915 0916 for (unsigned int i = 0; i < (unsigned int) datalen; i++) 0917 { 0918 unsigned char ch = data[i]; 0919 if (d->iac || d->iac2 || d->insb || (ch == TN_IAC)) 0920 { 0921 //there are many possibilities here: 0922 //1. this is IAC, previous character was regular data 0923 if (! (d->iac || d->iac2 || d->insb) && (ch == TN_IAC)) 0924 { 0925 d->iac = true; 0926 d->command += ch; 0927 } 0928 else 0929 //2. seq. of two IACs 0930 if (d->iac && (ch == TN_IAC) && (!d->insb)) 0931 { 0932 d->iac = false; 0933 cleandata += ch; 0934 d->command = ""; 0935 } 0936 else 0937 //3. IAC DO/DONT/WILL/WONT 0938 if (d->iac && (!d->insb) && 0939 ((ch == TN_WILL) || (ch == TN_WONT) || (ch == TN_DO) || (ch == TN_DONT))) 0940 { 0941 d->iac = false; 0942 d->iac2 = true; 0943 d->command += ch; 0944 } 0945 else 0946 //4. IAC DO/DONT/WILL/WONT <command code> 0947 if (d->iac2) 0948 { 0949 d->iac2 = false; 0950 d->command += ch; 0951 processTelnetCommand (d->command); 0952 d->command = ""; 0953 } 0954 else 0955 //5. IAC SB 0956 if (d->iac && (!d->insb) && (ch == TN_SB)) 0957 { 0958 d->iac = false; 0959 d->insb = true; 0960 d->command += ch; 0961 } 0962 else 0963 //6. IAC SE without IAC SB - error - ignored 0964 if (d->iac && (!d->insb) && (ch == TN_SE)) 0965 { 0966 d->command = ""; 0967 d->iac = false; 0968 } 0969 else 0970 //7. inside IAC SB 0971 if (d->insb) 0972 { 0973 d->command += ch; 0974 if (d->iac && (ch == TN_SE)) //IAC SE - end of subcommand 0975 { 0976 processTelnetCommand (d->command); 0977 d->command = ""; 0978 d->iac = false; 0979 d->insb = false; 0980 } 0981 if (d->iac) 0982 d->iac = false; 0983 else 0984 if (ch == TN_IAC) 0985 d->iac = true; 0986 } 0987 else 0988 //8. IAC fol. by something else than IAC, SB, SE, DO, DONT, WILL, WONT 0989 { 0990 d->iac = false; 0991 d->command += ch; 0992 processTelnetCommand (d->command); 0993 //this could have set receivedGA to true; we'll handle that later 0994 // (at the end of this function) 0995 d->command = ""; 0996 } 0997 } 0998 else //plaintext 0999 { 1000 //everything except CRLF is okay; CRLF is replaced by LF(\n) (CR ignored) 1001 if (ch != 13) 1002 cleandata += ch; 1003 } 1004 1005 // TODO: do something about all that code duplication ... 1006 1007 //we've just received the GA signal - higher layers shall be informed about it 1008 if (d->recvdGA) 1009 { 1010 //ask MSP parser to look for MSP tags if MSP is enabled 1011 //look if the user want to have MSP all the time 1012 bool alwaysmsp = sett ? sett->getBool ("always-msp") : false; 1013 //hand data to MSP parser if desired 1014 if (d->usingmsp || alwaysmsp) 1015 cleandata = d->MSP->parseServerOutput (cleandata); 1016 1017 //prepend a newline, if needed 1018 if (d->prependGANewLine && d->t_lpmudstyle) 1019 cleandata = "\n" + cleandata; 1020 d->prependGANewLine = false; 1021 //forward data for further processing 1022 QString unicodeData = d->inCoder->toUnicode (cleandata.data(), cleandata.length()); 1023 invokeEvent ("data-received", sess(), unicodeData); 1024 1025 if (sett && sett->getBool ("prompt-console")) 1026 //we'll need to prepend a new-line in next data sending 1027 d->prependGANewLine = true; 1028 //we got a prompt 1029 invokeEvent ("received-ga", sess()); 1030 1031 //clean the flag, and the data (so that we don't send it multiple times) 1032 cleandata = ""; 1033 d->recvdGA = false; 1034 } 1035 } 1036 1037 //some data left to send - do it now! 1038 if (!cleandata.empty()) 1039 { 1040 //ask MSP parser to look for MSP tags if MSP is enabled 1041 //look if the user want to have MSP all the time 1042 bool alwaysmsp = sett ? sett->getBool ("always-msp") : false; 1043 //hand data to MSP parser if desired 1044 if (d->usingmsp || alwaysmsp) 1045 cleandata = d->MSP->parseServerOutput (cleandata); 1046 1047 //prepend a newline, if needed 1048 if (d->prependGANewLine && d->t_lpmudstyle) 1049 cleandata = "\n" + cleandata; 1050 d->prependGANewLine = false; 1051 //forward data for further processing 1052 QString unicodeData = d->inCoder->toUnicode (cleandata.data(), cleandata.length()); 1053 invokeEvent ("data-received", sess(), unicodeData); 1054 } 1055 } 1056 1057 invokeEvent ("text-here", sess()); 1058 } 1059 1060 void cTelnet::socketClosed () 1061 { 1062 disconnect (); 1063 } 1064 1065 void cTelnet::socketHostFound () 1066 { 1067 invokeEvent ("message", sess(), i18n ("The remote server has been found, attempting connection.")); 1068 } 1069 1070 bool cTelnet::isOffLineConnection () { 1071 return d->offLineConnection; 1072 } 1073 1074 int cTelnet::compressedBytes () { 1075 return d->MCCP->compressedBytes (); 1076 } 1077 1078 int cTelnet::uncompressedBytes () { 1079 return d->MCCP->uncompressedBytes (); 1080 } 1081 1082 int cTelnet::sentBytes () { 1083 return d->sentbytes; 1084 } 1085 1086 bool cTelnet::usingMCCP () { 1087 return d->MCCP->usingMCCP(); 1088 } 1089 1090 int cTelnet::MCCPVer () { 1091 return d->MCCP->MCCPVer(); 1092 } 1093 1094 void cTelnet::setCommandEcho (bool cmdEcho) 1095 { 1096 d->t_cmdEcho = cmdEcho; 1097 } 1098 1099 void cTelnet::setLPMudStyle (bool lpmudstyle) 1100 { 1101 d->t_lpmudstyle = lpmudstyle; 1102 } 1103 1104 void cTelnet::setNegotiateOnStartup (bool startupneg) 1105 { 1106 d->_startupneg = startupneg; 1107 } 1108 1109 #include "moc_ctelnet.cpp"