$NetBSD: patch-ag,v 1.2 2009/02/03 12:07:26 martti Exp $

Modified to use IPv6/v4 patch (https://support.process-one.net/browse/EJAB-389)

--- src/ejabberd_listener.erl.orig	2009-01-14 11:54:15.000000000 +0200
+++ src/ejabberd_listener.erl	2009-02-03 08:21:35.000000000 +0200
@@ -32,6 +32,7 @@
 	 init_ssl/4,
 	 start_listener/3,
 	 stop_listener/1,
+	 parse_listener_portip/2,
 	 add_listener/3,
 	 delete_listener/1
 	]).
@@ -50,8 +51,7 @@
 	undefined ->
 	    ignore;
 	Ls ->
-	    {ok, {{one_for_one, 10, 1},
-		  lists:map(
+	    Ls2 = lists:map(
 		    fun({Port, Module, Opts}) ->
 			    {Port,
 			     {?MODULE, start, [Port, Module, Opts]},
@@ -59,7 +59,20 @@
 			     brutal_kill,
 			     worker,
 			     [?MODULE]}
-		    end, Ls)}}
+		    end, Ls),
+	    report_duplicated_portips(Ls),
+	    {ok, {{one_for_one, 10, 1}, Ls2}}
+    end.
+
+report_duplicated_portips(L) ->
+    LKeys = [Port || {Port, _, _} <- L],
+    LNoDupsKeys = proplists:get_keys(L),
+    case LKeys -- LNoDupsKeys of
+	[] -> ok;
+	Dups ->
+	    ?CRITICAL_MSG("In the ejabberd configuration there are duplicated "
+			  "Port number + IP address:~n  ~p",
+			  [Dups])
     end.
 
 
@@ -84,7 +97,11 @@
 	    end
     end.
 
-init(Port, Module, Opts) ->
+init(PortIP, Module, Opts1) ->
+    {Port, IPT, IPS, IPV, OptsClean} = parse_listener_portip(PortIP, Opts1),
+    %% The first inet|inet6 and the last {ip, _} work,
+    %% so overriding those in Opts
+    Opts = [IPV | OptsClean] ++ [{ip, IPT}],
     SockOpts = lists:filter(fun({ip, _}) -> true;
 			       (inet6) -> true;
 			       (inet) -> true;
@@ -103,11 +120,77 @@
 	{ok, ListenSocket} ->
 	    accept(ListenSocket, Module, Opts);
 	{error, Reason} ->
-	    ?ERROR_MSG("Failed to open socket for ~p: ~p",
-		       [{Port, Module, Opts}, Reason]),
-	    error
+	    ReasonT = case Reason of
+			  eaddrnotavail -> "IP address not available: " ++ IPS;
+			  _ -> atom_to_list(Reason)
+		      end,
+	    ?ERROR_MSG("Failed to open socket:~n  ~p~nReason: ~s",
+		       [{Port, Module, SockOpts}, ReasonT]),
+	    {error, ReasonT}
     end.
 
+%% @spec (PortIP, Opts) -> {Port, IPT, IPS, IPV, OptsClean}
+%% where
+%%      PortIP = Port | {Port, IPT | IPS}
+%%      Port = integer()
+%%      IPT = tuple()
+%%      IPS = string()
+%%      IPV = inet | inet6
+%%      Opts = [IPV | {ip, IPT} | atom() | tuple()]
+%%      OptsClean = [atom() | tuple()]
+%% @doc Parse any kind of ejabberd listener specification.
+%% The parsed options are returned in several formats.
+%% OptsClean does not include inet/inet6 or ip options.
+%% Opts can include the options inet6 and {ip, Tuple},
+%% but they are only used when no IP address was specified in the PortIP.
+%% The IP version (either IPv4 or IPv6) is inferred from the IP address type,
+%% so the option inet/inet6 is only used when no IP is specified at all.
+parse_listener_portip(PortIP, Opts) ->
+    {IPOpt, Opts2} = strip_ip_option(Opts),
+    {IPVOpt, OptsClean} = case lists:member(inet6, Opts2) of
+			      true -> {inet6, Opts2 -- [inet6]};
+			      false -> {inet, Opts2}
+			  end,
+    {Port, IPT, IPS} = case PortIP of
+			   P when is_integer(P) ->
+			       T = get_ip_tuple(IPOpt, IPVOpt),
+			       S = inet_parse:ntoa(T),
+			       {P, T, S};
+			   {P, T} when is_integer(P) and is_tuple(T) ->
+			       S = inet_parse:ntoa(T),
+			       {P, T, S};
+			   {P, S} when is_integer(P) and is_list(S) ->
+			       [S | _] = string:tokens(S, "/"),
+			       {ok, T} = inet_parse:address(S),
+			       {P, T, S}
+		       end,
+    IPV = case size(IPT) of
+	      4 -> inet;
+	      8 -> inet6
+	  end,
+    {Port, IPT, IPS, IPV, OptsClean}.
+
+strip_ip_option(Opts) ->
+    {IPL, OptsNoIP} = lists:partition(
+			fun({ip, _}) -> true;
+			   (_) -> false
+			end,
+			Opts),
+    case IPL of
+	%% Only the first ip option is considered
+	[{ip, T1} | _] when is_tuple(T1) ->
+	    {T1, OptsNoIP};
+	[] ->
+	    {no_ip_option, OptsNoIP}
+    end.
+
+get_ip_tuple(no_ip_option, inet) ->
+    {0, 0, 0, 0};
+get_ip_tuple(no_ip_option, inet6) ->
+    {0, 0, 0, 0, 0, 0, 0, 0};
+get_ip_tuple(IPOpt, _IPVOpt) ->
+    IPOpt.
+
 accept(ListenSocket, Module, Opts) ->
     case gen_tcp:accept(ListenSocket) of
 	{ok, Socket} ->
@@ -182,6 +265,7 @@
     end.
 
 
+%% @spec (Port, Module, Opts) -> {ok, Pid} | {error, Error}
 start_listener(Port, Module, Opts) ->
     ChildSpec = {Port,
 		 {?MODULE, start, [Port, Module, Opts]},
@@ -195,26 +279,49 @@
     supervisor:terminate_child(ejabberd_listeners, Port),
     supervisor:delete_child(ejabberd_listeners, Port).
 
-add_listener(Port, Module, Opts) ->
+%% @spec (PortIP, Module, Opts) -> {ok, Pid} | {error, Error}
+%% where
+%%      PortIP = {Port, IPT | IPS}
+%%      Port = integer()
+%%      IPT = tuple()
+%%      IPS = string()
+%%      IPV = inet | inet6
+%%      Module = atom()
+%%      Opts = [IPV | {ip, IPT} | atom() | tuple()]
+%% @doc Add a listener and store in config if success
+add_listener(PortIP, Module, Opts) ->
+    case start_listener(PortIP, Module, Opts) of
+	{ok, _Pid} ->
     Ports = case ejabberd_config:get_local_option(listen) of
 		undefined ->
 		    [];
 		Ls ->
 		    Ls
 	    end,
-    Ports1 = lists:keydelete(Port, 1, Ports),
-    Ports2 = [{Port, Module, Opts} | Ports1],
+    Ports1 = lists:keydelete(PortIP, 1, Ports),
+    Ports2 = [{PortIP, Module, Opts} | Ports1],
     ejabberd_config:add_local_option(listen, Ports2),
-    start_listener(Port, Module, Opts).
+		ok;
+	{error, {already_started, _Pid}} ->
+	    {error, {already_started, PortIP}};
+	{error, Error} ->
+	    {error, Error}
+    end.
 
-delete_listener(Port) ->
+%% @spec (PortIP) -> ok
+%% where
+%%      PortIP = {Port, IPT | IPS}
+%%      Port = integer()
+%%      IPT = tuple()
+%%      IPS = string()
+delete_listener(PortIP) ->
     Ports = case ejabberd_config:get_local_option(listen) of
 		undefined ->
 		    [];
 		Ls ->
 		    Ls
 	    end,
-    Ports1 = lists:keydelete(Port, 1, Ports),
+    Ports1 = lists:keydelete(PortIP, 1, Ports),
     ejabberd_config:add_local_option(listen, Ports1),
-    stop_listener(Port).
+    stop_listener(PortIP).
 
