Logo Search packages:      
Sourcecode: papaya version File versions  Download package

TeloptFilter.cpp

#ifdef WIN32
#pragma warning(disable : 4786)
#include <windows.h>
#endif

#include <glib.h>
#include <string.h>
#include <stdio.h>

#include "Connection.h"
//#include "Socket.h"
#include "TeloptFilter.h"
#ifdef ZLIB
#include "CompressFilter.h"
#endif // ZLIB
#include "Telnet.h"
#include "PluginHandler.h"
#undef TELOPT_DEBUG
//#define debug printf

extern PluginHandler * phandler;

TeloptFilter::TeloptFilter(Connection *c)
    : Filter(c, "TelnetOptions")
{
  num_codes = 0;
}

int
TeloptFilter::order() const
{ 
    return 10; 
}

// The gory bits..
bool
TeloptFilter::process(Buffer &out)
{
    bool stream_format_changed = false;

    // if false, we can do a direct transfer of in->out with no changes needed
    bool changed = false; 

    char *start = input.getText();
    char *end = start + input.getLength();
    char *last = start, *p = start;

    // Find next IAC sequence, if any.
    while ( (p = (char*)memchr(p, Telnet::IAC, end - p)) != NULL )
    {
      int maxlen = end - p; // max. len of sequence before we run out of data
      if (maxlen < 2)       // minimum sequence is IAC cmd
          break;

      // Process this IAC sequence. To do this we have to cut data out of
      // the input stream, so we can't use the fastpath any more.

      changed = true;
      out.append(last, p - last);  // copy pending data up to the IAC

      bool processed = false; // TRUE when the full sequence is handled

      Telnet::command_t cmd = (Telnet::command_t) (unsigned char)p[1];
      switch (cmd)
      {
          case Telnet::IAC:
#ifdef TELOPT_DEBUG           
            debug("telopt: IAC IAC\n");
#endif

            // IAC IAC -> literal IAC
            out.append(p, 1);   // copy one IAC
            p += 2;             // skip both IACs
            processed = true;
            break;

            // These all behave very similarly:
            //  IAC cmd option
            // Dispatch to different methods as needed.
          case Telnet::DO:
          case Telnet::DONT:
          case Telnet::WILL:
          case Telnet::WONT:
          {
            if (maxlen < 3) break;

            Telnet::option_t opt = (Telnet::option_t) (unsigned char)p[2];

#ifdef TELOPT_DEBUG           
            debug("telopt: IAC %s %s\n", Telnet::command_name(cmd), Telnet::option_name(opt));
#endif

            // Received a code:
            num_codes++;
            if (num_codes == 1) // The other end does negotiation.  Request SGA.
              request_remote(Telnet::SGA, true);

            switch (cmd)
            {
                case Telnet::DO:
                  process_do(opt);
                  break;
                case Telnet::DONT:
                  process_dont(opt);
                  break;
                case Telnet::WILL:
                  process_will(opt);
                  break;
                case Telnet::WONT:
                  process_wont(opt);
                  break;
                default:
                  break;
            }
            
            p += 3;
            processed = true;
            break;
          }     

          // IAC SB option data.. IAC SE
          // (or: IAC SB COMPRESS data.. SE, since COMPRESS is broken)
          case Telnet::SB:
          {
            if (maxlen < 4) break;

            // Sort it out so that:
            //   sb_dataend points one past end of data (i.e. at IAC of
            //      IAC SE normally, or SE for COMPRESS)
            //   sb_end points one past the end of the whole sequence

            Telnet::option_t opt = (Telnet::option_t) p[2];
#ifdef TELOPT_DEBUG
            debug("telopt: IAC SB %s ...\n", Telnet::option_name(opt));
#endif

            char *sb_dataend = NULL, *sb_end = NULL;
            if (opt == Telnet::COMPRESS)
            {
#ifdef TELOPT_DEBUG
                debug("telopt: special COMPRESS handling\n");
#endif
                sb_dataend = (char *)memchr(p + 3, Telnet::SE, end - p - 3);
                if (sb_dataend)
                  sb_end = sb_dataend + 1;
            }
            else
            {
                // Look for an IAC SE
                sb_dataend = p + 3;
                while ( (sb_dataend = (char *)memchr(sb_dataend, Telnet::IAC, end - sb_dataend - 1)) != NULL)
                {
                  if ((unsigned char)sb_dataend[1] == Telnet::SE)
                      break;

                  sb_dataend += 2;
                }
                
                if (sb_dataend)
                  sb_end = sb_dataend + 2;
            }

            if (!sb_end) break;

            // Take a copy of the data and resync input, so we aren't
            // bitten by stream format changes.
            int sb_datalen = sb_dataend - p - 3;

#ifdef TELOPT_DEBUG
            debug("telopt: IAC SB %s +%d\n", Telnet::option_name(opt), sb_datalen);
#endif

            char *tempdata = new char[sb_datalen];
            memcpy(tempdata, p + 3, sb_datalen);
            input.strip(sb_end - start);

            // Do the subneg..
            stream_format_changed = handle_subneg(opt, tempdata, sb_datalen);

            // Clean up.
            delete[] tempdata;
            last = p = start = input.getText();  // buffer might have changed
            end = start + input.getLength();
            changed = false;  // we can safely do a full copy now!
            processed = true;
            break;
          }

          case Telnet::GA:
          case Telnet::EOR:
          case Telnet::BOGUS_EOR:
          {
            // We propogate all of these as a magic character for later
            // processing by the rest of the system.
            
            static char eolstr[] = { MAGIC_PROMPT };
            out.append(eolstr, 1);
          }
            
            // .. fall through

          default:
            // IAC xyz -> nothing
#ifdef TELOPT_DEBUG
            debug("telopt: IAC %s\n", Telnet::command_name(cmd));
#endif
            p += 2;
            processed = true;
            break;
      }

      if (!processed)
          break; // needs more input..

      if (stream_format_changed)
          break; // don't process further.

      last = p;
    }

    if (!p)
    {
#ifdef TELOPT_DEBUG
      debug("telopt: no more sequences\n");
#endif
      // Completely processed.
      if (!changed)
          input.transfer_to(out);  // fastpath
      else
      {
          out.append(last, end - last);  // append residuals
          input.reset();
      }
    }
    else
    {
#ifdef TELOPT_DEBUG
      debug("telopt: partial at end\n");
#endif
      // Only partially processed
      if (last != p)
          out.append(last, p - last);
      input.strip(p - start);
    }

    return !stream_format_changed;
}

void TeloptFilter::write_cmd(Telnet::command_t cmd, Telnet::option_t opt)
{
#ifdef TELOPT_DEBUG
    debug("telopt: send IAC %s %s\n", Telnet::command_name(cmd), Telnet::option_name(opt));
#endif

    char buf[3] = { Telnet::IAC, 0, 0 };
    buf[1] = cmd;
    buf[2] = opt;
    conn->getSocket()->write(buf, 3);
}

void TeloptFilter::write_subneg(Telnet::option_t opt, char *data, int len)
{
#ifdef TELOPT_DEBUG
    debug("telopt: send IAC %s SB +%d IAC SE\n", Telnet::option_name(opt), len);
#endif
    char buf[5] = { Telnet::IAC, Telnet::SB, 0, Telnet::IAC, Telnet::SE };
    buf[2] = opt;
    conn->getSocket()->write(buf, 3);
    conn->getSocket()->write(data, len);
    conn->getSocket()->write(buf+3, 2);
}

bool TeloptFilter::get_local(Telnet::option_t opt) const
{
    switch (local_state(opt))
    {
      case ON:
          return true;
      default:
          return false;
    }
}

bool TeloptFilter::get_remote(Telnet::option_t opt) const
{
    switch (remote_state(opt))
    {
      case ON:
      case RETRACTED:
          return true;
      default:
          return false;
    }
}

void TeloptFilter::set_local(Telnet::option_t opt, TeloptFilter::state_t state)
{
    bool old_onoff = get_local(opt);
    local[opt] = state;
    bool new_onoff = get_local(opt);

    if (old_onoff != new_onoff)
      handle_local(opt, new_onoff);
}

void TeloptFilter::set_remote(Telnet::option_t opt, TeloptFilter::state_t state)
{
    bool old_onoff = get_remote(opt);
    remote[opt] = state;
    bool new_onoff = get_remote(opt);

    if (old_onoff != new_onoff)
      handle_remote(opt, new_onoff);
}

void TeloptFilter::request_local(Telnet::option_t opt, bool onoff)
{
#ifdef TELOPT_DEBUG
    debug("telopt: request_local(%s,%d)\n", Telnet::option_name(opt), onoff ? 1 : 0);
#endif

    if (onoff && local_state(opt) != ON)
    {
      // Send WILL
      write_cmd(Telnet::WILL, opt);
      set_local(opt, OFFERED);
    }

    else if (!onoff && local_state(opt) != OFF)
    {
      // Send WONT
      write_cmd(Telnet::WONT, opt);
      set_local(opt, RETRACTED);
    }
}

void TeloptFilter::request_remote(Telnet::option_t opt, bool onoff)
{
#ifdef TELOPT_DEBUG
    debug("telopt: request_remote(%s,%d)\n", Telnet::option_name(opt), onoff ? 1 : 0);
#endif

    if (onoff && remote_state(opt) != ON)
    {
      // Send DO
      write_cmd(Telnet::DO, opt);
      set_remote(opt, OFFERED);
    }

    else if (!onoff && remote_state(opt) != OFF)
    {
      // Send DONT
      write_cmd(Telnet::DONT, opt);
      set_remote(opt, RETRACTED);
    }
}
void TeloptFilter::process_do(Telnet::option_t opt)
{
    switch (local_state(opt))
    {
      case OFF:
      case RETRACTED:
          // see if we should do this option
          if (allow_local(opt))
          {
            write_cmd(Telnet::WILL, opt);
            set_local(opt, ON);
          }
          else
          {
            write_cmd(Telnet::WONT, opt);
          }
          break;

      case OFFERED:
          set_local(opt, ON);
          break;

      case ON:
          break;
    }
}

void TeloptFilter::process_dont(Telnet::option_t opt)
{
    switch (local_state(opt))
    {
      case OFF:
          break;

      case RETRACTED:
      case OFFERED:
          set_local(opt, OFF);
          break;

      case ON:
          write_cmd(Telnet::WONT, opt);
          set_local(opt, OFF);
          break;
    }
}

void TeloptFilter::process_will(Telnet::option_t opt)
{
    switch (remote_state(opt))
    {
      case OFF:
      case RETRACTED:
          if (allow_remote(opt))
          {
            write_cmd(Telnet::DO, opt);
            set_remote(opt, ON);
          }
          else
          {
            write_cmd(Telnet::DONT, opt);
          }
          break;

      case OFFERED:
          set_remote(opt, ON);
          break;

      case ON:
          break;
    }
}

void TeloptFilter::process_wont(Telnet::option_t opt)
{
    switch (remote_state(opt))
    {
      case OFF:
          break;

      case RETRACTED:
      case OFFERED:
          set_remote(opt, OFF);
          break;

      case ON:
          write_cmd(Telnet::DONT, opt);
          set_remote(opt, OFF);
          break;
    }
}

bool TeloptFilter::handle_subneg(Telnet::option_t opt, char *data, int len)
{
#ifdef TELOPT_DEBUG
    debug("telopt: handle_subneg(%s,+%d)\n", Telnet::option_name(opt), len);
#endif

    switch (opt)
    {
#ifdef ZLIB
      case Telnet::COMPRESS:
      case Telnet::COMPRESS2:
          if ((opt == Telnet::COMPRESS && len == 1 && (unsigned char)data[0] == Telnet::WILL) ||
            (opt == Telnet::COMPRESS2 && len == 0))
          {
            if (conn->getSocket()->inputFilters.findFilter("Compression") != NULL)
            {
                // Odd. Ignore it.
                return false;
            }
            
#ifdef TELOPT_DEBUG
            debug("telopt: enabling compression\n");
#endif
            
            // Compression just went on. Pass the remainder of our
            // input back through the compression filter.
            
            // Set up the compression filter
            Filter *compressFilter = new CompressFilter(conn);
            // Give it out pending input
            input.transfer_to(compressFilter->input);
            // Turn it on.
            conn->getSocket()->inputFilters.addFilter(compressFilter);

                // Tell the negotiator that compression is on
                set_local(opt, ON);
            
            // Tell our caller to get the hell out of there..
            return true;
          }

          // OJ: experimental v2.1 support
          if (opt == Telnet::COMPRESS2)
          {
            // note: len > 0 here
            if ((unsigned char)data[0] == MCCP::OFFER_VERSIONS) {
                int i;
                char response[2] =
                  { MCCP::ACCEPT_VERSION, MCCP::VERSION_NONE };

                for (i = 1; i < len; ++i) {
                  // We only accept 2.1 currently.
                  if (data[i] == MCCP::VERSION_2_1) {
                      response[1] = MCCP::VERSION_2_1;
                      break;
                  }
                }
            
                write_subneg(Telnet::COMPRESS2, response, 2);
            }

            return false;
          }

          return false;
#endif

      case Telnet::TTYPE:
          if (len == 1 && data[0] == 1) {
            // IAC SB TTYPE SEND IAC SE
            // respond with IAC SB TTYPE IS <type> IAC SE
            write_subneg(opt, "\0ANSI", 5);
          }
          return false;
          
      case Telnet::TSPEED:
          if (len == 1 && data[0] == 1) {
            // IAC SB TSPEED SEND IAC SE
            // respond with IAC SB TSPEED IS transmit,receive IAC SE
            write_subneg(opt, "\0000,0", 4);
          }
          return false;

      default:
        // Call our plugin handler to see if there are any plugins handling this option. 
        phandler->teloptSubneg(conn, opt, data, len);
        return false;
    }
}


bool TeloptFilter::allow_local(Telnet::option_t opt)
{
    switch (opt)
    {
      case Telnet::TTYPE:
      case Telnet::NAWS:
      case Telnet::TSPEED:
        return true;
          
      default:
        return phandler->teloptAllowLocal(conn, opt);
    }
}

bool TeloptFilter::allow_remote(Telnet::option_t opt)
{
  switch (opt)
    {
    case Telnet::EOR:
    case Telnet::SGA:
    case Telnet::ECHO:
      return true;
      
#ifdef ZLIB
    case Telnet::COMPRESS:
      return (conn->queryPreferences()->getPreferenceBoolean("Compression") &&
            !conn->queryPreferences()->getPreferenceBoolean("ProxyTurfHTTPd")  &&
            !get_local(Telnet::COMPRESS2));
    case Telnet::COMPRESS2:
      return (conn->queryPreferences()->getPreferenceBoolean("Compression") &&
            !conn->queryPreferences()->getPreferenceBoolean("ProxyTurfHTTPd"));
#endif
      
    default:
      return phandler->teloptAllowRemote(conn, opt);
    }
}

void TeloptFilter::handle_local(Telnet::option_t opt, bool onoff)
{
#ifdef TELOPT_DEBUG
    debug("telopt: handle_local(%s,%d)\n", Telnet::option_name(opt), onoff ? 1 : 0);
#endif

    switch (opt)
    {
      case Telnet::NAWS:
          if (onoff)
            do_naws();
          break;

      default:
          break;
    }
}

// Called when NAWS is negotiated, and potentially later when window size
// changes.
void TeloptFilter::do_naws(void)
{
    if (!get_local(Telnet::NAWS))
      return; // NAWS not enabled

    // send: IAC SB NAWS <width> <height> IAC SE
    char subneg[] = { 0, 0, 0, 0 };

    int width = conn->getVT()->width();
    int height = conn->getVT()->height();
    subneg[0] = (char)(width/256);
    subneg[1] = (char)(width%256);
    subneg[2] = (char)(height/256);
    subneg[3] = (char)(height%256);
    
    write_subneg(Telnet::NAWS, subneg, 4);
}

void TeloptFilter::handle_remote(Telnet::option_t opt, bool onoff)
{
#ifdef TELOPT_DEBUG
    debug("telopt: handle_remote(%s,%d)\n", Telnet::option_name(opt), onoff ? 1 : 0);
#endif

    switch (opt)
    {
      case Telnet::SGA:
          conn->setCharMode(onoff);
          break;

      case Telnet::ECHO:
          if (!get_remote(Telnet::SGA))
            if (onoff)
                conn->getVT()->hideCommands();
            else
                conn->getVT()->showCommands();
          break;

      default:
        // Let plugins handle remote options if need be, except for the built in options here.
        phandler->teloptHandleRemote(conn, opt, onoff);
        break;
    }
}

TeloptFilter::state_t TeloptFilter::local_state(Telnet::option_t opt) const
{
    state_map::const_iterator i = local.find(opt);
    if (i == local.end())
      return OFF;
    else
      return (*i).second;
}

TeloptFilter::state_t TeloptFilter::remote_state(Telnet::option_t opt) const
{
    state_map::const_iterator i = remote.find(opt);
    if (i == remote.end())
      return OFF;
    else
      return (*i).second;
}
    

Generated by  Doxygen 1.6.0   Back to index