[ejabberd] IRC nick handling patch

Brian Campbell bacam at z273.org.uk
Wed Jan 19 23:07:55 MSK 2005


Hi everyone,

I've been playing around with improving the error handling of mod_irc,
especially for nicks that are already in use.  I've included a patch
(made against the current CVS) that reports failed nick registration/
changes.  A couple of other effects are noticable: it starts the IRC
connection with the correct nick (it would use the user name from the
Jabber id before, then change it on joining a channel), complains if no
nick is given, and only sends NICK and JOIN to the IRC server when
necessary.

The main shortcoming is that if nick changes are sent to several
channels at once, it might report a failure on the wrong one, but only a
bot is likely to ever do that.  I'm also toying with setting it up to
report any fatal error while setting up the IRC connection, but haven't
got around to it yet.

Any feedback welcome.

  Brian

-------------- next part --------------
? src/mod_irc/Makefile
? src/mod_irc/mod_irc_connection.erl.2nd
? src/mod_irc/mod_irc_connection.erl.3rd
Index: src/mod_irc/mod_irc.erl
===================================================================
RCS file: /home/cvs/ejabberd/src/mod_irc/mod_irc.erl,v
retrieving revision 1.15
diff -u -r1.15 mod_irc.erl
--- src/mod_irc/mod_irc.erl	13 Jan 2005 23:04:47 -0000	1.15
+++ src/mod_irc/mod_irc.erl	19 Jan 2005 19:22:39 -0000
@@ -126,15 +126,23 @@
 			    io:format("open new connection~n"),
 			    {Username, Encoding} = get_user_and_encoding(
 						     From, Server),
-			    {ok, Pid} = mod_irc_connection:start(
-					  From, Host, Server,
-					  Username, Encoding),
-			    ets:insert(
-			      irc_connection,
-			      #irc_connection{userserver = {From, Server},
-					      pid = Pid}),
-			    mod_irc_connection:route_chan(
-			      Pid, Channel, Resource, Packet),
+			    case Resource of
+				"" ->
+				    Err = jlib:make_error_reply(packet,
+					?ERR_JID_MALFORMED),
+				    ejabberd_router:route(To, From, Err);
+				_ ->
+				    {ok, Pid} = mod_irc_connection:start(
+						  From, Host, Server,
+						  Resource, Encoding),
+				    ets:insert(
+				      irc_connection,
+				      #irc_connection{userserver = {From,
+								    Server},
+						      pid = Pid}),
+				    mod_irc_connection:route_chan(
+				      Pid, Channel, Resource, Packet)
+			    end,
 			    ok;
 			[R] ->
 			    Pid = R#irc_connection.pid,
Index: src/mod_irc/mod_irc_connection.erl
===================================================================
RCS file: /home/cvs/ejabberd/src/mod_irc/mod_irc_connection.erl,v
retrieving revision 1.21
diff -u -r1.21 mod_irc_connection.erl
--- src/mod_irc/mod_irc_connection.erl	13 Jan 2005 23:04:47 -0000	1.21
+++ src/mod_irc/mod_irc_connection.erl	19 Jan 2005 19:22:40 -0000
@@ -33,6 +33,7 @@
 
 -record(state, {socket, encoding, receiver, queue,
 		user, myname, server, nick,
+		nickrequestchannel,
 		channels = dict:new(),
 		inbuf = "", outbuf = ""}).
 
@@ -194,15 +195,21 @@
 			   _ ->
 			       Resource
 		       end,
-		S1 = ?SEND(io_lib:format("NICK ~s\r\n"
-					 "JOIN #~s\r\n",
-					 [Nick, Channel])),
+		S1 = if
+		    Nick /= StateData#state.nick ->
+			S11 = ?SEND(io_lib:format("NICK ~s\r\n", [Nick])),
+			% The server reply will change the copy of the
+			% nick in the state (or indicate a clash).
+			S11#state{nickrequestchannel = Channel};
+		    true ->
+			StateData
+		     end,
 		case dict:is_key(Channel, S1#state.channels) of
 		    true ->
-			S1#state{nick = Nick};
+			S1;
 		    _ ->
-			S1#state{nick = Nick,
-				 channels =
+			S2 = ?SEND(io_lib:format("JOIN #~s\r\n", [Channel])),
+			S2#state{channels =
 				 dict:store(Channel, ?SETS:new(),
 					    S1#state.channels)}
 		end
@@ -390,6 +397,8 @@
 	    [_, "332", Nick, [$# | Chan] | _] ->
 		process_channel_topic(StateData, Chan, String),
 		StateData;
+	    [_, "433" | _] ->
+		process_nick_in_use(StateName, StateData, String);
 	    [From, "PRIVMSG", [$# | Chan] | _] ->
 		process_chanprivmsg(StateData, Chan, From, String),
 		StateData;
@@ -431,15 +440,20 @@
 		?DEBUG("unknown irc command '~s'~n", [String]),
 		StateData
 	end,
-    NewStateData1 =
-	case StateData#state.outbuf of
-	    "" ->
-		NewStateData;
-	    Data ->
-		send_text(NewStateData, Data),
-		NewStateData#state{outbuf = ""}
-	end,
-    {next_state, stream_established, NewStateData1};
+    case NewStateData of
+	{error, _, _} ->
+	    {stop, normal, NewStateData};
+	_ ->
+	    NewStateData1 =
+		case StateData#state.outbuf of
+		    "" ->
+			NewStateData;
+		    Data ->
+			send_text(NewStateData, Data),
+			NewStateData#state{outbuf = ""}
+		end,
+	    {next_state, stream_established, NewStateData1}
+    end;
 
 handle_info({ircstring, [$E, $R, $R, $O, $R | _] = String},
 	    StateName, StateData) ->
@@ -473,7 +487,16 @@
 %% Purpose: Shutdown the fsm
 %% Returns: any
 %%----------------------------------------------------------------------
-terminate(Reason, StateName, StateData) ->
+terminate(Reason, StateName, FullStateData) ->
+    % Extract error message if there was one.
+    {Error, StateData} = case FullStateData of
+	{error, SError, SStateData} ->
+	    {SError, SStateData};
+	_ ->
+	    {{xmlelement, "error", [{"code", "502"}],
+	      [{xmlcdata, "Server Connect Failed"}]},
+	    FullStateData}
+    end,
     mod_irc:closed_conection(StateData#state.user,
 			     StateData#state.server),
     bounce_messages("Server Connect Failed"),
@@ -485,8 +508,7 @@
 		  StateData#state.myname, StateData#state.nick),
 		StateData#state.user,
 		{xmlelement, "presence", [{"type", "error"}],
-		 [{xmlelement, "error", [{"code", "502"}],
-		   [{xmlcdata, "Server Connect Failed"}]}]})
+		 [Error]})
       end, dict:fetch_keys(StateData#state.channels)),
     case StateData#state.socket of
 	undefined ->
@@ -623,6 +645,33 @@
       {xmlelement, "message", [{"type", "groupchat"}],
        [{xmlelement, "subject", [], [{xmlcdata, Msg1}]}]}).
 
+process_nick_in_use(StateName, StateData, String) ->
+    {ok, Msg, _} = regexp:sub(String, ".*433 +[^ ]* +", ""),
+    Msg1 = filter_message(Msg),
+    % We can't use the jlib macro because we don't know the language of the
+    % message.
+    Error = {xmlelement, "error", [{"code", "409"}, {"type", "cancel"}],
+	     [{xmlelement, "conflict", [{"xmlns", ?NS_STANZAS}], []},
+	      {xmlelement, "text", [{"xmlns", ?NS_STANZAS}],
+	       [{xmlcdata, Msg1}]}]},
+    case StateName of
+	wait_for_registration ->
+	    {error, Error, StateData};
+	_ ->
+	    case StateData#state.nickrequestchannel of
+		undefined ->
+		    % Shouldn't happen with a well behaved server
+		    StateData;
+		Chan ->
+		    ejabberd_router:route(
+		      jlib:make_jid(
+			lists:concat([Chan, "%", StateData#state.server]),
+			StateData#state.myname, StateData#state.nick),
+		      StateData#state.user,
+		      {xmlelement, "presence", [{"type", "error"}], [Error]}),
+		    StateData#state{nickrequestchannel = undefined}
+	    end
+    end.
 
 process_chanprivmsg(StateData, Chan, From, String) ->
     [FromUser | _] = string:tokens(From, "!"),
@@ -912,7 +961,14 @@
 			  Ps
 		  end
 	  end, StateData#state.channels),
-    StateData#state{channels = NewChans}.
+    if
+	FromUser == StateData#state.nick ->
+	    StateData#state{nick = Nick,
+			    nickrequestchannel = undefined,
+			    channels = NewChans};
+	true ->
+	    StateData#state{channels = NewChans}
+    end.
 
 
 process_error(StateData, String) ->


More information about the ejabberd mailing list