вторник, 28 сентября 2010 г.

Relay auth to an XMPP component

Standard ejabberd auth modules include odbc, ldap, external (with script). Also there is original module "internal" which hosts users data in Mnesia. But sometimes these modules are not enough.

Writing a custom auth module for ejabberd is easy. Copy, for example, ejabberd_auth_internal.erl and replace its interface methods with your own.

Recently I needed to relay auth to an XMPP component. I have to make an IQ inside check_password method. The problem is that check_password is called in user session process and routing for the session does not work yet. This means, you wont receive XMPP stanzas in this method by calling receive.

Take a look at the working snippet:


check_password(User, Server, Password) ->
    SelfJid = jlib:string_to_jid(Server),
    AuthJid = jlib:string_to_jid("profile1." ++ Server),
    IQGet = #iq{
      type = get,
      sub_el = [{xmlelement, "query", [{"xmlns", ?NS_SUP_AUTH}, {"user", User}, {"password", Password}], []}]
     },
    Pid = self(),

    F = fun(IQReply) ->
                Pid ! IQReply
        end,

    ejabberd_local:route_iq(SelfJid, AuthJid, IQGet, F),

    receive #iq{type = result} ->
            true;
    Other ->
            ?INFO_MSG("Auth IQ for ~s failed: ~p", [User, Other]),
            false
    end.


profile1.server.com is a JID of the auth component. The component receives an IQ with user id and password and returns "result" IQ if auth is fine, "error" otherwise.

The trick here is to use ejabberd_local:route_iq. But then we need to block the call flow of check_password until the auth component returns us the result. route_iq takes a function parameter which is called in local router process. It routes the reply back to a function basing on its own IQ id -> function map. Another trick is to make our original (client) process pid to reside in function's closure. Then we can safely block with receive and wait for a message from the function.

Комментариев нет: