#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 } }