#region Arthea License
/***********************************************************************
* Arthea MUD by R. Jennings (2007) http://arthea.googlecode.com/ *
* By using this code you comply with the Artistic and GPLv2 Licenses. *
***********************************************************************/
#endregion
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using Arthea.Environment;
namespace Arthea.Connections
{
/// <summary>
/// Telnet options
/// </summary>
public struct Telopt
{
/// <summary>
/// backspace
/// </summary>
public const byte Backspace = 8;
/// <summary>
/// MCCP v1
/// </summary>
public const byte Compress = 85;
/// <summary>
/// MCCP v2
/// </summary>
public const byte Compress2 = 86;
/// <summary>
/// server does
/// </summary>
public const byte Do = 253;
/// <summary>
/// server doesn't
/// </summary>
public const byte Dont = 254;
/// <summary>
/// echo
/// </summary>
public const byte Echo = 1;
/// <summary>
/// iac
/// </summary>
public const byte IAC = 255;
/// <summary>
/// negotiate about window size
/// </summary>
public const byte NAWS = 31;
/// <summary>
/// new-environment
/// </summary>
public const byte NE = 39;
/// <summary>
/// no operation
/// </summary>
public const byte NOP = 241;
/// <summary>
/// sub negotiations
/// </summary>
public const byte SB = 250;
/// <summary>
/// signal end of sub negotiations
/// </summary>
public const byte SE = 240;
/// <summary>
/// send option
/// </summary>
public const byte Send = 1;
/// <summary>
/// Suppress Go Ahead
/// </summary>
public const byte SGA = 3;
/// <summary>
/// Terminal speed
/// </summary>
public const byte TSPEED = 32;
/// <summary>
/// terminal type
/// </summary>
public const byte TTYPE = 24;
/// <summary>
/// Value for NE.
/// </summary>
public const byte Value = 1;
/// <summary>
/// Variable for NE.
/// </summary>
public const byte Var = 0;
/// <summary>
/// client will
/// </summary>
public const byte Will = 251;
/// <summary>
/// client wont
/// </summary>
public const byte Wont = 252;
}
/// <summary>
/// Implementation of telnet.
/// </summary>
public struct Telnet
{
#region Fields (6)
/// <summary>
/// Turn on MCCP v2
/// </summary>
public static readonly byte[] Compress2Start =
new byte[] {Telopt.IAC, Telopt.SB, Telopt.Compress2, Telopt.IAC, Telopt.SE, 0};
/// <summary>
/// Turn on MCCP v1
/// </summary>
public static readonly byte[] CompressStart =
new byte[] {Telopt.IAC, Telopt.SB, Telopt.Compress, Telopt.Will, Telopt.SE, 0};
/// <summary>
/// Turns echo off
/// </summary>
public static readonly byte[] EchoOff = new byte[] {Telopt.IAC, Telopt.Will, Telopt.Echo, 0};
/// <summary>
/// Turns echo on
/// </summary>
public static readonly byte[] EchoOn = new byte[] {Telopt.IAC, Telopt.Wont, Telopt.Echo, 0};
/// <summary>
/// Announces naws capability
/// </summary>
public static readonly byte[] NawsDo = new byte[] {Telopt.IAC, Telopt.Do, Telopt.NAWS, 0};
private static readonly byte[] RequestTType =
new byte[] {Telopt.IAC, Telopt.SB, Telopt.TTYPE, Telopt.Send, Telopt.IAC, Telopt.SE};
#endregion
#region Methods (3)
private static byte[] RequestEnvVars()
{
List<byte> bytes = new List<byte>();
bytes.Add(Telopt.IAC);
bytes.Add(Telopt.SB);
bytes.Add(Telopt.NE);
bytes.Add(Telopt.Send);
bytes.Add(Telopt.Var);
foreach (byte c in Globals.Encoding.GetBytes("\"SYSTEMTYPE\""))
bytes.Add(c);
bytes.Add(Telopt.IAC);
bytes.Add(Telopt.SE);
return bytes.ToArray();
}
/// <summary>
/// Advertises telnet options to the specified connection.
/// </summary>
/// <param name="conn">The connection.</param>
public static void Advertise(Connection conn)
{
conn.WriteToSocket(new byte[] {Telopt.IAC, Telopt.Will, Telopt.Compress2, 0});
conn.WriteToSocket(new byte[] {Telopt.IAC, Telopt.Will, Telopt.Compress, 0});
conn.WriteToSocket(new byte[] {Telopt.IAC, Telopt.Will, Telopt.NAWS, 0});
conn.WriteToSocket(new byte[] {Telopt.IAC, Telopt.Will, Telopt.NE, 0});
conn.WriteToSocket(new byte[] {Telopt.IAC, Telopt.Will, Telopt.TSPEED, 0});
conn.WriteToSocket(new byte[] {Telopt.IAC, Telopt.Will, Telopt.TTYPE, 0});
}
/// <summary>
/// Processes any telnet options.
/// </summary>
/// <param name="bytes">The bytes.</param>
/// <param name="index">The index.</param>
/// <param name="con">The connection.</param>
public static void Process(byte[] bytes, ref int index, Connection con)
{
Debug.Assert(bytes[index] == Telopt.IAC);
index++;
switch (bytes[index])
{
case Telopt.Will:
switch (bytes[++index])
{
case Telopt.TSPEED:
break;
case Telopt.TTYPE:
con.WriteToSocket(RequestTType);
break;
case Telopt.NE:
con.WriteToSocket(RequestEnvVars());
break;
case Telopt.NAWS:
con.WriteToSocket(NawsDo);
break;
case Telopt.SGA:
break;
default:
Log.Error("unknown telopt will {0}", bytes[index]);
break;
}
break;
case Telopt.Wont:
index++;
Log.Error("unknown telopt won't {0}", bytes[index]);
break;
case Telopt.Do:
switch (bytes[++index])
{
case Telopt.Echo:
break;
case Telopt.SGA:
break;
case Telopt.Compress:
if (con.Flags.Has(ConnectionFlags.MCCP))
{
con.WriteToSocket(new byte[] {Telopt.IAC, Telopt.Wont, Telopt.Compress, 0});
}
else
{
con.WriteToSocket(CompressStart);
con.StartCompression();
}
break;
case Telopt.Compress2:
if (con.Flags.Has(ConnectionFlags.MCCP))
{
con.WriteToSocket(new byte[] {Telopt.IAC, Telopt.Wont, Telopt.Compress2, 0});
}
else
{
con.WriteToSocket(Compress2Start);
con.StartCompression();
}
break;
default:
Log.Error("unknown telopt do {0}", bytes[index]);
break;
}
break;
case Telopt.Dont:
switch (bytes[++index])
{
case Telopt.Echo:
break;
case Telopt.Compress:
case Telopt.Compress2:
break;
default:
Log.Error("unknown telopt don't {0}", bytes[index]);
break;
}
break;
case Telopt.SE:
break;
case Telopt.SB:
switch (bytes[++index])
{
case Telopt.NAWS:
con.ScreenWidth = Convert.ToInt16(bytes[++index])*255;
con.ScreenWidth += Convert.ToInt16(bytes[++index]);
con.ScreenHeight = Convert.ToInt16(bytes[++index])*255;
con.ScreenHeight += Convert.ToInt16(bytes[++index]);
con.Flags.Set(ConnectionFlags.NAWS);
break;
case Telopt.TTYPE:
StringBuilder buf = new StringBuilder();
index += 2;
while (bytes[index] != Telopt.IAC)
{
buf.Append(Convert.ToChar(bytes[index++]));
}
con.TerminalType = buf.ToString();
con.Flags.Set(ConnectionFlags.TType);
++index;
break;
case Telopt.NE:
index += 2;
// This could be handy... I can set environment variables
// in my windows putty client and have them read here.
// Haven't tried linux yet.
// - RJ Aug'07
while (bytes[index] != Telopt.IAC)
{
StringBuilder var = new StringBuilder();
++index;
while (bytes[index] != Telopt.Value && bytes[index] != Telopt.IAC)
{
var.Append(Convert.ToChar(bytes[index++]));
}
if (bytes[index] == Telopt.IAC)
break;
++index;
StringBuilder value = new StringBuilder();
while (bytes[index] != Telopt.Var && bytes[index] != Telopt.IAC)
{
value.Append(Convert.ToChar(bytes[index++]));
}
if (var.ToString().ToLower() == "systemtype")
con.SystemType = value.ToString();
}
con.Flags.Set(ConnectionFlags.NE);
++index; // SE
break;
default:
Log.Bug("unknown telnet sub negotiation {0}.", bytes[index]);
break;
}
break;
default:
Log.Bug("Unknown telnet option IAC {0} {1}", bytes[index++], bytes[index]);
break;
}
}
#endregion
}
}