[ejabberd] IRC errors patch

Brian Campbell bacam at z273.org.uk
Sat Apr 30 18:07:17 MSD 2005


Hi everyone,

I finally got around to improving that patch I posted a few months ago
to report IRC errors.  It now makes an effort to report any error
stopping the IRC session from being established.  As before, it also
prevents duplicate NICK and JOIN commands being sent.

The patch was made against the current sources in subversion.

Cheers,

  Brian

-------------- next part --------------
Index: src/mod_irc/mod_irc_connection.erl
===================================================================
--- src/mod_irc/mod_irc_connection.erl	(revision 332)
+++ src/mod_irc/mod_irc_connection.erl	(working copy)
@@ -34,6 +34,7 @@
 -record(state, {socket, encoding, receiver, queue,
 		user, host, server, nick,
 		channels = dict:new(),
+		nickchannel,
 		inbuf = "", outbuf = ""}).
 
 %-define(DBGFSM, true).
@@ -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{nickchannel = 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
@@ -381,6 +388,35 @@
     send_text(StateData, "PONG " ++ ID ++ "\r\n"),
     {next_state, StateName, StateData};
 
+handle_info({ircstring, [$: | String]}, wait_for_registration, StateData) ->
+    Words = string:tokens(String, " "),
+    {NewState, NewStateData} =
+	case Words of
+	    [_, "001" | _] ->
+		{stream_established, StateData};
+	    [_, "433" | _] ->
+		{error,
+		 {error, error_nick_in_use(StateData, String), StateData}};
+	    [_, [$4, _, _] | _] ->
+		{error,
+		 {error, error_unknown_num(StateData, String, "cancel"),
+		  StateData}};
+	    [_, [$5, _, _] | _] ->
+		{error,
+		 {error, error_unknown_num(StateData, String, "cancel"),
+		  StateData}};
+	    _ ->
+		?DEBUG("unknown irc command '~s'~n", [String]),
+		{wait_for_registration, StateData}
+	end,
+    % Note that we don't send any data at this stage.
+    if
+	NewState == error ->
+	    {stop, normal, NewStateData};
+	true ->
+	    {next_state, NewState, NewStateData}
+    end;
+
 handle_info({ircstring, [$: | String]}, StateName, StateData) ->
     Words = string:tokens(String, " "),
     NewStateData =
@@ -390,6 +426,15 @@
 	    [_, "332", Nick, [$# | Chan] | _] ->
 		process_channel_topic(StateData, Chan, String),
 		StateData;
+	    [_, "433" | _] ->
+		process_nick_in_use(StateData, String);
+	    % CODEPAGE isn't standard, so don't complain if it's not there.
+	    [_, "421", _, "CODEPAGE" | _] ->
+		StateData;
+	    [_, [$4, _, _] | _] ->
+		process_num_error(StateData, String);
+	    [_, [$5, _, _] | _] ->
+		process_num_error(StateData, String);
 	    [From, "PRIVMSG", [$# | Chan] | _] ->
 		process_chanprivmsg(StateData, Chan, From, String),
 		StateData;
@@ -473,7 +518,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_connection(StateData#state.host,
 			      StateData#state.user,
 			      StateData#state.server),
@@ -486,8 +540,7 @@
 		  StateData#state.host, 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 ->
@@ -624,7 +677,47 @@
       {xmlelement, "message", [{"type", "groupchat"}],
        [{xmlelement, "subject", [], [{xmlcdata, Msg1}]}]}).
 
+error_nick_in_use(StateData, String) ->
+    {ok, Msg, _} = regexp:sub(String, ".*433 +[^ ]* +", ""),
+    Msg1 = filter_message(Msg),
+    {xmlelement, "error", [{"code", "409"}, {"type", "cancel"}],
+     [{xmlelement, "conflict", [{"xmlns", ?NS_STANZAS}], []},
+      {xmlelement, "text", [{"xmlns", ?NS_STANZAS}],
+       [{xmlcdata, Msg1}]}]}.
 
+process_nick_in_use(StateData, String) ->
+    % We can't use the jlib macro because we don't know the language of the
+    % message.
+    Error = error_nick_in_use(StateData, String),
+    case StateData#state.nickchannel 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.host, StateData#state.nick),
+	      StateData#state.user,
+	      {xmlelement, "presence", [{"type", "error"}], [Error]}),
+	    StateData#state{nickchannel = undefined}
+    end.
+
+process_num_error(StateData, String) ->
+    Error = error_unknown_num(StateData, String, "continue"),
+    lists:foreach(
+      fun(Chan) ->
+	      ejabberd_router:route(
+		jlib:make_jid(
+		  lists:concat([Chan, "%", StateData#state.server]),
+		  StateData#state.host, StateData#state.nick),
+		StateData#state.user,
+		{xmlelement, "message", [{"type", "error"}],
+		 [Error]})
+      end, dict:fetch_keys(StateData#state.channels)),
+      StateData.
+
+
 process_chanprivmsg(StateData, Chan, From, String) ->
     [FromUser | _] = string:tokens(From, "!"),
     {ok, Msg, _} = regexp:sub(String, ".*PRIVMSG[^:]*:", ""),
@@ -913,7 +1006,14 @@
 			  Ps
 		  end
 	  end, StateData#state.channels),
-    StateData#state{channels = NewChans}.
+    if
+	FromUser == StateData#state.nick ->
+	    StateData#state{nick = Nick,
+			    nickchannel = undefined,
+			    channels = NewChans};
+	true ->
+	    StateData#state{channels = NewChans}
+    end.
 
 
 process_error(StateData, String) ->
@@ -929,6 +1029,13 @@
 		   [{xmlcdata, String}]}]})
       end, dict:fetch_keys(StateData#state.channels)).
 
+error_unknown_num(StateData, String, Type) ->
+    {ok, Msg, _} = regexp:sub(String, ".*[45][0-9][0-9] +[^ ]* +", ""),
+    Msg1 = filter_message(Msg),
+    {xmlelement, "error", [{"code", "500"}, {"type", Type}],
+     [{xmlelement, "undefined-condition", [{"xmlns", ?NS_STANZAS}], []},
+      {xmlelement, "text", [{"xmlns", ?NS_STANZAS}],
+       [{xmlcdata, Msg1}]}]}.
 
 
 
Index: src/mod_irc/mod_irc.erl
===================================================================
--- src/mod_irc/mod_irc.erl	(revision 332)
+++ src/mod_irc/mod_irc.erl	(working copy)
@@ -129,15 +129,22 @@
 			    io:format("open new connection~n"),
 			    {Username, Encoding} = get_user_and_encoding(
 						     Host, From, Server),
-			    {ok, Pid} = mod_irc_connection:start(
-					  From, Host, Server,
-					  Username, Encoding),
-			    ets:insert(
-			      irc_connection,
-			      #irc_connection{jid_server_host = {From, Server, Host},
-					      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{jid_server_host = {From, Server, Host},
+						      pid = Pid}),
+				    mod_irc_connection:route_chan(
+				      Pid, Channel, Resource, Packet)
+			    end,
 			    ok;
 			[R] ->
 			    Pid = R#irc_connection.pid,


More information about the ejabberd mailing list