]> granicus.if.org Git - ejabberd/commitdiff
* src/mod_irc/: New IRC transport (not completed yet)
authorAlexey Shchepin <alexey@process-one.net>
Sun, 16 Feb 2003 20:07:21 +0000 (20:07 +0000)
committerAlexey Shchepin <alexey@process-one.net>
Sun, 16 Feb 2003 20:07:21 +0000 (20:07 +0000)
SVN Revision: 74

ChangeLog
src/ejabberd.cfg
src/ejabberd_router.erl
src/mod_irc/Makefile [new file with mode: 0644]
src/mod_irc/iconv.erl [new file with mode: 0644]
src/mod_irc/iconv_erl.c [new file with mode: 0644]
src/mod_irc/mod_irc.erl [new file with mode: 0644]
src/mod_irc/mod_irc_connection.erl [new file with mode: 0644]
src/namespaces.hrl

index cd995573cd7359557347c0faa86a56790320c3dd..cb82a538aed0c45e32286424ccd25f8473b8f88a 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+2003-02-16  Alexey Shchepin  <alexey@sevcom.net>
+
+       * src/mod_irc/: New IRC transport (not completed yet)
+
 2003-02-14  Alexey Shchepin  <alexey@sevcom.net>
 
        * src/ejabberd_service.erl: Answer "Bad Request" on unknown tags
index c119b6d3a76e329dcf03ef0a576349378ec419d9..f910398f213bc849904400d2c24e3134c5b62004 100644 (file)
@@ -58,6 +58,7 @@
            {mod_offline,   []},
            {mod_echo,      [{host, "echo.e.localhost"}]},
            {mod_private,   []},
+           {mod_irc,       []},
            {mod_time,      [{iqdisc, no_queue}]},
            {mod_version,   []}
           ]}.
index 9de7e67f4b52d374da9382714f98e120e6a8cbaf..956a497ccf8c286c89f51d3397a822f1737ff466 100644 (file)
@@ -112,7 +112,7 @@ do_route(From, To, Packet) ->
                    Node = R#route.node,
                    case node() of
                        Node ->
-                           Pid = R#local_route.pid,
+                           Pid = R#route.pid,
                            ?DEBUG("routed to process ~p~n", [Pid]),
                            Pid ! {route, From, To, Packet};
                        _ ->
diff --git a/src/mod_irc/Makefile b/src/mod_irc/Makefile
new file mode 100644 (file)
index 0000000..e5bf891
--- /dev/null
@@ -0,0 +1,44 @@
+# $Id$
+
+include ../Makefile.inc
+
+INCLUDES = -I/usr/lib/erlang/usr/include \
+          -I$(EI_DIR)/include \
+          -I/usr/local/include
+
+LIBDIRS = -L$(EI_DIR)/lib -L/usr/local/lib
+
+ERLSHLIBS = ../iconv_erl.so
+
+
+
+OUTDIR = ..
+EFLAGS = -I .. -pz ..
+OBJS   = \
+       $(OUTDIR)/mod_irc.beam \
+       $(OUTDIR)/mod_irc_connection.beam \
+       $(OUTDIR)/iconv.beam
+
+all:    $(OBJS) $(ERLSHLIBS)
+
+$(OUTDIR)/%.beam:       %.erl
+       erlc -W $(EFLAGS) -o $(OUTDIR) $<
+
+
+
+#all:  $(ERLSHLIBS)
+#      erl -s make all report "{outdir, \"..\"}" -noinput -s erlang halt
+
+$(ERLSHLIBS):  ../%.so:        %.c
+                       gcc -Wall $(INCLUDES) $(LIBDIRS) \
+                       $(subst ../,,$(subst .so,.c,$@)) \
+                       -lerl_interface \
+                       -lei \
+                       -o $@ -fpic -shared \
+
+clean:
+       rm -f *.beam
+
+TAGS:
+       etags *.erl
+
diff --git a/src/mod_irc/iconv.erl b/src/mod_irc/iconv.erl
new file mode 100644 (file)
index 0000000..ea42f0e
--- /dev/null
@@ -0,0 +1,75 @@
+%%%----------------------------------------------------------------------
+%%% File    : iconv.erl
+%%% Author  : Alexey Shchepin <alexey@sevcom.net>
+%%% Purpose : Interface to libiconv
+%%% Created : 16 Feb 2003 by Alexey Shchepin <alexey@sevcom.net>
+%%% Id      : $Id$
+%%%----------------------------------------------------------------------
+
+-module(iconv).
+-author('alexey@sevcom.net').
+-vsn('$Revision$ ').
+
+-behaviour(gen_server).
+
+-export([start/0, start_link/0, convert/3]).
+
+%% Internal exports, call-back functions.
+-export([init/1,
+        handle_call/3,
+        handle_cast/2,
+        handle_info/2,
+        code_change/3,
+        terminate/2]).
+
+
+
+start() ->
+    gen_server:start({local, ?MODULE}, ?MODULE, [], []).
+
+start_link() ->
+    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+
+init([]) ->
+    ok = erl_ddll:load_driver(".", iconv_erl),
+    Port = open_port({spawn, iconv_erl}, []),
+    ets:new(iconv_table, [set, public, named_table]),
+    ets:insert(iconv_table, {port, Port}),
+    {ok, Port}.
+
+
+%%% --------------------------------------------------------
+%%% The call-back functions.
+%%% --------------------------------------------------------
+
+handle_call(_, _, State) ->
+    {noreply, State}.
+
+handle_cast(_, State) ->
+    {noreply, State}.
+
+handle_info({'EXIT', Pid, Reason}, Port) ->
+    {noreply, Port};
+
+handle_info({'EXIT', Port, Reason}, Port) ->
+    {stop, {port_died, Reason}, Port};
+handle_info(_, State) ->
+    {noreply, State}.
+
+code_change(OldVsn, State, Extra) ->
+    {ok, State}.
+
+terminate(_Reason, Port) ->
+    Port ! {self, close},
+    ok.
+
+
+
+convert(From, To, String) ->
+    [{port, Port} | _] = ets:lookup(iconv_table, port),
+    Bin = term_to_binary({From, To, String}),
+    BRes = port_control(Port, 1, Bin),
+    binary_to_list(BRes).
+
+
+
diff --git a/src/mod_irc/iconv_erl.c b/src/mod_irc/iconv_erl.c
new file mode 100644 (file)
index 0000000..4cca2ec
--- /dev/null
@@ -0,0 +1,115 @@
+/* $Id$ */
+
+#include <stdio.h>
+#include <erl_driver.h>
+#include <ei.h>
+#include <iconv.h>
+
+typedef struct {
+      ErlDrvPort port;
+      iconv_t cd;
+} iconv_data;
+
+
+static ErlDrvData iconv_erl_start(ErlDrvPort port, char *buff)
+{
+   iconv_data* d = (iconv_data*)driver_alloc(sizeof(iconv_data));
+   d->port = port;
+   d->cd = NULL;
+
+   set_port_control_flags(port, PORT_CONTROL_FLAG_BINARY);
+   
+   return (ErlDrvData)d;
+}
+
+static void iconv_erl_stop(ErlDrvData handle)
+{
+   driver_free((char*)handle);
+}
+
+static int iconv_erl_control(ErlDrvData drv_data,
+                            unsigned int command,
+                            char *buf, int len,
+                            char **rbuf, int rlen)
+{
+   int i;
+   int size;
+   int index = 0;
+   int avail;
+   int inleft, outleft;
+   ErlDrvBinary *b;
+   char *from, *to, *string, *stmp, *rstring, *rtmp;
+   iconv_t cd;
+
+   ei_decode_version(buf, &index, &i);
+   ei_decode_tuple_header(buf, &index, &i);
+   ei_get_type(buf, &index, &i, &size);
+   from = malloc(size + 1); 
+   ei_decode_string(buf, &index, from);
+
+   ei_get_type(buf, &index, &i, &size);
+   to = malloc(size + 1); 
+   ei_decode_string(buf, &index, to);
+  
+   ei_get_type(buf, &index, &i, &size);
+   stmp = string = malloc(size + 1); 
+   ei_decode_string(buf, &index, string);
+  
+   cd = iconv_open(to, from);
+   // TODO: check result
+   /*
+   if(cd == (iconv_t) -1)
+   {
+      perror ("iconv_open");
+   }
+   else 
+   {
+      printf("iconv_open from=%s, to=%s OK\r\n", from, to);
+      printf("string=%s size=%d\r\n", string, size);
+   }
+   */
+   
+   outleft = avail = 4*size;
+   inleft = size;
+   rtmp = rstring = malloc(avail);
+   iconv(cd, &stmp, &inleft, &rtmp, &outleft);
+   
+   size = rtmp - rstring;
+
+   //printf("size=%d, res=%s\r\n", size, rstring);
+   
+   *rbuf = (char*)(b = driver_alloc_binary(size));
+   memcpy(b->orig_bytes, rstring, size);
+
+   free(from);
+   free(to);
+   free(string);
+   free(rstring);
+   iconv_close(cd);
+   
+   return size;
+}
+
+
+
+ErlDrvEntry iconv_driver_entry = {
+   NULL,                       /* F_PTR init, N/A */
+   iconv_erl_start,          /* L_PTR start, called when port is opened */
+   iconv_erl_stop,           /* F_PTR stop, called when port is closed */
+   NULL,         /* F_PTR output, called when erlang has sent */
+   NULL,                       /* F_PTR ready_input, called when input descriptor ready */
+   NULL,                       /* F_PTR ready_output, called when output descriptor ready */
+   "iconv_erl",              /* char *driver_name, the argument to open_port */
+   NULL,                       /* F_PTR finish, called when unloaded */
+   NULL,                       /* handle */
+   iconv_erl_control,          /* F_PTR control, port_command callback */
+   NULL,                       /* F_PTR timeout, reserved */
+   NULL                        /* F_PTR outputv, reserved */
+};
+
+DRIVER_INIT(iconv_erl) /* must match name in driver_entry */
+{
+   return &iconv_driver_entry;
+}
+
+
diff --git a/src/mod_irc/mod_irc.erl b/src/mod_irc/mod_irc.erl
new file mode 100644 (file)
index 0000000..12a540a
--- /dev/null
@@ -0,0 +1,93 @@
+%%%----------------------------------------------------------------------
+%%% File    : mod_irc.erl
+%%% Author  : Alexey Shchepin <alexey@sevcom.net>
+%%% Purpose : IRC transport
+%%% Created : 15 Feb 2003 by Alexey Shchepin <alexey@sevcom.net>
+%%% Id      : $Id$
+%%%----------------------------------------------------------------------
+
+-module(mod_irc).
+-author('alexey@sevcom.net').
+-vsn('$Revision$ ').
+
+-behaviour(gen_mod).
+
+-export([start/1, init/1, stop/0]).
+
+-include("ejabberd.hrl").
+
+-record(irc_connection, {userserver, pid}).
+
+start(Opts) ->
+    iconv:start(),
+    Host = gen_mod:get_opt(host, Opts, "irc." ++ ?MYNAME),
+    register(ejabberd_mod_irc, spawn(?MODULE, init, [Host])).
+
+init(Host) ->
+    catch ets:new(irc_connection, [named_table,
+                                  public,
+                                  {keypos, #irc_connection.userserver}]),
+    ejabberd_router:register_route(Host),
+    loop(Host).
+
+loop(Host) ->
+    receive
+       {route, From, To, Packet} ->
+           case catch do_route(Host, From, To, Packet) of
+               {'EXIT', Reason} ->
+                   ?ERROR_MSG("~p", [Reason]);
+               _ ->
+                   ok
+           end,
+           loop(Host);
+       stop ->
+           ejabberd_router:unregister_global_route(Host),
+           ok;
+       _ ->
+           loop(Host)
+    end.
+
+
+do_route(Host, From, To, Packet) ->
+    {ChanServ, _, Resource} = To,
+    case ChanServ of
+       "" ->
+           % TODO
+           Err = jlib:make_error_reply(Packet, "406", "Not Acceptable"),
+           ejabberd_router:route(To, From, Err);
+       _ ->
+           case string:tokens(ChanServ, "%") of
+               [[_ | _] = Channel, [_ | _] = Server] ->
+                   case ets:lookup(irc_connection, {From, Server}) of
+                       [] ->
+                           io:format("open new connection~n"),
+                           {ok, Pid} = mod_irc_connection:start(
+                                         From, Host, Server),
+                           ets:insert(
+                             irc_connection,
+                             #irc_connection{userserver = {From, Server},
+                                             pid = Pid}),
+                           mod_irc_connection:route(
+                             Pid, Channel, Resource, Packet),
+                           ok;
+                       [R] ->
+                           Pid = R#irc_connection.pid,
+                           io:format("send to process ~p~n",
+                                     [Pid]),
+                           mod_irc_connection:route(
+                             Pid, Channel, Resource, Packet),
+                           ok
+                   end;
+               _ ->
+                   Err = jlib:make_error_reply(
+                           Packet, "406", "Not Acceptable"),
+                   ejabberd_router:route(To, From, Err)
+           end
+    end.
+
+
+
+
+stop() ->
+    ejabberd_mod_irc ! stop,
+    ok.
diff --git a/src/mod_irc/mod_irc_connection.erl b/src/mod_irc/mod_irc_connection.erl
new file mode 100644 (file)
index 0000000..268572d
--- /dev/null
@@ -0,0 +1,443 @@
+%%%----------------------------------------------------------------------
+%%% File    : mod_irc_connection.erl
+%%% Author  : Alexey Shchepin <alexey@sevcom.net>
+%%% Purpose : 
+%%% Created : 15 Feb 2003 by Alexey Shchepin <alexey@sevcom.net>
+%%% Id      : $Id$
+%%%----------------------------------------------------------------------
+
+-module(mod_irc_connection).
+-author('alexey@sevcom.net').
+-vsn('$Revision$ ').
+
+-behaviour(gen_fsm).
+
+%% External exports
+-export([start/3, receiver/2, route/4]).
+
+%% gen_fsm callbacks
+-export([init/1,
+        open_socket/2,
+        %wait_for_registration/2,
+        stream_established/2,
+        handle_event/3,
+        handle_sync_event/4,
+        handle_info/3,
+        terminate/3,
+        code_change/4]).
+
+-include("ejabberd.hrl").
+-include("namespaces.hrl").
+
+-record(state, {socket, receiver, queue,
+               user, myname, server, nick,
+               inbuf = "", outbuf = ""}).
+
+-define(IRC_ENCODING, "koi8-r").
+
+-define(DBGFSM, true).
+
+-ifdef(DBGFSM).
+-define(FSMOPTS, [{debug, [trace]}]).
+-else.
+-define(FSMOPTS, []).
+-endif.
+
+%%%----------------------------------------------------------------------
+%%% API
+%%%----------------------------------------------------------------------
+start(From, Host, Server) ->
+    gen_fsm:start(?MODULE, [From, Host, Server], ?FSMOPTS).
+
+%%%----------------------------------------------------------------------
+%%% Callback functions from gen_fsm
+%%%----------------------------------------------------------------------
+
+%%----------------------------------------------------------------------
+%% Func: init/1
+%% Returns: {ok, StateName, StateData}          |
+%%          {ok, StateName, StateData, Timeout} |
+%%          ignore                              |
+%%          {stop, StopReason}                   
+%%----------------------------------------------------------------------
+init([From, Host, Server]) ->
+    gen_fsm:send_event(self(), init),
+    {Nick, _, _} = From,
+    {ok, open_socket, #state{queue = queue:new(),
+                            user = From,
+                            nick = Nick,
+                            myname = Host,
+                            server = Server}}.
+
+%%----------------------------------------------------------------------
+%% Func: StateName/2
+%% Returns: {next_state, NextStateName, NextStateData}          |
+%%          {next_state, NextStateName, NextStateData, Timeout} |
+%%          {stop, Reason, NewStateData}                         
+%%----------------------------------------------------------------------
+open_socket(init, StateData) ->
+    Addr = StateData#state.server,
+    Port = 6667,
+    ?DEBUG("connecting to ~s:~p~n", [Addr, Port]),
+    case gen_tcp:connect(Addr, Port, [binary, {packet, 0}]) of
+       {ok, Socket} ->
+           % TODO: send nick, etc...
+           %send_text(Socket, io_lib:format(?STREAM_HEADER,
+           %                               [StateData#state.server])),
+           %send_queue(StateData#state.socket, StateData#state.queue),
+           send_text(Socket, io_lib:format("NICK ~s\r\n",
+                                           [StateData#state.nick])),
+           send_text(Socket,
+                     io_lib:format(
+                       "USER ~s ~s ~s :~s\r\n",
+                       [StateData#state.nick,
+                        StateData#state.nick,
+                        StateData#state.myname,
+                        StateData#state.nick])),
+           {next_state, wait_for_registration,
+            StateData#state{socket = Socket}};
+       {error, Reason} ->
+           ?DEBUG("connect return ~p~n", [Reason]),
+           Text = case Reason of
+                      timeout -> "Server Connect Timeout";
+                      _ -> "Server Connect Failed"
+                  end,
+           bounce_messages(Text),
+           {stop, normal, StateData}
+    end.
+
+
+stream_established({xmlstreamend, Name}, StateData) ->
+    {stop, normal, StateData};
+
+stream_established(timeout, StateData) ->
+    {stop, normal, StateData};
+
+stream_established(closed, StateData) ->
+    {stop, normal, StateData}.
+
+
+
+%%----------------------------------------------------------------------
+%% Func: StateName/3
+%% Returns: {next_state, NextStateName, NextStateData}            |
+%%          {next_state, NextStateName, NextStateData, Timeout}   |
+%%          {reply, Reply, NextStateName, NextStateData}          |
+%%          {reply, Reply, NextStateName, NextStateData, Timeout} |
+%%          {stop, Reason, NewStateData}                          |
+%%          {stop, Reason, Reply, NewStateData}                    
+%%----------------------------------------------------------------------
+%state_name(Event, From, StateData) ->
+%    Reply = ok,
+%    {reply, Reply, state_name, StateData}.
+
+%%----------------------------------------------------------------------
+%% Func: handle_event/3
+%% Returns: {next_state, NextStateName, NextStateData}          |
+%%          {next_state, NextStateName, NextStateData, Timeout} |
+%%          {stop, Reason, NewStateData}                         
+%%----------------------------------------------------------------------
+handle_event(Event, StateName, StateData) ->
+    {next_state, StateName, StateData}.
+
+%%----------------------------------------------------------------------
+%% Func: handle_sync_event/4
+%% Returns: {next_state, NextStateName, NextStateData}            |
+%%          {next_state, NextStateName, NextStateData, Timeout}   |
+%%          {reply, Reply, NextStateName, NextStateData}          |
+%%          {reply, Reply, NextStateName, NextStateData, Timeout} |
+%%          {stop, Reason, NewStateData}                          |
+%%          {stop, Reason, Reply, NewStateData}                    
+%%----------------------------------------------------------------------
+handle_sync_event(Event, From, StateName, StateData) ->
+    Reply = ok,
+    {reply, Reply, StateName, StateData}.
+
+code_change(OldVsn, StateName, StateData, Extra) ->
+    {ok, StateName, StateData}.
+
+-define(SEND(S),
+       if
+           StateName == stream_established ->
+               send_text(StateData#state.socket, S),
+               StateData;
+           true ->
+               StateData#state{outbuf = StateData#state.outbuf ++ S}
+       end).
+
+%%----------------------------------------------------------------------
+%% Func: handle_info/3
+%% Returns: {next_state, NextStateName, NextStateData}          |
+%%          {next_state, NextStateName, NextStateData, Timeout} |
+%%          {stop, Reason, NewStateData}                         
+%%----------------------------------------------------------------------
+handle_info({route, Channel, Resource, {xmlelement, "presence", Attrs, Els}},
+           StateName, StateData) ->
+    NewStateData =
+       case xml:get_attr_s("type", Attrs) of
+           "unavailable" ->
+               ?SEND(io_lib:format("PART #~s\r\n", [Channel]));
+           "subscribe" -> StateData;
+           "subscribed" -> StateData;
+           "unsubscribe" -> StateData;
+           "unsubscribed" -> StateData;
+           _ ->
+               ?SEND(io_lib:format("JOIN #~s\r\n", [Channel]))
+       end,
+    {next_state, StateName, NewStateData};
+
+handle_info({route, Channel, Resource,
+            {xmlelement, "message", Attrs, Els} = El},
+           StateName, StateData) ->
+    NewStateData =
+       case xml:get_attr_s("type", Attrs) of
+           "groupchat" ->
+               ejabberd_router:route(
+                 {lists:concat([Channel, "%", StateData#state.server]),
+                  StateData#state.myname, StateData#state.nick},
+                 StateData#state.user, El),
+               % TODO: remove newlines from body
+               Body = xml:get_path_s(El, [{elem, "body"}, cdata]),
+               ?SEND(io_lib:format("PRIVMSG #~s :~s\r\n", [Channel, Body]));
+           _ -> StateData
+       end,
+    {next_state, StateName, NewStateData};
+
+handle_info({route, Channel, Resource, Packet}, StateName, StateData) ->
+    {next_state, StateName, StateData};
+
+
+handle_info({ircstring, [$P, $I, $N, $G, $  | ID]}, StateName, StateData) ->
+    send_text(StateData#state.socket, "PONG " ++ ID ++ "\r\n"),
+    {next_state, StateName, StateData};
+
+handle_info({ircstring, [$: | String]}, StateName, StateData) ->
+    Words = string:tokens(String, " "),
+    case Words of
+       [_, "353" | Items] ->
+           process_channel_list(StateData, Items);
+       [From, "PRIVMSG", [$# | Chan] | _] ->
+           process_privmsg(StateData, Chan, From, String);
+       [From, "PART", [$# | Chan] | _] ->
+           process_part(StateData, Chan, From, String);
+       [From, "JOIN", Chan | _] ->
+           process_join(StateData, Chan, From, String);
+       [From, "MODE", [$# | Chan], "+o", Nick | _] ->
+           process_mode_o(StateData, Chan, From, Nick,
+                          "admin", "moderator");
+       [From, "MODE", [$# | Chan], "-o", Nick | _] ->
+           process_mode_o(StateData, Chan, From, Nick,
+                          "member", "participant");
+       _ ->
+           io:format("unknown irc command '~s'~n", [String])
+    end,
+    NewStateData =
+       case StateData#state.outbuf of
+           "" ->
+               StateData;
+           Data ->
+               send_text(StateData#state.socket, Data),
+               StateData#state{outbuf = ""}
+       end,
+    {next_state, stream_established, NewStateData};
+
+
+handle_info({ircstring, String}, StateName, StateData) ->
+    io:format("unknown irc command '~s'~n", [String]),
+    {next_state, StateName, StateData};
+
+
+handle_info({send_text, Text}, StateName, StateData) ->
+    send_text(StateData#state.socket, Text),
+    {next_state, StateName, StateData};
+handle_info({send_element, El}, StateName, StateData) ->
+    case StateName of
+       stream_established ->
+           send_element(StateData#state.socket, El),
+           {next_state, StateName, StateData};
+       _ ->
+           Q = queue:in(El, StateData#state.queue),
+           {next_state, StateName, StateData#state{queue = Q}}
+    end;
+handle_info({tcp, Socket, Data}, StateName, StateData) ->
+    Buf = StateData#state.inbuf ++ binary_to_list(Data),
+    {ok, Strings} = regexp:split(Buf, "\r\n"),
+    io:format("strings=~p~n", [Strings]),
+    NewBuf = process_lines(Strings),
+    {next_state, StateName, StateData#state{inbuf = NewBuf}};
+handle_info({tcp_closed, Socket}, StateName, StateData) ->
+    gen_fsm:send_event(self(), closed),
+    {next_state, StateName, StateData};
+handle_info({tcp_error, Socket, Reason}, StateName, StateData) ->
+    gen_fsm:send_event(self(), closed),
+    {next_state, StateName, StateData}.
+
+%%----------------------------------------------------------------------
+%% Func: terminate/3
+%% Purpose: Shutdown the fsm
+%% Returns: any
+%%----------------------------------------------------------------------
+terminate(Reason, StateName, StateData) ->
+    ejabberd_mod_irc ! {closed_conection, {StateData#state.user,
+                                          StateData#state.server}},
+    case StateData#state.socket of
+       undefined ->
+           ok;
+       Socket ->
+           gen_tcp:close(Socket)
+    end,
+    ok.
+
+%%%----------------------------------------------------------------------
+%%% Internal functions
+%%%----------------------------------------------------------------------
+
+receiver(Socket, C2SPid) ->
+    XMLStreamPid = xml_stream:start(C2SPid),
+    receiver(Socket, C2SPid, XMLStreamPid).
+
+receiver(Socket, C2SPid, XMLStreamPid) ->
+    case gen_tcp:recv(Socket, 0) of
+        {ok, Text} ->
+           xml_stream:send_text(XMLStreamPid, Text),
+           receiver(Socket, C2SPid, XMLStreamPid);
+        {error, Reason} ->
+           exit(XMLStreamPid, closed),
+           gen_fsm:send_event(C2SPid, closed),
+           ok
+    end.
+
+send_text(Socket, Text) ->
+    CText = iconv:convert("utf-8", ?IRC_ENCODING, lists:flatten(Text)),
+    %io:format("IRC OUTu: ~s~nIRC OUTk: ~s~n", [Text, CText]),
+    gen_tcp:send(Socket, CText).
+
+send_element(Socket, El) ->
+    send_text(Socket, xml:element_to_string(El)).
+
+send_queue(Socket, Q) ->
+    case queue:out(Q) of
+       {{value, El}, Q1} ->
+           send_element(Socket, El),
+           send_queue(Socket, Q1);
+       {empty, Q1} ->
+           ok
+    end.
+
+bounce_messages(Reason) ->
+    receive
+       {send_element, El} ->
+           {xmlelement, Name, Attrs, SubTags} = El,
+           case xml:get_attr_s("type", Attrs) of
+               "error" ->
+                   ok;
+               _ ->
+                   Err = jlib:make_error_reply(El,
+                                               "502", Reason),
+                   From = jlib:string_to_jid(xml:get_attr_s("from", Attrs)),
+                   To = jlib:string_to_jid(xml:get_attr_s("to", Attrs)),
+                   ejabberd_router ! {route, To, From, Err}
+           end,
+           bounce_messages(Reason)
+    after 0 ->
+           ok
+    end.
+
+
+route(Pid, Channel, Resource, Packet) ->
+    Pid ! {route, Channel, Resource, Packet}.
+
+
+process_lines([S]) ->
+    S;
+process_lines([S | Ss]) ->
+    self() ! {ircstring, iconv:convert(?IRC_ENCODING, "utf-8", S)},
+    process_lines(Ss).
+
+process_channel_list(StateData, Items) ->
+    process_channel_list_find_chan(StateData, Items).
+
+process_channel_list_find_chan(StateData, []) ->
+    ok;
+process_channel_list_find_chan(StateData, [[$# | Chan] | Items]) ->
+    process_channel_list_users(StateData, Chan, Items);
+process_channel_list_find_chan(StateData, [_ | Items]) ->
+    process_channel_list_find_chan(StateData, Items).
+
+process_channel_list_users(StateData, Chan, []) ->
+    ok;
+process_channel_list_users(StateData, Chan, [User | Items]) ->
+    process_channel_list_user(StateData, Chan, User),
+    process_channel_list_users(StateData, Chan, Items).
+
+process_channel_list_user(StateData, Chan, User) ->
+    User1 = case User of
+               [$: | U1] -> U1;
+               _ -> User
+           end,
+    {User2, Affiliation, Role} =
+       case User1 of
+           [$@ | U2] -> {U2, "admin", "moderator"};
+           _ -> {User1, "member", "participant"}
+       end,
+    ejabberd_router:route({lists:concat([Chan, "%", StateData#state.server]),
+                          StateData#state.myname, User2},
+                         StateData#state.user,
+                         {xmlelement, "presence", [],
+                          [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}],
+                            [{xmlelement, "item",
+                              [{"affiliation", Affiliation},
+                               {"role", Role}],
+                              []}]}]}).
+
+
+process_privmsg(StateData, Chan, From, String) ->
+    [FromUser | _] = string:tokens(From, "!"),
+    Msg = lists:last(string:tokens(String, ":")),
+    ejabberd_router:route({lists:concat([Chan, "%", StateData#state.server]),
+                          StateData#state.myname, FromUser},
+                         StateData#state.user,
+                         {xmlelement, "message", [{"type", "groupchat"}],
+                          [{xmlelement, "body", [], [{xmlcdata, Msg}]}]}).
+
+process_part(StateData, Chan, From, String) ->
+    [FromUser | _] = string:tokens(From, "!"),
+    %Msg = lists:last(string:tokens(String, ":")),
+    ejabberd_router:route({lists:concat([Chan, "%", StateData#state.server]),
+                          StateData#state.myname, FromUser},
+                         StateData#state.user,
+                         {xmlelement, "presence", [{"type", "unavailable"}],
+                          [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}],
+                            [{xmlelement, "item",
+                              [{"affiliation", "member"},
+                               {"role", "none"}],
+                              []}]}]}).
+
+
+process_join(StateData, Channel, From, String) ->
+    [FromUser | _] = string:tokens(From, "!"),
+    Chan = lists:subtract(Channel, ":#"),
+    ejabberd_router:route({lists:concat([Chan, "%", StateData#state.server]),
+                          StateData#state.myname, FromUser},
+                         StateData#state.user,
+                         {xmlelement, "presence", [],
+                          [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}],
+                            [{xmlelement, "item",
+                              [{"affiliation", "member"},
+                               {"role", "participant"}],
+                              []}]}]}).
+
+
+process_mode_o(StateData, Chan, From, Nick, Affiliation, Role) ->
+    %Msg = lists:last(string:tokens(String, ":")),
+    ejabberd_router:route({lists:concat([Chan, "%", StateData#state.server]),
+                          StateData#state.myname, Nick},
+                         StateData#state.user,
+                         {xmlelement, "presence", [],
+                          [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}],
+                            [{xmlelement, "item",
+                              [{"affiliation", Affiliation},
+                               {"role", Role}],
+                              []}]}]}).
+
+
index 712348a9114cd2b50833cce8eec62ed4ef05aa69..e26b9750e163e19a774b09a25b1acb74a30c5cc3 100644 (file)
@@ -19,5 +19,7 @@
 -define(NS_DELAY,       "jabber:x:delay").
 -define(NS_EVENT,       "jabber:x:event").
 -define(NS_STATS,       "http://jabber.org/protocol/stats").
+-define(NS_MUC,         "http://jabber.org/protocol/muc").
+-define(NS_MUC_USER,    "http://jabber.org/protocol/muc#user").