Permalink
Browse files

Use protocol version MSNP18

  • Loading branch information...
1 parent 008dd40 commit a8804bb878e0a6cbfd55c7f321b7a75d6fea0acb @asterite asterite committed Nov 2, 2012
Showing with 191 additions and 39 deletions.
  1. +12 −0 Gemfile.lock
  2. +4 −0 em-msn.gemspec
  3. +6 −0 lib/em-msn.rb
  4. +18 −0 lib/msn/messenger.rb
  5. +70 −0 lib/msn/nexus.rb
  6. +27 −37 lib/msn/notification_server.rb
  7. +3 −2 lib/msn/protocol.rb
  8. +51 −0 lib/msn/soap/msn_sso_template.xml
View
@@ -3,18 +3,30 @@ PATH
specs:
em-msn (0.1)
eventmachine
+ guid
rest-client
GEM
remote: http://rubygems.org/
specs:
+ diff-lcs (1.1.3)
eventmachine (1.0.0)
+ guid (0.1.1)
mime-types (1.19)
rest-client (1.6.7)
mime-types (>= 1.16)
+ rspec (2.11.0)
+ rspec-core (~> 2.11.0)
+ rspec-expectations (~> 2.11.0)
+ rspec-mocks (~> 2.11.0)
+ rspec-core (2.11.1)
+ rspec-expectations (2.11.3)
+ diff-lcs (~> 1.1.3)
+ rspec-mocks (2.11.3)
PLATFORMS
ruby
DEPENDENCIES
em-msn!
+ rspec (~> 2.7)
View
@@ -15,6 +15,7 @@ Gem::Specification.new do |s|
s.files = [
"lib/em-msn.rb",
"lib/msn/message.rb",
+ "lib/msn/nexus.rb",
"lib/msn/messenger.rb",
"lib/msn/notification_server.rb",
"lib/msn/protocol.rb",
@@ -28,4 +29,7 @@ Gem::Specification.new do |s|
s.add_dependency "eventmachine"
s.add_dependency "rest-client"
+ s.add_dependency "guid"
+
+ s.add_development_dependency "rspec", ["~> 2.7"]
end
View
@@ -5,10 +5,16 @@ module Msn
require 'rest-client'
require 'fiber'
require 'cgi'
+require 'guid'
+require 'base64'
require 'digest/md5'
+require 'digest/hmac'
+require 'erb'
+require "rexml/document"
require_relative 'msn/protocol'
require_relative 'msn/message'
+require_relative 'msn/nexus'
require_relative 'msn/notification_server'
require_relative 'msn/switchboard'
require_relative 'msn/messenger'
View
@@ -32,6 +32,14 @@ def set_online_status(status)
end
end
+ def add_contact(email, display_name = email)
+ @notification_server.add "AL", email, display_name
+ end
+
+ def remove_contact(email)
+ @notification_server.rem "AL", email
+ end
+
def on_ready(&handler)
@on_ready_handler = handler
end
@@ -40,6 +48,10 @@ def on_message(&handler)
@on_message_handler = handler
end
+ def on_contact_request(&handler)
+ @on_contact_request = handler
+ end
+
def send_message(email, text)
@notification_server.send_message email, text
end
@@ -50,6 +62,12 @@ def accept_message(message)
end
end
+ def contact_request(email, display_name)
+ if @on_contact_request
+ Fiber.new { @on_contact_request.call(email, display_name) }.resume
+ end
+ end
+
def ready
if @on_ready_handler
Fiber.new { @on_ready_handler.call }.resume
View
@@ -0,0 +1,70 @@
+class Msn::Nexus
+ attr_reader :policy
+ attr_reader :nonce
+
+ Namespaces = {
+ "wsse" => "http://schemas.xmlsoap.org/ws/2003/06/secext",
+ "wst" => "http://schemas.xmlsoap.org/ws/2004/04/trust",
+ "wsp" => "http://schemas.xmlsoap.org/ws/2002/12/policy",
+ "wsa" => "http://schemas.xmlsoap.org/ws/2004/03/addressing",
+ }
+
+ def initialize(policy, nonce)
+ @policy = policy
+ @nonce = nonce
+ end
+
+ def login(username, password)
+ token, secret = get_binary_secret username, password
+ return_value = compute_return_value secret
+ [token, return_value]
+ end
+
+ def get_binary_secret(username, password)
+ msn_sso_template_file = File.expand_path('../soap/msn_sso_template.xml', __FILE__)
+ msn_sso_template = ERB.new File.read(msn_sso_template_file)
+ soap = msn_sso_template.result(binding)
+
+ response = RestClient.post "https://login.live.com/RST.srf", soap
+ xml = REXML::Document.new response
+
+ rstr = REXML::XPath.first(xml, "//wst:RequestSecurityTokenResponse[wsp:AppliesTo/wsa:EndpointReference/wsa:Address!='http://Passport.NET/tb']", Namespaces)
+ token = REXML::XPath.first(rstr, "wst:RequestedSecurityToken/wsse:BinarySecurityToken[@Id='Compact1']", Namespaces).text
+ secret = REXML::XPath.first(rstr, "wst:RequestedProofToken/wst:BinarySecret", Namespaces).text
+
+ [token, secret]
+ end
+
+ def compute_return_value(binary_secret, iv = Random.new.bytes(8))
+ key1 = Base64.decode64 binary_secret
+
+ key2 = compute_key key1, "WS-SecureConversationSESSION KEY HASH"
+ key3 = compute_key key1, "WS-SecureConversationSESSION KEY ENCRYPTION"
+
+ hash = sha1_hmac key2, @nonce
+
+ nonce = "#{@nonce}#{8.chr * 8}"
+
+ des = OpenSSL::Cipher::Cipher.new("des-ede3-cbc")
+ des.encrypt
+ des.iv = iv
+ des.key = key3
+ encrypted_data = des.update(nonce) + des.final
+
+ final = [28, 1, 0x6603, 0x8004, 8, 20, 72, iv, hash, encrypted_data].pack "L<L<L<L<L<L<L<A8A20A72"
+ Base64.strict_encode64 final
+ end
+
+ def compute_key(key, hash)
+ hash1 = sha1_hmac(key, hash)
+ hash2 = sha1_hmac(key, "#{hash1}#{hash}")
+ hash3 = sha1_hmac(key, hash1)
+ hash4 = sha1_hmac(key, "#{hash3}#{hash}")
+
+ "#{hash2}#{hash4[0 ... 4]}"
+ end
+
+ def sha1_hmac(data, key)
+ Digest::HMAC.digest(key, data, Digest::SHA1)
+ end
+end
@@ -3,10 +3,24 @@ class Msn::NotificationServer < EventMachine::Connection
attr_reader :messenger
attr_reader :display_name
+ attr_reader :guid
def initialize(messenger)
@messenger = messenger
+ @guid = Guid.new.to_s
@switchboards = {}
+
+ on_event 'ADD' do |header|
+ if header[3] =~ /\A\d+\Z/
+ messenger.contact_request header[4], header[5]
+ else
+ messenger.contact_request header[3], header[4]
+ end
+ end
+ end
+
+ def username_guid
+ @username_guid ||= "#{messenger.username};{#{guid}}"
end
def send_message(email, text)
@@ -21,7 +35,7 @@ def send_message(email, text)
switchboard.clear_event 'JOI'
switchboard.send_message text
end
- switchboard.usr messenger.username, response[5]
+ switchboard.usr username_guid, response[5]
switchboard.cal email
end.resume
end
@@ -43,48 +57,22 @@ def post_init
def login
Fiber.new do
- response = ver "MSNP8"
- if response[2] != "MSNP8"
- raise "Expected response to be 'VER 0 MSNP8' but it was '#{response}'"
- end
-
- response = cvr "0x0409", "winnt", "5.1", "i386", "MSNMSGR", "6.0.0602", "MSMSGS", username
- if response[2] == "1.0.0000"
- raise "The client version we are sending is not compatible anymore :-("
- end
-
- response = usr "TWN", "I", username
+ ver "MSNP18", "CVR0"
+ cvr "0x0409", "winnt", "5.1", "i386", "MSNMSGR", "8.5.1302", "BC01", username
+ response = usr "SSO", "I", username
if response[0] == "XFR" && response[2] == "NS"
host, port = response[3].split ':'
@reconnect_host, @reconnect_port = response[3].split ':'
close_connection
else
- login_with_challenge(response[4])
+ login_to_nexus(response[4], response[5])
end
end.resume
end
- def login_with_challenge(challenge)
- nexus_response = RestClient.get "https://nexus.passport.com/rdr/pprdr.asp"
- passport_urls = nexus_response.headers[:passporturls]
- passport_urls = Hash[passport_urls.split(',').map { |key_value| key_value.split('=', 2) }]
- passport_url = "https://#{passport_urls['DALogin']}"
-
- authorization = "Passport1.4 OrgVerb=GET,OrgURL=http%3A%2F%2Fmessenger%2Emsn%2Ecom,sign-in=#{CGI.escape username},pwd=#{CGI.escape password},#{challenge}"
- da_login_response = RestClient.get passport_url, 'Authorization' => authorization
- if da_login_response.net_http_res.code != "200"
- raise "Login failed (1)"
- end
-
- authentication_info = da_login_response.headers[:authentication_info]
- authentication_info = authentication_info["Passport1.4 ".length .. -1]
- authentication_info = Hash[authentication_info.split(',').map { |key_value| key_value.split('=', 2) }]
- if authentication_info['da-status'] != "success"
- raise "Login failed (2)"
- end
-
- from_pp = authentication_info['from-PP']
- token = from_pp[1 .. -2] # remove single quotes
+ def login_to_nexus(policy, nonce)
+ nexus = Msn::Nexus.new policy, nonce
+ token, return_value = nexus.login messenger.username, messenger.password
first_msg = true
on_event('MSG') do
@@ -96,22 +84,24 @@ def login_with_challenge(challenge)
on_event('RNG') do |header|
switchboard = create_switchboard header[5], header[2]
- switchboard.ans username, header[4], header[1]
+ switchboard.ans username_guid, header[4], header[1]
end
- response = usr "TWN", "S", token
+ response = usr "SSO", "S", token, return_value, guid
if response[2] != "OK"
raise "Login failed (3)"
end
+ messenger.ready
+
@display_name = CGI.unescape response[4]
end
def create_switchboard(email, host_and_port)
host, port = host_and_port.split(':')
switchboard = EM.connect host, port, Msn::Switchboard, messenger
switchboard.on_event 'BYE' do |header|
- destroy_switchboard email if header[1] == email
+ destroy_switchboard email if header[1] =~ /#{email}/
end
@switchboards[email] = switchboard
end
View
@@ -2,20 +2,21 @@ module Msn::Protocol
include EventMachine::Protocols::LineText2
def post_init
- @trid = 0
+ @trid = 1
@command_fibers = {}
end
def receive_line(line)
puts "<< #{line}" if Msn::Messenger.debug
+
pieces = line.split(' ')
case pieces[0]
when 'CHL'
answer_challenge pieces[2]
when 'RNG'
handle_event pieces
- when 'MSG'
+ when 'MSG', 'NOT', 'GCF'
@header = pieces
size = pieces.last.to_i
@@ -0,0 +1,51 @@
+<Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/"
+ xmlns:wsse="http://schemas.xmlsoap.org/ws/2003/06/secext"
+ xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion"
+ xmlns:wsp="http://schemas.xmlsoap.org/ws/2002/12/policy"
+ xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
+ xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/03/addressing"
+ xmlns:wssc="http://schemas.xmlsoap.org/ws/2004/04/sc"
+ xmlns:wst="http://schemas.xmlsoap.org/ws/2004/04/trust">
+ <Header>
+ <ps:AuthInfo
+ xmlns:ps="http://schemas.microsoft.com/Passport/SoapServices/PPCRL"
+ Id="PPAuthInfo">
+ <ps:HostingApp>{7108E71A-9926-4FCB-BCC9-9A9D3F32E423}</ps:HostingApp>
+ <ps:BinaryVersion>4</ps:BinaryVersion>
+ <ps:UIVersion>1</ps:UIVersion>
+ <ps:Cookies></ps:Cookies>
+ <ps:RequestParams>AQAAAAIAAABsYwQAAAAxMDMz</ps:RequestParams>
+ </ps:AuthInfo>
+ <wsse:Security>
+ <wsse:UsernameToken Id="user">
+ <wsse:Username><%= username %></wsse:Username>
+ <wsse:Password><%= password %></wsse:Password>
+ </wsse:UsernameToken>
+ </wsse:Security>
+ </Header>
+ <Body>
+ <ps:RequestMultipleSecurityTokens
+ xmlns:ps="http://schemas.microsoft.com/Passport/SoapServices/PPCRL"
+ Id="RSTS">
+ <wst:RequestSecurityToken Id="RST0">
+ <wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>
+ <wsp:AppliesTo>
+ <wsa:EndpointReference>
+ <wsa:Address>http://Passport.NET/tb</wsa:Address>
+ </wsa:EndpointReference>
+ </wsp:AppliesTo>
+ </wst:RequestSecurityToken>
+ <wst:RequestSecurityToken Id="RST1">
+ <wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>
+ <wsp:AppliesTo>
+ <wsa:EndpointReference>
+ <wsa:Address>messengerclear.live.com</wsa:Address>
+ </wsa:EndpointReference>
+ </wsp:AppliesTo>
+ <wsse:PolicyReference URI="<%= policy %>"></wsse:PolicyReference>
+ </wst:RequestSecurityToken>
+ ...
+ ...
+ </ps:RequestMultipleSecurityTokens>
+ </Body>
+</Envelope>

0 comments on commit a8804bb

Please sign in to comment.