Development of mod_register and mod_vcard

Hi,

I am using ejabberd 2.1.4. I have noticed that mod_register does not conform to XEP-0077 (http://xmpp.org/extensions/xep-0077.html) in that it does not respond with the element if a username already exists. I have made the following modifications so that this is possible. I have never even seen Erlang before, let alone programmed in it so there will probably be a better way to accomplish this change...

In ejabberd_c2s.erl:

Add:

    get_attrs_internal({xmlelement, _Name, Attrs, _Els}) ->
        Attrs.

Modify process_unauthenticated_stanza():

    process_unauthenticated_stanza(StateData, El) ->
        case jlib:iq_query_info(El) of
        #iq{} = IQ ->
            Attrs = get_attrs_internal(El),
            From = xml:get_attr_s("from", Attrs),
            Res = ejabberd_hooks:run_fold(c2s_unauthenticated_iq,
                          StateData#state.server,
                          empty,
                          [StateData#state.server, IQ,
                           StateData#state.ip, From]),
            case Res of
            empty ->
                % The only reasonable IQ's here are auth and register IQ's
                % They contain secrets, so don't include subelements to response
                ResIQ = IQ#iq{type = error,
                      sub_el = [?ERR_SERVICE_UNAVAILABLE]},
                Res1 = jlib:replace_from_to(
                     jlib:make_jid("", StateData#state.server, ""),
                     jlib:make_jid("", "", ""),
                     jlib:iq_to_xml(ResIQ)),
                send_element(StateData, jlib:remove_attr("to", Res1));
            _ ->
                send_element(StateData, Res)
            end;
        _ ->
            % Drop any stanza, which isn't IQ stanza
            ok
        end.

Then in mod_register.erl:

Modify -export. Change:

         unauthenticated_iq_register/4,

to:

         unauthenticated_iq_register/5,

Modify unauthenticated_iq_register():

    unauthenticated_iq_register(_Acc,
                    Server, #iq{type = Type, xmlns = ?NS_REGISTER} = IQ, IP, From) ->
        Address = case IP of
             {A, _Port} -> A;
              _ -> undefined
              end,
        FromJid =
            case Type of
                get -> jlib:string_to_jid(From);
                _ -> jlib:make_jid("", "", "")
            end,
        ResIQ = process_iq(FromJid,
                   jlib:make_jid("", Server, ""),
                   IQ,
                   Address),
        Res1 = jlib:replace_from_to(jlib:make_jid("", Server, ""),
                    jlib:make_jid("", "", ""),
                    jlib:iq_to_xml(ResIQ)),
        jlib:remove_attr("to", Res1);

    unauthenticated_iq_register(Acc, _Server, _IQ, _IP, _From) ->
        Acc.

Replace "get" case of process_iq()/4 (to end of function) with:

	get ->
		case From of
		#jid{user = User,
			 lserver = Server} ->
			case ejabberd_auth:is_user_exists(User,Server) of
			true ->
				IQ#iq{type = result,
				  sub_el = [{xmlelement,
						 "query",
						 [{"xmlns", "jabber:iq:register"}],
						 [{xmlelement, "registered", [], []},
						  {xmlelement, "instructions", [],
						   [{xmlcdata,
							translate:translate(
								Lang,
								"Choose a username and password to register with this server")}]},
						  {xmlelement, "username", [], [{xmlcdata,translate:translate(Lang,User)}]},
						  {xmlelement, "password", [], []}]}]};
			false ->
				IQ#iq{type = result,
				  sub_el = [{xmlelement,
						 "query",
						 [{"xmlns", "jabber:iq:register"}],
						 [{xmlelement, "instructions", [],
						   [{xmlcdata,
							translate:translate(
								Lang,
								"Choose a username and password to register with this server")}]},
						  {xmlelement, "username", [], [{xmlcdata,translate:translate(Lang,User)}]},
						  {xmlelement, "password", [], []} ]}]}
			end;
		_ ->
			IQ#iq{type = result,
			  sub_el = [{xmlelement,
					 "query",
					 [{"xmlns", "jabber:iq:register"}],
					 [{xmlelement, "instructions", [],
					   [{xmlcdata,
						translate:translate(
							Lang,
							"Choose a username and password to register with this server")}]},
					  {xmlelement, "username", [], []},
					  {xmlelement, "password", [], []} ]}]}
		end
    end.

My other modification is to make the vCard functionality conform better to XEP-0054 (http://xmpp.org/extensions/xep-0054.html). In particular, examples 3 and 4 in section 3.1 where the vCard does not exist in the server. My modification returns example 3 if the user does not exist in the ejabberd database and example 4 if the user exists, but there is no vCard defined for that user. Previously, the empty "vCard" element would not be returned in either case.

Modify mod_vcard.erl:

Replace function process_sm_iq()/3 with:

	process_sm_iq(From, To, #iq{type = Type, sub_el = SubEl} = IQ) ->
		case Type of
		set ->
			#jid{user = User, lserver = LServer} = From,
			case lists:member(LServer, ?MYHOSTS) of
			true ->
				set_vcard(User, LServer, SubEl),
				IQ#iq{type = result, sub_el = []};
			false ->
				IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
			end;
		get ->
			#jid{luser = LUser, lserver = LServer} = To,
			case ejabberd_auth:is_user_exists(LUser,LServer) of
			true ->
				US = {LUser, LServer},
				F = fun() ->
					mnesia:read({vcard, US})
				end,
				Els = case mnesia:transaction(F) of
						{atomic, Rs} ->
							lists:map(
								fun(R) -> R#vcard.vcard end,
								Rs);
						{aborted, _Reason} ->
							[]
				end,
				case Els of
					[] -> IQ#iq{type = result, sub_el = [{xmlelement,"vCard",[{"xmlns","vcard-temp"}],[]}]};
					_  -> IQ#iq{type = result, sub_el = Els}
				end;
			false ->
				Els = [
						{xmlelement,"vCard",[{"xmlns","vcard-temp"}],[]},
						{xmlelement,"error",[{"type","cancel"}],[{xmlelement,"item-not-found",[{"xmlns","urn:ietf:params:xml:ns:xmpp-stanzas"}],[]}]}
					],
				IQ#iq{type = error, sub_el = Els}
			end
		end.

I hope you find these modifications useful.

Cheers, Mike.

Regarding Register, I guess

Regarding Register, I guess you refer to the paragraph "If the host determines (based on the 'from' address) that the entity is already registered, ", and the example
http://xmpp.org/extensions/xep-0077.html#example-3

I couldn't review your changes because it's almost unreadable.

Please provide your changes in a way easy to apply:
A) Either provide a patch (if you got ejabberd code from git, you can run "git diff origin/2.1.x > registerfix.diff")
B) Or the full modified files.

If you don't have hosting, you can use http://paste.jabbim.cz/ or similar sites, or email them to me. Remember to indicate what ejabberd version are those changes to apply (2.1.4 I imagine).

Regarding Vcard, I agree with your bug report, but I propose a slightly different solution.

You provide a different response depending if the account exists or not. That seems a nice feature. But this feature can be abused: it allows to know if an account is registered or not in the server. I'm writting your change to return the same IQ-error in both cases:

<iq type='get' to='user666@localhost'>
  <vCard xmlns='vcard-temp'/>
<iq>
<iq from='user666@localhost'
	to='badlop@localhost/work'
	type='error'>
  <vCard xmlns='vcard-temp'/>
  <error type='cancel'>
    <item-not-found xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
  </error>
<iq>
<iq type='get' to='user_no_vcard@localhost'>
  <vCard xmlns='vcard-temp'/>
<iq>
<iq from='user_no_vcard@localhost'
	to='badlop@localhost/work'
	type='error'>
  <vCard xmlns='vcard-temp'/>
  <error type='cancel'>
    <item-not-found xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
  </error>
<iq>

Full files provided

Hi,

Sorry for the delay in responding. I expected to receive an email if anyone posted to this thread. Sorry for the unreadable source code. I have posted the full files (based on V2.1.4) to http://paste.jabbim.cz as follows:

ejabberd_c2s.erl #1 (lines 1 to 2079): http://paste.jabbim.cz/4746
ejabberd_c2s.erl #2 (lines 2080 to 2294): http://paste.jabbim.cz/4749
mod_register.erl: http://paste.jabbim.cz/4747
mod_vcard.erl: http://paste.jabbim.cz/4748

There seems to be a post limit, so ejabberd_c2s.erl is split into 2 different posts. Line 2080 on #1 is not complete.

Regarding Register - yes, I do refer to that paragraph.

Regarding your comment about security and abusing the system, a client is already able to determine if an account is registered by applying the Register modification I have submitted to you. Allowing the request of a vCard to respond differently if an account is registered or not is therefore no more of a security risk. It is a useful feature because my client application can request the vCard for an account when a user wishes to subscribe to presence notifications from another user's account and can provide a friendly message to indicate that the subscription request has been posted to the specified user account (and show the avatar) or it can show an error message to indicate that the user account does not exist. For my purposes, I need this feature. Is there another way for a client application (which is already logged in) to check if another account exists?

Cheers, Mike.

Long reply

I split my reply in four sections.

1. Include username and "registered": patch applied

PioneerMike wrote:

I have posted the full files (based on V2.1.4) to http://paste.jabbim.cz as follows:

Great, I was able to download the files, get the differences and see your changes.

PioneerMike wrote:

Regarding Register - yes, I do refer to that paragraph.

Ok, and your changes work. I've simplified the code that includes the Username and the "registered" element, and included it in ejabberd 2.1.x and master branches.

2. Is it worth to change a hook for unauthenticated user?

You expect that the unauthenticated client may provide a From attribute. However, I have doubts about how many clients provide a From when they are unauthenticated yet:

+unauthenticated_iq_register(_Acc, Server,
+			    #iq{type = Type, xmlns = ?NS_REGISTER} = IQ,
+			    IP, From) ->
     Address = case IP of
 		 {A, _Port} -> A;
 		  _ -> undefined
 	      end,
-    ResIQ = process_iq(jlib:make_jid("", "", ""),
+    FromJid = case Type of
+		  get -> jlib:string_to_jid(From);

Another concern I have about that part is that it modifies an existing ejabberd hook to add the new argument From, so that change can't be included in the stable releases ejabberd 2.1.x. It can be included in future ejabberd 3.x releases.

Do you think that part about getting the From when the client is unathenticated is worth the effort to include in future releases?

3. vCard XEP is ambiguous: I'll get a clarification

PioneerMike wrote:

Allowing the request of a vCard to respond differently if an account is registered or not

Ok, I noticed that http://xmpp.org/extensions/xep-0054.html is ambiguous when it says:

Quote:

If no vCard exists or the user does not exist, the server MUST return a stanza error, which SHOULD <service-unavailable/> or <item-not-found/>.

You interpreted that sentence as if it had the word respectively in the end, like this:

PioneerMike wrote:

If no vCard exists or the user does not exist, the server MUST return a stanza error, which SHOULD be <service-unavailable/> or <item-not-found/> , respectively.

I'll contact the XEP author to get a clarification of how to interpret that sentence. If he agrees that each case should produce a different error, then I'll apply your patch.

4. Other ways to find account registration: maybe with iq, presence or message

For my purposes, I need this feature. Is there another way for a client application (which is already logged in) to check if another account exists?

Maybe yes, I found in http://xmpp.org/rfcs/rfc3921.html#rfc.section.11.1 this:

Quote:

if the JID is of the form <user@domain> or <user@domain/resource> and the associated user account does not exist, the recipient's server (a) SHOULD silently ignore the stanza (i.e., neither deliver it nor return an error) if it is a presence stanza, (b) MUST return a <service-unavailable/> stanza error to the sender if it is an IQ stanza, and (c) SHOULD return a <service-unavailable/> stanza error to the sender if it is a message stanza.

I made some quick test, maybe they give you some idea. I tried from account 'badlop' to query an existing account 'badlop2' and to an unexistent account 'user123123':

<iq to='badlop2@localhost' type='get'>
  <query xmlns='jabber:iq:last'/>
<iq>
<iq from='badlop2@localhost'
	to='badlop@localhost/work'
	type='result'>
  <query seconds='9'
	xmlns='jabber:iq:last'/>
<iq>
<iq to='user123123@localhost'	type='get'>
  <query xmlns='jabber:iq:last'/>
<iq>
<iq from='user123123@localhost'
	to='badlop@localhost/work'
	type='error'>
  <query xmlns='jabber:iq:last'/>
  <error code='503'
	type='cancel'>
    <service-unavailable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
  <error>
<iq>

And sending an empty message:

<message to='badlop2@localhost'><message>
<message to='user123123@localhost'><message>
<message from='user123123@localhost'
	to='badlop@localhost/work'
	type='error'
	xml:lang='es'>
  <error code='503'
	type='cancel'>
    <service-unavailable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
  <error>
<message>

Modifications

1. Include username and "registered": patch applied

Thank you.

2. Is it worth to change a hook for unauthenticated user?

Quote:

Do you think that part about getting the From when the client is unathenticated is worth the effort to include in future releases?

Yes, I believe that this is a very useful feature in some circumstances. For instance, if the client wishes to be able to determine if the account is available in the server without actually registering the new account yet. For instance, when the user in my client program changes their name, being able to check the availability of the XMPP account for that name is essential before confirming the new name in the application database. The user is not necessarily logged in at that time. The reason why I thought it an acceptable modification for the ejabberd code is the following clause from XEP-0077 (just before http://xmpp.org/extensions/xep-0077.html#example-3):

Quote:

If the host determines (based on the 'from' address) that the entity is already registered, the IQ result that it sends in response to the IQ get MUST contain an empty element (indicating that the entity is already registered) ...

I hope that you can accept this modification, but it is a shame that it cannot be included in the 2.1.x releases.

3. vCard XEP is ambiguous: I'll get a clarification

Yes, you are correct - I did interpret that sentence to include respectively. Also, yes, the specification is ambiguous as it stands. It will be useful to get clarification.

4. Other ways to find account registration: maybe with iq, presence or message

That is a useful suggestion. Thank you. If you do not accept the modification for a separate vCard error depending on whether the account exists or not then I will change my client code according to this method.

Cheers, Mike.

Syndicate content