CSharp Telnet client

In previous articles, I have explained how to setup automated Cisco backup processes - however all the previous examples used existing software. There are other scenarios where a custom programming solution could be required. Writing your own software gives you the most control over the program and the process. However, this usually requires more effort and understanding in order to obtain this level of control and/or functionality. This article will go through the process of compiling an entire program that accomplishes logging into a Cisco IOS device via telnet and displays the running configuration.

First off, most of the credit for the following code goes to a contributor on Codeproject, which is where the source came from to build the telnet component of this program. We will code the remainder of the program that utilizes the telnet code obtained from codeproject.

The code contained in this article can be compiled using the Microsoft 2.0 framework that is most likely already installed on your computer. We will compile this with the command line compiler that comes with the .Net runtime. By using this method, it not only provides a very simple process to compile the program, it also prevents having to download Microsoft Visual Studio Express. I would suggest, however, that if you plan to extend this program - you can benefit greatly from having a full blown IDE to write the code in.

First, let's look at the telnet component, which is the majority of the program. This portion of the code is compiled as a library (.dll) under the name scottp.Net.Comm.dll and will be a dependency for the ConfigSafe project. This code could have just as easily been put in the executable, which would have kept the program to a single file. However, in bigger programs, this type of code would go into a library anyway - so there is no time like the present to begin following standard practices.

The telnet method accepts three arguments as input, which is the IP address, port number, and a timeout value in seconds:

    public Telnet(string Address, int Port, int CommandTimeout)
    {
        address = Address;
        port = Port;
        timeout = CommandTimeout;
    }

Once connected, the following method is used to search through the incoming data stream for the string defined as the argument in the WaitFor method:

    public int WaitFor(string DataToWaitFor)
    {
        // Get the starting time
        long lngStart = DateTime.Now.AddSeconds(this.timeout).Ticks;
        long lngCurTime = 0;

        while (strWorkingData.ToLower().IndexOf(DataToWaitFor.ToLower()) == -1)
        {
            // Timeout logic
            lngCurTime = DateTime.Now.Ticks;
            if (lngCurTime > lngStart)
            {
                throw new Exception("Timed Out waiting for : " + DataToWaitFor);
            }
            Thread.Sleep(1);
        }
        strWorkingData = "";
        return 0;
    }

One of the methods available (and the one we will use) to send data back to the Telnet service:

    public void SendMessage(string Message)
    {
        DoSend(Message + "\r");
    }
    private void DoSend(string strText)
    {
        try
        {
            Byte[] smk = new Byte[strText.Length];
            for (int i = 0; i < strText.Length; i++)
            {
                Byte ss = Convert.ToByte(strText[i]);
                smk[i] = ss;
            }

            s.Send(smk, 0, smk.Length, SocketFlags.None);
        }
        catch (Exception ers)
        {
            Console.Error.WriteLine(ers.ToString());
            //MessageBox.Show("ERROR IN RESPOND OPTIONS");
        }
    }

To compile the dll, we follow this simple process: First, you will need to locate where the .net runtime is installed on your computer. One of the easier ways to do this is to perform a search for csc.exe on your machine. Most likely, the path will be the same as it is on my computer: \Windows\Microsoft.NET\Framework\v2.0.50727. In order to compile, this needs to be added to your %PATH. This can be done at the command line or by modifying the Advanced System Properties -> Environment Variables. When using the latter method, all future cmd windows will use the updated path - if you have a cmd window already open and then modify the path in the system properties, it will not have the updated %PATH statement. So, just be sure you are working in a cmd window that is opened after adding to the path in the system properties.

At the command window, change to the directory where the source files are located and compile:

csc /t:library /out:scottp.Net.Comm.dll telnet.cs

We have told the compiler (csc.exe) to compile a library and name it scottp.Net.Comm.dll using the source code contained in telnet.cs

Next, we will write the remainder of code that makes up the overall program. The executable will be much smaller in terms of lines of code than the library we just looked at. In this example, the program would be considered unusable in a production environment, because we have hard coded an IP address, username, and password for the router we want to download the configuration from. To have a usable program, these three values could be taken in at the command line as arguments when running the program. However, since this is just for demonstration purposes, the program will be kept simple. In future articles, we will expand the functionality of the program.

Below is the entire source of the ConfigSafe.exe program:

using System;
using System.Collections.Generic;
using System.Text;
using scottp.Net.Comm;

namespace ConfigBackup
{
    class Program
    {
        static void Main(string[] args)
        {
            CiscoNoEnable cNE = new CiscoNoEnable();
            cNE.sHostName = "10.1.100.1";
            cNE.sUsername = "admin";
            cNE.sPassword = "cisco";
            cNE.getConfig();
        }
    }
        public class CiscoNoEnable
        {

        public string sHostName;
        public string sUsername;
        public string sPassword;

        private void Initialize_Components()
        {
            sHostName = "";
            sUsername = "";
            sPassword = "";
        }

        public CiscoNoEnable()
        {
            Initialize_Components();
        }
        public void getConfig()
        {

            this.sHostName = this.sHostName.Trim();
            this.sUsername = this.sUsername.Trim();
            this.sPassword = this.sPassword.Trim();

            Telnet mST = new Telnet(this.sHostName, 23, 8);

            if (mST.Connect() == false)
            {
                Console.WriteLine("");
                Console.WriteLine("Error: ");
                Console.WriteLine("Timeout connecting to: " + this.sHostName);
                Console.WriteLine("");
            }
            else
            {
                try
                {
                    mST.WaitFor("Username:");
                }
                catch (Exception exc)
                {
                    Console.WriteLine(exc.Message);
                }
                mST.SendMessage(this.sUsername);
                mST.WaitFor("Password:");
                mST.SendMessage(this.sPassword);
                mST.WaitFor("#");
                mST.SendMessage("term len 0");
                mST.WaitFor("#");
                mST.SendMessage("show run");
                mST.WaitFor("#");
                mST.SendMessage("exit");
                Console.Write(mST.FindStringBetween("bytes\r\n", "\r\n\r\n",
                "Error: Configuration not obtained"));
            }
        }
    }
}

Let's pick a couple of the important areas to understand and talk about a little further. First the include statement we need for the library:

using scottp.Net.Comm;

This tells the compiler that we are accessing methods in the previously created library.

Next, here is the code that makes up the Main code block:

    static void Main(string[] args)
    {
        CiscoNoEnable cNE = new CiscoNoEnable();
        cNE.sHostName = "10.1.100.1";
        cNE.sUsername = "admin";
        cNE.sPassword = "cisco";
        cNE.getConfig();
    }

So, we created a new CiscoNoEnable object called cNE and then set three properties that is required before executing the getConfig method. If we take a closer look at the getConfig method:

public void getConfig()
        {

            this.sHostName = this.sHostName.Trim();
            this.sUsername = this.sUsername.Trim();
            this.sPassword = this.sPassword.Trim();

            Telnet mST = new Telnet(this.sHostName, 23, 8);

            if (mST.Connect() == false)
            {
                Console.WriteLine("");
                Console.WriteLine("Error: ");
                Console.WriteLine("Timeout connecting to: " + this.sHostName);
                Console.WriteLine("");
            }
            else
            {
                try
                {
                    mST.WaitFor("Username:");
                }
                catch (Exception exc)
                {
                    Console.WriteLine(exc.Message);
                }
                mST.SendMessage(this.sUsername);
                mST.WaitFor("Password:");
                mST.SendMessage(this.sPassword);
                mST.WaitFor("#");
                mST.SendMessage("term len 0");
                mST.WaitFor("#");
                mST.SendMessage("show run");
                mST.WaitFor("#");
                mST.SendMessage("exit");
                Console.Write(mST.FindStringBetween("bytes\r\n", "\r\n\r\n",
                "Error: Configuration not obtained"));
            }
        }

We notice it uses the Telnet method in our library using the hostname set in the CiscoNoEnable property and has port 23 and a value of 8 seconds hard coded into the program. If the Telnet object is able to connect, we use a try/catch block and wait for the telnet server to return the text 'Username'. If/When we see this text, the value set in the UserName property is sent to the telnet server. The telnet server is expected to return a 'Password:' prompt, in which the value of the password property is sent back to the telnet server.

After logging in, we expect a #, which tells us we are in enable mode and then issue the 'term len 0 command', followed by a show run command, and then terminate the connection. We then find all the text between the word 'bytes' (which will be contained in the first line of the response) and the end of the file and writes that text to the console. If we can't find that text, then the telnet server didn't send us the response expected, so an error message is written to the console instead.

To compile the executable, issue the command:

csc /t:exe /out:ConfigSafe.exe /r:scottp.Net.Comm.dll ConfigSafe.cs

This tells the compiler to compile into an executable file with the name ConfigSafe.exe and that the scottp.Net.Comm.dll library is a requirement in order to compile and last, the code to compile is contained in ConfigSafe.cs

By default, a successful run will output the configuration to the console, which is not that useful - so we will pipe the output to a file.

Now we will take a look at the output by opening config.txt in notepad:

The configuration in the text file also serves as the test configuration used for the IOS device in this example. As you can see, the authorization command was used to give the admin user privileged access, which puts us directly into enable mode. We could have just as easily looked for a greater than sign '>' and issued an 'enable' command, in order to enter into enable mode.

I hope you have found this useful and stay tuned for future articles building on this foundation to make a program that can be used in your daily work.

ConfigSafe Source files