File indexing completed on 2024-04-14 14:32:08

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"