уторак, 8. мај 2012.





C# MSN Bot – The beginnings.

Following on from my C# IRC bot that I wrote a few months ago, I thought I’d share some of my work with the MSN protocol. This is a little bit more complicated than the IRC protocol in that it requires hashing and authentification using HTTPS. Excellent generic documentation for implementing the MSN protocol which can be used with pretty much any programming language can be found here.
Pretty much the main purpose of this code right now is to control the notification server and authenticate the client with a few methods there for future use, which is the main server where statuses and other such things are written. It isn’t the best written code but it should be a good example of how the connection method using MSN works and how it can be implemented in the C# language. Any feedback is welcome via comment, but there is more to come and probably a lot more cleanup to be done (much of the stuff in dispatch_server method is reused in notification_server method for example). Throwing error messages can also be improved, but I hope it is useful to someone.
Controlling chat connections and the switchboard will be dealt with in further blogs on this.
Code below:
    /* Implementing MSN Protocol 8 in C# (Notification Server)
     * by Gareth Peoples
     * mail[at]gpeopl.es / gareth.peoples.2009[at]nuim.ie
     * Blog at www.gpeopl.es
     *
     * See http://msnpiki.msnfanatic.com/index.php/MSNP8:Example_Session
     * for protocol information.
     */


using System;
using System.IO;
using System.Net;
using System.Web;
using System.Text;
using System.Net.Sockets;
using System.Net.Security;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

namespace msnprotocol
{
    public class MSN    {
        int count = 1; // Count to record the current step in the transaction
        String username = ""; // String to record the username
        String password = ""; // String to record the password
        TcpClient socket; // Initialising socket variable
        StreamWriter writer; // Initialising a writer instance
        StreamReader reader; // Initialising a reader instance
       
        /* Constructor to load username and password in from an
         * external C# class that wishes to use this work */

        public MSN (String username, String password) {
            this.username = username;
            this.password = password;
            String[] notification = dispatch_server("messenger.hotmail.com", 1863);
            notification_server(notification[0], Convert.ToInt32(notification[1]));
        }
       
        /* Method to connect to the dispatch server with set host and
         * port information to be provided by an external class that wishes
         * to use our basic MSNP8 library */

        public String[] dispatch_server(String host, int port) {
            String data = "";
            String[] splitData;
            String[] notificationData;
            try {
                socket = new TcpClient(host, port); // Open connection
                socket.ReceiveBufferSize = 1024;
                Console.WriteLine("Successfully connected to dispatch server");
                NetworkStream stream = socket.GetStream(); // getting stream for connection
                reader = new StreamReader(stream); // setting up reader to read from stream
                writer = new StreamWriter(stream); // setting up writer to write to stream
                write("VER " + count + " MSNP8 CVR0"); // Write the first data string to the server
                // Reading and interpreting data given by the dispatch server
                try {
                    while(true) {
                        data = reader.ReadLine(); // Read a line of data from stream
                        Console.WriteLine("< << " + data); // Write server responses to console
                        splitData = data.Split(' '); // Split data up for interpretation
                        if (splitData[0].Equals("VER")) {
                            write("CVR " + count + " 0x0409 win 4.10 i386 MSNMSGR 5.0.544 MSNMSGS " + username);
                        }
                        else if (splitData[0].Equals("CVR")) {
                            write("USR " + count + " TWN I " + username);
                        }
                       
                        // Transfer to a different server
                        else if (splitData[0].Equals("XFR") && !splitData[2].Equals("SB")) {
                            notificationData = splitData[3].Split(':');
                            reader.Close();
                            writer.Close();
                            stream.Close();
                            return notificationData; // returns notification data for the server
                        }
                    }
                }
                catch {
                    Console.WriteLine("Stopped reading from dispatch server");
                }  
            }
            catch {
                Console.WriteLine("Error: Unable to connect to server");
            }
            return null; // if the code crashes return nothing
        }
       
        /* Method to connect to the notification server with set host and
         * port information to be provided by an external class that wishes
         * to use our basic MSNP8 library */

        public void notification_server(String host, int port) {
            String data;
            String[] splitData;
            String authdata;
            try {
                // Create a new socket
                socket = new TcpClient(host, port);
                // Set the buffer size
                socket.ReceiveBufferSize = 1024;
                Console.WriteLine("Connected to notification server");
                NetworkStream stream = socket.GetStream();
                reader = new StreamReader(stream);
                writer = new StreamWriter(stream);
                write("VER " + count + " MSNP8 CVR0");
                    while(true) {
                        data = reader.ReadLine();
                            splitData = data.Split(' ');
                            Console.WriteLine("<<< " + data);
                            // Send server information about the system
                            if (splitData[0].Equals("VER")) {
                                write("CVR " + count + " 0x0409 win 4.10 i386 MSNMSGR 5.0.544 MSNMSGS " + username);
                            }
                            // Request authentification
                            else if (splitData[0].Equals("CVR")) {
                                write("USR " + count + " TWN I " + username);
                            }
                            // If the server is ready for authentification do this using HTTPS methods
                            else if (splitData[0].Equals("USR") && splitData[2].Equals("TWN")) {
                                authdata = splitData[4];
                                authentification(authdata);
                            }
                            // If the server has finished logging in
                            else if(splitData[0].Equals("ClientPort:")) {
                                Console.WriteLine("Successfully logged in");
                                // Request information about contacts
                                write("SYN " + count + " 0");
                                // Set online status
                                write("CHG " + count + " NLN");
                            }
                            // If a challenge is received
                            else if(splitData[0].Equals("CHL")) {
                                challenge(splitData[2]);
                            }
                            // If a contact's status is received
                            else if(splitData[0].Equals("ILN")) {
                            }
                    }
            }
            catch {
                Console.WriteLine("Connection to notification server closed");
            }
        }
       
        /* Tweener MSN Passport authentification method using HTTPS takes place here
         * using data from the notification server */

        public void authentification(String auth) {
            String working = "";
            String ticket = "";
            String connecturl = "";
            String authoriseheader = "";
            String[] split;
            try {
                Console.WriteLine("Connecting to nexus server");
                // Send all certificate validation requests to Validator method
                ServicePointManager.ServerCertificateValidationCallback = Validator;
                // Address for nexus server
                String address = "https://nexus.passport.com/rdr/pprdr.asp";
                // Set up web request
                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(address);
                // Set up web response
                HttpWebResponse response = (HttpWebResponse) request.GetResponse();
                // Get the passport URL's header for redirecting to login server
                working = response.GetResponseHeader("PassportURLs");
                split = working.Split(',');
                split = split[1].Split('=');
                connecturl = split[1];
                response.Close();
                /* Connect to login server provided by nexus server to get official
                 * log in ticket */

                try {
                    Console.WriteLine("Connecting to login server: https://" + connecturl);
                    // Set up web request for
                    request = (HttpWebRequest)WebRequest.Create("https://" + connecturl);
                    // Set up a header for authentification by server
                    authoriseheader = "Passport1.4 OrgVerb=GET,OrgURL=http%3A%2F%2Fmessenger%2Emsn%2Ecom,";
                    authoriseheader += "sign-in=" + urlencode(username) + ",pwd=" + urlencode(password) + "," + auth;
                    request.Headers.Add("Authorization", authoriseheader);
                    response = (HttpWebResponse) request.GetResponse();
                    working = response.GetResponseHeader("Authentication-Info");
                    split = working.Split(',');
                    ticket = split[1].Substring(9);
                    ticket = ticket.TrimEnd('\'');
                    write("USR " + count + " TWN S " + ticket);
                }
                catch  {
                    Console.WriteLine("Error: Unable to connect to login server https://" + connecturl);
                }
            }
            catch {
                Console.WriteLine("Error: Unable to connect to nexus server");
            }
        }
        /* Avoiding SSL validation errors
         * See http://www.mono-project.com/UsingTrustedRootsRespectfully for more information */

       
        public static bool Validator(object sender, X509Certificate certificate,
                                     X509Chain chain, SslPolicyErrors sslPolicyErrors) {
            return true;
        }
       
        /* Method for writing to the network stream */
        public void write(String data) {
            try {
                writer.WriteLine(data); // Write data to network stream
                Console.WriteLine(">>> " + data); // Show this on console
                writer.Flush();
                count++;
            }
            catch {
                Console.WriteLine("Error: in writing line to server");
            }
        }
       
        /* Method for sending challenge to MSN notification server */
        public void challenge(String data) {
            try {
                data += "I2EBK%PYNLZL5_J4";
                byte[] array = Encoding.Default.GetBytes(data);
                MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
                array = md5.ComputeHash(array);
                data = "";
                foreach (byte a in array) {
                    if (a < 16) data += "0" + a.ToString("x");
                    else data += a.ToString("x");
                }
                write("QRY " + count + " PROD00504RLUG%WL " + data.Length);
                Console.WriteLine("Sent hash: " + data);
                writer.Write(data);
                writer.Flush();
            }
            catch {
                Console.WriteLine("Unable to compute and send challenge hash");
            }
        }
       
        /* Method for dealing with setting status on the MSN notification server */
        public void setStatus(String status) {
            if (status.Equals("online")) {
                Console.WriteLine("Changing status to online...");
                write("CHG " + count + " NLN 0");
            }
            else if (status.Equals("away")) {
                Console.WriteLine("Changing status to away...");
                write("CHG " + count + " AWY 0");
            }
            else if (status.Equals("busy")) {
                Console.WriteLine("Changing status to busy...");
                write("CHG " + count + " BSY 0");
            }
            else if (status.Equals("lunch")) {
                Console.WriteLine("Changing status to out to lunch...");
                write("CHG " + count + " LUN 0");
            }
            else if (status.Equals("idle")) {
                Console.WriteLine("Setting status to idle...");
                write("CHG " + count + " IDL 0");
            }
            else if (status.Equals("phone")) {
                Console.WriteLine("Changing status to on the phone...");
                write("CHG " + count + " PHN 0");
            }
            else if (status.Equals("hidden")) {
                Console.WriteLine("Changing status to appear offline...");
                write("CHG " + count + " HDN 0");
            }
            else {
                Console.WriteLine("Incorrect: must be away, busy, idle or online");
            }
        }
       
        /* Method for setting new nickname on the MSN notification server */
        public void setNickname(String nick) {
            Console.WriteLine("Changing your nick name to " + nick);
            write("REA " + count + " " + passport + " " + urlencode(nick));
        }
       
        /* Method for terminating connection to MSN notification server */
        public void close() {
            Console.WriteLine("Closing connection...");
            write("OUT");
        }
       
        // Method for handling url encoding (upper case)
        public String urlencode(String data) {
            data = HttpUtility.UrlEncode(data);
            char[] current = data.ToCharArray();
            // Converting lower case characters to upper case
            for (int i = 0; i < data.Length; i++) {
                if (current[i].Equals('%')) {
                    current[i+1] = Char.ToUpper(current[i+1]);
                    current[i+2] = Char.ToUpper(current[i+2]);
                }
            }
            data = new String(current);
            return data;
        }
       
        // Method for blocking users
        public void blockUser(String passport) {
            Console.WriteLine("Blocking user with passport: " + passport);
            // Remove user with passport from allow list
            write("REM " + count + " AL " + passport);
            // Move user with passport to block list
            write("ADD " + count + " BL " + passport);
        }
       
        // Method for adding users
        public void addUser(String passport) {
            Console.WriteLine("Adding user with passport: " + passport);
            // Place user on allow list
            write("ADD " + count + " AL " + passport + " " + passport);
        }
       
    }
}

Нема коментара:

Постави коментар