Jingle Relay Nodes

A couple of weeks ago, Thiago helped me out understand the basics of JIngle Relay Nodes. He explained that, the server needs to open 4 ports and transfer data coming into them with each one of the other. His exact email

In fact your server will have to open 4 ports in total, 1rtp+ 1rtcp
for each end point.
Lets call them A, A', B, B' respectively.

Every packet that arrives at A needs to be sent through B to whichever
address(IP:Port) is sending to B.
Every packet that arrives at A' needs to be sent through B' to
whichever address(IP:Port) is sending to B'.
Every packet that arrives at B needs to be sent through A to whichever
address(IP:Port) is sending to A.
Every packet that arrives at B' needs to be sent through A' to
whichever address(IP:Port) is sending to A'.

Exception:
* If no packet was received in a given port, packets to be sent
through that port should be discarded.

Remarks:
* There is no process to verify that a sender to a given port is the
real sender of the stream. The recommended mitigation is to create a
race of packets, for the latest 10 received packets in a given port,
only relay and update Address to the one sender holding the majority
of the packets.

I ended up implementing the same in TCP. mod_jinglerelaynodes.lua and mod_jinglechannel.lua

mod_captcha code

I'll start right away with the code. http://code.google.com/p/prosody-gsoc/source/browse/mod_captcha.lua



module:hook("presence/full", generate_captcha, 20);


A hook is written to override the priority of 'presense/full', which is an event generated when someone tries to log into an muc. This hook redirects that event to the generate_captcha function



local function generate_captcha(event)
    local stanza = event.stanza;
    if stanza.attr.captcha_verified and stanza.attr.captcha_verified == "true" then
        return nil
    else
        local options = {}
        options.body = "api_key";
        local request_url = "http://www.google.com/recaptcha/api/noscript";
        http.request(request_url, options, function(...) return generate_captcha_response(event, ...) end);
    end
end


The function generate_captcha basically sends a requests via the recaptcha API, and gets a captcha image. This image is then passed to the function generate_captcha_response which send the image along with the data form to the jid. It also checks if the attr.captcha_verified field in the stanza is set to "true". If it has been set to "true" this means the user has already passed the captcha test, and hence the stanza is forwarded to the mod_muc plugin.



local function generate_captcha_response(event, result, status, r)
    local url = "http://www.google.com/recaptcha/api/";
    local session, stanza = event.origin, event.stanza;
    local node, host, resource = jid.split(stanza.attr.to);
    local ip = session.ip;
    local img_src = result:match([[<img .*alt="".* src="([^"]+)"]])
    url = url..img_src
    local challenge_id = img_src:match("=.+")
    local sid = stanza.attr.id;
    if requests[stanza.attr.from] then
        requests[stanza.attr.from][img_src] = true;
    else
        requests[stanza.attr.from] = {};
        requests[stanza.attr.from][img_src] = true;
    end

    -- Store events here for firing later
    events[challenge_id] = event

    stanza.name = "message";

    local reply = st.reply(stanza);


This function is responsible for generating the data form and sending it to the client. It also saves maps the attr.from and the image_src in a table which is used in case mutiple requests come from the same client. Also the event is stored in the events table mapped to the challenge_id.



module:hook("iq-set/host/urn:xmpp:captcha:captcha", verify_captcha)


 

This is a hook which sends the iq-set/host/urn:xmpp:captcha:captcha event to the verify_captcha function.

 



local function verify_captcha(event)
    local pri_key = "pri_key";
    local session, stanza = event.origin, event.stanza;
    local captcha_form = stanza.tags[1]:get_child("x", "jabber:x:data");
    local fields = captcha_response_layout:data(captcha_form);
    local url = "http://www.google.com/recaptcha/api/verify";
    if requests[stanza.attr.from] and requests[stanza.attr.from][fields.challenge] and fields.ocr ~= "" then
        requests[stanza.attr.from][fields.challenge] = nil;
        local options = {}
        options.body = "privatekey="..pri_key.."&remoteip="..session.ip.."&challenge="..fields.challenge.."&response="..fields.ocr
        http.request(url, options, function(...) return verify_captcha_response(event, fields.challenge, ...) end);
    end
end

The verify_captcha function verifies if the client has entered the correct value for a captcha. I uses the recaptcha API to check the same. From here the response from the API is redirected to the verify_captcha_response function.

 



local function verify_captcha_response(event, challenge_id, result, status, r)
    local session, stanza = event.origin, event.stanza;
    local node, host, resource = jid.split(stanza.attr.to);
    local entries = {}
    for entry in result:gmatch("[^\n]+") do
        table.insert(entries, entry);
    end

    if entries[1] and entries[1]=="true" then
        session.send(st.iq({type="result", from=host}));
        -- fire the original event again with an added field of captcha_verified = 'true'
        local original_event = events.challenge_id;
        original_event.stanza.attr.verifired = "true";
        module:fire_event("presence/full", original_event)
    else
        session.send(st.error_reply(stanza, "cancel", "service-unavailable", "Not a valid input"));
    end
end


In this function, the response is checked, If it is true then the attr.verified flag is set to "true" and the event is fired again. If the response is not correct, then an error reply is sent.

About stanzas' and origins'

Here is a structure of the stanza property of an event,

stanza = 
 {name = "message"; 
 attr = {  from = "...";  to = "..."; }; 
 };
 

A basic event I'm using to test my script has a stanza and a origin property. The origin property looks like this.

origin = 
 {   ip = "...";   
 send = function(stanza) print(stanza) end; 
 }; 
 

We must now define the stanza property, doing it manually would require you to set the metatables manually too. So heres an easy way of doing it. Use the stanza util module.



st = require "util.stanza"; 
 event.stanza = st.stanza("message", 
 {from="robot@abuser.com/zombie", to="innocent@victim.com"}); 
 

 

Getting Started

Generating a stanza in Prosody is fairly easy. Here is how you do it.

 local st = require "util.stanza"; 
 local reply = st.stanza("message"); 
 reply:tag("body"):text("Enter the valid keys"):up() 
 :tag("x", {xmlns="jabber:x:oob"}):tag("url"):text("some_url"):up():up() 
 :tag("captcha", {xmlns="urn:xmpp:captcha"}):up(); 
 print (reply); 
 

 

Debugging Techniques.

Talking to waqas, I got to know several ways to debug my code.

  1. Enable mod_admin_telnet in the config. Connect to the telnet, to be greeted by the Prosody ASCII
  2. telnet localhost 5582

  3. Load the required module by doing module:load("module_name")
  4. Files can also be loaded by doing ;dofile("filename")

Make sure prosody is not run as a daemon, i.e the posix plugin must be disabled. The print statements get discarded if prosody is run as a daemon. A few ways to debug are

a) Using the print command. (It will print to the shell and not the telnet console)

b) Using log("debug", "message"). (It will be written to the log file, Run a tail -f log_file to follow the messages)

c) Using ;dofile("filename") will print the messages to the telnet console, if you have used the print command.