游子的博客
慈母手中线,游子身上衣, 临行密密缝,意恐迟迟归, 谁言寸草心,报得三春晖。 数据读取中,请稍候......
posts - 337,  comments - 546,  trackbacks - 0

Introduction

MSN messenger is a well known program. It lets you communicate with other users through Instant Messaging. Some people like to write their own MSN messenger, so that they can make some own adjustments and cool features. I have written, together with another student, our own MSN client in C#.  Recently Microsoft no longer supported protocols below MSNP 8. So I had to adjust our program to use MSNP9 (it used MSNP7).

After I have posted an earlier article about MSN, people asked me to give some more information on how to connect to the MSN servers.

So with this little article I would like to explain how you can connect to MSN with the use of the MSNP9 protocol. This article tells you what commands you have to send, and how to react to it. It uses an article that I have posted before. You can find it here.

Using the code

You can use this code in your own program, or you can look and see how you can connect to MSN in C#. The demo programs shows how you can use the following code.

What do we need?

First we use a separate class that will connect to the MSN server. We call it MSNConnection. Next we build another class that handles the commands that we receive from the servers, lets call this class ServerCommand.

Next we need to declare some variables that we will use in the class MSNConnection.

				private
				long            _transactionID = 0;
private TcpClient       _socket;
private NetworkStream   _stream;
private StreamReader    _reader;
private StreamWriter    _writer;

The transactionID is sent with almost every message that we send (also in chat sessions). The _reader and _writer are the streams from the socket.

Now we have the above, let's define some functions that will do the work for us. First we need to initialize the socket and StreamReaders.

_transactionID = 0;
_socket = new TcpClient(host, port);
_stream = _socket.GetStream();
_reader = new StreamReader(_stream, Encoding.ASCII);
_writer = new StreamWriter(_stream, Encoding.ASCII);
_writer.AutoFlush = true;

We call this function ConnectSocket and it takes two parameters (host and port). Every time we make a new connection the transactionId is set to zero. If we make a function that creates the socket, why not make a function that closes the socket. Let's call it dispose.

				if( _socket != null )
{
    _reader.Close();
    _writer.Close();
    _stream.Close();
    _socket.Close();
    _socket = null;
}

We are making functions that read and write to the StreamReader and StreamWriter. First we define the StreamWriter. We then make a function that builds the string: WriteCommand. It takes 3 parameters: first is the command to send, next the parameters and last the option to leave the transactionId out.

				string line;
// check what type of format it should beif (bSendId) 
    line = string.Format("{0} {1} {2}", command, _transactionID, parameters);
else
    line = string.Format("{0} {1}", command, parameters);
// Write the line
WriteLine(line, true);

The function WriteLine eventually writes the string to the StreamWriter. This function has two parameters first is the entire string and next is the option to send only a string and no end character.

				if (writeNewLine)
    _writer.WriteLine(line);
else
    _writer.Write(line);
// raise the transactionId
_transactionID++;

We have defined some writing functions. We also need to read the information. Let's call this function ReadCommand. This function reads from the reader, if there is nothing in the socket then makes an empty ServerCommand, else we make a ServerCommand with the given response.

				string  line = _reader.ReadLine();
Console.WriteLine("Reading: " + line);
if (line == null) 
{
    Console.WriteLine("Nothing received");
    returnnew ServerCommand();
}
else
{
    returnnew ServerCommand(line);
}

You noticed that I use the ServerCommand object. Lets look at the ServerCommand class.

				private
				string _cmdID;
privatestring _line;
privatestring[] _params;

public ServerCommand(string line)
{
    _line = line;
    // always 3 characters command
    _cmdID = line.Substring(0, 3);
    if (!(_cmdID == "QNG"))
    {
        _params = line.Substring(4).Split(' ');
    }
}

public ServerCommand()
{
    _line = "";
    _cmdID = "ERROR";
}

If we use the constructor with a valid line, then we will get a ServerCommand with the right information. If we get a QNG command then there will be no parameters. The command is always a three letter combination. If we use the constructor with no string, then the program knows that there is something wrong.

The other functions are used for retrieving the data from this class.

				public
				string CommandName
{
    get { return _cmdID; }
}

publicstring Param(int index)
{
    return _params[index];
}

Now all the functions are explained above, we can make the Connect function. This function needs two parameters: first is a valid username that has a passport, next is the password for that username.

First we are going to connect to the server.

				string  host = "messenger.hotmail.com"; 
int port = 1863;

ConnectSocket(host, port);

Now we are going to write the server commands, the first command we are going to write is the VER command. This command indicates what protocol we are using. We are using protocol MSNP9. We read the result in a new ServerCommand Object.

ServerCommand ServCom

WriteCommand("VER", "MSNP9 CVRO", true); 
ServCom = ReadCommand();

Next we are going to check if we received the right information, if not then exit this function.

				if (ServCom.CommandName != "VER")
{
    return1;
}

Now we have to send the CVR command, the parameters are the same like a real MSN messenger client. The server will response with a CVRcommand and a download place where you can get a newer MSN messenger, we just ignore this.

WriteCommand("CVR", 
  "0x0409 win 4.10 i386 MSNMSGR 5.0.0544 MSMSGS " + UserName, true);
ServCom = ReadCommand(); 

After this is successful, we send de USR command with a TWN parameter and your username. TWN stands for TWEENER, this is based on the passport authentication.

WriteCommand("USR", "TWN I " + UserName, true);
ServCom = ReadCommand();

If the command was not the USR command, it was probably the XFR command, this indicates to us that we have to transfer to another server. In the result is the new host and port, parse it.

				string[] arIP = ServCom.Param(2).Split(':');
host = arIP[0];
port = int.Parse(arIP[1]);

Now disconnect and connect again using the new host and port.

Dispose();

We were operating in a while loop, so the connect sequence starts again. You have to send all the commands again, but this time it is to another server.

If the responsecommand is USR then we are going to connect to this server. The response will hold a ChallengeString, we need this ChallengeString to get a valid ClientTicket.

				if (ServCom.CommandName == "USR")
{
    ChallengeString = ServCom.Param(3);
    break;
}

With the given ChallengeString we will get a valid clientticket.

				string clientticket = GetClientTicket(UserPassword, 
                                   UserName, ChallengeString);

This step is a rather large step. I have already wrote an article on this part, read all about it right here.

Finally now we have a ticketID, send it to the server.

WriteCommand("USR", "TWN S " + clientticket, true);
ServCom = ReadCommand();

If we are in luck, we get the USR |transid| OK message. This indicates that we have successfully connected to the MSN servers.

Let's get our username and our screen name, this information was send together with de USR command.

_UserName = ServCom.Param(2);
_ScreenName = ServCom.Param(3);

Last we are going to notify that we are going online, you can put a number of initial status messages here. NLN just means "Online", the rest are:

  • BSY - Busy
  • IDL - Idle
  • BRB - Be Right Back
  • AWY - Away
  • LUN - Out to Lunch
  • PHN - On the Phone
  • FLN - Offline
  • HDN - Hidden
WriteCommand("CHG", "NLN", true);

Now you are connected and everybody who have you in their list will see you online.

Right now, you have to get all the contacts, but that part is too big to explain right here.

Conclusion

Connecting to a MSN server is not that hard, you have to understand what to send and how to reply. Hopefully you have liked this article, so maybe now you want to write your own MSN messenger program.

Good luck!

About Paul Wijntjes (dominion99)


I am 24 year old student and live in the Netherlands. I have graduated in 2004, for the study Computer Science. After my study I have start working as a Technical Consultant in the Identity & Access management branch.

I have aquired my MCSE and MCSD certification. Currently I developing a lot of cool things in .NET & Java.

I am the main programmer for the tool that we are developing within our company. This tool lets users work with MIIS (Microsoft Identity Integration Server).

You can reach me at pwijntjes@home.nl

Click here to view Paul Wijntjes (dominion99)'s online profile.


Other popular Internet / Network articles:

posted on 2006-12-21 15:03 游子 阅读(1531) 评论(1)  编辑 收藏 引用 所属分类: 软件

FeedBack:
# Generate Valid MSNP9 ClientTicket--转载
2006-12-21 15:05 | 游子
Introduction
MSN messenger is a well known program. It lets you communicate with other users through Instant Messaging. Some people like to write their own MSN messenger, so that they can make some own adjustments and cool features. I have written, together with another student, our own MSN client in C#. Recently Microsoft no longer supported protocols below MSNP 8. So I had to adjust our program to use MSNP9 (it used MSNP7).

With this little article I would like to explain how you can connect to MSN again with use of the MSNP9 protocol. Note this article only describes how you can get a valid TicketId. The rest of the connection is another part. If somebody is interested in that part, email me so that I post that information right here.

Generating a Client Ticket
There are two functions that I have used to write this. You can do this in one function. But the function GetLoginServerAddres() is actually an optional function, because the addresses do not change often. First we define some variables, to make it a bit easier. We could use a IDictionary here to get a Key Mapping. But the response is a static response so we could leave it like this.

public int DARREALM = 0;
public int DALOGIN = 1;
public int DAREG = 2;
public int PROPERTIES = 3;
public int GENERALDIR = 4;
public int HELP = 5;
public int CONFIGVERSION = 6;

public ArrayList PassportUrls;

Now we can start getting the ClientTicket.

First you call the function GetClientTicket() with 3 parameters, first the password, second a valid username and last a valid ChallengeString (you get a valid challengestring while connecting to a MSN server, after that you have to send a clienticket to a MSN server to get authenticated)

public string GetClientTicket(string Password, string Username,
string ChallengeString)

First thing that the function GetClientTicket() does is to call the GetLoginServerAdress() function.

This function does not need any parameters. Lets look at this function. First it connects to the nexus server.

HttpWebRequest ServerRequest = (HttpWebRequest)WebRequest.Create(
"https://nexus.passport.com/rdr/pprdr.asp");
HttpWebResponse ServerResponse = (HttpWebResponse)ServerRequest.GetResponse();

If all succeeds, the response will look like this

HTTP/1.1 200 OK \r\n
Server: Microsoft-IIS/5.0\r\n
Date: Mon, 28 Okt 2003 11:57:47 GMT\r\n
Connection: close\r\n
PassportURLs:
DARealm=Passport.Net,DALogin=login.passport.com/login2.srf,
DAReg=http://register.passport.net/uixpwiz.srf,
Properties=https://register.passport.net/editprof.srf,
Privacy=http://www.passport.com/consumer/privacypolicy.asp,
GeneralRedir=http://nexusrdr.passport.com/redir.asp,
Help=http://memberservices.passport.net/memberservice.srf,
ConfigVersion=11\r\n
Content-Length: 0\r\n
Content-Type: text/html\r\n
Cache-control: private\r\n
\r\n

If the result is OK

if (ServerResponse.StatusCode == HttpStatusCode.OK)

Then pick up the result

string retrieveddata = ServerResponse.Headers.ToString();
Get the line for PassportURLs and last split it and put it in the ArrayList

PassportUrls = new ArrayList();
string[] result = ServerResponse.Headers.Get("PassportURLs").Split(',');
foreach (string s in result)
{
// The actual adres is provided behind the '=' sign
PassportUrls.Add(s.Substring(s.IndexOf('=') + 1));
}

If the GetLoginServerAdress() is ready, return to the GetClientTicket()

Now we have a valid address on the DALOGIN position


string uri = "https://" + PassportUrls[DALOGIN];

HttpWebRequest ServerRequest;
HttpWebResponse ServerResponse;
try
{

The login can be redirected to another server. Because you can not predict the amount of attempts that you will need for login, so put this step in an endless loop.


while( true )
{

Make a new request, and set some properties. (protocol 1.0 works better that 1.1)

ServerRequest = (HttpWebRequest)HttpWebRequest.Create(uri);
ServerRequest.AllowAutoRedirect = false;
ServerRequest.Pipelined = false;
ServerRequest.KeepAlive = false;
ServerRequest.ProtocolVersion = new Version(1,0);

Build the authentication header, this will be sent to the TicketServer. And get the response.

ServerRequest.Headers.Add("Authorization",
"Passport1.4 OrgVerb=GET,OrgURL=http%3A%2F%2Fmessenger%2Emsn%2Ecom,sign-in="
+ Username.Replace("@", "%40") + ",pwd=" + Password + ","
+ ChallengeString + "\n");
ServerResponse = (HttpWebResponse)ServerRequest.GetResponse();

Know we look at the status-code of the response. If it is OK then we have to parse the Authentication-Info

if (ServerResponse.StatusCode == HttpStatusCode.OK)
{
// Pick up the information of the authentications
string AuthenticationInfo = ServerResponse.Headers.Get(
"Authentication-Info");
// Get the startposition of the ticket id (note it is between two quotes)
int startposition = AuthenticationInfo.IndexOf('\'');
// Get the endposition
int endposition = AuthenticationInfo.LastIndexOf('\'');
// Substract the startposition of the endposition
endposition = endposition - startposition ;

// Close connection
ServerResponse.Close();

// Generate a new substring and return it
return AuthenticationInfo.Substring(startposition + 1, endposition -1 );

}

Here is a OK response from the MSN server, in the Authentication-Info line you see between two quotes a valid clientticket (start from "from-PP=").

Collapse
HTTP/1.1 200 OK \r\n
Server: Microsoft-IIS/5.0\r\n
Date: Mon, 28 Okt 2003 11:57:49 GMT\r\n
PPServer: H: LAWPPIIS6B061\r\n
Connection: close\r\n
Content-Type: text/html\r\n
Expires: Mon, 02 Jun 2003 11:58:00 GMT\r\n
Cache-Control: no-cache\r\n
cachecontrol: no-store\r\n
Pragma: no-cache\r\n
P3P: CP="DSP CUR OTPi IND OTRi ONL FIN"\r\n
Set-Cookie:
MSPSec1= ; expires=Thu, 30-Oct-1980 16:00:00 GMT;
domain=.passport.com;path=/;HTTPOnly=;version=1 \r\n
Set-Cookie:
MSPSec=5Cdd1SshOELpwqafsSuYSiDEuEtP1PUaX99YOZca
oJP3vkIn7DXozt868I7eJNjcWG;
HTTPOnly= ; domain=.passport.com;path=/;secure=\r\n
Set-Cookie:
MSPAuth=5yDBU0BqvDa7UiY9W9nVEncRXCLD4gjLmtEr2XkunnafkOgdgG5x
*CEpqe7MyZEOir*EiA1PbwLKzqCGO671TeTQ$$;S
HTTPOnly= ; domain=.passport.com;path=/\r\n
Set-Cookie:
MSPProf=5a0mKE6PKDsxz!*4apQt0amnQOGLYqcCm78ie! MmHq0KnA
iIJM0z0Zajs8NL7ux7Ae0hnH5AAoB!zXIZ9
jTA2rcQttC*RKKRsc9k7JflwThB!H0Qa*6ipGcdj5co6taPir;
HTTPOnly= ; domain=.passport.com;path=/\r\n
Set-Cookie:
MSPVis=507;domain=.passport.com;path=/\r\n
Set-Cookie: MSPPre=esqkk@hotmail.com;
HTTPOnly= ; domain=.passport.com;path=/;
Expires=Wed, 30-Dec-2037 16:00:00 GMT \r\n
Set-Cookie: MSPShared= ;
HTTPOnly= ; domain=.passport.com;path=/;
Expires=Thu, 30-Oct-1980 16:00:00 GMT\r\n
Authentication-Info:
Passport1.4
da-status=success,tname=MSPAuth,tname=MSPProf,tname=MSPSec,
from-PP='t=53*1hAu8ADuD3TEwdXoOMi08sD*2!cMrntTw
VMTjoB3p6stWTqzbkKZPVQzA5NOt19SLI60PY!b8K4YhC!Ooo5ug$$&
p=5eKBBC!yBH6ex5mftp!a9DrSb0B3hU8aqAWpaPn07iCGBw5akemi
WSd7t2ot!okPvIR!Wqk
!MKvi1IMpxfhkao9wpxlMWYAZ!DqRfACmyQGG112Bp9xrk04!BVBUa9
*H9mJLoWw39m63YQRE1yHnYNv08nyz43D3OnMcaCoe
SaEHVM7LpR*LWDme29qq2X3j8N'
,ru=http://messenger.msn.com\r\n
Content-Length: 0\r\n
\r\n
If the statuscode is 302, then there is a redirect, read the new address and connect again (we are still in the while loop)
else if (ServerResponse.StatusCode == HttpStatusCode.Found)
{
uri = ServerResponse.Headers.Get("Location");
}

Here is a redirect response from the server. You see in the location part that there is a new login address.

HTTP/1.1 302 Found\r\n
Date: Mon, 28 Okt 2003 11:57:48 GMT\r\n
PPServer: H: LAWPPLOG5C006\r\n
Connection: close\r\n
Content-Type: text/html\r\n
Expires: Mon, 02 Jun 2003 11:57:32 GMT\r\n
Cache-Control: no-cache\r\n
cachecontrol: no-store\r\n
Pragma: no-cache\r\n
P3P: CP="DSP CUR OTPi IND OTRi ONL FIN"\r\n
Authentication-Info: Passport1.4 da-status=redir\r\n
Location: https://loginnet.passport.com/login2.srf?lc=1033\r\n
\r\n

A last we have some error handling. If your credentials where not correct you get a 401 error. Give back the error to the calling function so that the can warn the user for instance.

catch (WebException e)
{
if (e.Status == WebExceptionStatus.ProtocolError)
{
return "401";
}
else
{
return "0";
}
}

A last here is a 401 response. The official MSN client uses the "WWW-Authenticate" part to give back a nice error message.

Collapse
HTTP/1.1 401 Unauthorized\r\n
Server: Microsoft-IIS/5.0\r\n
Date: Mon, 28 Okt 2003 11:58.03 GMT\r\n
PPServer: H: LAWPPIIS6B077\r\n
Connection: close\r\n
Content-Type: text/html\r\n
Expires: Mon, 15 Sep 2003 07:57:14 GMT\r\n
Cache-Control: no-cache\r\n
cachecontrol: no-store\r\n
Pragma: no-cache\r\n
P3P: CP="DSP CUR OTPi IND OTRi ONL FIN"\r\n
PassportConfig: ConfigVersion=11\r\nWWW-Authenticate: Passport1.4
da-status=failed,srealm=Passport.NET,ts=-3,prompt,
cburl=http://www.passportimages.com/XPPassportLogo.gif,
cbtxt=Type%20your%20e-mail%20address%20and%20password
%20correctly.%20If%20you%20haven%E2%80%99t%20registered%20
with%20.NET%20Passport%2C%20click%20the%20Get%20a
%20.NET%20Passport%20link.\r\n
Content-Length: 390\r\n\r\n
Points of Interest
I noticed that this code won't work when you are trying to connect multiple times. I'm still finding what is causing the error. (I there is a delay in connecting another account it works quit well)

Conclusion
So now you have read this article you can make your own program that connects to the MSN server. I'm currently expanding our MSN client, maybe some day it will be freeware. If you want some more explanation or have some comments, Email me

About Paul Wijntjes (dominion99)

I am 24 year old student and live in the Netherlands. I have graduated in 2004, for the study Computer Science. After my study I have start working as a Technical Consultant in the Identity & Access management branch.

I have aquired my MCSE and MCSD certification. Currently I developing a lot of cool things in .NET & Java.

I am the main programmer for the tool that we are developing within our company. This tool lets users work with MIIS (Microsoft Identity Integration Server).

You can reach me at pwijntjes@home.nl



  回复  更多评论
  
只有注册用户登录后才能发表评论。

欢迎大家扔鸡蛋!送鲜花!

博客可以收入过千吗?

<2007年3月>
日一二三四五六252627281234567891011121314151617
18192021222324252627282930311234567

常用链接

留言簿(8)

随笔分类(314)

随笔档案(337)

文章分类(7)

文章档案(10)

相册

收藏夹(1)

其它

友情链接

数字电视

生活、旅游

自己的链接

计算机

搜索

  •  

积分与排名

  • 积分 - 408390
  • 排名 - 9

最新评论

阅读排行榜

评论排行榜