Skip to content

Instantly share code, notes, and snippets.

@eladmeidar
Forked from radar/talka.rb
Created July 22, 2009 04:37
Show Gist options
  • Save eladmeidar/151816 to your computer and use it in GitHub Desktop.
Save eladmeidar/151816 to your computer and use it in GitHub Desktop.
require 'rubygems'
require 'eventmachine'
require 'digest/md5'
require 'base64'
require 'socket'
# Dunno, may need this later on.
class Session < Struct.new(:username, :id)
end
SESSIONS = {}
module Jabber
def domain_name
"example.com"
end
def time_hash
Digest::MD5.hexdigest(Time.now.to_f.to_s)
end
def determine_ip
@port, @ip = Socket.unpack_sockaddr_in(get_peername)
end
def receive_data(data)
determine_ip
# Client is connecting.
if stream = /<stream:stream.*to='(.*?)'/.match(data)
send_data("<stream:stream xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:client' from='#{stream[1]}' xml:lang='en' version='1.0'>")
# They are trying to log in, send them features.
if !(session = SESSIONS[@ip])
send_data("<stream:features><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>DIGEST-MD5</mechanism><mechanism>PLAIN</mechanism></mechanisms></stream:features>")
# They have logged in, and now are starting a new stream. Welcome them.
else
puts "Welcome aboard, captain."
send_data("<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' id='#{session.username}_#{time_hash}' from='example.com' version='1.0'><stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/><session xmlns='urn:ietf:params:xml:ns:xmpp-session'/></stream:features>")
end
# Client is trying to auth...
elsif auth = /^<auth(.*?)mechanism='(.*?)'(.*?)\/>/.match(data)
if auth[2] == "DIGEST-MD5"
@server_nonce = time_hash
encodee = "realm=\"talka\",nonce=\"#{@nonce}\",qop=\"auth\",charset=utf-8,algorithm=md5-sess\n"
encoded = Base64.encode64(encodee)
# Challenge the client to auth correctly.
send_data("<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>
#{encoded}
</challenge>")
else
puts "UNKNOW AUTH PROTOCOL SENT"
end
# Client sends back response from our auth challenge
elsif response = /^<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>(.*?)<\/response>$/.match(data)
decoded = Base64.decode64(response[1])
# Gather valuable resources...
username = /username="(.*?)"/.match(decoded)[1]
nonce = /nonce="(.*?)"/.match(decoded)[1]
if nonce = @server_nonce
puts "LOGGING IN!"
SESSIONS[@ip] = Session.new(username, nonce)
# Client responds to challenge and then server tells client they are successfully authenticated.
# Even though this isn't a real server we still have to go through the motions.
encoded = "rspauth=#{time_hash}"
send_data("<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>#{encoded}</challenge>")
else
# I don't know how this would happen, but better to assume it can rather than it can not.
puts "BAD NONCE"
# Bad nonce
send_data("<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><temporary-auth-failure/></failure></stream:stream>")
end
# Responding to the next challenge.
elsif response = /<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'\/>/.match(data)
# Welcome aboard, you are now logged in.
send_data("<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>")
# Client asks if it can auth, of course you can!
elsif iq = /<iq type='set' id='(\d+)'><query xmlns='jabber:iq:auth'><username>(.*?)<\/username><digest>(.*?)<\/digest><\/query><\/iq>/.match(data)
send_data("<iq type='result' id='#{iq[1]}'><query xmlns='jabber:iq:auth'><username>#{iq[2]}<\/username><digest>#{iq[3]}<\/digest><\/query></iq>")
# Client asks if it can bind resources, of course you can!
elsif iq = /<iq type='set' id='(\d+)'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'\/><\/iq>/.match(data)
SESSIONS[ip].id = time_hash[0..7].upcase
session = SESSIONS[ip]
send_data("<iq type='result' id='#{iq[1]}'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><jid>#{session.username}@#{name}/#{session.id}</jid></bind></iq>")
# Client asks if it can have a session, of course you can!
elsif iq = /<iq type='set' id='(\d+)'><session xmlns='urn:ietf:params:xml:ns:xmpp-session'\/><\/iq>/.match(data)
send_data("<iq type='result' id='#{iq[1]}' />")
# Client asks if there's anybody out there...
elsif presence = /^<presence\/>$/.match(data)
session = SESSIONS[ip]
# Since this is not a real server, we'll lie and say there's some people.
send_data("<presence from='alice@#{domain}.111A111' to='#{session.username}@#{domain}/#{session.id}'><priority>1</priority></presence>")
send_data("<presence from='bobby@#{domain}.222A222' to='#{session.username}@#{domain}/#{session.id}'><priority>1</priority></presence>")
else
puts "UNKNOWN: #{data}"
end
end
end
EM.run {
EM.start_server "0.0.0.0", 5222, Jabber
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment