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:
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);
}
}
}
* 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);
}
}
}
Нема коментара:
Постави коментар